diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 19b655c29..000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,12 +0,0 @@ -# These are supported funding model platforms - -github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: #GeyserMC # Disabled currently -open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 326a1ebd5..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - - - - - -**Describe the bug** - - -**To Reproduce** - - - - - - -**Expected behavior** - - -**Screenshots / Videos** - - -**Server Version** - - -**Geyser Version** - - -**Minecraft: Bedrock Edition Version** - - -**Additional Context** - diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..f4a0e21ff --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,64 @@ +name: Bug report +description: Create a report to help us improve +body: +- type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report for Geyser! Fill out the following form to your best ability to help us fix the problem. + Only use this if you're absolutely sure that you found a bug and can reproduce it. For anything else, use: [our Discord server](https://discord.gg/geysermc), [the FAQ](https://github.com/GeyserMC/Geyser/wiki/FAQ) or the [Common Issues](https://github.com/GeyserMC/Geyser/wiki/Common-Issues). +- type: textarea + attributes: + label: Describe the bug + description: A clear and concise description of what the bug is. + validations: + required: true +- type: textarea + attributes: + label: To Reproduce + description: Steps to reproduce this behaviour + placeholder: | + 1. Go to '...' + 2. Click on '...' + 3. Scroll down to '...' + 4. See error + validations: + required: true +- type: textarea + attributes: + label: Expected behaviour + description: A clear and concise description of what you expected to happen. + validations: + required: true +- type: textarea + attributes: + label: Screenshots / Videos + description: If applicable, add screenshots to help explain your problem. +- type: textarea + attributes: + label: Server Version and Plugins + description: | + If you just run Geyser-Spigot, you can leave this area blank as the next section covers this information. + If you're running a multi-server instance or using Geyser Standalone: + * Give us the exact output from `/version` on all servers involved. Saying "latest" does not help us at all. + * Please list all plugins on all servers involved. + If this bug occurs on a server you do not control, please fill this in to the best of your knowledge. +- type: input + attributes: + label: Geyser Dump + description: If Geyser starts correctly, please include the link to a dump using `/geyser dump`. If you're using the Standalone GUI, you can find the option under `Commands` => `Dump`. Doing this provides us information about your server that we can use to debug your issue. +- type: input + attributes: + label: Geyser Version + description: What version of Geyser are you running? + placeholder: "For example: 1.2.0-SNAPSHOT (git-master-2d9baf1)" + validations: + required: true +- type: input + attributes: + label: "Minecraft: Bedrock Edition Version" + description: "What version of Minecraft: Bedrock Edition are you using? Leave empty if the bug happens before you can connect with Minecraft: Bedrock Edition." + placeholder: "For example: 1.16.201" +- type: textarea + attributes: + label: Additional Context + description: Add any other context about the problem here diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 92085a35b..862e9c0be 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,11 @@ blank_issues_enabled: false contact_links: - - name: GeyserMC Discord - url: http://discord.geysermc.org/ + - name: Common Issues + url: https://github.com/GeyserMC/Geyser/wiki/Common-Issues + about: Check the common issues to see if you are not alone with that issue and see how you can fix them. + - name: Frequently Asked Questions + url: https://github.com/GeyserMC/Geyser/wiki/FAQ + about: Look at the FAQ page for answers to frequently asked questions. + - name: Get help on the GeyserMC Discord server + url: https://discord.gg/geysermc about: If your issue seems like it could possibly be an easy fix due to configuration, please hop on our Discord. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 46653e624..000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - ---- - -**What feature do you want?** -Add a description - -**Alternatives?** -List any alternatives you might have tried diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..d0e42478b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,21 @@ +name: Feature request +description: Suggest an idea for this project +labels: "Feature Request" +body: +- type: markdown + attributes: + value: | + Thanks for taking the time to fill out this feature request for Geyser! Please fill out the following form to your best ability to help us understand your feature request and significantly improve the chance of getting added. + For anything else than a feature request, use: [our Discord server](https://discord.gg/geysermc), [the FAQ](https://github.com/GeyserMC/Geyser/wiki/FAQ) or [the Common Issues](https://github.com/GeyserMC/Geyser/wiki/Common-Issues). +- type: textarea + attributes: + label: What feature do you want to see added? + description: A clear and concise description of your feature request. + validations: + required: true +- type: textarea + attributes: + label: Are there any alternatives? + description: List any alternatives you might have tried + validations: + required: true \ No newline at end of file diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 78a3ce299..9621fa1d0 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -15,10 +15,10 @@ jobs: key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- - - name: Set up JDK 1.8 + - name: Set up JDK 16 uses: actions/setup-java@v1 with: - java-version: 1.8 + java-version: 16 - name: submodules-init uses: snickerbockers/submodules-init@v4 - name: Build with Maven diff --git a/.gitmodules b/.gitmodules index 0c014712b..1fdd03343 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ -[submodule "connector/src/main/resources/mappings"] - path = connector/src/main/resources/mappings +[submodule "core/src/main/resources/mappings"] + path = core/src/main/resources/mappings url = https://github.com/GeyserMC/mappings.git -[submodule "connector/src/main/resources/languages"] - path = connector/src/main/resources/languages +[submodule "core/src/main/resources/languages"] + path = core/src/main/resources/languages url = https://github.com/GeyserMC/languages.git diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c7a5a3d28..ce6894845 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,39 +1,46 @@ -Thank for for considering a contribution! Generally, Geyser welcomes PRs from everyone. There are some guidelines about what features should go where: +Thank you for considering a contribution! Generally, Geyser welcomes PRs from everyone. There are some guidelines about what features should go where: -*Pull requests that may not get accepted:* Niche features that apply to a specific group, for example integration with a specific plugin. For now, please create a separate plugin if possible. +*Pull requests that may not get accepted:* Niche features that apply to a specific group, for example, integration with a specific plugin. For now, please create a separate plugin if possible. *Pull requests for Floodgate:* Anything that opens up information within the game for developers to use. -*Pull requests for Geyser:* Anything that fixes compatibility between Java or Bedrock, or improves the quality of play for Bedrock players. The exception is wherever direct server access is required; in this case it may be better for Floodgate. +*Pull requests for Geyser:* Anything that fixes compatibility between Java or Bedrock or improves the quality of play for Bedrock players. The exception is wherever direct server access is required; in this case, it may be better for Floodgate. We have some general style guides that should be applied throughout the code: ```java +public class LongClassName { + private static final int AIR_ITEM = 0; // Static item names should be capitalized -private static final AIR_ITEM = 0; // Static item names should be capitalized + public Int2IntMap items = new Int2IntOpenHashMap(); // Use the interface as the class type but initialize with the implementation. -public Int2IntMap items = new Int2IntOpenHashMap(); // Use the interface as the class type but initialize with the implementation. + public int nameWithMultipleWords = 0; -public int nameWithMultipleWords = 0; + /** + * Javadoc comment to explain what a function does. + */ + @RandomAnnotation(stuff = true, moreStuff = "might exist") + public void applyStuff() { + Variable variable = new Variable(); + Variable otherVariable = new Variable(); -/** -* Javadoc comment to explain what a function does. -*/ -public void applyStuff() { - if (condition) { - // Do stuff. - } else if (anotherCondition) { - // Do something else. - } - - switch (value) { - case 0: - break; - case 1: - break: - } + if (condition) { + // Do stuff. + } else if (anotherCondition) { + // Do something else. + } + + switch (value) { + case 0: + stuff(); + break; + case 1: + differentStuff(); + break; + } + } } ``` @@ -41,4 +48,4 @@ Make sure to comment your code where possible. The nature of our software requires a lot of arrays and maps to be stored - where possible, use Fastutil's specialized maps. For example, if you're storing block state translations, use an `Int2IntMap`. -We have a rundown of all the tools you need to develop over on our [wiki](https://github.com/GeyserMC/Geyser/wiki/Developer-Guide). If you have any questions, please feel free to reach out to our [Discord](https://discord.geysermc.org)! +We have a rundown of all the tools you need to develop over on our [wiki](https://github.com/GeyserMC/Geyser/wiki/Developer-Guide). If you have any questions, please feel free to reach out to our [Discord](https://discord.gg/geysermc)! diff --git a/Jenkinsfile b/Jenkinsfile index 7dfdaf304..481c02310 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,7 +2,7 @@ pipeline { agent any tools { maven 'Maven 3' - jdk 'Java 8' + jdk 'Java 16' } options { buildDiscarder(logRotator(artifactNumToKeepStr: '20')) @@ -26,7 +26,27 @@ pipeline { } steps { - sh 'mvn javadoc:jar source:jar deploy -DskipTests' + rtMavenDeployer( + id: "maven-deployer", + serverId: "opencollab-artifactory", + releaseRepo: "maven-releases", + snapshotRepo: "maven-snapshots" + ) + rtMavenResolver( + id: "maven-resolver", + serverId: "opencollab-artifactory", + releaseRepo: "maven-deploy-release", + snapshotRepo: "maven-deploy-snapshot" + ) + rtMavenRun( + pom: 'pom.xml', + goals: 'javadoc:jar source:jar install -pl :core -am -DskipTests', + deployerId: "maven-deployer", + resolverId: "maven-resolver" + ) + rtPublishBuildInfo( + serverId: "opencollab-artifactory" + ) } } } @@ -66,7 +86,15 @@ pipeline { } deleteDir() withCredentials([string(credentialsId: 'geyser-discord-webhook', variable: 'DISCORD_WEBHOOK')]) { - discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n${changes}\n\n[**Artifacts on Jenkins**](https://ci.nukkitx.com/job/Geyser)", footer: 'Cloudburst Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK + discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n${changes}\n\n[**Artifacts on Jenkins**](https://ci.opencollab.dev/job/GeyserMC/job/Geyser)", footer: 'Open Collaboration Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK + } + } + success { + script { + if (env.BRANCH_NAME == 'master') { + build propagate: false, wait: false, job: 'GeyserMC/Geyser-Fabric/java-1.18', parameters: [booleanParam(name: 'SKIP_DISCORD', value: true)] + build propagate: false, wait: false, job: 'GeyserMC/GeyserConnect/master', parameters: [booleanParam(name: 'SKIP_DISCORD', value: true)] + } } } } diff --git a/LICENSE b/LICENSE index acd4af141..0e368d546 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2019-2020 GeyserMC. http://geysermc.org +Copyright (c) 2019-2021 GeyserMC. http://geysermc.org Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index ab1ff24ac..d4b375a5c 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ Geyser -[![forthebadge made-with-java](http://ForTheBadge.com/images/badges/made-with-java.svg)](https://java.com/) +[![forthebadge made-with-java](https://forthebadge.com/images/badges/made-with-java.svg)](https://java.com/) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) -[![Build Status](https://ci.nukkitx.com/job/Geyser/job/master/badge/icon)](https://ci.nukkitx.com/job/GeyserMC/job/Geyser/job/master/) -[![Discord](https://img.shields.io/discord/613163671870242838.svg?color=%237289da&label=discord)](http://discord.geysermc.org/) -[![HitCount](http://hits.dwyl.io/Geyser/GeyserMC.svg)](http://hits.dwyl.io/Geyser/GeyserMC) +[![Build Status](https://ci.opencollab.dev/job/Geyser/job/master/badge/icon)](https://ci.opencollab.dev/job/GeyserMC/job/Geyser/job/master/) +[![Discord](https://img.shields.io/discord/613163671870242838.svg?color=%237289da&label=discord)](https://discord.gg/geysermc) [![Crowdin](https://badges.crowdin.net/geyser/localized.svg)](https://translate.geysermc.org/) Geyser is a bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition, closing the gap from those wanting to play true cross-platform. @@ -14,41 +13,38 @@ Geyser is an open collaboration project by [CubeCraft Games](https://cubecraft.n ## What is Geyser? Geyser is a proxy, bridging the gap between Minecraft: Bedrock Edition and Minecraft: Java Edition servers. -The ultimate goal of this project is to allow Minecraft: Bedrock Edition users to join Minecraft: Java Edition servers as seamlessly as possible. **Please note, this project is still a work in progress and should not be used on production. Expect bugs!** +The ultimate goal of this project is to allow Minecraft: Bedrock Edition users to join Minecraft: Java Edition servers as seamlessly as possible. However, due to the nature of Geyser translating packets over the network of two different games, *do not expect everything to work perfectly!* -Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have now joined us here! +Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here! -### Currently supporting Minecraft Bedrock v1.16.100 and Minecraft Java v1.16.4. +### Currently supporting Minecraft Bedrock 1.17.30 - 1.17.41 + 1.18.0 - 1.18.2 and Minecraft Java 1.18/1.18.1. ## Setting Up -Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser. +Take a look [here](https://github.com/GeyserMC/Geyser/wiki/Setup) for how to set up Geyser. [![YouTube Video](https://img.youtube.com/vi/U7dZZ8w7Gi4/0.jpg)](https://www.youtube.com/watch?v=U7dZZ8w7Gi4) ## Links: - Website: https://geysermc.org - Docs: https://github.com/GeyserMC/Geyser/wiki -- Download: http://ci.geysermc.org -- Discord: http://discord.geysermc.org/ -- ~~Donate: https://patreon.com/GeyserMC~~ Currently disabled. +- Download: https://ci.geysermc.org +- Discord: https://discord.gg/geysermc +- Donate: https://opencollective.com/geysermc - Test Server: `test.geysermc.org` port `25565` for Java and `19132` for Bedrock ## What's Left to be Added/Fixed -- The Following Inventories - - [ ] Enchantment Table (as a proper GUI) - - [ ] Beacon - - [ ] Cartography Table - - [ ] Stonecutter - - [ ] Structure Block - - [ ] Horse Inventory - - [ ] Loom - - [ ] Smithing Table +- Near-perfect movement (to the point where anticheat on large servers is unlikely to ban you) +- Resource pack conversion/CustomModelData - Some Entity Flags +- Structure block UI + +## What can't be fixed +There are a few things Geyser is unable to support due to various differences between Minecraft Bedrock and Java. For a list of these limitations, see the [Current Limitations](https://github.com/GeyserMC/Geyser/wiki/Current-Limitations) page. ## Compiling 1. Clone the repo to your computer 2. [Install Maven](https://maven.apache.org/install.html) -3. Navigate to the Geyser root directory and run `git submodule update --init --recursive`. This downloads all the needed submodules for Geyser and is a crucial step in this process. +3. Navigate to the Geyser root directory and run `git submodule update --init --recursive`. This command downloads all the needed submodules for Geyser and is a crucial step in this process. 4. Run `mvn clean install` and locate to the `target` folder. ## Contributing @@ -56,6 +52,7 @@ Any contributions are appreciated. Please feel free to reach out to us on [Disco you're interested in helping out with Geyser. ## Libraries Used: +- [Adventure Text Library](https://github.com/KyoriPowered/adventure) - [NukkitX Bedrock Protocol Library](https://github.com/NukkitX/Protocol) - [Steveice10's Java Protocol Library](https://github.com/Steveice10/MCProtocolLib) - [TerminalConsoleAppender](https://github.com/Minecrell/TerminalConsoleAppender) diff --git a/ap/pom.xml b/ap/pom.xml new file mode 100644 index 000000000..cc282dd55 --- /dev/null +++ b/ap/pom.xml @@ -0,0 +1,14 @@ + + + 4.0.0 + + org.geysermc + geyser-parent + 2.0.0-SNAPSHOT + + + ap + 2.0.0-SNAPSHOT + \ No newline at end of file diff --git a/ap/src/main/java/org/geysermc/processor/BlockEntityProcessor.java b/ap/src/main/java/org/geysermc/processor/BlockEntityProcessor.java new file mode 100644 index 000000000..5a0b2d2f9 --- /dev/null +++ b/ap/src/main/java/org/geysermc/processor/BlockEntityProcessor.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.processor; + +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; + +@SupportedAnnotationTypes("*") +@SupportedSourceVersion(SourceVersion.RELEASE_16) +public class BlockEntityProcessor extends ClassProcessor { + public BlockEntityProcessor() { + super("org.geysermc.geyser.translator.level.block.entity.BlockEntity"); + } +} diff --git a/ap/src/main/java/org/geysermc/processor/ClassProcessor.java b/ap/src/main/java/org/geysermc/processor/ClassProcessor.java new file mode 100644 index 000000000..409306f1f --- /dev/null +++ b/ap/src/main/java/org/geysermc/processor/ClassProcessor.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.processor; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; +import javax.tools.FileObject; +import javax.tools.StandardLocation; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class ClassProcessor extends AbstractProcessor { + private final String annotationClassName; + + private Path outputPath; + + private final Set locations = new HashSet<>(); + + public ClassProcessor(String annotationClassName) { + this.annotationClassName = annotationClassName; + } + + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + super.init(processingEnv); + + this.processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Initializing processor " + this.annotationClassName); + + String outputFile = processingEnv.getOptions().get("metadataOutputFile"); + if (outputFile != null && !outputFile.isEmpty()) { + this.outputPath = Paths.get(outputFile); + } + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + if (roundEnv.processingOver()) { + if (!roundEnv.errorRaised()) { + complete(); + } + + return false; + } + + if (!contains(annotations, this.annotationClassName)) { + return false; + } + + for (Element element : roundEnv.getRootElements()) { + if (element.getKind() != ElementKind.CLASS) { + continue; + } + + if (!contains(element.getAnnotationMirrors(), this.annotationClassName)) { + continue; + } + + TypeElement typeElement = (TypeElement) element; + this.locations.add(typeElement.getQualifiedName().toString()); + } + return false; + } + + public boolean contains(Collection elements, String className) { + if (elements.isEmpty()) { + return false; + } + + for (TypeElement element : elements) { + if (element.getQualifiedName().contentEquals(className)) { + return true; + } + } + + return false; + } + + public boolean contains(List elements, String className) { + if (elements.isEmpty()) { + return false; + } + + for (AnnotationMirror element : elements) { + if (element.getAnnotationType().toString().equals(className)) { + return true; + } + } + + return false; + } + + public void complete() { + // Read existing annotation list and verify each class still has this annotation + try (BufferedReader reader = this.createReader()) { + if (reader != null) { + reader.lines().forEach(canonicalName -> { + if (!locations.contains(canonicalName)) { + TypeElement element = this.processingEnv.getElementUtils().getTypeElement(canonicalName); + if (element != null && element.getKind() == ElementKind.CLASS && contains(element.getAnnotationMirrors(), this.annotationClassName)) { + locations.add(canonicalName); + } + } + }); + } + } catch (IOException e) { + e.printStackTrace(); + } + + if (!locations.isEmpty()) { + try (BufferedWriter writer = this.createWriter()) { + for (String location : this.locations) { + writer.write(location); + writer.newLine(); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } else { + this.processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Did not find any classes annotated with " + this.annotationClassName); + } + this.processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Completed processing for " + this.annotationClassName); + } + + private BufferedReader createReader() throws IOException { + if (this.outputPath != null) { + this.processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Reading existing " + this.annotationClassName + " list from " + this.outputPath); + return Files.newBufferedReader(this.outputPath); + } + FileObject obj = this.processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", this.annotationClassName); + if (obj != null) { + this.processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Reading existing " + this.annotationClassName + " list from " + obj.toUri()); + return new BufferedReader(obj.openReader(false)); + } + return null; + } + + private BufferedWriter createWriter() throws IOException { + if (this.outputPath != null) { + this.processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Writing " + this.annotationClassName + " to " + this.outputPath); + return Files.newBufferedWriter(this.outputPath); + } + + FileObject obj = this.processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", this.annotationClassName); + this.processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Writing " + this.annotationClassName + " to " + obj.toUri()); + return new BufferedWriter(obj.openWriter()); + } +} diff --git a/ap/src/main/java/org/geysermc/processor/CollisionRemapperProcessor.java b/ap/src/main/java/org/geysermc/processor/CollisionRemapperProcessor.java new file mode 100644 index 000000000..eae2e5d97 --- /dev/null +++ b/ap/src/main/java/org/geysermc/processor/CollisionRemapperProcessor.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.processor; + +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; + +@SupportedAnnotationTypes("*") +@SupportedSourceVersion(SourceVersion.RELEASE_16) +public class CollisionRemapperProcessor extends ClassProcessor { + public CollisionRemapperProcessor() { + super("org.geysermc.geyser.translator.collision.CollisionRemapper"); + } +} diff --git a/ap/src/main/java/org/geysermc/processor/ItemRemapperProcessor.java b/ap/src/main/java/org/geysermc/processor/ItemRemapperProcessor.java new file mode 100644 index 000000000..cb49e8749 --- /dev/null +++ b/ap/src/main/java/org/geysermc/processor/ItemRemapperProcessor.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.processor; + +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; + +@SupportedAnnotationTypes("*") +@SupportedSourceVersion(SourceVersion.RELEASE_16) +public class ItemRemapperProcessor extends ClassProcessor { + public ItemRemapperProcessor() { + super("org.geysermc.geyser.translator.inventory.item.ItemRemapper"); + } +} diff --git a/ap/src/main/java/org/geysermc/processor/PacketTranslatorProcessor.java b/ap/src/main/java/org/geysermc/processor/PacketTranslatorProcessor.java new file mode 100644 index 000000000..5c46453e3 --- /dev/null +++ b/ap/src/main/java/org/geysermc/processor/PacketTranslatorProcessor.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.processor; + +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; + +@SupportedAnnotationTypes("*") +@SupportedSourceVersion(SourceVersion.RELEASE_16) +public class PacketTranslatorProcessor extends ClassProcessor { + public PacketTranslatorProcessor() { + super("org.geysermc.geyser.translator.protocol.Translator"); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/CreatureEntity.java b/ap/src/main/java/org/geysermc/processor/SoundHandlerProcessor.java similarity index 70% rename from connector/src/main/java/org/geysermc/connector/entity/living/CreatureEntity.java rename to ap/src/main/java/org/geysermc/processor/SoundHandlerProcessor.java index f0062f893..ad52533e9 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/CreatureEntity.java +++ b/ap/src/main/java/org/geysermc/processor/SoundHandlerProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,14 +23,16 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living; +package org.geysermc.processor; -import com.nukkitx.math.vector.Vector3f; -import org.geysermc.connector.entity.type.EntityType; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; -public class CreatureEntity extends InsentientEntity { - - public CreatureEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); +@SupportedAnnotationTypes("*") +@SupportedSourceVersion(SourceVersion.RELEASE_16) +public class SoundHandlerProcessor extends ClassProcessor { + public SoundHandlerProcessor() { + super("org.geysermc.geyser.translator.sound.SoundTranslator"); } } diff --git a/api/base/pom.xml b/api/base/pom.xml new file mode 100644 index 000000000..0d7ed05da --- /dev/null +++ b/api/base/pom.xml @@ -0,0 +1,27 @@ + + + + org.geysermc + api-parent + 2.0.0-SNAPSHOT + + 4.0.0 + + base-api + + + 16 + 16 + + + + + org.checkerframework + checker-qual + 3.19.0 + provided + + + \ No newline at end of file diff --git a/api/base/src/main/java/org/geysermc/api/Geyser.java b/api/base/src/main/java/org/geysermc/api/Geyser.java new file mode 100644 index 000000000..c5ae7fa4d --- /dev/null +++ b/api/base/src/main/java/org/geysermc/api/Geyser.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.api; + +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * General API class for Geyser. + */ +@NonNull +public class Geyser { + private static GeyserApiBase api; + + /** + * Returns the base api. + * + * @return the base api + */ + public static GeyserApiBase api() { + if (api == null) { + throw new RuntimeException("Api has not been registered yet!"); + } + + return api; + } + + /** + * Returns the api of the given type. + * + * @param apiClass the api class + * @param the type + * @return the api of the given type + */ + @SuppressWarnings("unchecked") + public static T api(@NonNull Class apiClass) { + if (apiClass.isInstance(api)) { + return (T) api; + } + + if (api == null) { + throw new RuntimeException("Api has not been registered yet!"); + } else { + throw new RuntimeException("Api was not an instance of " + apiClass + "! Was " + api.getClass().getCanonicalName()); + } + } + + /** + * Registers the given api type. The api cannot be + * registered if {@link #registered()} is true as + * an api has already been specified. + * + * @param api the api + */ + public static void set(@NonNull GeyserApiBase api) { + if (Geyser.api != null) { + throw new RuntimeException("Cannot redefine already registered api!"); + } + + Geyser.api = api; + } + + /** + * Gets if the api has been registered and + * is ready for usage. + * + * @return if the api has been registered + */ + public static boolean registered() { + return api != null; + } +} diff --git a/api/base/src/main/java/org/geysermc/api/GeyserApiBase.java b/api/base/src/main/java/org/geysermc/api/GeyserApiBase.java new file mode 100644 index 000000000..1acf9b5f8 --- /dev/null +++ b/api/base/src/main/java/org/geysermc/api/GeyserApiBase.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.api; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.api.session.Connection; + +import java.util.List; +import java.util.UUID; + +/** + * The base API class. + */ +public interface GeyserApiBase { + /** + * Gets the session from the given UUID, if applicable. The player must be logged in to the Java server + * for this to return a non-null value. + * + * @param uuid the UUID of the session + * @return the session from the given UUID, if applicable + */ + @Nullable + Connection connectionByUuid(@NonNull UUID uuid); + + /** + * Gets the session from the given + * XUID, if applicable. + * + * @param xuid the XUID of the session + * @return the session from the given UUID, if applicable + */ + @Nullable + Connection connectionByXuid(@NonNull String xuid); + + /** + * Gets the session from the given + * name, if applicable. + * + * @param name the uuid of the session + * @return the session from the given name, if applicable + */ + @Nullable + Connection connectionByName(@NonNull String name); + + /** + * Gets all the online sessions. + * + * @return all the online sessions + */ + @NonNull + List onlineConnections(); + + /** + * @return the major API version. Bumped whenever a significant breaking change or feature addition is added. + */ + default int majorApiVersion() { + return 0; + } + + /** + * @return the minor API version. May be bumped for new API additions. + */ + default int minorApiVersion() { + return 0; + } +} diff --git a/api/base/src/main/java/org/geysermc/api/session/Connection.java b/api/base/src/main/java/org/geysermc/api/session/Connection.java new file mode 100644 index 000000000..dc4fb4701 --- /dev/null +++ b/api/base/src/main/java/org/geysermc/api/session/Connection.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.api.session; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.UUID; + +/** + * Represents a player connection. + */ +@NonNull +public interface Connection { + /** + * Gets the name of the connection. + * + * @return the name of the connection + */ + String name(); + + /** + * Gets the {@link UUID} of the connection. + * + * @return the UUID of the connection + */ + UUID uuid(); + + /** + * Gets the XUID of the connection. + * + * @return the XUID of the connection + */ + String xuid(); + + +} diff --git a/api/geyser/pom.xml b/api/geyser/pom.xml new file mode 100644 index 000000000..89349e8ac --- /dev/null +++ b/api/geyser/pom.xml @@ -0,0 +1,33 @@ + + + + org.geysermc + api-parent + 2.0.0-SNAPSHOT + + 4.0.0 + + geyser-api + + + 16 + 16 + + + + + org.checkerframework + checker-qual + 3.19.0 + provided + + + org.geysermc + base-api + 2.0.0-SNAPSHOT + compile + + + \ No newline at end of file diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java b/api/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java new file mode 100644 index 000000000..6a5e7ccab --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.api.GeyserApiBase; +import org.geysermc.geyser.api.connection.GeyserConnection; + +import java.util.List; +import java.util.UUID; + +/** + * Represents the API used in Geyser. + */ +public interface GeyserApi extends GeyserApiBase { + /** + * Shuts down the current Geyser instance. + */ + void shutdown(); + + /** + * Reloads the current Geyser instance. + */ + void reload(); + + /** + * Gets if this Geyser instance is running in an IDE. This only needs to be used in cases where files + * expected to be in a jarfile are not present. + * + * @return true if the version number is not 'DEV'. + */ + boolean productionEnvironment(); + + /** + * {@inheritDoc} + */ + @Override + @Nullable GeyserConnection connectionByUuid(@NonNull UUID uuid); + + /** + * {@inheritDoc} + */ + @Override + @Nullable GeyserConnection connectionByXuid(@NonNull String xuid); + + /** + * {@inheritDoc} + */ + @Override + @Nullable GeyserConnection connectionByName(@NonNull String name); + + /** + * {@inheritDoc} + */ + @NonNull + List onlineConnections(); +} diff --git a/connector/src/main/java/org/geysermc/connector/network/remote/RemoteServer.java b/api/geyser/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java similarity index 81% rename from connector/src/main/java/org/geysermc/connector/network/remote/RemoteServer.java rename to api/geyser/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java index c65301d0c..a38dc2f91 100644 --- a/connector/src/main/java/org/geysermc/connector/network/remote/RemoteServer.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,15 +23,12 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.remote; +package org.geysermc.geyser.api.connection; -import lombok.AllArgsConstructor; -import lombok.Getter; +import org.geysermc.api.session.Connection; -@Getter -@AllArgsConstructor -public class RemoteServer { - - private String address; - private int port; -} \ No newline at end of file +/** + * Represents a player session used in Geyser. + */ +public interface GeyserConnection extends Connection { +} diff --git a/api/pom.xml b/api/pom.xml new file mode 100644 index 000000000..b3d0262ea --- /dev/null +++ b/api/pom.xml @@ -0,0 +1,24 @@ + + + 4.0.0 + + org.geysermc + geyser-parent + 2.0.0-SNAPSHOT + + + api-parent + pom + + + 16 + 16 + + + + base + geyser + + \ No newline at end of file diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 124967b0a..9dcd0943e 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -6,21 +6,22 @@ org.geysermc bootstrap-parent - 1.2.0-SNAPSHOT + 2.0.0-SNAPSHOT bootstrap-bungeecord org.geysermc - connector - 1.2.0-SNAPSHOT + core + 2.0.0-SNAPSHOT compile + - net.md-5 - bungeecord-api - 1.15-SNAPSHOT + com.github.SpigotMC.BungeeCord + bungeecord-proxy + a7c6ede provided @@ -40,7 +41,7 @@ - org.geysermc.platform.bungeecord.GeyserBungeeMain + org.geysermc.geyser.platform.bungeecord.GeyserBungeeMain @@ -48,7 +49,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.1 + 3.3.0-SNAPSHOT package @@ -59,35 +60,21 @@ net.md_5.bungee.jni - org.geysermc.platform.bungeecord.shaded.jni + org.geysermc.geyser.platform.bungeecord.shaded.jni com.fasterxml.jackson - org.geysermc.platform.bungeecord.shaded.jackson + org.geysermc.geyser.platform.bungeecord.shaded.jackson - io.netty - org.geysermc.platform.bungeecord.shaded.netty + + io.netty.channel.kqueue + org.geysermc.geyser.platform.bungeecord.shaded.io.netty.channel.kqueue - org.reflections - org.geysermc.platform.bungeecord.shaded.reflections - - - com.google.common - org.geysermc.platform.bungeecord.shaded.google.common - - - com.google.guava - org.geysermc.platform.bungeecord.shaded.google.guava - - - org.dom4j - org.geysermc.platform.bungeecord.shaded.dom4j - - - net.kyori.adventure - org.geysermc.platform.bungeecord.shaded.adventure + net.kyori + org.geysermc.geyser.platform.bungeecord.shaded.kyori @@ -96,8 +83,17 @@ - com.google.code.gson:* + com.google.*:* org.yaml:* + io.netty:netty-transport-native-epoll:* + io.netty:netty-transport-native-unix-common:* + io.netty:netty-handler:* + io.netty:netty-common:* + io.netty:netty-buffer:* + io.netty:netty-resolver:* + io.netty:netty-transport:* + io.netty:netty-codec:* + io.netty:netty-resolver-dns:* diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeConfiguration.java similarity index 85% rename from bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java rename to bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeConfiguration.java index 00b091d1f..866a0657f 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,14 +23,14 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.bungeecord; +package org.geysermc.geyser.platform.bungeecord; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Getter; import net.md_5.bungee.api.plugin.Plugin; -import org.geysermc.connector.FloodgateKeyLoader; -import org.geysermc.connector.configuration.GeyserJacksonConfiguration; +import org.geysermc.geyser.FloodgateKeyLoader; +import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; import java.nio.file.Path; @@ -41,10 +41,10 @@ public final class GeyserBungeeConfiguration extends GeyserJacksonConfiguration private Path floodgateKeyPath; public void loadFloodgate(GeyserBungeePlugin plugin) { - Plugin floodgate = plugin.getProxy().getPluginManager().getPlugin("floodgate-bungee"); + Plugin floodgate = plugin.getProxy().getPluginManager().getPlugin("floodgate"); Path geyserDataFolder = plugin.getDataFolder().toPath(); Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null; - floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgate, floodgateDataFolder, geyserDataFolder, plugin.getGeyserLogger()); + floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgateDataFolder, geyserDataFolder, plugin.getGeyserLogger()); } } diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeDumpInfo.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeDumpInfo.java similarity index 81% rename from bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeDumpInfo.java rename to bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeDumpInfo.java index ce2b1fc30..ded11fd91 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeDumpInfo.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeDumpInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,26 +23,25 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.bungeecord; +package org.geysermc.geyser.platform.bungeecord; import lombok.Getter; import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.plugin.Plugin; -import org.geysermc.connector.common.serializer.AsteriskSerializer; -import org.geysermc.connector.dump.BootstrapDumpInfo; +import org.geysermc.geyser.text.AsteriskSerializer; +import org.geysermc.geyser.dump.BootstrapDumpInfo; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.List; @Getter public class GeyserBungeeDumpInfo extends BootstrapDumpInfo { - - private String platformName; - private String platformVersion; - private boolean onlineMode; - private List listeners; - private List plugins; + private final String platformName; + private final String platformVersion; + private final boolean onlineMode; + private final List listeners; + private final List plugins; GeyserBungeeDumpInfo(ProxyServer proxy) { super(); @@ -63,7 +62,7 @@ public class GeyserBungeeDumpInfo extends BootstrapDumpInfo { } for (Plugin plugin : proxy.getPluginManager().getPlugins()) { - this.plugins.add(new PluginInfo(true, plugin.getDescription().getName(), plugin.getDescription().getVersion(), plugin.getDescription().getMain(), Arrays.asList(plugin.getDescription().getAuthor()))); + this.plugins.add(new PluginInfo(true, plugin.getDescription().getName(), plugin.getDescription().getVersion(), plugin.getDescription().getMain(), Collections.singletonList(plugin.getDescription().getAuthor()))); } } } diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeInjector.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeInjector.java new file mode 100644 index 000000000..389f0e37f --- /dev/null +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeInjector.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.platform.bungeecord; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.local.LocalAddress; +import io.netty.util.AttributeKey; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.config.ListenerInfo; +import net.md_5.bungee.api.event.ProxyReloadEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.event.EventHandler; +import net.md_5.bungee.netty.PipelineUtils; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.GeyserBootstrap; +import org.geysermc.geyser.network.netty.GeyserInjector; +import org.geysermc.geyser.network.netty.LocalServerChannelWrapper; +import org.geysermc.geyser.network.netty.LocalSession; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Set; + +public class GeyserBungeeInjector extends GeyserInjector implements Listener { + private final Plugin plugin; + private final ProxyServer proxy; + /** + * Set as a variable so it is only set after the proxy has finished initializing + */ + private ChannelInitializer channelInitializer = null; + private Set bungeeChannels = null; + private boolean eventRegistered = false; + + public GeyserBungeeInjector(Plugin plugin) { + this.plugin = plugin; + this.proxy = plugin.getProxy(); + } + + @Override + @SuppressWarnings("unchecked") + protected void initializeLocalChannel0(GeyserBootstrap bootstrap) throws Exception { + // TODO - allow Geyser to specify its own listener info properties + if (proxy.getConfig().getListeners().size() != 1) { + throw new UnsupportedOperationException("Geyser does not currently support multiple listeners with injection! " + + "Please reach out to us on our Discord at https://discord.gg/GeyserMC so we can hear feedback on your setup."); + } + ListenerInfo listenerInfo = proxy.getConfig().getListeners().stream().findFirst().orElseThrow(IllegalStateException::new); + + Class proxyClass = proxy.getClass(); + // Using the specified EventLoop is required, or else an error will be thrown + EventLoopGroup bossGroup; + EventLoopGroup workerGroup; + try { + EventLoopGroup eventLoops = (EventLoopGroup) proxyClass.getField("eventLoops").get(proxy); + // Netty redirects ServerBootstrap#group(EventLoopGroup) to #group(EventLoopGroup, EventLoopGroup) and uses the same event loop for both. + bossGroup = eventLoops; + workerGroup = eventLoops; + bootstrap.getGeyserLogger().debug("BungeeCord event loop style detected."); + } catch (NoSuchFieldException e) { + // Waterfall uses two separate event loops + // https://github.com/PaperMC/Waterfall/blob/fea7ec356dba6c6ac28819ff11be604af6eb484e/BungeeCord-Patches/0022-Use-a-worker-and-a-boss-event-loop-group.patch + bossGroup = (EventLoopGroup) proxyClass.getField("bossEventLoopGroup").get(proxy); + workerGroup = (EventLoopGroup) proxyClass.getField("workerEventLoopGroup").get(proxy); + bootstrap.getGeyserLogger().debug("Waterfall event loop style detected."); + } + + // Is currently just AttributeKey.valueOf("ListerInfo") but we might as well copy the value itself. + AttributeKey listener = PipelineUtils.LISTENER; + listenerInfo = new ListenerInfo( + listenerInfo.getSocketAddress(), + listenerInfo.getMotd(), + listenerInfo.getMaxPlayers(), + listenerInfo.getTabListSize(), + listenerInfo.getServerPriority(), + listenerInfo.isForceDefault(), + listenerInfo.getForcedHosts(), + listenerInfo.getTabListType(), + listenerInfo.isSetLocalAddress(), + listenerInfo.isPingPassthrough(), + listenerInfo.getQueryPort(), + listenerInfo.isQueryEnabled(), + bootstrap.getGeyserConfig().getRemote().isUseProxyProtocol() // If Geyser is expecting HAProxy, so should the Bungee end + ); + + // The field that stores all listeners in BungeeCord + // As of https://github.com/ViaVersion/ViaVersion/pull/2698 ViaVersion adds a wrapper to this field to + // add its connections + Field listenerField = proxyClass.getDeclaredField("listeners"); + listenerField.setAccessible(true); + bungeeChannels = (Set) listenerField.get(proxy); + + // This method is what initializes the connection in Java Edition, after Netty is all set. + Method initChannel = ChannelInitializer.class.getDeclaredMethod("initChannel", Channel.class); + initChannel.setAccessible(true); + + ChannelFuture channelFuture = (new ServerBootstrap() + .channel(LocalServerChannelWrapper.class) + .childHandler(new ChannelInitializer<>() { + @Override + protected void initChannel(Channel ch) throws Exception { + if (proxy.getConfig().getServers() == null) { + // Proxy hasn't finished loading all plugins - it loads the config after all plugins + // Probably doesn't need to be translatable? + bootstrap.getGeyserLogger().info("Disconnecting player as Bungee has not finished loading"); + ch.close(); + return; + } + + if (channelInitializer == null) { + // Proxy has finished initializing; we can safely grab this variable without fear of plugins modifying it + // (Older versions of ViaVersion replace this to inject) + channelInitializer = PipelineUtils.SERVER_CHILD; + } + initChannel.invoke(channelInitializer, ch); + } + }) + .childAttr(listener, listenerInfo) + .group(bossGroup, workerGroup) + .localAddress(LocalAddress.ANY)) + .bind() + .syncUninterruptibly(); + + this.localChannel = channelFuture; + this.bungeeChannels.add(this.localChannel.channel()); + this.serverSocketAddress = channelFuture.channel().localAddress(); + + if (!this.eventRegistered) { + // Register reload listener + this.proxy.getPluginManager().registerListener(this.plugin, this); + this.eventRegistered = true; + } + + // Only affects Waterfall, but there is no sure way to differentiate between a proxy with this patch and a proxy without this patch + // Patch causing the issue: https://github.com/PaperMC/Waterfall/blob/7e6af4cef64d5d377a6ffd00a534379e6efa94cf/BungeeCord-Patches/0045-Don-t-use-a-bytebuf-for-packet-decoding.patch + // If native compression is enabled, then this line is tripped up if a heap buffer is sent over in such a situation + // as a new direct buffer is not created with that patch (HeapByteBufs throw an UnsupportedOperationException here): + // https://github.com/SpigotMC/BungeeCord/blob/a283aaf724d4c9a815540cd32f3aafaa72df9e05/native/src/main/java/net/md_5/bungee/jni/zlib/NativeZlib.java#L43 + // This issue could be mitigated down the line by preventing Bungee from setting compression + LocalSession.createDirectByteBufAllocator(); + } + + @Override + public void shutdown() { + if (this.localChannel != null && this.bungeeChannels != null) { + this.bungeeChannels.remove(this.localChannel.channel()); + this.bungeeChannels = null; + } + super.shutdown(); + } + + /** + * The reload process clears the listeners field. Since we need to add to the listeners for maximum compatibility, + * we also need to re-add and re-enable our listener if a reload is initiated. + */ + @EventHandler + public void onProxyReload(ProxyReloadEvent event) { + this.bungeeChannels = null; + if (this.localChannel != null) { + shutdown(); + initializeLocalChannel(GeyserImpl.getInstance().getBootstrap()); + } + } +} diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeLogger.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeLogger.java similarity index 93% rename from bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeLogger.java rename to bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeLogger.java index e40f404c1..fbc09956c 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeLogger.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeLogger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,11 +23,11 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.bungeecord; +package org.geysermc.geyser.platform.bungeecord; import lombok.Getter; import lombok.Setter; -import org.geysermc.connector.GeyserLogger; +import org.geysermc.geyser.GeyserLogger; import java.util.logging.Level; import java.util.logging.Logger; diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeMain.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeMain.java similarity index 87% rename from bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeMain.java rename to bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeMain.java index 3da1d0937..224c500a1 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeMain.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeMain.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,11 +23,11 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.bungeecord; +package org.geysermc.geyser.platform.bungeecord; -import org.geysermc.connector.common.main.IGeyserMain; +import org.geysermc.geyser.GeyserMain; -public class GeyserBungeeMain extends IGeyserMain { +public class GeyserBungeeMain extends GeyserMain { public static void main(String[] args) { new GeyserBungeeMain().displayMessage(); diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePingPassthrough.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePingPassthrough.java similarity index 88% rename from bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePingPassthrough.java rename to bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePingPassthrough.java index 15c8fa9e0..393517aa2 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePingPassthrough.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePingPassthrough.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.bungeecord; +package org.geysermc.geyser.platform.bungeecord; import lombok.AllArgsConstructor; import net.md_5.bungee.api.ProxyServer; @@ -34,10 +34,9 @@ import net.md_5.bungee.api.connection.PendingConnection; import net.md_5.bungee.api.event.ProxyPingEvent; import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.protocol.ProtocolConstants; -import org.geysermc.connector.common.ping.GeyserPingInfo; -import org.geysermc.connector.ping.IGeyserPingPassthrough; +import org.geysermc.geyser.ping.GeyserPingInfo; +import org.geysermc.geyser.ping.IGeyserPingPassthrough; -import java.net.Inet4Address; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.Arrays; @@ -47,14 +46,12 @@ import java.util.concurrent.CompletableFuture; @AllArgsConstructor public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, Listener { - private static final GeyserPendingConnection PENDING_CONNECTION = new GeyserPendingConnection(); - private final ProxyServer proxyServer; @Override - public GeyserPingInfo getPingInformation() { + public GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) { CompletableFuture future = new CompletableFuture<>(); - proxyServer.getPluginManager().callEvent(new ProxyPingEvent(PENDING_CONNECTION, getPingInfo(), (event, throwable) -> { + proxyServer.getPluginManager().callEvent(new ProxyPingEvent(new GeyserPendingConnection(inetSocketAddress), getPingInfo(), (event, throwable) -> { if (throwable != null) future.completeExceptionally(throwable); else future.complete(event); })); @@ -66,9 +63,8 @@ public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, List new GeyserPingInfo.Version(response.getVersion().getName(), response.getVersion().getProtocol()) ); if (event.getResponse().getPlayers().getSample() != null) { - Arrays.stream(event.getResponse().getPlayers().getSample()).forEach(proxiedPlayer -> { - geyserPingInfo.getPlayerList().add(proxiedPlayer.getName()); - }); + Arrays.stream(event.getResponse().getPlayers().getSample()).forEach(proxiedPlayer -> + geyserPingInfo.getPlayerList().add(proxiedPlayer.getName())); } return geyserPingInfo; } @@ -89,7 +85,12 @@ public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, List private static class GeyserPendingConnection implements PendingConnection { private static final UUID FAKE_UUID = UUID.nameUUIDFromBytes("geyser!internal".getBytes()); - private static final InetSocketAddress FAKE_REMOTE = new InetSocketAddress(Inet4Address.getLoopbackAddress(), 69); + + private final InetSocketAddress remote; + + public GeyserPendingConnection(InetSocketAddress remote) { + this.remote = remote; + } @Override public String getName() { @@ -143,7 +144,7 @@ public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, List @Override public InetSocketAddress getAddress() { - return FAKE_REMOTE; + return remote; } @Override diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java similarity index 63% rename from bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java rename to bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java index a65646bf9..36e6ffb1d 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,27 +23,31 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.bungeecord; +package org.geysermc.geyser.platform.bungeecord; import net.md_5.bungee.api.config.ListenerInfo; import net.md_5.bungee.api.plugin.Plugin; import org.geysermc.common.PlatformType; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.bootstrap.GeyserBootstrap; -import org.geysermc.connector.command.CommandManager; -import org.geysermc.connector.configuration.GeyserConfiguration; -import org.geysermc.connector.dump.BootstrapDumpInfo; -import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; -import org.geysermc.connector.ping.IGeyserPingPassthrough; -import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.LanguageUtils; -import org.geysermc.platform.bungeecord.command.GeyserBungeeCommandExecutor; -import org.geysermc.platform.bungeecord.command.GeyserBungeeCommandManager; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.GeyserBootstrap; +import org.geysermc.geyser.command.CommandManager; +import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.dump.BootstrapDumpInfo; +import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; +import org.geysermc.geyser.ping.IGeyserPingPassthrough; +import org.geysermc.geyser.util.FileUtils; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.platform.bungeecord.command.GeyserBungeeCommandExecutor; +import org.geysermc.geyser.platform.bungeecord.command.GeyserBungeeCommandManager; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.UUID; import java.util.logging.Level; @@ -51,24 +55,29 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { private GeyserBungeeCommandManager geyserCommandManager; private GeyserBungeeConfiguration geyserConfig; + private GeyserBungeeInjector geyserInjector; private GeyserBungeeLogger geyserLogger; private IGeyserPingPassthrough geyserBungeePingPassthrough; - private GeyserConnector connector; + private GeyserImpl geyser; @Override public void onEnable() { + GeyserLocale.init(this); + if (!getDataFolder().exists()) getDataFolder().mkdir(); try { if (!getDataFolder().exists()) getDataFolder().mkdir(); - File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); + File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), + "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); this.geyserConfig = FileUtils.loadConfig(configFile, GeyserBungeeConfiguration.class); } catch (IOException ex) { - getLogger().log(Level.WARNING, LanguageUtils.getLocaleStringLog("geyser.config.failed"), ex); + getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); ex.printStackTrace(); + return; } if (getProxy().getConfig().getListeners().size() == 1) { @@ -94,33 +103,47 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - if (geyserConfig.getRemote().getAuthType().equals("floodgate") && getProxy().getPluginManager().getPlugin("floodgate-bungee") == null) { - geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); + // Remove this in like a year + if (getProxy().getPluginManager().getPlugin("floodgate-bungee") != null) { + geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.outdated", "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/")); return; - } else if (geyserConfig.isAutoconfiguredRemote() && getProxy().getPluginManager().getPlugin("floodgate-bungee") != null) { + } + + if (geyserConfig.getRemote().getAuthType() == AuthType.FLOODGATE && getProxy().getPluginManager().getPlugin("floodgate") == null) { + geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); + return; + } else if (geyserConfig.isAutoconfiguredRemote() && getProxy().getPluginManager().getPlugin("floodgate") != null) { // Floodgate installed means that the user wants Floodgate authentication geyserLogger.debug("Auto-setting to Floodgate authentication."); - geyserConfig.getRemote().setAuthType("floodgate"); + geyserConfig.getRemote().setAuthType(AuthType.FLOODGATE); } geyserConfig.loadFloodgate(this); - this.connector = GeyserConnector.start(PlatformType.BUNGEECORD, this); + this.geyser = GeyserImpl.start(PlatformType.BUNGEECORD, this); - this.geyserCommandManager = new GeyserBungeeCommandManager(connector); + this.geyserInjector = new GeyserBungeeInjector(this); + this.geyserInjector.initializeLocalChannel(this); + + this.geyserCommandManager = new GeyserBungeeCommandManager(geyser); if (geyserConfig.isLegacyPingPassthrough()) { - this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(connector); + this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser); } else { this.geyserBungeePingPassthrough = new GeyserBungeePingPassthrough(getProxy()); } - this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor(connector)); + this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor(geyser)); } @Override public void onDisable() { - connector.shutdown(); + if (geyser != null) { + geyser.shutdown(); + } + if (geyserInjector != null) { + geyserInjector.shutdown(); + } } @Override @@ -152,4 +175,15 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { public BootstrapDumpInfo getDumpInfo() { return new GeyserBungeeDumpInfo(getProxy()); } + + @Override + public Path getLogsPath() { + return Paths.get(getProxy().getName().equals("BungeeCord") ? "proxy.log.0" : "logs/latest.log"); + } + + @Nullable + @Override + public SocketAddress getSocketAddress() { + return this.geyserInjector.getServerSocketAddress(); + } } diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/BungeeCommandSender.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSender.java similarity index 77% rename from bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/BungeeCommandSender.java rename to bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSender.java index 3ad8b54fd..7dc04f95b 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/BungeeCommandSender.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSender.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,12 +23,12 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.bungeecord.command; +package org.geysermc.geyser.platform.bungeecord.command; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.connection.ProxiedPlayer; -import org.geysermc.connector.command.CommandSender; -import org.geysermc.connector.utils.LanguageUtils; +import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.text.GeyserLocale; public class BungeeCommandSender implements CommandSender { @@ -37,11 +37,11 @@ public class BungeeCommandSender implements CommandSender { public BungeeCommandSender(net.md_5.bungee.api.CommandSender handle) { this.handle = handle; // Ensure even Java players' languages are loaded - LanguageUtils.loadGeyserLocale(getLocale()); + GeyserLocale.loadGeyserLocale(getLocale()); } @Override - public String getName() { + public String name() { return handle.getName(); } @@ -57,11 +57,15 @@ public class BungeeCommandSender implements CommandSender { @Override public String getLocale() { - if (handle instanceof ProxiedPlayer) { - ProxiedPlayer player = (ProxiedPlayer) handle; + if (handle instanceof ProxiedPlayer player) { String locale = player.getLocale().getLanguage() + "_" + player.getLocale().getCountry(); - return LanguageUtils.formatLocale(locale); + return GeyserLocale.formatLocale(locale); } - return LanguageUtils.getDefaultLocale(); + return GeyserLocale.getDefaultLocale(); + } + + @Override + public boolean hasPermission(String permission) { + return handle.hasPermission(permission); } } diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandExecutor.java similarity index 51% rename from bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java rename to bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandExecutor.java index c25da0869..f0a05687c 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,56 +23,63 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.bungeecord.command; +package org.geysermc.geyser.platform.bungeecord.command; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.plugin.Command; import net.md_5.bungee.api.plugin.TabExecutor; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.GeyserCommand; -import org.geysermc.connector.utils.LanguageUtils; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandExecutor; +import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.GeyserLocale; -import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; public class GeyserBungeeCommandExecutor extends Command implements TabExecutor { + private final CommandExecutor commandExecutor; - private final GeyserConnector connector; - - public GeyserBungeeCommandExecutor(GeyserConnector connector) { + public GeyserBungeeCommandExecutor(GeyserImpl geyser) { super("geyser"); - this.connector = connector; + this.commandExecutor = new CommandExecutor(geyser); } @Override public void execute(CommandSender sender, String[] args) { + BungeeCommandSender commandSender = new BungeeCommandSender(sender); + GeyserSession session = this.commandExecutor.getGeyserSession(commandSender); + if (args.length > 0) { - if (getCommand(args[0]) != null) { - if (!sender.hasPermission(getCommand(args[0]).getPermission())) { - BungeeCommandSender commandSender = new BungeeCommandSender(sender); - String message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.getLocale()); + GeyserCommand command = this.commandExecutor.getCommand(args[0]); + if (command != null) { + if (!sender.hasPermission(command.getPermission())) { + String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.getLocale()); commandSender.sendMessage(ChatColor.RED + message); return; } - getCommand(args[0]).execute(new BungeeCommandSender(sender), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); + if (command.isBedrockOnly() && session == null) { + String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", commandSender.getLocale()); + + commandSender.sendMessage(ChatColor.RED + message); + return; + } + command.execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); } } else { - getCommand("help").execute(new BungeeCommandSender(sender), new String[0]); + this.commandExecutor.getCommand("help").execute(session, commandSender, new String[0]); } } @Override public Iterable onTabComplete(CommandSender sender, String[] args) { if (args.length == 1) { - return connector.getCommandManager().getCommandNames(); + return commandExecutor.tabComplete(new BungeeCommandSender(sender)); + } else { + return Collections.emptyList(); } - return new ArrayList<>(); - } - - private GeyserCommand getCommand(String label) { - return connector.getCommandManager().getCommands().get(label); } } diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandManager.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandManager.java similarity index 81% rename from bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandManager.java rename to bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandManager.java index bb79c5779..06e5da71e 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandManager.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,15 +23,15 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.bungeecord.command; +package org.geysermc.geyser.platform.bungeecord.command; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.CommandManager; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandManager; public class GeyserBungeeCommandManager extends CommandManager { - public GeyserBungeeCommandManager(GeyserConnector connector) { - super(connector); + public GeyserBungeeCommandManager(GeyserImpl geyser) { + super(geyser); } @Override diff --git a/bootstrap/bungeecord/src/main/resources/bungee.yml b/bootstrap/bungeecord/src/main/resources/bungee.yml index 8781ea31b..7390a4623 100644 --- a/bootstrap/bungeecord/src/main/resources/bungee.yml +++ b/bootstrap/bungeecord/src/main/resources/bungee.yml @@ -1,4 +1,4 @@ -main: org.geysermc.platform.bungeecord.GeyserBungeePlugin +main: org.geysermc.geyser.platform.bungeecord.GeyserBungeePlugin name: ${outputName}-BungeeCord author: ${project.organization.name} website: ${project.organization.url} diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index a5ad53cb5..3b0bdda55 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 1.2.0-SNAPSHOT + 2.0.0-SNAPSHOT bootstrap-parent pom @@ -16,13 +16,9 @@ spigot-public https://hub.spigotmc.org/nexus/content/repositories/public/ - - bukkit-public - https://repo.md-5.net/content/repositories/public/ - sponge-repo - https://repo.spongepowered.org/maven + https://repo.spongepowered.org/repository/maven-public/ bungeecord-repo @@ -33,6 +29,16 @@ https://repo.velocitypowered.com/snapshots/ + + + + org.geysermc + ap + 2.0.0-SNAPSHOT + provided + + + bungeecord spigot diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index f3b9cf88d..5d17d2619 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -6,15 +6,22 @@ org.geysermc bootstrap-parent - 1.2.0-SNAPSHOT + 2.0.0-SNAPSHOT bootstrap-spigot + + + viaversion-repo + https://repo.viaversion.com + + + org.geysermc - connector - 1.2.0-SNAPSHOT + core + 2.0.0-SNAPSHOT compile @@ -24,11 +31,16 @@ provided - us.myles + com.viaversion viaversion - 3.2.0 + 4.0.0 provided + + org.geysermc.geyser.adapters + spigot-all + 1.3-SNAPSHOT + ${outputName}-Spigot @@ -46,7 +58,7 @@ - org.geysermc.platform.spigot.GeyserSpigotMain + org.geysermc.geyser.platform.spigot.GeyserSpigotMain @@ -54,7 +66,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.1 + 3.3.0-SNAPSHOT package @@ -63,37 +75,21 @@ - - io.netty - org.geysermc.platform.spigot.shaded.netty - it.unimi.dsi.fastutil - org.geysermc.platform.spigot.shaded.fastutil + org.geysermc.geyser.platform.spigot.shaded.fastutil com.fasterxml.jackson - org.geysermc.platform.spigot.shaded.jackson + org.geysermc.geyser.platform.spigot.shaded.jackson - org.reflections - org.geysermc.platform.spigot.shaded.reflections + net.kyori + org.geysermc.geyser.platform.spigot.shaded.kyori - com.google.common - org.geysermc.platform.spigot.shaded.google.common - - - com.google.guava - org.geysermc.platform.spigot.shaded.google.guava - - - org.dom4j - org.geysermc.platform.spigot.shaded.dom4j - - - net.kyori.adventure - org.geysermc.platform.spigot.shaded.adventure + org.objectweb.asm + org.geysermc.geyser.platform.spigot.shaded.asm @@ -102,8 +98,22 @@ - com.google.code.gson:* + com.google.*:* org.yaml:* + + + io.netty:netty-transport-native-epoll:* + io.netty:netty-transport-native-unix-common:* + io.netty:netty-transport-native-kqueue:* + io.netty:netty-handler:* + io.netty:netty-common:* + io.netty:netty-buffer:* + io.netty:netty-resolver:* + io.netty:netty-transport:* + io.netty:netty-codec:* + io.netty:netty-codec-dns:* + io.netty:netty-resolver-dns:* + io.netty:netty-resolver-dns-native-macos:* diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotConfiguration.java similarity index 80% rename from bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java rename to bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotConfiguration.java index 5c48efe88..60bc05f13 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,15 +23,15 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.spigot; +package org.geysermc.geyser.platform.spigot; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Getter; import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; -import org.geysermc.connector.FloodgateKeyLoader; -import org.geysermc.connector.configuration.GeyserJacksonConfiguration; +import org.geysermc.geyser.FloodgateKeyLoader; +import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; import java.nio.file.Path; @@ -42,15 +42,10 @@ public final class GeyserSpigotConfiguration extends GeyserJacksonConfiguration private Path floodgateKeyPath; public void loadFloodgate(GeyserSpigotPlugin plugin) { - Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate-bukkit"); + Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate"); Path geyserDataFolder = plugin.getDataFolder().toPath(); Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null; - floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgate, floodgateDataFolder, geyserDataFolder, plugin.getGeyserLogger()); - } - - @Override - public boolean isCacheChunks() { - return true; // We override this as with Bukkit, we have direct access to the server implementation + floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgateDataFolder, geyserDataFolder, plugin.getGeyserLogger()); } } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotDumpInfo.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotDumpInfo.java similarity index 82% rename from bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotDumpInfo.java rename to bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotDumpInfo.java index 71134b6b4..189ca2356 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotDumpInfo.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotDumpInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,13 +23,13 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.spigot; +package org.geysermc.geyser.platform.spigot; import lombok.Getter; import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; -import org.geysermc.connector.common.serializer.AsteriskSerializer; -import org.geysermc.connector.dump.BootstrapDumpInfo; +import org.geysermc.geyser.text.AsteriskSerializer; +import org.geysermc.geyser.dump.BootstrapDumpInfo; import java.util.ArrayList; import java.util.List; @@ -37,13 +37,13 @@ import java.util.List; @Getter public class GeyserSpigotDumpInfo extends BootstrapDumpInfo { - private String platformName; - private String platformVersion; - private String platformAPIVersion; - private boolean onlineMode; - private String serverIP; - private int serverPort; - private List plugins; + private final String platformName; + private final String platformVersion; + private final String platformAPIVersion; + private final boolean onlineMode; + private final String serverIP; + private final int serverPort; + private final List plugins; GeyserSpigotDumpInfo() { super(); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java new file mode 100644 index 000000000..7f390f9d8 --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.platform.spigot; + +import com.viaversion.viaversion.bukkit.handlers.BukkitChannelInitializer; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.*; +import io.netty.channel.local.LocalAddress; +import io.netty.util.concurrent.DefaultThreadFactory; +import org.bukkit.Bukkit; +import org.geysermc.geyser.GeyserBootstrap; +import org.geysermc.geyser.network.netty.GeyserInjector; +import org.geysermc.geyser.network.netty.LocalServerChannelWrapper; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.util.List; + +public class GeyserSpigotInjector extends GeyserInjector { + /** + * Used to determine if ViaVersion is setup to a state where Geyser players will fail at joining if injection is enabled + */ + private final boolean isViaVersion; + /** + * Used to uninject ourselves on shutdown. + */ + private List allServerChannels; + + public GeyserSpigotInjector(boolean isViaVersion) { + this.isViaVersion = isViaVersion; + } + + @Override + @SuppressWarnings("unchecked") + protected void initializeLocalChannel0(GeyserBootstrap bootstrap) throws Exception { + Class serverClazz; + try { + serverClazz = Class.forName("net.minecraft.server.MinecraftServer"); + // We're using 1.17+ + } catch (ClassNotFoundException e) { + // We're using pre-1.17 + String prefix = Bukkit.getServer().getClass().getPackage().getName().replace("org.bukkit.craftbukkit", "net.minecraft.server"); + serverClazz = Class.forName(prefix + ".MinecraftServer"); + } + Method getServer = serverClazz.getDeclaredMethod("getServer"); + Object server = getServer.invoke(null); + Object connection = null; + // Find the class that manages network IO + for (Method m : serverClazz.getDeclaredMethods()) { + if (m.getReturnType() != null) { + // First is Spigot-mapped name, second is Mojang-mapped name which is implemented as future-proofing + if (m.getReturnType().getSimpleName().equals("ServerConnection") || m.getReturnType().getSimpleName().equals("ServerConnectionListener")) { + if (m.getParameterTypes().length == 0) { + connection = m.invoke(server); + } + } + } + } + if (connection == null) { + throw new RuntimeException("Unable to find ServerConnection class!"); + } + + // Find the channel that Minecraft uses to listen to connections + ChannelFuture listeningChannel = null; + for (Field field : connection.getClass().getDeclaredFields()) { + if (field.getType() != List.class) { + continue; + } + field.setAccessible(true); + boolean rightList = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0] == ChannelFuture.class; + if (!rightList) continue; + + allServerChannels = (List) field.get(connection); + for (ChannelFuture o : allServerChannels) { + listeningChannel = o; + break; + } + } + if (listeningChannel == null) { + throw new RuntimeException("Unable to find listening channel!"); + } + + // Making this a function prevents childHandler from being treated as a non-final variable + ChannelInitializer childHandler = getChildHandler(bootstrap, listeningChannel); + // This method is what initializes the connection in Java Edition, after Netty is all set. + Method initChannel = childHandler.getClass().getDeclaredMethod("initChannel", Channel.class); + initChannel.setAccessible(true); + + ChannelFuture channelFuture = (new ServerBootstrap() + .channel(LocalServerChannelWrapper.class) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + initChannel.invoke(childHandler, ch); + } + }) + // Set to MAX_PRIORITY as MultithreadEventLoopGroup#newDefaultThreadFactory which DefaultEventLoopGroup implements does by default + .group(new DefaultEventLoopGroup(0, new DefaultThreadFactory("Geyser Spigot connection thread", Thread.MAX_PRIORITY))) + .localAddress(LocalAddress.ANY)) + .bind() + .syncUninterruptibly(); + // We don't need to add to the list, but plugins like ProtocolSupport and ProtocolLib that add to the main pipeline + // will work when we add to the list. + allServerChannels.add(channelFuture); + this.localChannel = channelFuture; + this.serverSocketAddress = channelFuture.channel().localAddress(); + } + + @SuppressWarnings("unchecked") + private ChannelInitializer getChildHandler(GeyserBootstrap bootstrap, ChannelFuture listeningChannel) { + List names = listeningChannel.channel().pipeline().names(); + ChannelInitializer childHandler = null; + for (String name : names) { + ChannelHandler handler = listeningChannel.channel().pipeline().get(name); + try { + Field childHandlerField = handler.getClass().getDeclaredField("childHandler"); + childHandlerField.setAccessible(true); + childHandler = (ChannelInitializer) childHandlerField.get(handler); + // ViaVersion non-Paper-injector workaround so we aren't double-injecting + if (isViaVersion && childHandler instanceof BukkitChannelInitializer) { + childHandler = ((BukkitChannelInitializer) childHandler).getOriginal(); + } + break; + } catch (Exception e) { + if (bootstrap.getGeyserConfig().isDebugMode()) { + bootstrap.getGeyserLogger().debug("The handler " + name + " isn't a ChannelInitializer. THIS ERROR IS SAFE TO IGNORE!"); + e.printStackTrace(); + } + } + } + if (childHandler == null) { + throw new RuntimeException(); + } + return childHandler; + } + + @Override + public void shutdown() { + if (this.allServerChannels != null) { + this.allServerChannels.remove(this.localChannel); + this.allServerChannels = null; + } + super.shutdown(); + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotLogger.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotLogger.java similarity index 93% rename from bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotLogger.java rename to bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotLogger.java index b462f1f1c..98c85977f 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotLogger.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotLogger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,12 +23,12 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.spigot; +package org.geysermc.geyser.platform.spigot; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; -import org.geysermc.connector.GeyserLogger; +import org.geysermc.geyser.GeyserLogger; import java.util.logging.Level; import java.util.logging.Logger; diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotMain.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotMain.java similarity index 87% rename from bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotMain.java rename to bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotMain.java index 49c0339c9..5d18e4f94 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotMain.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotMain.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,11 +23,11 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.spigot; +package org.geysermc.geyser.platform.spigot; -import org.geysermc.connector.common.main.IGeyserMain; +import org.geysermc.geyser.GeyserMain; -public class GeyserSpigotMain extends IGeyserMain { +public class GeyserSpigotMain extends GeyserMain { public static void main(String[] args) { new GeyserSpigotMain().displayMessage(); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPingPassthrough.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPingPassthrough.java similarity index 79% rename from bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPingPassthrough.java rename to bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPingPassthrough.java index 7196f4290..a6ec9e329 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPingPassthrough.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPingPassthrough.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,18 +23,20 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.spigot; +package org.geysermc.geyser.platform.spigot; -import com.github.steveice10.mc.protocol.MinecraftConstants; import lombok.AllArgsConstructor; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.server.ServerListPingEvent; import org.bukkit.util.CachedServerIcon; -import org.geysermc.connector.common.ping.GeyserPingInfo; -import org.geysermc.connector.ping.IGeyserPingPassthrough; +import org.geysermc.geyser.ping.GeyserPingInfo; +import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.ping.IGeyserPingPassthrough; +import javax.annotation.Nonnull; import java.net.InetAddress; +import java.net.InetSocketAddress; import java.util.Collections; import java.util.Iterator; @@ -44,13 +46,13 @@ public class GeyserSpigotPingPassthrough implements IGeyserPingPassthrough { private final GeyserSpigotLogger logger; @Override - public GeyserPingInfo getPingInformation() { + public GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) { try { - ServerListPingEvent event = new GeyserPingEvent(InetAddress.getLocalHost(), Bukkit.getMotd(), Bukkit.getOnlinePlayers().size(), Bukkit.getMaxPlayers()); + ServerListPingEvent event = new GeyserPingEvent(inetSocketAddress.getAddress(), Bukkit.getMotd(), Bukkit.getOnlinePlayers().size(), Bukkit.getMaxPlayers()); Bukkit.getPluginManager().callEvent(event); GeyserPingInfo geyserPingInfo = new GeyserPingInfo(event.getMotd(), new GeyserPingInfo.Players(event.getMaxPlayers(), event.getNumPlayers()), - new GeyserPingInfo.Version(Bukkit.getVersion(), MinecraftConstants.PROTOCOL_VERSION) // thanks Spigot for not exposing this, just default to latest + new GeyserPingInfo.Version(Bukkit.getVersion(), MinecraftProtocol.getJavaProtocolVersion()) // thanks Spigot for not exposing this, just default to latest ); Bukkit.getOnlinePlayers().stream().map(Player::getName).forEach(geyserPingInfo.getPlayerList()::add); return geyserPingInfo; @@ -71,9 +73,10 @@ public class GeyserSpigotPingPassthrough implements IGeyserPingPassthrough { public void setServerIcon(CachedServerIcon icon) throws IllegalArgumentException, UnsupportedOperationException { } + @Nonnull @Override public Iterator iterator() throws UnsupportedOperationException { - return Collections.EMPTY_LIST.iterator(); + return Collections.emptyIterator(); } } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java new file mode 100644 index 000000000..12a27190d --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java @@ -0,0 +1,365 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.platform.spigot; + +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.data.MappingData; +import com.viaversion.viaversion.api.protocol.ProtocolPathEntry; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; +import org.bukkit.Bukkit; +import org.bukkit.plugin.java.JavaPlugin; +import org.geysermc.common.PlatformType; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.GeyserBootstrap; +import org.geysermc.geyser.command.CommandManager; +import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.dump.BootstrapDumpInfo; +import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.level.WorldManager; +import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; +import org.geysermc.geyser.ping.IGeyserPingPassthrough; +import org.geysermc.geyser.Constants; +import org.geysermc.geyser.util.FileUtils; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.adapters.spigot.SpigotAdapters; +import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandExecutor; +import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandManager; +import org.geysermc.geyser.platform.spigot.command.SpigotCommandSender; +import org.geysermc.geyser.platform.spigot.world.GeyserPistonListener; +import org.geysermc.geyser.platform.spigot.world.GeyserSpigot1_11CraftingListener; +import org.geysermc.geyser.platform.spigot.world.GeyserSpigotBlockPlaceListener; +import org.geysermc.geyser.platform.spigot.world.manager.*; + +import java.io.File; +import java.io.IOException; +import java.net.SocketAddress; +import java.nio.file.Path; +import java.util.List; +import java.util.UUID; +import java.util.logging.Level; + +public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { + private GeyserSpigotCommandManager geyserCommandManager; + private GeyserSpigotConfiguration geyserConfig; + private GeyserSpigotInjector geyserInjector; + private GeyserSpigotLogger geyserLogger; + private IGeyserPingPassthrough geyserSpigotPingPassthrough; + private GeyserSpigotWorldManager geyserWorldManager; + + private GeyserImpl geyser; + + /** + * The Minecraft server version, formatted as 1.#.# + */ + private String minecraftVersion; + + @Override + public void onEnable() { + GeyserLocale.init(this); + + // This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed + try { + if (!getDataFolder().exists()) { + getDataFolder().mkdir(); + } + File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml", + (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); + this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpigotConfiguration.class); + } catch (IOException ex) { + getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); + ex.printStackTrace(); + Bukkit.getPluginManager().disablePlugin(this); + return; + } + + try { + // Required for the Cloudburst Network dependency to initialize. + Class.forName("io.netty.channel.kqueue.KQueue"); + } catch (ClassNotFoundException e) { + // While we could support these older versions, the downside is not having KQueue working at all + // And since there are alternative ways to get Geyser working for these aging platforms, it's not worth it. + getLogger().severe("*********************************************"); + getLogger().severe(""); + getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.header")); + getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.message", "1.12.2")); + getLogger().severe(""); + getLogger().severe("*********************************************"); + + Bukkit.getPluginManager().disablePlugin(this); + return; + } + + // By default this should be localhost but may need to be changed in some circumstances + if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { + geyserConfig.setAutoconfiguredRemote(true); + // Don't use localhost if not listening on all interfaces + if (!Bukkit.getIp().equals("0.0.0.0") && !Bukkit.getIp().equals("")) { + geyserConfig.getRemote().setAddress(Bukkit.getIp()); + } + geyserConfig.getRemote().setPort(Bukkit.getPort()); + } + + if (geyserConfig.getBedrock().isCloneRemotePort()) { + geyserConfig.getBedrock().setPort(Bukkit.getPort()); + } + + this.geyserLogger = new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode()); + GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); + + // Remove this in like a year + if (Bukkit.getPluginManager().getPlugin("floodgate-bukkit") != null) { + geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.outdated", Constants.FLOODGATE_DOWNLOAD_LOCATION)); + this.getPluginLoader().disablePlugin(this); + return; + } + + if (geyserConfig.getRemote().getAuthType() == AuthType.FLOODGATE && Bukkit.getPluginManager().getPlugin("floodgate") == null) { + geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); + this.getPluginLoader().disablePlugin(this); + return; + } else if (geyserConfig.isAutoconfiguredRemote() && Bukkit.getPluginManager().getPlugin("floodgate") != null) { + // Floodgate installed means that the user wants Floodgate authentication + geyserLogger.debug("Auto-setting to Floodgate authentication."); + geyserConfig.getRemote().setAuthType(AuthType.FLOODGATE); + } + + geyserConfig.loadFloodgate(this); + + // Turn "(MC: 1.16.4)" into 1.16.4. + this.minecraftVersion = Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0]; + + this.geyser = GeyserImpl.start(PlatformType.SPIGOT, this); + + if (geyserConfig.isLegacyPingPassthrough()) { + this.geyserSpigotPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); + } else { + this.geyserSpigotPingPassthrough = new GeyserSpigotPingPassthrough(geyserLogger); + } + + this.geyserCommandManager = new GeyserSpigotCommandManager(geyser); + + boolean isViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null; + if (isViaVersion) { + try { + // Ensure that we have the latest 4.0.0 changes and not an older ViaVersion version + Class.forName("com.viaversion.viaversion.api.ViaManager"); + } catch (ClassNotFoundException e) { + geyserLogger.warning(GeyserLocale.getLocaleStringLog("geyser.bootstrap.viaversion.too_old", + "https://ci.viaversion.com/job/ViaVersion/")); + isViaVersion = false; + if (this.geyserConfig.isDebugMode()) { + e.printStackTrace(); + } + } + } + // Used to determine if Block.getBlockData() is present. + boolean isLegacy = !isCompatible(Bukkit.getServer().getVersion(), "1.13.0"); + if (isLegacy) + geyserLogger.debug("Legacy version of Minecraft (1.12.2 or older) detected; falling back to ViaVersion for block state retrieval."); + + boolean isPre1_12 = !isCompatible(Bukkit.getServer().getVersion(), "1.12.0"); + // Set if we need to use a different method for getting a player's locale + SpigotCommandSender.setUseLegacyLocaleMethod(isPre1_12); + + // We want to do this late in the server startup process to allow plugins such as ViaVersion and ProtocolLib + // To do their job injecting, then connect into *that* + this.geyserInjector = new GeyserSpigotInjector(isViaVersion); + this.geyserInjector.initializeLocalChannel(this); + + if (Boolean.parseBoolean(System.getProperty("Geyser.UseDirectAdapters", "true"))) { + try { + String name = Bukkit.getServer().getClass().getPackage().getName(); + String nmsVersion = name.substring(name.lastIndexOf('.') + 1); + SpigotAdapters.registerWorldAdapter(nmsVersion); + if (isViaVersion && isViaVersionNeeded()) { + if (isLegacy) { + // Pre-1.13 + this.geyserWorldManager = new GeyserSpigot1_12NativeWorldManager(this); + } else { + // Post-1.13 + this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this); + } + } else { + // No ViaVersion + this.geyserWorldManager = new GeyserSpigotNativeWorldManager(this); + } + geyserLogger.debug("Using NMS adapter: " + this.geyserWorldManager.getClass() + ", " + nmsVersion); + } catch (Exception e) { + if (geyserConfig.isDebugMode()) { + geyserLogger.debug("Error while attempting to find NMS adapter. Most likely, this can be safely ignored. :)"); + e.printStackTrace(); + } + } + } else { + geyserLogger.debug("Not using NMS adapter as it is disabled via system property."); + } + if (this.geyserWorldManager == null) { + // No NMS adapter + if (isLegacy && isViaVersion) { + // Use ViaVersion for converting pre-1.13 block states + this.geyserWorldManager = new GeyserSpigot1_12WorldManager(this); + } else if (isLegacy) { + // Not sure how this happens - without ViaVersion, we don't know any block states, so just assume everything is air + this.geyserWorldManager = new GeyserSpigotFallbackWorldManager(this); + } else { + // Post-1.13 + this.geyserWorldManager = new GeyserSpigotWorldManager(this); + } + geyserLogger.debug("Using default world manager: " + this.geyserWorldManager.getClass()); + } + GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager); + Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this); + + Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this); + + if (isPre1_12) { + // Register events needed to send all recipes to the client + Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigot1_11CraftingListener(geyser), this); + } + + this.getCommand("geyser").setExecutor(new GeyserSpigotCommandExecutor(geyser)); + } + + @Override + public void onDisable() { + if (geyser != null) { + geyser.shutdown(); + } + if (geyserInjector != null) { + geyserInjector.shutdown(); + } + } + + @Override + public GeyserSpigotConfiguration getGeyserConfig() { + return geyserConfig; + } + + @Override + public GeyserSpigotLogger getGeyserLogger() { + return geyserLogger; + } + + @Override + public CommandManager getGeyserCommandManager() { + return this.geyserCommandManager; + } + + @Override + public IGeyserPingPassthrough getGeyserPingPassthrough() { + return geyserSpigotPingPassthrough; + } + + @Override + public WorldManager getWorldManager() { + return this.geyserWorldManager; + } + + @Override + public Path getConfigFolder() { + return getDataFolder().toPath(); + } + + @Override + public BootstrapDumpInfo getDumpInfo() { + return new GeyserSpigotDumpInfo(); + } + + @Override + public String getMinecraftServerVersion() { + return this.minecraftVersion; + } + + @Override + public SocketAddress getSocketAddress() { + return this.geyserInjector.getServerSocketAddress(); + } + + public boolean isCompatible(String version, String whichVersion) { + int[] currentVersion = parseVersion(version); + int[] otherVersion = parseVersion(whichVersion); + int length = Math.max(currentVersion.length, otherVersion.length); + for (int index = 0; index < length; index = index + 1) { + int self = (index < currentVersion.length) ? currentVersion[index] : 0; + int other = (index < otherVersion.length) ? otherVersion[index] : 0; + + if (self != other) { + return (self - other) > 0; + } + } + return true; + } + + private int[] parseVersion(String versionParam) { + versionParam = (versionParam == null) ? "" : versionParam; + if (versionParam.contains("(MC: ")) { + versionParam = versionParam.split("\\(MC: ")[1]; + versionParam = versionParam.split("\\)")[0]; + } + String[] stringArray = versionParam.split("[_.-]"); + int[] temp = new int[stringArray.length]; + for (int index = 0; index <= (stringArray.length - 1); index = index + 1) { + String t = stringArray[index].replaceAll("\\D", ""); + try { + temp[index] = Integer.parseInt(t); + } catch (NumberFormatException ex) { + temp[index] = 0; + } + } + return temp; + } + + /** + * @return the server version before ViaVersion finishes initializing + */ + public ProtocolVersion getServerProtocolVersion() { + return ProtocolVersion.getClosest(this.minecraftVersion); + } + + /** + * This function should not run unless ViaVersion is installed on the server. + * + * @return true if there is any block mappings difference between the server and client. + */ + private boolean isViaVersionNeeded() { + ProtocolVersion serverVersion = getServerProtocolVersion(); + List protocolList = Via.getManager().getProtocolManager().getProtocolPath(MinecraftProtocol.getJavaProtocolVersion(), + serverVersion.getVersion()); + if (protocolList == null) { + // No translation needed! + return false; + } + for (int i = protocolList.size() - 1; i >= 0; i--) { + MappingData mappingData = protocolList.get(i).getProtocol().getMappingData(); + if (mappingData != null) { + return true; + } + } + // All mapping data is null, which means client and server block states are the same + return false; + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandExecutor.java similarity index 54% rename from bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java rename to bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandExecutor.java index 1ff3e7fe5..af92091e5 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,42 +23,51 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.spigot.command; +package org.geysermc.geyser.platform.spigot.command; -import lombok.AllArgsConstructor; import org.bukkit.ChatColor; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.command.TabExecutor; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.GeyserCommand; -import org.geysermc.connector.utils.LanguageUtils; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandExecutor; +import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.GeyserLocale; -import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; -@AllArgsConstructor -public class GeyserSpigotCommandExecutor implements TabExecutor { +public class GeyserSpigotCommandExecutor extends CommandExecutor implements TabExecutor { - private final GeyserConnector connector; + public GeyserSpigotCommandExecutor(GeyserImpl geyser) { + super(geyser); + } @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + SpigotCommandSender commandSender = new SpigotCommandSender(sender); + GeyserSession session = getGeyserSession(commandSender); + if (args.length > 0) { - if (getCommand(args[0]) != null) { - if (!sender.hasPermission(getCommand(args[0]).getPermission())) { - SpigotCommandSender commandSender = new SpigotCommandSender(sender); - String message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.getLocale());; + GeyserCommand geyserCommand = getCommand(args[0]); + if (geyserCommand != null) { + if (!sender.hasPermission(geyserCommand.getPermission())) { + String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.getLocale()); commandSender.sendMessage(ChatColor.RED + message); return true; } - getCommand(args[0]).execute(new SpigotCommandSender(sender), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); + if (geyserCommand.isBedrockOnly() && session == null) { + sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", commandSender.getLocale())); + return true; + } + geyserCommand.execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); return true; } } else { - getCommand("help").execute(new SpigotCommandSender(sender), new String[0]); + getCommand("help").execute(session, commandSender, new String[0]); return true; } return true; @@ -67,12 +76,8 @@ public class GeyserSpigotCommandExecutor implements TabExecutor { @Override public List onTabComplete(CommandSender sender, Command command, String label, String[] args) { if (args.length == 1) { - return connector.getCommandManager().getCommandNames(); + return tabComplete(new SpigotCommandSender(sender)); } - return new ArrayList<>(); - } - - private GeyserCommand getCommand(String label) { - return connector.getCommandManager().getCommands().get(label); + return Collections.emptyList(); } } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandManager.java similarity index 81% rename from bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandManager.java rename to bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandManager.java index 2fbec1562..13ba3691c 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,14 +23,13 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.spigot.command; +package org.geysermc.geyser.platform.spigot.command; import org.bukkit.Bukkit; import org.bukkit.command.Command; import org.bukkit.command.CommandMap; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.CommandManager; -import org.geysermc.platform.spigot.GeyserSpigotPlugin; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandManager; import java.lang.reflect.Field; @@ -48,12 +47,8 @@ public class GeyserSpigotCommandManager extends CommandManager { } } - private GeyserSpigotPlugin plugin; - - public GeyserSpigotCommandManager(GeyserSpigotPlugin plugin, GeyserConnector connector) { - super(connector); - - this.plugin = plugin; + public GeyserSpigotCommandManager(GeyserImpl geyser) { + super(geyser); } @Override diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/SpigotCommandSender.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSender.java similarity index 83% rename from bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/SpigotCommandSender.java rename to bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSender.java index 93a500669..7fbaac4f8 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/SpigotCommandSender.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSender.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,13 +23,13 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.spigot.command; +package org.geysermc.geyser.platform.spigot.command; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Player; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.CommandSender; -import org.geysermc.connector.utils.LanguageUtils; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.text.GeyserLocale; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -50,11 +50,11 @@ public class SpigotCommandSender implements CommandSender { this.handle = handle; this.locale = getSpigotLocale(); // Ensure even Java players' languages are loaded - LanguageUtils.loadGeyserLocale(locale); + GeyserLocale.loadGeyserLocale(locale); } @Override - public String getName() { + public String name() { return handle.getName(); } @@ -73,6 +73,11 @@ public class SpigotCommandSender implements CommandSender { return locale; } + @Override + public boolean hasPermission(String permission) { + return handle.hasPermission(permission); + } + /** * Set if we are on pre-1.12, and therefore {@code player.getLocale()} doesn't exist and we have to get * {@code player.spigot().getLocale()}. @@ -86,7 +91,7 @@ public class SpigotCommandSender implements CommandSender { //noinspection JavaReflectionMemberAccess - of course it doesn't exist; that's why we're doing it LOCALE_METHOD = Player.Spigot.class.getMethod("getLocale"); } catch (NoSuchMethodException e) { - GeyserConnector.getInstance().getLogger().debug("Player.Spigot.getLocale() doesn't exist? Not a big deal but if you're seeing this please report it to the developers!"); + GeyserImpl.getInstance().getLogger().debug("Player.Spigot.getLocale() doesn't exist? Not a big deal but if you're seeing this please report it to the developers!"); } } } @@ -97,8 +102,7 @@ public class SpigotCommandSender implements CommandSender { * @return the locale of the Spigot player */ private String getSpigotLocale() { - if (handle instanceof Player) { - Player player = (Player) handle; + if (handle instanceof Player player) { if (USE_LEGACY_METHOD) { try { // sigh @@ -110,6 +114,6 @@ public class SpigotCommandSender implements CommandSender { return player.getLocale(); } } - return LanguageUtils.getDefaultLocale(); + return GeyserLocale.getDefaultLocale(); } } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserPistonListener.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserPistonListener.java new file mode 100644 index 000000000..4d504bcf5 --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserPistonListener.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.platform.spigot.world; + +import com.github.steveice10.mc.protocol.data.game.level.block.value.PistonValueType; +import com.nukkitx.math.vector.Vector3i; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockPistonEvent; +import org.bukkit.event.block.BlockPistonExtendEvent; +import org.bukkit.event.block.BlockPistonRetractEvent; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.PistonCache; +import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity; +import org.geysermc.geyser.level.physics.Direction; +import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class GeyserPistonListener implements Listener { + private final GeyserImpl geyser; + private final GeyserSpigotWorldManager worldManager; + + public GeyserPistonListener(GeyserImpl geyser, GeyserSpigotWorldManager worldManager) { + this.geyser = geyser; + this.worldManager = worldManager; + } + + // The handlers' parent class cannot be registered + @EventHandler(priority = EventPriority.MONITOR) + public void onPistonExtend(BlockPistonExtendEvent event) { + onPistonAction(event); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPistonRetract(BlockPistonRetractEvent event) { + onPistonAction(event); + } + + private void onPistonAction(BlockPistonEvent event) { + if (event.isCancelled()) { + return; + } + + World world = event.getBlock().getWorld(); + boolean isExtend = event instanceof BlockPistonExtendEvent; + + Location location = event.getBlock().getLocation(); + Vector3i position = getVector(location); + PistonValueType type = isExtend ? PistonValueType.PUSHING : PistonValueType.PULLING; + boolean sticky = event.isSticky(); + + Object2IntMap attachedBlocks = new Object2IntOpenHashMap<>(); + boolean blocksFilled = false; + + for (Map.Entry entry : geyser.getSessionManager().getSessions().entrySet()) { + Player player = Bukkit.getPlayer(entry.getKey()); + if (player == null || !player.getWorld().equals(world)) { + continue; + } + GeyserSession session = entry.getValue(); + + int dX = Math.abs(location.getBlockX() - player.getLocation().getBlockX()) >> 4; + int dZ = Math.abs(location.getBlockZ() - player.getLocation().getBlockZ()) >> 4; + if ((dX * dX + dZ * dZ) > session.getRenderDistance() * session.getRenderDistance()) { + // Ignore pistons outside the player's render distance + continue; + } + + // Trying to grab the blocks from the world like other platforms would result in the moving piston block + // being returned instead. + if (!blocksFilled) { + // Blocks currently require a player for 1.12, so let's just leech off one player to get all blocks + // and call it a day for the rest of the sessions (mostly to save on execution time) + List blocks = isExtend ? ((BlockPistonExtendEvent) event).getBlocks() : ((BlockPistonRetractEvent) event).getBlocks(); + for (Block block : blocks) { + Location attachedLocation = block.getLocation(); + int blockId = worldManager.getBlockNetworkId(player, block, + attachedLocation.getBlockX(), attachedLocation.getBlockY(), attachedLocation.getBlockZ()); + // Ignore blocks that will be destroyed + if (BlockStateValues.canPistonMoveBlock(blockId, isExtend)) { + attachedBlocks.put(getVector(attachedLocation), blockId); + } + } + blocksFilled = true; + } + + int pistonBlockId = worldManager.getBlockNetworkId(player, event.getBlock(), location.getBlockX(), location.getBlockY(), location.getBlockZ()); + // event.getDirection() is unreliable + Direction orientation = BlockStateValues.getPistonOrientation(pistonBlockId); + + session.executeInEventLoop(() -> { + PistonCache pistonCache = session.getPistonCache(); + PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(position, pos -> + new PistonBlockEntity(session, position, orientation, sticky, !isExtend)); + blockEntity.setAction(type, attachedBlocks); + }); + } + } + + private Vector3i getVector(Location location) { + return Vector3i.from(location.getX(), location.getY(), location.getZ()); + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserSpigot1_11CraftingListener.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserSpigot1_11CraftingListener.java new file mode 100644 index 000000000..b6aea9a37 --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserSpigot1_11CraftingListener.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.platform.spigot.world; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; +import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; +import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; +import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; +import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket; +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.data.MappingData; +import com.viaversion.viaversion.api.protocol.ProtocolPathEntry; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; +import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; +import com.viaversion.viaversion.util.Pair; +import org.bukkit.Bukkit; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.inventory.Recipe; +import org.bukkit.inventory.ShapedRecipe; +import org.bukkit.inventory.ShapelessRecipe; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.ItemTranslator; +import org.geysermc.geyser.util.InventoryUtils; + +import java.util.*; + +/** + * Used to send all available recipes from the server to the client, as a valid recipe book packet won't be sent by the server. + * Requires ViaVersion. + */ +public class GeyserSpigot1_11CraftingListener implements Listener { + + private final GeyserImpl geyser; + /** + * Specific mapping data for 1.12 to 1.13. Used to convert the 1.12 item into 1.13. + */ + private final MappingData mappingData1_12to1_13; + /** + * The list of all protocols from the client's version to 1.13. + */ + private final List protocolList; + + public GeyserSpigot1_11CraftingListener(GeyserImpl geyser) { + this.geyser = geyser; + this.mappingData1_12to1_13 = Via.getManager().getProtocolManager().getProtocol(Protocol1_13To1_12_2.class).getMappingData(); + this.protocolList = Via.getManager().getProtocolManager().getProtocolPath(MinecraftProtocol.getJavaProtocolVersion(), + ProtocolVersion.v1_13.getVersion()); + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + GeyserSession session = null; + for (GeyserSession otherSession : geyser.getSessionManager().getSessions().values()) { + if (otherSession.name().equals(event.getPlayer().getName())) { + session = otherSession; + break; + } + } + if (session == null) { + return; + } + + sendServerRecipes(session); + } + + public void sendServerRecipes(GeyserSession session) { + int netId = InventoryUtils.LAST_RECIPE_NET_ID; + + CraftingDataPacket craftingDataPacket = new CraftingDataPacket(); + craftingDataPacket.setCleanRecipes(true); + + Iterator recipeIterator = Bukkit.getServer().recipeIterator(); + while (recipeIterator.hasNext()) { + Recipe recipe = recipeIterator.next(); + + Pair outputs = translateToBedrock(session, recipe.getResult()); + ItemStack javaOutput = outputs.getKey(); + ItemData output = outputs.getValue(); + if (output == null || output.getId() == 0) continue; // If items make air we don't want that + + boolean isNotAllAir = false; // Check for all-air recipes + if (recipe instanceof ShapedRecipe shapedRecipe) { + int size = shapedRecipe.getShape().length * shapedRecipe.getShape()[0].length(); + Ingredient[] ingredients = new Ingredient[size]; + ItemData[] input = new ItemData[size]; + for (int i = 0; i < input.length; i++) { + // Index is converting char to integer, adding i then converting back to char based on ASCII code + Pair result = translateToBedrock(session, shapedRecipe.getIngredientMap().get((char) ('a' + i))); + ingredients[i] = new Ingredient(new ItemStack[]{result.getKey()}); + input[i] = result.getValue(); + isNotAllAir |= input[i].getId() != 0; + } + + if (!isNotAllAir) continue; + UUID uuid = UUID.randomUUID(); + // Add recipe to our internal cache + ShapedRecipeData data = new ShapedRecipeData(shapedRecipe.getShape()[0].length(), shapedRecipe.getShape().length, + "", ingredients, javaOutput); + session.getCraftingRecipes().put(netId, + new com.github.steveice10.mc.protocol.data.game.recipe.Recipe(RecipeType.CRAFTING_SHAPED, uuid.toString(), data)); + + // Add recipe for Bedrock + craftingDataPacket.getCraftingData().add(CraftingData.fromShaped(uuid.toString(), + shapedRecipe.getShape()[0].length(), shapedRecipe.getShape().length, Arrays.asList(input), + Collections.singletonList(output), uuid, "crafting_table", 0, netId++)); + } else if (recipe instanceof ShapelessRecipe shapelessRecipe) { + Ingredient[] ingredients = new Ingredient[shapelessRecipe.getIngredientList().size()]; + ItemData[] input = new ItemData[shapelessRecipe.getIngredientList().size()]; + + for (int i = 0; i < input.length; i++) { + Pair result = translateToBedrock(session, shapelessRecipe.getIngredientList().get(i)); + ingredients[i] = new Ingredient(new ItemStack[]{result.getKey()}); + input[i] = result.getValue(); + isNotAllAir |= input[i].getId() != 0; + } + + if (!isNotAllAir) continue; + UUID uuid = UUID.randomUUID(); + // Add recipe to our internal cache + ShapelessRecipeData data = new ShapelessRecipeData("", ingredients, javaOutput); + session.getCraftingRecipes().put(netId, + new com.github.steveice10.mc.protocol.data.game.recipe.Recipe(RecipeType.CRAFTING_SHAPELESS, uuid.toString(), data)); + + // Add recipe for Bedrock + craftingDataPacket.getCraftingData().add(CraftingData.fromShapeless(uuid.toString(), + Arrays.asList(input), Collections.singletonList(output), uuid, "crafting_table", 0, netId++)); + } + } + + session.sendUpstreamPacket(craftingDataPacket); + } + + @SuppressWarnings("deprecation") + private Pair translateToBedrock(GeyserSession session, org.bukkit.inventory.ItemStack itemStack) { + if (itemStack != null && itemStack.getData() != null) { + if (itemStack.getType().getId() == 0) { + return new Pair<>(null, ItemData.AIR); + } + + int legacyId = (itemStack.getType().getId() << 4) | (itemStack.getData().getData() & 0xFFFF); + + if (itemStack.getType().getId() == 355 && itemStack.getData().getData() == (byte) 0) { // Handle bed color since the server will always be pre-1.12 + legacyId = (itemStack.getType().getId() << 4) | ((byte) 14 & 0xFFFF); + } + + // old version -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 and so on + int itemId; + if (mappingData1_12to1_13.getItemMappings().containsKey(legacyId)) { + itemId = mappingData1_12to1_13.getNewItemId(legacyId); + } else if (mappingData1_12to1_13.getItemMappings().containsKey((itemStack.getType().getId() << 4) | (0))) { + itemId = mappingData1_12to1_13.getNewItemId((itemStack.getType().getId() << 4) | (0)); + } else { + // No ID found, just send back air + return new Pair<>(null, ItemData.AIR); + } + + for (int i = protocolList.size() - 1; i >= 0; i--) { + MappingData mappingData = protocolList.get(i).getProtocol().getMappingData(); + if (mappingData != null) { + itemId = mappingData.getNewItemId(itemId); + } + } + + ItemStack mcItemStack = new ItemStack(itemId, itemStack.getAmount()); + ItemData finalData = ItemTranslator.translateToBedrock(session, mcItemStack); + return new Pair<>(mcItemStack, finalData); + } + + // Empty slot, most likely + return new Pair<>(null, ItemData.AIR); + } + +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserSpigotBlockPlaceListener.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserSpigotBlockPlaceListener.java new file mode 100644 index 000000000..a8fcee3e5 --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserSpigotBlockPlaceListener.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.platform.spigot.world; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.SoundEvent; +import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; +import lombok.AllArgsConstructor; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockPlaceEvent; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager; + +@AllArgsConstructor +public class GeyserSpigotBlockPlaceListener implements Listener { + private final GeyserImpl geyser; + private final GeyserSpigotWorldManager worldManager; + + @EventHandler + public void place(final BlockPlaceEvent event) { + GeyserSession session = geyser.connectionByUuid(event.getPlayer().getUniqueId()); + if (session == null) { + return; + } + + LevelSoundEventPacket placeBlockSoundPacket = new LevelSoundEventPacket(); + placeBlockSoundPacket.setSound(SoundEvent.PLACE); + placeBlockSoundPacket.setPosition(Vector3f.from(event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ())); + placeBlockSoundPacket.setBabySound(false); + if (worldManager.isLegacy()) { + placeBlockSoundPacket.setExtraData(session.getBlockMappings().getBedrockBlockId(worldManager.getBlockAt(session, + event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ()))); + } else { + String javaBlockId = event.getBlockPlaced().getBlockData().getAsString(); + placeBlockSoundPacket.setExtraData(session.getBlockMappings().getBedrockBlockId(BlockRegistries.JAVA_IDENTIFIERS.get().getOrDefault(javaBlockId, BlockStateValues.JAVA_AIR_ID))); + } + placeBlockSoundPacket.setIdentifier(":"); + session.sendUpstreamPacket(placeBlockSoundPacket); + session.setLastBlockPlacePosition(null); + session.setLastBlockPlacedId(null); + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java new file mode 100644 index 000000000..ea1fadd0e --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.platform.spigot.world.manager; + +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.storage.BlockStorage; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.adapters.spigot.SpigotAdapters; +import org.geysermc.geyser.adapters.spigot.SpigotWorldAdapter; + +/** + * Used with ViaVersion and pre-1.13. + */ +public class GeyserSpigot1_12NativeWorldManager extends GeyserSpigot1_12WorldManager { + private final SpigotWorldAdapter adapter; + + public GeyserSpigot1_12NativeWorldManager(Plugin plugin) { + super(plugin); + this.adapter = SpigotAdapters.getWorldAdapter(); + // Unlike post-1.13, we can't build up a cache of block states, because block entities need some special conversion + } + + @Override + public int getBlockAt(GeyserSession session, int x, int y, int z) { + Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); + if (player == null) { + return BlockStateValues.JAVA_AIR_ID; + } + // Get block entity storage + BlockStorage storage = Via.getManager().getConnectionManager().getConnectedClient(player.getUniqueId()).get(BlockStorage.class); + int blockId = adapter.getBlockAt(player.getWorld(), x, y, z); + return getLegacyBlock(storage, blockId, x, y, z); + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java new file mode 100644 index 000000000..f12968b1f --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.platform.spigot.world.manager; + +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.data.MappingData; +import com.viaversion.viaversion.api.minecraft.Position; +import com.viaversion.viaversion.api.protocol.ProtocolPathEntry; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; +import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; +import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.storage.BlockStorage; +import org.bukkit.Bukkit; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.block.BlockStateValues; + +import java.util.List; + +/** + * Should be used when ViaVersion is present, no NMS adapter is being used, and we are pre-1.13. + * + * You need ViaVersion to connect to an older server with the Geyser-Spigot plugin. + */ +public class GeyserSpigot1_12WorldManager extends GeyserSpigotWorldManager { + /** + * Specific mapping data for 1.12 to 1.13. Used to convert the 1.12 block into the 1.13 block state. + * (Block IDs did not change between server versions until 1.13 and after) + */ + private final MappingData mappingData1_12to1_13; + + /** + * The list of all protocols from the client's version to 1.13. + */ + private final List protocolList; + + public GeyserSpigot1_12WorldManager(Plugin plugin) { + super(plugin); + this.mappingData1_12to1_13 = Via.getManager().getProtocolManager().getProtocol(Protocol1_13To1_12_2.class).getMappingData(); + this.protocolList = Via.getManager().getProtocolManager().getProtocolPath(CLIENT_PROTOCOL_VERSION, + ProtocolVersion.v1_13.getVersion()); + } + + @Override + public int getBlockAt(GeyserSession session, int x, int y, int z) { + Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); + if (player == null) { + return BlockStateValues.JAVA_AIR_ID; + } + if (!player.getWorld().isChunkLoaded(x >> 4, z >> 4)) { + // Prevent nasty async errors if a player is loading in + return BlockStateValues.JAVA_AIR_ID; + } + + Block block = player.getWorld().getBlockAt(x, y, z); + return getBlockNetworkId(player, block, x, y, z); + } + + @Override + @SuppressWarnings("deprecation") + public int getBlockNetworkId(Player player, Block block, int x, int y, int z) { + // Get block entity storage + BlockStorage storage = Via.getManager().getConnectionManager().getConnectedClient(player.getUniqueId()).get(BlockStorage.class); + // Black magic that gets the old block state ID + int oldBlockId = (block.getType().getId() << 4) | (block.getData() & 0xF); + return getLegacyBlock(storage, oldBlockId, x, y, z); + } + + /** + * + * @param storage ViaVersion's block entity storage (used to fix block entity state differences) + * @param blockId the pre-1.13 block id + * @param x X coordinate of block + * @param y Y coordinate of block + * @param z Z coordinate of block + * @return the block state updated to the latest Minecraft version + */ + public int getLegacyBlock(BlockStorage storage, int blockId, int x, int y, int z) { + // Convert block state from old version (1.12.2) -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 + blockId = mappingData1_12to1_13.getNewBlockId(blockId); + // Translate block entity differences - some information was stored in block tags and not block states + if (storage.isWelcome(blockId)) { // No getOrDefault method + BlockStorage.ReplacementData data = storage.get(new Position(x, (short) y, z)); + if (data != null && data.getReplacement() != -1) { + blockId = data.getReplacement(); + } + } + for (int i = protocolList.size() - 1; i >= 0; i--) { + MappingData mappingData = protocolList.get(i).getProtocol().getMappingData(); + if (mappingData != null) { + blockId = mappingData.getNewBlockStateId(blockId); + } + } + return blockId; + } + + @Override + public boolean isLegacy() { + return true; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/DockerCheck.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java similarity index 54% rename from connector/src/main/java/org/geysermc/connector/utils/DockerCheck.java rename to bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java index 59a039887..1aacb73e7 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/DockerCheck.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,27 +23,34 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.utils; +package org.geysermc.geyser.platform.spigot.world.manager; -import java.nio.file.Files; -import java.nio.file.Paths; +import org.bukkit.plugin.Plugin; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.block.BlockStateValues; -public class DockerCheck { +/** + * Should only be used when we know {@link GeyserSpigotWorldManager#getBlockAt(GeyserSession, int, int, int)} + * cannot be accurate. Typically, this is when ViaVersion is not installed but a client still manages to connect. + * If this occurs to you somehow, please let us know!! + */ +public class GeyserSpigotFallbackWorldManager extends GeyserSpigotWorldManager { + public GeyserSpigotFallbackWorldManager(Plugin plugin) { + super(plugin); + } - // By default, Geyser now sets the IP to the local IP in all cases on plugin versions so we don't notify the user of anything - // However we still have this check for the potential future bug - public static boolean checkBasic() { - try { - String OS = System.getProperty("os.name").toLowerCase(); - if (OS.indexOf("nix") >= 0 || OS.indexOf("nux") >= 0 || OS.indexOf("aix") > 0) { - String output = new String(Files.readAllBytes(Paths.get("/proc/1/cgroup"))); - - if (output.contains("docker")) { - return true; - } - } - } catch (Exception ignored) { } // Ignore any errors, inc ip failed to fetch, process could not run or access denied + @Override + public int getBlockAt(GeyserSession session, int x, int y, int z) { + return BlockStateValues.JAVA_AIR_ID; + } + @Override + public boolean hasOwnChunkCache() { return false; } + + @Override + public boolean isLegacy() { + return true; + } } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java new file mode 100644 index 000000000..caeb257f7 --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.platform.spigot.world.manager; + +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.data.MappingData; +import com.viaversion.viaversion.api.protocol.ProtocolPathEntry; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntList; +import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.platform.spigot.GeyserSpigotPlugin; + +import java.util.List; + +/** + * Used when block IDs need to be translated to the latest version + */ +public class GeyserSpigotLegacyNativeWorldManager extends GeyserSpigotNativeWorldManager { + + private final Int2IntMap oldToNewBlockId; + + public GeyserSpigotLegacyNativeWorldManager(GeyserSpigotPlugin plugin) { + super(plugin); + IntList allBlockStates = adapter.getAllBlockStates(); + oldToNewBlockId = new Int2IntOpenHashMap(allBlockStates.size()); + ProtocolVersion serverVersion = plugin.getServerProtocolVersion(); + List protocolList = Via.getManager().getProtocolManager().getProtocolPath(MinecraftProtocol.getJavaProtocolVersion(), + serverVersion.getVersion()); + for (int oldBlockId : allBlockStates) { + int newBlockId = oldBlockId; + // protocolList should *not* be null; we checked for that before initializing this class + for (int i = protocolList.size() - 1; i >= 0; i--) { + MappingData mappingData = protocolList.get(i).getProtocol().getMappingData(); + if (mappingData != null) { + newBlockId = mappingData.getNewBlockStateId(newBlockId); + } + } + oldToNewBlockId.put(oldBlockId, newBlockId); + } + } + + @Override + public int getBlockAt(GeyserSession session, int x, int y, int z) { + int nativeBlockId = super.getBlockAt(session, x, y, z); + return oldToNewBlockId.getOrDefault(nativeBlockId, nativeBlockId); + } + + @Override + public boolean isLegacy() { + return true; + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java new file mode 100644 index 000000000..b1032671a --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.platform.spigot.world.manager; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.adapters.spigot.SpigotAdapters; +import org.geysermc.geyser.adapters.spigot.SpigotWorldAdapter; + +public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager { + protected final SpigotWorldAdapter adapter; + + public GeyserSpigotNativeWorldManager(Plugin plugin) { + super(plugin); + adapter = SpigotAdapters.getWorldAdapter(); + } + + @Override + public int getBlockAt(GeyserSession session, int x, int y, int z) { + Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); + if (player == null) { + return BlockStateValues.JAVA_AIR_ID; + } + return adapter.getBlockAt(player.getWorld(), x, y, z); + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java new file mode 100644 index 000000000..8972b0ac6 --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.platform.spigot.world.manager; + +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.nbt.NbtType; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.Lectern; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BookMeta; +import org.bukkit.plugin.Plugin; +import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator; +import org.geysermc.geyser.level.GeyserWorldManager; +import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.util.BlockEntityUtils; +import org.geysermc.geyser.level.GameRule; + +import java.util.ArrayList; +import java.util.List; + +/** + * The base world manager to use when there is no supported NMS revision + */ +public class GeyserSpigotWorldManager extends GeyserWorldManager { + /** + * The current client protocol version for ViaVersion usage. + */ + protected static final int CLIENT_PROTOCOL_VERSION = MinecraftProtocol.getJavaProtocolVersion(); + + private final Plugin plugin; + + public GeyserSpigotWorldManager(Plugin plugin) { + this.plugin = plugin; + } + + @Override + public int getBlockAt(GeyserSession session, int x, int y, int z) { + Player bukkitPlayer; + if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { + return BlockStateValues.JAVA_AIR_ID; + } + World world = bukkitPlayer.getWorld(); + if (!world.isChunkLoaded(x >> 4, z >> 4)) { + // If the chunk isn't loaded, how could we even be here? + return BlockStateValues.JAVA_AIR_ID; + } + + return getBlockNetworkId(bukkitPlayer, world.getBlockAt(x, y, z), x, y, z); + } + + public int getBlockNetworkId(Player player, Block block, int x, int y, int z) { + return BlockRegistries.JAVA_IDENTIFIERS.getOrDefault(block.getBlockData().getAsString(), BlockStateValues.JAVA_AIR_ID); + } + + @Override + public boolean hasOwnChunkCache() { + return true; + } + + @Override + public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad) { + // Run as a task to prevent async issues + Runnable lecternInfoGet = () -> { + Player bukkitPlayer; + if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { + return; + } + + Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z); + if (!(block.getState() instanceof Lectern lectern)) { + session.getGeyser().getLogger().error("Lectern expected at: " + Vector3i.from(x, y, z).toString() + " but was not! " + block.toString()); + return; + } + + ItemStack itemStack = lectern.getInventory().getItem(0); + if (itemStack == null || !(itemStack.getItemMeta() instanceof BookMeta bookMeta)) { + if (!isChunkLoad) { + // We need to update the lectern since it's not going to be updated otherwise + BlockEntityUtils.updateBlockEntity(session, LecternInventoryTranslator.getBaseLecternTag(x, y, z, 0).build(), Vector3i.from(x, y, z)); + } + // We don't care; return + return; + } + + // On the count: allow the book to show/open even there are no pages. We know there is a book here, after all, and this matches Java behavior + boolean hasBookPages = bookMeta.getPageCount() > 0; + NbtMapBuilder lecternTag = LecternInventoryTranslator.getBaseLecternTag(x, y, z, hasBookPages ? bookMeta.getPageCount() : 1); + lecternTag.putInt("page", lectern.getPage() / 2); + NbtMapBuilder bookTag = NbtMap.builder() + .putByte("Count", (byte) itemStack.getAmount()) + .putShort("Damage", (short) 0) + .putString("Name", "minecraft:writable_book"); + List pages = new ArrayList<>(bookMeta.getPageCount()); + if (hasBookPages) { + for (String page : bookMeta.getPages()) { + NbtMapBuilder pageBuilder = NbtMap.builder() + .putString("photoname", "") + .putString("text", page); + pages.add(pageBuilder.build()); + } + } else { + // Empty page + NbtMapBuilder pageBuilder = NbtMap.builder() + .putString("photoname", "") + .putString("text", ""); + pages.add(pageBuilder.build()); + } + + bookTag.putCompound("tag", NbtMap.builder().putList("pages", NbtType.COMPOUND, pages).build()); + lecternTag.putCompound("book", bookTag.build()); + NbtMap blockEntityTag = lecternTag.build(); + BlockEntityUtils.updateBlockEntity(session, blockEntityTag, Vector3i.from(x, y, z)); + }; + + if (isChunkLoad) { + // Delay to ensure the chunk is sent first, and then the lectern data + Bukkit.getScheduler().runTaskLater(this.plugin, lecternInfoGet, 5); + } else { + Bukkit.getScheduler().runTask(this.plugin, lecternInfoGet); + } + return LecternInventoryTranslator.getBaseLecternTag(x, y, z, 0).build(); // Will be updated later + } + + @Override + public boolean shouldExpectLecternHandled() { + return true; + } + + public Boolean getGameRuleBool(GeyserSession session, GameRule gameRule) { + String value = Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getGameRuleValue(gameRule.getJavaID()); + if (!value.isEmpty()) { + return Boolean.parseBoolean(value); + } + return (Boolean) gameRule.getDefaultValue(); + } + + @Override + public int getGameRuleInt(GeyserSession session, GameRule gameRule) { + String value = Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getGameRuleValue(gameRule.getJavaID()); + if (!value.isEmpty()) { + return Integer.parseInt(value); + } + return (int) gameRule.getDefaultValue(); + } + + @Override + public boolean hasPermission(GeyserSession session, String permission) { + return Bukkit.getPlayer(session.getPlayerEntity().getUsername()).hasPermission(permission); + } + + /** + * This must be set to true if we are pre-1.13, and {@link BlockData#getAsString() does not exist}. + * + * This should be set to true if we are post-1.13 but before the latest version, and we should convert the old block state id + * to the current one. + * + * @return whether there is a difference between client block state and server block state that requires extra processing + */ + public boolean isLegacy() { + return false; + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java deleted file mode 100644 index 741763ec1..000000000 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.platform.spigot; - -import org.bukkit.Bukkit; -import org.bukkit.plugin.java.JavaPlugin; -import org.geysermc.common.PlatformType; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.bootstrap.GeyserBootstrap; -import org.geysermc.connector.command.CommandManager; -import org.geysermc.connector.configuration.GeyserConfiguration; -import org.geysermc.connector.dump.BootstrapDumpInfo; -import org.geysermc.connector.network.translators.world.WorldManager; -import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; -import org.geysermc.connector.ping.IGeyserPingPassthrough; -import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.LanguageUtils; -import org.geysermc.platform.spigot.command.GeyserSpigotCommandExecutor; -import org.geysermc.platform.spigot.command.GeyserSpigotCommandManager; -import org.geysermc.platform.spigot.command.SpigotCommandSender; -import org.geysermc.platform.spigot.world.GeyserSpigotBlockPlaceListener; -import org.geysermc.platform.spigot.world.GeyserSpigotWorldManager; -import us.myles.ViaVersion.api.Via; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.UUID; -import java.util.logging.Level; - -public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { - - private GeyserSpigotCommandManager geyserCommandManager; - private GeyserSpigotConfiguration geyserConfig; - private GeyserSpigotLogger geyserLogger; - private IGeyserPingPassthrough geyserSpigotPingPassthrough; - private GeyserSpigotWorldManager geyserWorldManager; - - private GeyserConnector connector; - - @Override - public void onEnable() { - // This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed - try { - if (!getDataFolder().exists()) { - getDataFolder().mkdir(); - File bukkitConfig = new File("plugins/Geyser-Bukkit/config.yml"); - if (bukkitConfig.exists()) { // Copy over old configs - getLogger().log(Level.INFO, LanguageUtils.getLocaleStringLog("geyser.bootstrap.config.copy_bukkit_config")); - Files.copy(bukkitConfig.toPath(), new File(getDataFolder().toString() + "/config.yml").toPath()); - getLogger().log(Level.INFO, LanguageUtils.getLocaleStringLog("geyser.bootstrap.config.copied_bukkit_config")); - } - } - File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); - this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpigotConfiguration.class); - } catch (IOException ex) { - getLogger().log(Level.WARNING, LanguageUtils.getLocaleStringLog("geyser.config.failed"), ex); - ex.printStackTrace(); - } - - // By default this should be localhost but may need to be changed in some circumstances - if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { - geyserConfig.setAutoconfiguredRemote(true); - // Don't use localhost if not listening on all interfaces - if (!Bukkit.getIp().equals("0.0.0.0") && !Bukkit.getIp().equals("")) { - geyserConfig.getRemote().setAddress(Bukkit.getIp()); - } - geyserConfig.getRemote().setPort(Bukkit.getPort()); - } - - if (geyserConfig.getBedrock().isCloneRemotePort()) { - geyserConfig.getBedrock().setPort(Bukkit.getPort()); - } - - this.geyserLogger = new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode()); - GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - - if (geyserConfig.getRemote().getAuthType().equals("floodgate") && Bukkit.getPluginManager().getPlugin("floodgate-bukkit") == null) { - geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); - this.getPluginLoader().disablePlugin(this); - return; - } else if (geyserConfig.isAutoconfiguredRemote() && Bukkit.getPluginManager().getPlugin("floodgate-bukkit") != null) { - // Floodgate installed means that the user wants Floodgate authentication - geyserLogger.debug("Auto-setting to Floodgate authentication."); - geyserConfig.getRemote().setAuthType("floodgate"); - } - - geyserConfig.loadFloodgate(this); - - this.connector = GeyserConnector.start(PlatformType.SPIGOT, this); - - if (geyserConfig.isLegacyPingPassthrough()) { - this.geyserSpigotPingPassthrough = GeyserLegacyPingPassthrough.init(connector); - } else { - this.geyserSpigotPingPassthrough = new GeyserSpigotPingPassthrough(geyserLogger); - } - - this.geyserCommandManager = new GeyserSpigotCommandManager(this, connector); - - boolean isViaVersion = (Bukkit.getPluginManager().getPlugin("ViaVersion") != null); - if (isViaVersion) { - if (!isCompatible(Via.getAPI().getVersion().replace("-SNAPSHOT", ""), "3.2.0")) { - geyserLogger.warning(LanguageUtils.getLocaleStringLog("geyser.bootstrap.viaversion.too_old", - "https://ci.viaversion.com/job/ViaVersion/")); - isViaVersion = false; - } - } - // Used to determine if Block.getBlockData() is present. - boolean isLegacy = !isCompatible(Bukkit.getServer().getVersion(), "1.13.0"); - if (isLegacy) - geyserLogger.debug("Legacy version of Minecraft (1.12.2 or older) detected; falling back to ViaVersion for block state retrieval."); - - boolean use3dBiomes = isCompatible(Bukkit.getServer().getVersion(), "1.16.0"); - if (!use3dBiomes) { - geyserLogger.debug("Legacy version of Minecraft (1.15.2 or older) detected; not using 3D biomes."); - } - - // Set if we need to use a different method for getting a player's locale - SpigotCommandSender.setUseLegacyLocaleMethod(!isCompatible(Bukkit.getServer().getVersion(), "1.12.0")); - - this.geyserWorldManager = new GeyserSpigotWorldManager(isLegacy, use3dBiomes, isViaVersion); - GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(connector, isLegacy, isViaVersion); - - Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this); - - this.getCommand("geyser").setExecutor(new GeyserSpigotCommandExecutor(connector)); - } - - @Override - public void onDisable() { - if (connector != null) - connector.shutdown(); - } - - @Override - public GeyserSpigotConfiguration getGeyserConfig() { - return geyserConfig; - } - - @Override - public GeyserSpigotLogger getGeyserLogger() { - return geyserLogger; - } - - @Override - public CommandManager getGeyserCommandManager() { - return this.geyserCommandManager; - } - - @Override - public IGeyserPingPassthrough getGeyserPingPassthrough() { - return geyserSpigotPingPassthrough; - } - - @Override - public WorldManager getWorldManager() { - return this.geyserWorldManager; - } - - @Override - public Path getConfigFolder() { - return getDataFolder().toPath(); - } - - public boolean isCompatible(String version, String whichVersion) { - int[] currentVersion = parseVersion(version); - int[] otherVersion = parseVersion(whichVersion); - int length = Math.max(currentVersion.length, otherVersion.length); - for (int index = 0; index < length; index = index + 1) { - int self = (index < currentVersion.length) ? currentVersion[index] : 0; - int other = (index < otherVersion.length) ? otherVersion[index] : 0; - - if (self != other) { - return (self - other) > 0; - } - } - return true; - } - - private int[] parseVersion(String versionParam) { - versionParam = (versionParam == null) ? "" : versionParam; - if (versionParam.contains("(MC: ")) { - versionParam = versionParam.split("\\(MC: ")[1]; - versionParam = versionParam.split("\\)")[0]; - } - String[] stringArray = versionParam.split("[_.-]"); - int[] temp = new int[stringArray.length]; - for (int index = 0; index <= (stringArray.length - 1); index = index + 1) { - String t = stringArray[index].replaceAll("\\D", ""); - try { - temp[index] = Integer.parseInt(t); - } catch(NumberFormatException ex) { - temp[index] = 0; - } - } - return temp; - } - - @Override - public BootstrapDumpInfo getDumpInfo() { - return new GeyserSpigotDumpInfo(); - } -} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java deleted file mode 100644 index cb59e202b..000000000 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.platform.spigot.world; - -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.SoundEvent; -import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; -import lombok.AllArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.block.BlockPlaceEvent; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; - -@AllArgsConstructor -public class GeyserSpigotBlockPlaceListener implements Listener { - - private final GeyserConnector connector; - private final boolean isLegacy; - private final boolean isViaVersion; - - @EventHandler - public void place(final BlockPlaceEvent event) { - for (GeyserSession session : connector.getPlayers()) { - if (event.getPlayer() == Bukkit.getPlayer(session.getPlayerEntity().getUsername())) { - LevelSoundEventPacket placeBlockSoundPacket = new LevelSoundEventPacket(); - placeBlockSoundPacket.setSound(SoundEvent.PLACE); - placeBlockSoundPacket.setPosition(Vector3f.from(event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ())); - placeBlockSoundPacket.setBabySound(false); - String javaBlockId; - if (isLegacy) { - javaBlockId = BlockTranslator.getJavaIdBlockMap().inverse().get(GeyserSpigotWorldManager.getLegacyBlock(session, - event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ(), isViaVersion)); - } else { - javaBlockId = event.getBlockPlaced().getBlockData().getAsString(); - } - placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaIdBlockMap().getOrDefault(javaBlockId, 0))); - placeBlockSoundPacket.setIdentifier(":"); - session.sendUpstreamPacket(placeBlockSoundPacket); - session.setLastBlockPlacePosition(null); - session.setLastBlockPlacedId(null); - break; - } - } - } - -} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java deleted file mode 100644 index 3493fc25a..000000000 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.platform.spigot.world; - -import com.fasterxml.jackson.databind.JsonNode; -import com.github.steveice10.mc.protocol.MinecraftConstants; -import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; -import it.unimi.dsi.fastutil.ints.Int2IntMap; -import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.block.Biome; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.GeyserWorldManager; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.GameRule; -import org.geysermc.connector.utils.LanguageUtils; -import us.myles.ViaVersion.api.Pair; -import us.myles.ViaVersion.api.Via; -import us.myles.ViaVersion.api.data.MappingData; -import us.myles.ViaVersion.api.minecraft.Position; -import us.myles.ViaVersion.api.protocol.Protocol; -import us.myles.ViaVersion.api.protocol.ProtocolRegistry; -import us.myles.ViaVersion.api.protocol.ProtocolVersion; -import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; -import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage; - -import java.io.InputStream; -import java.util.List; - -public class GeyserSpigotWorldManager extends GeyserWorldManager { - /** - * The current client protocol version for ViaVersion usage. - */ - private static final int CLIENT_PROTOCOL_VERSION = MinecraftConstants.PROTOCOL_VERSION; - - /** - * Whether the server is pre-1.13. - */ - private final boolean isLegacy; - /** - * Whether the server is pre-1.16 and therefore does not support 3D biomes on an API level guaranteed. - */ - private final boolean use3dBiomes; - /** - * You need ViaVersion to connect to an older server with Geyser. - * However, we still check for ViaVersion in case there's some other way that gets Geyser on a pre-1.13 Bukkit server - */ - private final boolean isViaVersion; - /** - * Stores a list of {@link Biome} ordinal numbers to Minecraft biome numeric IDs. - * - * Working with the Biome enum in Spigot poses two problems: - * 1: The Biome enum values change in both order and names over the years. - * 2: There is no way to get the Minecraft biome ID from the name itself with Spigot. - * To solve both of these problems, we store a JSON file of every Biome enum that has existed, - * along with its 1.16 biome number. - * - * The key is the Spigot Biome ordinal; the value is the Minecraft Java biome numerical ID - */ - private final Int2IntMap biomeToIdMap = new Int2IntOpenHashMap(Biome.values().length); - - public GeyserSpigotWorldManager(boolean isLegacy, boolean use3dBiomes, boolean isViaVersion) { - this.isLegacy = isLegacy; - this.use3dBiomes = use3dBiomes; - this.isViaVersion = isViaVersion; - - // Load the values into the biome-to-ID map - InputStream biomeStream = FileUtils.getResource("biomes.json"); - JsonNode biomes; - try { - biomes = GeyserConnector.JSON_MAPPER.readTree(biomeStream); - } catch (Exception e) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e); - } - // Only load in the biomes that are present in this version of Minecraft - for (Biome enumBiome : Biome.values()) { - JsonNode biome = biomes.get(enumBiome.toString()); - if (biome != null) { - biomeToIdMap.put(enumBiome.ordinal(), biome.intValue()); - } else { - GeyserConnector.getInstance().getLogger().debug("No biome mapping found for " + enumBiome.toString() + - ", defaulting to 0"); - biomeToIdMap.put(enumBiome.ordinal(), 0); - } - } - } - - @Override - public int getBlockAt(GeyserSession session, int x, int y, int z) { - Player bukkitPlayer; - if ((this.isLegacy && !this.isViaVersion) - || session.getPlayerEntity() == null - || (bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { - return BlockTranslator.JAVA_AIR_ID; - } - World world = bukkitPlayer.getWorld(); - if (isLegacy) { - return getLegacyBlock(session, x, y, z, true); - } - //TODO possibly: detect server version for all versions and use ViaVersion for block state mappings like below - return BlockTranslator.getJavaIdBlockMap().getOrDefault(world.getBlockAt(x, y, z).getBlockData().getAsString(), 0); - } - - public static int getLegacyBlock(GeyserSession session, int x, int y, int z, boolean isViaVersion) { - if (isViaVersion) { - Player bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); - // Get block entity storage - BlockStorage storage = Via.getManager().getConnection(bukkitPlayer.getUniqueId()).get(BlockStorage.class); - return getLegacyBlock(storage, bukkitPlayer.getWorld(), x, y, z); - } else { - return BlockTranslator.JAVA_AIR_ID; - } - } - - @SuppressWarnings("deprecation") - public static int getLegacyBlock(BlockStorage storage, World world, int x, int y, int z) { - Block block = world.getBlockAt(x, y, z); - // Black magic that gets the old block state ID - int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF); - // Convert block state from old version (1.12.2) -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 - blockId = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData().getNewBlockId(blockId); - List> protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION, - ProtocolVersion.v1_13.getId()); - // Translate block entity differences - some information was stored in block tags and not block states - if (storage.isWelcome(blockId)) { // No getOrDefault method - BlockStorage.ReplacementData data = storage.get(new Position(x, (short) y, z)); - if (data != null && data.getReplacement() != -1) { - blockId = data.getReplacement(); - } - } - for (int i = protocolList.size() - 1; i >= 0; i--) { - MappingData mappingData = protocolList.get(i).getValue().getMappingData(); - if (mappingData != null) { - blockId = mappingData.getNewBlockStateId(blockId); - } - } - return blockId; - } - - @Override - public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) { - Player bukkitPlayer; - if ((this.isLegacy && !this.isViaVersion) - || session.getPlayerEntity() == null - || (bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { - return; - } - World world = bukkitPlayer.getWorld(); - if (this.isLegacy) { - // Get block entity storage - BlockStorage storage = Via.getManager().getConnection(bukkitPlayer.getUniqueId()).get(BlockStorage.class); - for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order - for (int blockZ = 0; blockZ < 16; blockZ++) { - for (int blockX = 0; blockX < 16; blockX++) { - chunk.set(blockX, blockY, blockZ, getLegacyBlock(storage, world, (x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ)); - } - } - } - } else { - //TODO: see above TODO in getBlockAt - for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order - for (int blockZ = 0; blockZ < 16; blockZ++) { - for (int blockX = 0; blockX < 16; blockX++) { - Block block = world.getBlockAt((x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ); - int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), BlockTranslator.JAVA_AIR_ID); - chunk.set(blockX, blockY, blockZ, id); - } - } - } - } - } - - @Override - public boolean hasMoreBlockDataThanChunkCache() { - return true; - } - - @Override - @SuppressWarnings("deprecation") - public int[] getBiomeDataAt(GeyserSession session, int x, int z) { - if (session.getPlayerEntity() == null) { - return new int[1024]; - } - int[] biomeData = new int[1024]; - World world = Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld(); - int chunkX = x << 4; - int chunkZ = z << 4; - int chunkXmax = chunkX + 16; - int chunkZmax = chunkZ + 16; - // 3D biomes didn't exist until 1.15 - if (use3dBiomes) { - for (int localX = chunkX; localX < chunkXmax; localX += 4) { - for (int localY = 0; localY < 255; localY += + 4) { - for (int localZ = chunkZ; localZ < chunkZmax; localZ += 4) { - // Index is based on wiki.vg's index requirements - final int i = ((localY >> 2) & 63) << 4 | ((localZ >> 2) & 3) << 2 | ((localX >> 2) & 3); - biomeData[i] = biomeToIdMap.getOrDefault(world.getBiome(localX, localY, localZ).ordinal(), 0); - } - } - } - } else { - // Looks like the same code, but we're not checking the Y coordinate here - for (int localX = chunkX; localX < chunkXmax; localX += 4) { - for (int localY = 0; localY < 255; localY += + 4) { - for (int localZ = chunkZ; localZ < chunkZmax; localZ += 4) { - // Index is based on wiki.vg's index requirements - final int i = ((localY >> 2) & 63) << 4 | ((localZ >> 2) & 3) << 2 | ((localX >> 2) & 3); - biomeData[i] = biomeToIdMap.getOrDefault(world.getBiome(localX, localZ).ordinal(), 0); - } - } - } - } - return biomeData; - } - - public Boolean getGameRuleBool(GeyserSession session, GameRule gameRule) { - return Boolean.parseBoolean(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getGameRuleValue(gameRule.getJavaID())); - } - - @Override - public int getGameRuleInt(GeyserSession session, GameRule gameRule) { - return Integer.parseInt(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getGameRuleValue(gameRule.getJavaID())); - } - - @Override - public boolean hasPermission(GeyserSession session, String permission) { - return Bukkit.getPlayer(session.getPlayerEntity().getUsername()).hasPermission(permission); - } -} diff --git a/bootstrap/spigot/src/main/resources/biomes.json b/bootstrap/spigot/src/main/resources/biomes.json deleted file mode 100644 index 56520e914..000000000 --- a/bootstrap/spigot/src/main/resources/biomes.json +++ /dev/null @@ -1,155 +0,0 @@ -{ - "MUTATED_ICE_FLATS" : 140, - "MUTATED_TAIGA" : 133, - "SAVANNA_PLATEAU_MOUNTAINS" : 164, - "DEEP_WARM_OCEAN" : 47, - "REDWOOD_TAIGA_HILLS" : 33, - "THE_VOID" : 127, - "COLD_TAIGA_MOUNTAINS" : 158, - "BAMBOO_JUNGLE_HILLS" : 169, - "MOUNTAINS" : 3, - "MESA_PLATEAU" : 39, - "SNOWY_TAIGA_HILLS" : 31, - "DEEP_FROZEN_OCEAN" : 50, - "EXTREME_HILLS" : 3, - "BIRCH_FOREST_MOUNTAINS" : 155, - "FOREST" : 4, - "BIRCH_FOREST" : 27, - "SNOWY_TUNDRA" : 12, - "ICE_SPIKES" : 140, - "FROZEN_OCEAN" : 10, - "WARPED_FOREST" : 172, - "WOODED_BADLANDS_PLATEAU" : 38, - "BADLANDS_PLATEAU" : 39, - "ICE_PLAINS_SPIKES" : 140, - "MEGA_TAIGA" : 32, - "MUTATED_SAVANNA_ROCK" : 164, - "SAVANNA_PLATEAU" : 36, - "DARK_FOREST_HILLS" : 157, - "END_MIDLANDS" : 41, - "SHATTERED_SAVANNA_PLATEAU" : 164, - "SAVANNA" : 35, - "MUSHROOM_ISLAND_SHORE" : 15, - "SWAMP" : 6, - "ICE_MOUNTAINS" : 13, - "BEACH" : 16, - "MUTATED_MESA_CLEAR_ROCK" : 167, - "END_HIGHLANDS" : 42, - "COLD_BEACH" : 26, - "JUNGLE" : 21, - "MUTATED_TAIGA_COLD" : 158, - "TALL_BIRCH_HILLS" : 156, - "DARK_FOREST" : 29, - "WOODED_HILLS" : 18, - "HELL" : 8, - "MUTATED_REDWOOD_TAIGA" : 160, - "MESA_PLATEAU_FOREST" : 38, - "MUSHROOM_ISLAND" : 14, - "BADLANDS" : 37, - "END_BARRENS" : 43, - "MUTATED_EXTREME_HILLS_WITH_TREES" : 162, - "MUTATED_JUNGLE_EDGE" : 151, - "MODIFIED_BADLANDS_PLATEAU" : 167, - "ROOFED_FOREST_MOUNTAINS" : 157, - "SOUL_SAND_VALLEY" : 170, - "DESERT" : 2, - "MUTATED_PLAINS" : 129, - "MUTATED_BIRCH_FOREST" : 155, - "WOODED_MOUNTAINS" : 34, - "TAIGA_HILLS" : 19, - "BAMBOO_JUNGLE" : 168, - "SWAMPLAND_MOUNTAINS" : 134, - "DESERT_MOUNTAINS" : 130, - "REDWOOD_TAIGA" : 32, - "MUSHROOM_FIELDS" : 14, - "GIANT_TREE_TAIGA_HILLS" : 33, - "PLAINS" : 1, - "JUNGLE_EDGE" : 23, - "SAVANNA_MOUNTAINS" : 163, - "DEEP_COLD_OCEAN" : 49, - "DESERT_LAKES" : 130, - "MOUNTAIN_EDGE" : 20, - "SNOWY_MOUNTAINS" : 13, - "MESA_PLATEAU_MOUNTAINS" : 167, - "JUNGLE_MOUNTAINS" : 149, - "SMALLER_EXTREME_HILLS" : 20, - "MESA_PLATEAU_FOREST_MOUNTAINS" : 166, - "NETHER_WASTES" : 8, - "BIRCH_FOREST_HILLS_MOUNTAINS" : 156, - "MUTATED_JUNGLE" : 149, - "WARM_OCEAN" : 44, - "DEEP_OCEAN" : 24, - "STONE_BEACH" : 25, - "MODIFIED_JUNGLE" : 149, - "MUTATED_SAVANNA" : 163, - "TAIGA_COLD_HILLS" : 31, - "OCEAN" : 0, - "SMALL_END_ISLANDS" : 40, - "MUSHROOM_FIELD_SHORE" : 15, - "GRAVELLY_MOUNTAINS" : 131, - "FROZEN_RIVER" : 11, - "TAIGA_COLD" : 30, - "BASALT_DELTAS" : 173, - "EXTREME_HILLS_WITH_TREES" : 34, - "MEGA_TAIGA_HILLS" : 33, - "MUTATED_FOREST" : 132, - "MUTATED_BIRCH_FOREST_HILLS" : 156, - "SKY" : 9, - "LUKEWARM_OCEAN" : 45, - "EXTREME_HILLS_MOUNTAINS" : 131, - "COLD_TAIGA_HILLS" : 31, - "THE_END" : 9, - "SUNFLOWER_PLAINS" : 129, - "SAVANNA_ROCK" : 36, - "ERODED_BADLANDS" : 165, - "STONE_SHORE" : 25, - "EXTREME_HILLS_PLUS_MOUNTAINS" : 162, - "CRIMSON_FOREST" : 171, - "VOID" : 127, - "SNOWY_TAIGA" : 30, - "SNOWY_TAIGA_MOUNTAINS" : 158, - "FLOWER_FOREST" : 132, - "COLD_OCEAN" : 46, - "BEACHES" : 16, - "MESA" : 37, - "MUSHROOM_SHORE" : 15, - "MESA_CLEAR_ROCK" : 39, - "NETHER" : 8, - "ICE_PLAINS" : 12, - "SHATTERED_SAVANNA" : 163, - "ROOFED_FOREST" : 29, - "GIANT_SPRUCE_TAIGA_HILLS" : 161, - "SNOWY_BEACH" : 26, - "MESA_BRYCE" : 165, - "JUNGLE_EDGE_MOUNTAINS" : 151, - "MUTATED_DESERT" : 130, - "MODIFIED_GRAVELLY_MOUNTAINS" : 158, - "MEGA_SPRUCE_TAIGA" : 160, - "TAIGA_MOUNTAINS" : 133, - "SMALL_MOUNTAINS" : 20, - "EXTREME_HILLS_PLUS" : 34, - "GIANT_SPRUCE_TAIGA" : 160, - "FOREST_HILLS" : 18, - "DESERT_HILLS" : 17, - "MUTATED_REDWOOD_TAIGA_HILLS" : 161, - "MEGA_SPRUCE_TAIGA_HILLS" : 161, - "RIVER" : 7, - "GIANT_TREE_TAIGA" : 32, - "SWAMPLAND" : 6, - "JUNGLE_HILLS" : 22, - "TALL_BIRCH_FOREST" : 155, - "DEEP_LUKEWARM_OCEAN" : 48, - "MESA_ROCK" : 38, - "SWAMP_HILLS" : 134, - "MODIFIED_WOODED_BADLANDS_PLATEAU" : 166, - "MODIFIED_JUNGLE_EDGE" : 151, - "BIRCH_FOREST_HILLS" : 28, - "COLD_TAIGA" : 30, - "TAIGA" : 5, - "MUTATED_MESA_ROCK" : 166, - "MUTATED_SWAMPLAND" : 134, - "ICE_FLATS" : 12, - "MUTATED_ROOFED_FOREST" : 157, - "MUTATED_MESA" : 165, - "MUTATED_EXTREME_HILLS" : 131 -} diff --git a/bootstrap/spigot/src/main/resources/plugin.yml b/bootstrap/spigot/src/main/resources/plugin.yml index fee71ab1f..18773402e 100644 --- a/bootstrap/spigot/src/main/resources/plugin.yml +++ b/bootstrap/spigot/src/main/resources/plugin.yml @@ -1,11 +1,42 @@ -main: org.geysermc.platform.spigot.GeyserSpigotPlugin +main: org.geysermc.geyser.platform.spigot.GeyserSpigotPlugin name: ${outputName}-Spigot author: ${project.organization.name} website: ${project.organization.url} version: ${project.version} -softdepend: ["ViaVersion"] +softdepend: ["ViaVersion", "floodgate"] api-version: 1.13 commands: geyser: description: The main command for Geyser. - usage: /geyser help \ No newline at end of file + usage: /geyser +permissions: + geyser.command.help: + description: Shows help for all registered commands. + default: true + geyser.command.offhand: + description: Puts an items in your offhand. + default: true + geyser.command.advancements: + description: Shows the advancements of the player on the server. + default: true + geyser.command.tooltips: + description: Toggles showing advanced tooltips on your items. + default: true + geyser.command.statistics: + description: Shows the statistics of the player on the server. + default: true + geyser.command.settings: + description: Modify user settings + default: true + geyser.command.list: + description: List all players connected through Geyser. + default: op + geyser.command.dump: + description: Dumps Geyser debug information for bug reports. + default: op + geyser.command.reload: + description: Reloads the Geyser configurations. Kicks all players when used! + default: false + geyser.command.version: + description: Shows the current Geyser version and checks for updates. + default: op diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index e6ce8f851..fa7989b43 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -6,15 +6,15 @@ org.geysermc bootstrap-parent - 1.2.0-SNAPSHOT + 2.0.0-SNAPSHOT bootstrap-sponge org.geysermc - connector - 1.2.0-SNAPSHOT + core + 2.0.0-SNAPSHOT compile @@ -40,7 +40,7 @@ - org.geysermc.platform.sponge.GeyserSpongeMain + org.geysermc.geyser.platform.sponge.GeyserSpongeMain @@ -48,7 +48,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.1 + 3.3.0-SNAPSHOT package @@ -59,35 +59,27 @@ com.fasterxml.jackson - org.geysermc.platform.sponge.shaded.jackson + org.geysermc.geyser.platform.sponge.shaded.jackson io.netty - org.geysermc.platform.sponge.shaded.netty + org.geysermc.geyser.platform.sponge.shaded.netty it.unimi.dsi.fastutil - org.geysermc.platform.sponge.shaded.fastutil - - - org.reflections - org.geysermc.platform.sponge.shaded.reflections + org.geysermc.geyser.platform.sponge.shaded.fastutil com.google.common - org.geysermc.platform.sponge.shaded.google.common + org.geysermc.geyser.platform.sponge.shaded.google.common com.google.guava - org.geysermc.platform.sponge.shaded.google.guava + org.geysermc.geyser.platform.sponge.shaded.google.guava - org.dom4j - org.geysermc.platform.sponge.shaded.dom4j - - - net.kyori.adventure - org.geysermc.platform.sponge.shaded.adventure + net.kyori + org.geysermc.geyser.platform.sponge.shaded.kyori diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongeConfiguration.java similarity index 89% rename from bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java rename to bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongeConfiguration.java index 2d5eefebd..b53160c29 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongeConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,9 +23,9 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.sponge; +package org.geysermc.geyser.platform.sponge; -import org.geysermc.connector.configuration.GeyserJacksonConfiguration; +import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; import java.nio.file.Path; diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeDumpInfo.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongeDumpInfo.java similarity index 85% rename from bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeDumpInfo.java rename to bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongeDumpInfo.java index 0d292b372..c947c34fd 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeDumpInfo.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongeDumpInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,10 +23,10 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.sponge; +package org.geysermc.geyser.platform.sponge; import lombok.Getter; -import org.geysermc.connector.dump.BootstrapDumpInfo; +import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.spongepowered.api.Platform; import org.spongepowered.api.Sponge; import org.spongepowered.api.plugin.PluginContainer; @@ -36,13 +36,12 @@ import java.util.List; @Getter public class GeyserSpongeDumpInfo extends BootstrapDumpInfo { - - private String platformName; - private String platformVersion; - private boolean onlineMode; - private String serverIP; - private int serverPort; - private List plugins; + private final String platformName; + private final String platformVersion; + private final boolean onlineMode; + private final String serverIP; + private final int serverPort; + private final List plugins; GeyserSpongeDumpInfo() { super(); diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeLogger.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongeLogger.java similarity index 93% rename from bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeLogger.java rename to bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongeLogger.java index bdbd25311..7e0f2a410 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeLogger.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongeLogger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,12 +23,12 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.sponge; +package org.geysermc.geyser.platform.sponge; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; -import org.geysermc.connector.GeyserLogger; +import org.geysermc.geyser.GeyserLogger; import org.slf4j.Logger; @AllArgsConstructor diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeMain.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongeMain.java similarity index 87% rename from bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeMain.java rename to bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongeMain.java index 5b28fea25..c20b95cc1 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeMain.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongeMain.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,11 +23,11 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.sponge; +package org.geysermc.geyser.platform.sponge; -import org.geysermc.connector.common.main.IGeyserMain; +import org.geysermc.geyser.GeyserMain; -public class GeyserSpongeMain extends IGeyserMain { +public class GeyserSpongeMain extends GeyserMain { public static void main(String[] args) { new GeyserSpongeMain().displayMessage(); diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePingPassthrough.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePingPassthrough.java similarity index 82% rename from bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePingPassthrough.java rename to bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePingPassthrough.java index 251b9703d..dcdeb53f5 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePingPassthrough.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePingPassthrough.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,11 +23,11 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.sponge; +package org.geysermc.geyser.platform.sponge; -import com.github.steveice10.mc.protocol.MinecraftConstants; -import org.geysermc.connector.common.ping.GeyserPingInfo; -import org.geysermc.connector.ping.IGeyserPingPassthrough; +import org.geysermc.geyser.ping.GeyserPingInfo; +import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.ping.IGeyserPingPassthrough; import org.spongepowered.api.MinecraftVersion; import org.spongepowered.api.Sponge; import org.spongepowered.api.event.SpongeEventFactory; @@ -38,31 +38,29 @@ import org.spongepowered.api.network.status.StatusClient; import org.spongepowered.api.profile.GameProfile; import java.lang.reflect.Method; -import java.net.Inet4Address; import java.net.InetSocketAddress; import java.util.Optional; public class GeyserSpongePingPassthrough implements IGeyserPingPassthrough { - private static final GeyserStatusClient STATUS_CLIENT = new GeyserStatusClient(); private static final Cause CAUSE = Cause.of(EventContext.empty(), Sponge.getServer()); private static Method SpongeStatusResponse_create; @SuppressWarnings({"unchecked", "rawtypes"}) @Override - public GeyserPingInfo getPingInformation() { + public GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) { // come on Sponge, this is in commons, why not expose it :( ClientPingServerEvent event; try { - if(SpongeStatusResponse_create == null) { + if (SpongeStatusResponse_create == null) { Class SpongeStatusResponse = Class.forName("org.spongepowered.common.network.status.SpongeStatusResponse"); Class MinecraftServer = Class.forName("net.minecraft.server.MinecraftServer"); SpongeStatusResponse_create = SpongeStatusResponse.getDeclaredMethod("create", MinecraftServer); } Object response = SpongeStatusResponse_create.invoke(null, Sponge.getServer()); - event = SpongeEventFactory.createClientPingServerEvent(CAUSE, STATUS_CLIENT, (ClientPingServerEvent.Response) response); + event = SpongeEventFactory.createClientPingServerEvent(CAUSE, new GeyserStatusClient(inetSocketAddress), (ClientPingServerEvent.Response) response); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } @@ -75,8 +73,8 @@ public class GeyserSpongePingPassthrough implements IGeyserPingPassthrough { ), new GeyserPingInfo.Version( event.getResponse().getVersion().getName(), - MinecraftConstants.PROTOCOL_VERSION) // thanks for also not exposing this sponge - ); + MinecraftProtocol.getJavaProtocolVersion()) // thanks for also not exposing this sponge + ); event.getResponse().getPlayers().get().getProfiles().stream() .map(GameProfile::getName) .map(op -> op.orElseThrow(IllegalStateException::new)) @@ -87,11 +85,15 @@ public class GeyserSpongePingPassthrough implements IGeyserPingPassthrough { @SuppressWarnings("NullableProblems") private static class GeyserStatusClient implements StatusClient { - private static final InetSocketAddress FAKE_REMOTE = new InetSocketAddress(Inet4Address.getLoopbackAddress(), 69); + private final InetSocketAddress remote; + + public GeyserStatusClient(InetSocketAddress remote) { + this.remote = remote; + } @Override public InetSocketAddress getAddress() { - return FAKE_REMOTE; + return this.remote; } @Override diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java similarity index 73% rename from bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java rename to bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java index c3231f3bf..f20f94a79 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,21 +23,21 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.sponge; +package org.geysermc.geyser.platform.sponge; import com.google.inject.Inject; import org.geysermc.common.PlatformType; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.bootstrap.GeyserBootstrap; -import org.geysermc.connector.command.CommandManager; -import org.geysermc.connector.configuration.GeyserConfiguration; -import org.geysermc.connector.dump.BootstrapDumpInfo; -import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; -import org.geysermc.connector.ping.IGeyserPingPassthrough; -import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.LanguageUtils; -import org.geysermc.platform.sponge.command.GeyserSpongeCommandExecutor; -import org.geysermc.platform.sponge.command.GeyserSpongeCommandManager; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.GeyserBootstrap; +import org.geysermc.geyser.command.CommandManager; +import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.dump.BootstrapDumpInfo; +import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; +import org.geysermc.geyser.ping.IGeyserPingPassthrough; +import org.geysermc.geyser.util.FileUtils; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.platform.sponge.command.GeyserSpongeCommandExecutor; +import org.geysermc.geyser.platform.sponge.command.GeyserSpongeCommandManager; import org.slf4j.Logger; import org.spongepowered.api.Sponge; import org.spongepowered.api.config.ConfigDir; @@ -52,7 +52,7 @@ import java.net.InetSocketAddress; import java.nio.file.Path; import java.util.UUID; -@Plugin(id = "geyser", name = GeyserConnector.NAME + "-Sponge", version = GeyserConnector.VERSION, url = "https://geysermc.org", authors = "GeyserMC") +@Plugin(id = "geyser", name = GeyserImpl.NAME + "-Sponge", version = GeyserImpl.VERSION, url = "https://geysermc.org", authors = "GeyserMC") public class GeyserSpongePlugin implements GeyserBootstrap { @Inject @@ -67,25 +67,29 @@ public class GeyserSpongePlugin implements GeyserBootstrap { private GeyserSpongeLogger geyserLogger; private IGeyserPingPassthrough geyserSpongePingPassthrough; - private GeyserConnector connector; + private GeyserImpl geyser; @Override public void onEnable() { + GeyserLocale.init(this); + if (!configDir.exists()) configDir.mkdirs(); - File configFile = null; + File configFile; try { - configFile = FileUtils.fileOrCopiedFromResource(new File(configDir, "config.yml"), "config.yml", (file) -> file.replaceAll("generateduuid", UUID.randomUUID().toString())); + configFile = FileUtils.fileOrCopiedFromResource(new File(configDir, "config.yml"), "config.yml", + (file) -> file.replaceAll("generateduuid", UUID.randomUUID().toString()), this); } catch (IOException ex) { - logger.warn(LanguageUtils.getLocaleStringLog("geyser.config.failed")); + logger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed")); ex.printStackTrace(); + return; } try { this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpongeConfiguration.class); } catch (IOException ex) { - logger.warn(LanguageUtils.getLocaleStringLog("geyser.config.failed")); + logger.warn(GeyserLocale.getLocaleStringLog("geyser.config.failed")); ex.printStackTrace(); return; } @@ -101,27 +105,27 @@ public class GeyserSpongePlugin implements GeyserBootstrap { } } - if (geyserConfig.getBedrock().isCloneRemotePort()){ + if (geyserConfig.getBedrock().isCloneRemotePort()) { geyserConfig.getBedrock().setPort(geyserConfig.getRemote().getPort()); } this.geyserLogger = new GeyserSpongeLogger(logger, geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - this.connector = GeyserConnector.start(PlatformType.SPONGE, this); + this.geyser = GeyserImpl.start(PlatformType.SPONGE, this); if (geyserConfig.isLegacyPingPassthrough()) { - this.geyserSpongePingPassthrough = GeyserLegacyPingPassthrough.init(connector); + this.geyserSpongePingPassthrough = GeyserLegacyPingPassthrough.init(geyser); } else { this.geyserSpongePingPassthrough = new GeyserSpongePingPassthrough(); } - this.geyserCommandManager = new GeyserSpongeCommandManager(Sponge.getCommandManager(), connector); - Sponge.getCommandManager().register(this, new GeyserSpongeCommandExecutor(connector), "geyser"); + this.geyserCommandManager = new GeyserSpongeCommandManager(Sponge.getCommandManager(), geyser); + Sponge.getCommandManager().register(this, new GeyserSpongeCommandExecutor(geyser), "geyser"); } @Override public void onDisable() { - connector.shutdown(); + geyser.shutdown(); } @Override @@ -163,4 +167,9 @@ public class GeyserSpongePlugin implements GeyserBootstrap { public BootstrapDumpInfo getDumpInfo() { return new GeyserSpongeDumpInfo(); } + + @Override + public String getMinecraftServerVersion() { + return Sponge.getPlatform().getMinecraftVersion().getName(); + } } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/GeyserSpongeCommandExecutor.java similarity index 59% rename from bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java rename to bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/GeyserSpongeCommandExecutor.java index 3171a1a91..5b8496680 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/GeyserSpongeCommandExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,15 +23,16 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.sponge.command; +package org.geysermc.geyser.platform.sponge.command; -import lombok.AllArgsConstructor; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.common.ChatColor; -import org.geysermc.connector.command.GeyserCommand; -import org.geysermc.connector.utils.LanguageUtils; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandExecutor; +import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.text.ChatColor; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.GeyserLocale; import org.spongepowered.api.command.CommandCallable; -import org.spongepowered.api.command.CommandException; import org.spongepowered.api.command.CommandResult; import org.spongepowered.api.command.CommandSource; import org.spongepowered.api.text.Text; @@ -39,40 +40,49 @@ import org.spongepowered.api.world.Location; import org.spongepowered.api.world.World; import javax.annotation.Nullable; -import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Optional; -@AllArgsConstructor -public class GeyserSpongeCommandExecutor implements CommandCallable { +public class GeyserSpongeCommandExecutor extends CommandExecutor implements CommandCallable { - private GeyserConnector connector; + public GeyserSpongeCommandExecutor(GeyserImpl geyser) { + super(geyser); + } @Override - public CommandResult process(CommandSource source, String arguments) throws CommandException { + public CommandResult process(CommandSource source, String arguments) { + CommandSender commandSender = new SpongeCommandSender(source); + GeyserSession session = getGeyserSession(commandSender); + String[] args = arguments.split(" "); if (args.length > 0) { - if (getCommand(args[0]) != null) { - if (!source.hasPermission(getCommand(args[0]).getPermission())) { + GeyserCommand command = getCommand(args[0]); + if (command != null) { + if (!source.hasPermission(command.getPermission())) { // Not ideal to use log here but we dont get a session - source.sendMessage(Text.of(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail"))); + source.sendMessage(Text.of(ChatColor.RED + GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.permission_fail"))); return CommandResult.success(); } - getCommand(args[0]).execute(new SpongeCommandSender(source), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); + if (command.isBedrockOnly() && session == null) { + source.sendMessage(Text.of(ChatColor.RED + GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.bedrock_only"))); + return CommandResult.success(); + } + getCommand(args[0]).execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); } } else { - getCommand("help").execute(new SpongeCommandSender(source), new String[0]); + getCommand("help").execute(session, commandSender, new String[0]); } return CommandResult.success(); } @Override - public List getSuggestions(CommandSource source, String arguments, @Nullable Location targetPosition) throws CommandException { + public List getSuggestions(CommandSource source, String arguments, @Nullable Location targetPosition) { if (arguments.split(" ").length == 1) { - return connector.getCommandManager().getCommandNames(); + return tabComplete(new SpongeCommandSender(source)); } - return new ArrayList<>(); + return Collections.emptyList(); } @Override @@ -94,8 +104,4 @@ public class GeyserSpongeCommandExecutor implements CommandCallable { public Text getUsage(CommandSource source) { return Text.of("/geyser help"); } - - private GeyserCommand getCommand(String label) { - return connector.getCommandManager().getCommands().get(label); - } } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandManager.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/GeyserSpongeCommandManager.java similarity index 75% rename from bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandManager.java rename to bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/GeyserSpongeCommandManager.java index c36511a4c..d6cc967a4 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandManager.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/GeyserSpongeCommandManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,26 +23,27 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.sponge.command; +package org.geysermc.geyser.platform.sponge.command; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.CommandManager; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandManager; import org.spongepowered.api.Sponge; import org.spongepowered.api.command.CommandMapping; import org.spongepowered.api.text.Text; public class GeyserSpongeCommandManager extends CommandManager { + private final org.spongepowered.api.command.CommandManager handle; - private org.spongepowered.api.command.CommandManager handle; - - public GeyserSpongeCommandManager(org.spongepowered.api.command.CommandManager handle, GeyserConnector connector) { - super(connector); + public GeyserSpongeCommandManager(org.spongepowered.api.command.CommandManager handle, GeyserImpl geyser) { + super(geyser); this.handle = handle; } @Override public String getDescription(String command) { - return handle.get(command).map(CommandMapping::getCallable).map(callable -> callable.getShortDescription(Sponge.getServer().getConsole()).orElse(Text.EMPTY)).orElse(Text.EMPTY).toPlain(); + return handle.get(command).map(CommandMapping::getCallable) + .map(callable -> callable.getShortDescription(Sponge.getServer().getConsole()).orElse(Text.EMPTY)) + .orElse(Text.EMPTY).toPlain(); } } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/SpongeCommandSender.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/SpongeCommandSender.java similarity index 84% rename from bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/SpongeCommandSender.java rename to bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/SpongeCommandSender.java index a309a26f3..07c0f410a 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/SpongeCommandSender.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/SpongeCommandSender.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,11 +23,11 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.sponge.command; +package org.geysermc.geyser.platform.sponge.command; import lombok.AllArgsConstructor; -import org.geysermc.connector.command.CommandSender; +import org.geysermc.geyser.command.CommandSender; import org.spongepowered.api.command.CommandSource; import org.spongepowered.api.command.source.ConsoleSource; import org.spongepowered.api.text.Text; @@ -38,7 +38,7 @@ public class SpongeCommandSender implements CommandSender { private CommandSource handle; @Override - public String getName() { + public String name() { return handle.getName(); } @@ -51,4 +51,9 @@ public class SpongeCommandSender implements CommandSender { public boolean isConsole() { return handle instanceof ConsoleSource; } + + @Override + public boolean hasPermission(String permission) { + return handle.hasPermission(permission); + } } diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index 831239f66..e8653ee8e 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -6,15 +6,19 @@ org.geysermc bootstrap-parent - 1.2.0-SNAPSHOT + 2.0.0-SNAPSHOT bootstrap-standalone + + 2.16.0 + + org.geysermc - connector - 1.2.0-SNAPSHOT + core + 2.0.0-SNAPSHOT compile @@ -43,32 +47,32 @@ org.jline jline-terminal - 3.9.0 + 3.20.0 org.jline jline-terminal-jna - 3.9.0 + 3.20.0 org.jline jline-reader - 3.9.0 + 3.20.0 org.apache.logging.log4j log4j-api - 2.13.1 + ${log4j.version} org.apache.logging.log4j log4j-core - 2.13.2 + ${log4j.version} org.apache.logging.log4j log4j-slf4j18-impl - 2.13.1 + ${log4j.version} @@ -81,7 +85,7 @@ - org.geysermc.platform.standalone.GeyserStandaloneBootstrap + org.geysermc.geyser.platform.standalone.GeyserStandaloneBootstrap @@ -89,7 +93,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.1 + 3.3.0-SNAPSHOT com.github.edwgiz @@ -119,7 +123,7 @@ - org.geysermc.platform.standalone.GeyserStandaloneBootstrap + org.geysermc.geyser.platform.standalone.GeyserStandaloneBootstrap true diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java similarity index 78% rename from bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java rename to bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java index a0a8a3aea..b599550e3 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,32 +23,34 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.standalone; +package org.geysermc.geyser.platform.standalone; import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.introspect.AnnotatedField; import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; +import io.netty.util.ResourceLeakDetector; import lombok.Getter; import net.minecrell.terminalconsole.TerminalConsoleAppender; +import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.appender.ConsoleAppender; import org.geysermc.common.PlatformType; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.bootstrap.GeyserBootstrap; -import org.geysermc.connector.command.CommandManager; -import org.geysermc.connector.configuration.GeyserConfiguration; -import org.geysermc.connector.configuration.GeyserJacksonConfiguration; -import org.geysermc.connector.dump.BootstrapDumpInfo; -import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; -import org.geysermc.connector.ping.IGeyserPingPassthrough; -import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.LanguageUtils; -import org.geysermc.platform.standalone.command.GeyserCommandManager; -import org.geysermc.platform.standalone.gui.GeyserStandaloneGUI; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.GeyserBootstrap; +import org.geysermc.geyser.command.CommandManager; +import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; +import org.geysermc.geyser.dump.BootstrapDumpInfo; +import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; +import org.geysermc.geyser.ping.IGeyserPingPassthrough; +import org.geysermc.geyser.util.FileUtils; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.platform.standalone.command.GeyserCommandManager; +import org.geysermc.geyser.platform.standalone.gui.GeyserStandaloneGUI; import java.io.File; import java.io.IOException; @@ -72,18 +74,24 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { private boolean useGui = System.console() == null && !isHeadless(); private String configFilename = "config.yml"; - private GeyserConnector connector; + private GeyserImpl geyser; private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final Map argsConfigKeys = new HashMap<>(); public static void main(String[] args) { + if (System.getProperty("io.netty.leakDetection.level") == null) { + ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.DISABLED); // Can eat performance + } + GeyserStandaloneBootstrap bootstrap = new GeyserStandaloneBootstrap(); // Set defaults boolean useGuiOpts = bootstrap.useGui; String configFilenameOpt = bootstrap.configFilename; + GeyserLocale.init(bootstrap); + List availableProperties = getPOJOForClass(GeyserJacksonConfiguration.class); for (int i = 0; i < args.length; i++) { @@ -92,32 +100,26 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { // Allows gui and nogui without options, for backwards compatibility String arg = args[i]; switch (arg) { - case "--gui": - case "gui": - useGuiOpts = true; - break; - case "--nogui": - case "nogui": - useGuiOpts = false; - break; - case "--config": - case "-c": + case "--gui", "gui" -> useGuiOpts = true; + case "--nogui", "nogui" -> useGuiOpts = false; + case "--config", "-c" -> { if (i >= args.length - 1) { - System.err.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.config_not_specified"), "-c")); + System.err.println(MessageFormat.format(GeyserLocale.getLocaleStringLog("geyser.bootstrap.args.config_not_specified"), "-c")); return; } - configFilenameOpt = args[i+1]; i++; - System.out.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.config_specified"), configFilenameOpt)); - break; - case "--help": - case "-h": - System.out.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.usage"), "[java -jar] Geyser.jar [opts]")); - System.out.println(" " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.options")); - System.out.println(" -c, --config [file] " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.config")); - System.out.println(" -h, --help " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.help")); - System.out.println(" --gui, --nogui " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.gui")); + configFilenameOpt = args[i + 1]; + i++; + System.out.println(MessageFormat.format(GeyserLocale.getLocaleStringLog("geyser.bootstrap.args.config_specified"), configFilenameOpt)); + } + case "--help", "-h" -> { + System.out.println(MessageFormat.format(GeyserLocale.getLocaleStringLog("geyser.bootstrap.args.usage"), "[java -jar] Geyser.jar [opts]")); + System.out.println(" " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.args.options")); + System.out.println(" -c, --config [file] " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.args.config")); + System.out.println(" -h, --help " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.args.help")); + System.out.println(" --gui, --nogui " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.args.gui")); return; - default: + } + default -> { // We have likely added a config option argument if (arg.startsWith("--")) { // Split the argument by an = @@ -153,9 +155,9 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { } } } - - System.err.println(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.unrecognised", arg)); + System.err.println(GeyserLocale.getLocaleStringLog("geyser.bootstrap.args.unrecognised", arg)); return; + } } } bootstrap.onEnable(useGuiOpts, configFilenameOpt); @@ -167,11 +169,6 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { this.onEnable(); } - public void onEnable(boolean useGui) { - this.useGui = useGui; - this.onEnable(); - } - @Override public void onEnable() { Logger logger = (Logger) LogManager.getRootLogger(); @@ -193,7 +190,8 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { LoopbackUtil.checkLoopback(geyserLogger); try { - File configFile = FileUtils.fileOrCopiedFromResource(new File(configFilename), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); + File configFile = FileUtils.fileOrCopiedFromResource(new File(configFilename), "config.yml", + (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); geyserConfig = FileUtils.loadConfig(configFile, GeyserStandaloneConfiguration.class); handleArgsConfigOptions(); @@ -203,19 +201,27 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { geyserConfig.getRemote().setAddress("127.0.0.1"); } } catch (IOException ex) { - geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.config.failed"), ex); - System.exit(0); + geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); + if (gui == null) { + System.exit(1); + } else { + // Leave the process running so the GUI is still visible + return; + } } GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - connector = GeyserConnector.start(PlatformType.STANDALONE, this); - geyserCommandManager = new GeyserCommandManager(connector); + // Allow libraries like Protocol to have their debug information passthrough + logger.get().setLevel(geyserConfig.isDebugMode() ? Level.DEBUG : Level.INFO); + + geyser = GeyserImpl.start(PlatformType.STANDALONE, this); + geyserCommandManager = new GeyserCommandManager(geyser); if (gui != null) { gui.setupInterface(geyserLogger, geyserCommandManager); } - geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector); + geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); if (!useGui) { geyserLogger.start(); // Throws an error otherwise @@ -239,7 +245,7 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { @Override public void onDisable() { - connector.shutdown(); + geyser.shutdown(); System.exit(0); } @@ -306,6 +312,7 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { * @param parentObject The object to alter * @param value The new value of the property */ + @SuppressWarnings("unchecked") // Required for enum usage private static void setConfigOption(BeanPropertyDefinition property, Object parentObject, Object value) { Object parsedValue = value; @@ -314,6 +321,8 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { parsedValue = Integer.valueOf((String) parsedValue); } else if (boolean.class.equals(property.getRawPrimaryType())) { parsedValue = Boolean.valueOf((String) parsedValue); + } else if (Enum.class.isAssignableFrom(property.getRawPrimaryType())) { + parsedValue = Enum.valueOf((Class) property.getRawPrimaryType(), ((String) parsedValue).toUpperCase(Locale.ROOT)); } // Force the value to be set @@ -339,7 +348,7 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { // Loop through the sub property if the first part matches for (BeanPropertyDefinition subProperty : getPOJOForClass(property.getRawPrimaryType())) { if (configKeyParts[1].equals(subProperty.getName())) { - geyserLogger.info(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.set_config_option", configKey.getKey(), configKey.getValue())); + geyserLogger.info(GeyserLocale.getLocaleStringLog("geyser.bootstrap.args.set_config_option", configKey.getKey(), configKey.getValue())); // Set the sub property value on the config try { @@ -353,7 +362,7 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { } } } else { - geyserLogger.info(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.set_config_option", configKey.getKey(), configKey.getValue())); + geyserLogger.info(GeyserLocale.getLocaleStringLog("geyser.bootstrap.args.set_config_option", configKey.getKey(), configKey.getValue())); // Set the property value on the config try { diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneConfiguration.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneConfiguration.java similarity index 89% rename from bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneConfiguration.java rename to bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneConfiguration.java index 5707863c2..9eb3eebd2 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneConfiguration.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,11 +23,11 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.standalone; +package org.geysermc.geyser.platform.standalone; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Getter; -import org.geysermc.connector.configuration.GeyserJacksonConfiguration; +import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; import java.nio.file.Path; import java.nio.file.Paths; diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneDumpInfo.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneDumpInfo.java similarity index 87% rename from bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneDumpInfo.java rename to bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneDumpInfo.java index 5210c5196..7524578ec 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneDumpInfo.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneDumpInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,15 +23,14 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.standalone; +package org.geysermc.geyser.platform.standalone; import lombok.Getter; -import org.geysermc.connector.dump.BootstrapDumpInfo; +import org.geysermc.geyser.dump.BootstrapDumpInfo; @Getter public class GeyserStandaloneDumpInfo extends BootstrapDumpInfo { - - private boolean isGui; + private final boolean isGui; GeyserStandaloneDumpInfo(GeyserStandaloneBootstrap bootstrap) { super(); diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java similarity index 66% rename from bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java rename to bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java index f0f7156f9..b585ebe7b 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,73 +23,68 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.standalone; +package org.geysermc.geyser.platform.standalone; import lombok.extern.log4j.Log4j2; import net.minecrell.terminalconsole.SimpleTerminalConsole; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.config.Configurator; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.GeyserLogger; -import org.geysermc.connector.command.CommandSender; -import org.geysermc.connector.common.ChatColor; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.GeyserLogger; +import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.text.ChatColor; @Log4j2 public class GeyserStandaloneLogger extends SimpleTerminalConsole implements GeyserLogger, CommandSender { - private boolean colored = true; @Override protected boolean isRunning() { - return !GeyserConnector.getInstance().isShuttingDown(); + return !GeyserImpl.getInstance().isShuttingDown(); } @Override protected void runCommand(String line) { - GeyserConnector.getInstance().getCommandManager().runCommand(this, line); + GeyserImpl.getInstance().getCommandManager().runCommand(this, line); } @Override protected void shutdown() { - GeyserConnector.getInstance().getBootstrap().onDisable(); + GeyserImpl.getInstance().getBootstrap().onDisable(); } @Override public void severe(String message) { - log.fatal(printConsole(ChatColor.DARK_RED + message, colored)); + log.fatal(ChatColor.DARK_RED + message); } @Override public void severe(String message, Throwable error) { - log.fatal(printConsole(ChatColor.DARK_RED + message, colored), error); + log.fatal(ChatColor.DARK_RED + message, error); } @Override public void error(String message) { - log.error(printConsole(ChatColor.RED + message, colored)); + log.error(ChatColor.RED + message); } @Override public void error(String message, Throwable error) { - log.error(printConsole(ChatColor.RED + message, colored), error); + log.error(ChatColor.RED + message, error); } @Override public void warning(String message) { - log.warn(printConsole(ChatColor.YELLOW + message, colored)); + log.warn(ChatColor.YELLOW + message); } @Override public void info(String message) { - log.info(printConsole(ChatColor.RESET + ChatColor.BOLD + message, colored)); + log.info(ChatColor.RESET + message); } @Override public void debug(String message) { - log.debug(printConsole(ChatColor.GRAY + message, colored)); - } - - public static String printConsole(String message, boolean colors) { - return colors ? ChatColor.toANSI(message + ChatColor.RESET) : ChatColor.stripColors(message + ChatColor.RESET); + log.debug(ChatColor.GRAY + message); } @Override @@ -102,7 +97,7 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements Gey } @Override - public String getName() { + public String name() { return "CONSOLE"; } @@ -115,4 +110,9 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements Gey public boolean isConsole() { return true; } + + @Override + public boolean hasPermission(String permission) { + return true; + } } diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/LoopbackUtil.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/LoopbackUtil.java similarity index 81% rename from bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/LoopbackUtil.java rename to bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/LoopbackUtil.java index 1619beb60..d2b4656f0 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/LoopbackUtil.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/LoopbackUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,14 +23,13 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.standalone; +package org.geysermc.geyser.platform.standalone; -import org.geysermc.connector.common.ChatColor; -import org.geysermc.connector.utils.LanguageUtils; +import org.geysermc.geyser.text.ChatColor; +import org.geysermc.geyser.text.GeyserLocale; import java.io.InputStream; import java.nio.file.Files; -import java.nio.file.OpenOption; import java.nio.file.Paths; public class LoopbackUtil { @@ -54,15 +53,15 @@ public class LoopbackUtil { String result = sb.toString(); if (!result.contains("minecraftuwp")) { - Files.write(Paths.get(System.getenv("temp") + "/loopback_minecraft.bat"), loopbackCommand.getBytes(), new OpenOption[0]); - process = Runtime.getRuntime().exec(startScript); + Files.write(Paths.get(System.getenv("temp") + "/loopback_minecraft.bat"), loopbackCommand.getBytes()); + Runtime.getRuntime().exec(startScript); - geyserLogger.info(ChatColor.AQUA + LanguageUtils.getLocaleStringLog("geyser.bootstrap.loopback.added")); + geyserLogger.info(ChatColor.AQUA + GeyserLocale.getLocaleStringLog("geyser.bootstrap.loopback.added")); } } catch (Exception e) { e.printStackTrace(); - geyserLogger.error(LanguageUtils.getLocaleStringLog("geyser.bootstrap.loopback.failed")); + geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.loopback.failed")); } } } diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/command/GeyserCommandManager.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/command/GeyserCommandManager.java similarity index 82% rename from bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/command/GeyserCommandManager.java rename to bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/command/GeyserCommandManager.java index 41bf61c12..83da038a1 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/command/GeyserCommandManager.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/command/GeyserCommandManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,15 +23,15 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.standalone.command; +package org.geysermc.geyser.platform.standalone.command; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.CommandManager; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandManager; public class GeyserCommandManager extends CommandManager { - public GeyserCommandManager(GeyserConnector connector) { - super(connector); + public GeyserCommandManager(GeyserImpl geyser) { + super(geyser); } @Override diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/ANSIColor.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/ANSIColor.java similarity index 96% rename from bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/ANSIColor.java rename to bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/ANSIColor.java index 898b9474d..1d6057b8c 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/ANSIColor.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/ANSIColor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.standalone.gui; +package org.geysermc.geyser.platform.standalone.gui; import lombok.Getter; diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/ColorPane.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/ColorPane.java similarity index 94% rename from bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/ColorPane.java rename to bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/ColorPane.java index bab4ea998..0c8790ddd 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/ColorPane.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/ColorPane.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.standalone.gui; +package org.geysermc.geyser.platform.standalone.gui; import javax.swing.*; import javax.swing.text.*; @@ -60,8 +60,8 @@ public class ColorPane extends JTextPane { */ public void appendANSI(String s) { // convert ANSI color codes first int aPos = 0; // current char position in addString - int aIndex = 0; // index of next Escape sequence - int mIndex = 0; // index of "m" terminating Escape sequence + int aIndex; // index of next Escape sequence + int mIndex; // index of "m" terminating Escape sequence String tmpString = ""; boolean stillSearching = true; // true until no more Escape sequences String addString = remaining + s; @@ -83,7 +83,6 @@ public class ColorPane extends JTextPane { // while there's text in the input buffer - stillSearching = true; while (stillSearching) { mIndex = addString.indexOf("m", aPos); // find the end of the escape sequence if (mIndex < 0) { // the buffer ends halfway through the ansi string! diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GeyserStandaloneGUI.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java similarity index 82% rename from bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GeyserStandaloneGUI.java rename to bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java index aeee84624..56d211986 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GeyserStandaloneGUI.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,14 +23,14 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.standalone.gui; +package org.geysermc.geyser.platform.standalone.gui; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.GeyserCommand; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.utils.LanguageUtils; -import org.geysermc.platform.standalone.GeyserStandaloneLogger; -import org.geysermc.platform.standalone.command.GeyserCommandManager; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.platform.standalone.GeyserStandaloneLogger; +import org.geysermc.geyser.platform.standalone.command.GeyserCommandManager; import javax.swing.*; import javax.swing.table.DefaultTableModel; @@ -67,7 +67,7 @@ public class GeyserStandaloneGUI { public GeyserStandaloneGUI() { // Create the frame and setup basic settings - JFrame frame = new JFrame(LanguageUtils.getLocaleStringLog("geyser.gui.title")); + JFrame frame = new JFrame(GeyserLocale.getLocaleStringLog("geyser.gui.title")); frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); frame.setSize(800, 400); frame.setMinimumSize(frame.getSize()); @@ -82,8 +82,8 @@ public class GeyserStandaloneGUI { @Override public void windowClosing(WindowEvent we) { - String[] buttons = {LanguageUtils.getLocaleStringLog("geyser.gui.exit.confirm"), LanguageUtils.getLocaleStringLog("geyser.gui.exit.deny")}; - int result = JOptionPane.showOptionDialog(frame, LanguageUtils.getLocaleStringLog("geyser.gui.exit.message"), frame.getTitle(), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, buttons, buttons[1]); + String[] buttons = {GeyserLocale.getLocaleStringLog("geyser.gui.exit.confirm"), GeyserLocale.getLocaleStringLog("geyser.gui.exit.deny")}; + int result = JOptionPane.showOptionDialog(frame, GeyserLocale.getLocaleStringLog("geyser.gui.exit.message"), frame.getTitle(), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, buttons, buttons[1]); if (result == JOptionPane.YES_OPTION) { System.exit(0); } @@ -124,12 +124,12 @@ public class GeyserStandaloneGUI { JMenuBar menuBar = new JMenuBar(); // Create 'File' - JMenu fileMenu = new JMenu(LanguageUtils.getLocaleStringLog("geyser.gui.menu.file")); + JMenu fileMenu = new JMenu(GeyserLocale.getLocaleStringLog("geyser.gui.menu.file")); fileMenu.setMnemonic(KeyEvent.VK_F); menuBar.add(fileMenu); // 'Open Geyser folder' button - JMenuItem openButton = new JMenuItem(LanguageUtils.getLocaleStringLog("geyser.gui.menu.file.open_folder"), KeyEvent.VK_O); + JMenuItem openButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.file.open_folder"), KeyEvent.VK_O); openButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_MASK)); openButton.addActionListener(e -> { try { @@ -141,40 +141,40 @@ public class GeyserStandaloneGUI { fileMenu.addSeparator(); // 'Exit' button - JMenuItem exitButton = new JMenuItem(LanguageUtils.getLocaleStringLog("geyser.gui.menu.file.exit"), KeyEvent.VK_X); + JMenuItem exitButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.file.exit"), KeyEvent.VK_X); exitButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, InputEvent.ALT_MASK)); exitButton.addActionListener(e -> System.exit(0)); fileMenu.add(exitButton); // Create 'Commands' - commandsMenu = new JMenu(LanguageUtils.getLocaleStringLog("geyser.gui.menu.commands")); + commandsMenu = new JMenu(GeyserLocale.getLocaleStringLog("geyser.gui.menu.commands")); commandsMenu.setMnemonic(KeyEvent.VK_C); menuBar.add(commandsMenu); // Create 'View' - JMenu viewMenu = new JMenu(LanguageUtils.getLocaleStringLog("geyser.gui.menu.view")); + JMenu viewMenu = new JMenu(GeyserLocale.getLocaleStringLog("geyser.gui.menu.view")); viewMenu.setMnemonic(KeyEvent.VK_V); menuBar.add(viewMenu); // 'Zoom in' button - JMenuItem zoomInButton = new JMenuItem(LanguageUtils.getLocaleStringLog("geyser.gui.menu.view.zoom_in")); + JMenuItem zoomInButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.view.zoom_in")); zoomInButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, InputEvent.CTRL_DOWN_MASK)); zoomInButton.addActionListener(e -> consolePane.setFont(new Font(consolePane.getFont().getName(), consolePane.getFont().getStyle(), consolePane.getFont().getSize() + 1))); viewMenu.add(zoomInButton); // 'Zoom in' button - JMenuItem zoomOutButton = new JMenuItem(LanguageUtils.getLocaleStringLog("geyser.gui.menu.view.zoom_out")); + JMenuItem zoomOutButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.view.zoom_out")); zoomOutButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, InputEvent.CTRL_DOWN_MASK)); zoomOutButton.addActionListener(e -> consolePane.setFont(new Font(consolePane.getFont().getName(), consolePane.getFont().getStyle(), consolePane.getFont().getSize() - 1))); viewMenu.add(zoomOutButton); // 'Reset Zoom' button - JMenuItem resetZoomButton = new JMenuItem(LanguageUtils.getLocaleStringLog("geyser.gui.menu.view.reset_zoom")); + JMenuItem resetZoomButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.view.reset_zoom")); resetZoomButton.addActionListener(e -> consolePane.setFont(new Font(consolePane.getFont().getName(), consolePane.getFont().getStyle(), originalFontSize))); viewMenu.add(resetZoomButton); // create 'Options' - optionsMenu = new JMenu(LanguageUtils.getLocaleStringLog("geyser.gui.menu.options")); + optionsMenu = new JMenu(GeyserLocale.getLocaleStringLog("geyser.gui.menu.options")); viewMenu.setMnemonic(KeyEvent.VK_O); menuBar.add(optionsMenu); @@ -195,11 +195,11 @@ public class GeyserStandaloneGUI { ramValues.add(0); } ramGraph.setValues(ramValues); - ramGraph.setXLabel(LanguageUtils.getLocaleStringLog("geyser.gui.graph.loading")); + ramGraph.setXLabel(GeyserLocale.getLocaleStringLog("geyser.gui.graph.loading")); rightContentPane.add(ramGraph); - playerTableModel.addColumn(LanguageUtils.getLocaleStringLog("geyser.gui.table.ip")); - playerTableModel.addColumn(LanguageUtils.getLocaleStringLog("geyser.gui.table.username")); + playerTableModel.addColumn(GeyserLocale.getLocaleStringLog("geyser.gui.table.ip")); + playerTableModel.addColumn(GeyserLocale.getLocaleStringLog("geyser.gui.table.username")); JScrollPane playerScrollPane = new JScrollPane(playerTable); rightContentPane.add(playerScrollPane); @@ -271,17 +271,17 @@ public class GeyserStandaloneGUI { JMenuItem commandButton = hasSubCommands ? new JMenu(command.getValue().getName()) : new JMenuItem(command.getValue().getName()); commandButton.getAccessibleContext().setAccessibleDescription(command.getValue().getDescription()); if (!hasSubCommands) { - commandButton.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{ })); + commandButton.addActionListener(e -> command.getValue().execute(null, geyserStandaloneLogger, new String[]{ })); } else { // Add a submenu that's the same name as the menu can't be pressed JMenuItem otherCommandButton = new JMenuItem(command.getValue().getName()); otherCommandButton.getAccessibleContext().setAccessibleDescription(command.getValue().getDescription()); - otherCommandButton.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{ })); + otherCommandButton.addActionListener(e -> command.getValue().execute(null, geyserStandaloneLogger, new String[]{ })); commandButton.add(otherCommandButton); // Add a menu option for all possible subcommands for (String subCommandName : command.getValue().getSubCommands()) { JMenuItem item = new JMenuItem(subCommandName); - item.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{subCommandName})); + item.addActionListener(e -> command.getValue().execute(null, geyserStandaloneLogger, new String[]{subCommandName})); commandButton.add(item); } } @@ -289,7 +289,7 @@ public class GeyserStandaloneGUI { } // 'Debug Mode' toggle - JCheckBoxMenuItem debugMode = new JCheckBoxMenuItem(LanguageUtils.getLocaleStringLog("geyser.gui.menu.options.toggle_debug_mode")); + JCheckBoxMenuItem debugMode = new JCheckBoxMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.options.toggle_debug_mode")); debugMode.setSelected(geyserStandaloneLogger.isDebug()); debugMode.addActionListener(e -> geyserStandaloneLogger.setDebug(!geyserStandaloneLogger.isDebug())); optionsMenu.add(debugMode); @@ -302,11 +302,11 @@ public class GeyserStandaloneGUI { ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); Runnable periodicTask = () -> { - if (GeyserConnector.getInstance() != null) { + if (GeyserImpl.getInstance() != null) { // Update player table playerTableModel.getDataVector().removeAllElements(); - for (GeyserSession player : GeyserConnector.getInstance().getPlayers()) { + for (GeyserSession player : GeyserImpl.getInstance().getSessionManager().getSessions().values()) { Vector row = new Vector<>(); row.add(player.getSocketAddress().getHostName()); row.add(player.getPlayerEntity().getUsername()); @@ -323,7 +323,7 @@ public class GeyserStandaloneGUI { final int freePercent = (int)(freeMemory * 100.0 / totalMemory + 0.5); ramValues.add(100 - freePercent); - ramGraph.setXLabel(LanguageUtils.getLocaleStringLog("geyser.gui.graph.usage", String.format("%,d", (totalMemory - freeMemory) / MEGABYTE), freePercent)); + ramGraph.setXLabel(GeyserLocale.getLocaleStringLog("geyser.gui.graph.usage", String.format("%,d", (totalMemory - freeMemory) / MEGABYTE), freePercent)); // Trim the list int k = ramValues.size(); diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GraphPanel.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GraphPanel.java similarity index 96% rename from bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GraphPanel.java rename to bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GraphPanel.java index 82c38e254..83100ddac 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GraphPanel.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GraphPanel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.standalone.gui; +package org.geysermc.geyser.platform.standalone.gui; import lombok.Setter; @@ -46,7 +46,7 @@ public final class GraphPanel extends JPanel { private final static Color pointColor = new Color(100, 100, 100, 255); private final static Color gridColor = new Color(200, 200, 200, 255); private static final Stroke graphStroke = new BasicStroke(2f); - private List values = new ArrayList<>(10); + private final List values = new ArrayList<>(10); @Setter private String xLabel = ""; @@ -68,11 +68,10 @@ public final class GraphPanel extends JPanel { @Override protected void paintComponent(Graphics graphics) { super.paintComponent(graphics); - if (!(graphics instanceof Graphics2D)) { + if (!(graphics instanceof final Graphics2D g)) { graphics.drawString("Graphics is not Graphics2D, unable to render", 0, 0); return; } - final Graphics2D g = (Graphics2D) graphics; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); final int length = values.size(); @@ -172,6 +171,7 @@ public final class GraphPanel extends JPanel { for (Point graphPoint : graphPoints) { final int x = graphPoint.x - pointWidth / 2; final int y = graphPoint.y - pointWidth / 2; + //noinspection SuspiciousNameCombination g.fillOval(x, y, pointWidth, pointWidth); } } diff --git a/bootstrap/standalone/src/main/resources/log4j2.xml b/bootstrap/standalone/src/main/resources/log4j2.xml index 238a27da5..cd101f306 100644 --- a/bootstrap/standalone/src/main/resources/log4j2.xml +++ b/bootstrap/standalone/src/main/resources/log4j2.xml @@ -8,7 +8,7 @@ - + diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index 86de99ba6..e1e3331ef 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -6,21 +6,21 @@ org.geysermc bootstrap-parent - 1.2.0-SNAPSHOT + 2.0.0-SNAPSHOT bootstrap-velocity org.geysermc - connector - 1.2.0-SNAPSHOT + core + 2.0.0-SNAPSHOT compile com.velocitypowered velocity-api - 1.1.0 + 3.0.0 provided @@ -40,7 +40,7 @@ - org.geysermc.platform.velocity.GeyserVelocityMain + org.geysermc.geyser.platform.velocity.GeyserVelocityMain @@ -48,7 +48,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.1 + 3.3.0-SNAPSHOT package @@ -59,31 +59,15 @@ com.fasterxml.jackson - org.geysermc.platform.velocity.shaded.jackson + org.geysermc.geyser.platform.velocity.shaded.jackson it.unimi.dsi.fastutil - org.geysermc.platform.velocity.shaded.fastutil + org.geysermc.geyser.platform.velocity.shaded.fastutil - org.reflections - org.geysermc.platform.velocity.shaded.reflections - - - com.google.common - org.geysermc.platform.velocity.shaded.google.common - - - com.google.guava - org.geysermc.platform.velocity.shaded.google.guava - - - org.dom4j - org.geysermc.platform.velocity.shaded.dom4j - - - net.kyori.adventure - org.geysermc.platform.velocity.shaded.adventure + net.kyori.adventure.text.serializer.gson.legacyimpl + org.geysermc.geyser.platform.velocity.shaded.kyori.legacyimpl @@ -92,10 +76,27 @@ - com.google.code.gson:* - io.netty:* + com.google.*:* + + io.netty:netty-transport-native-epoll:* + io.netty:netty-transport-native-unix-common:* + io.netty:netty-transport-native-kqueue:* + io.netty:netty-handler:* + io.netty:netty-common:* + io.netty:netty-buffer:* + io.netty:netty-resolver:* + io.netty:netty-transport:* + io.netty:netty-codec:* + io.netty:netty-codec-haproxy:* org.slf4j:* org.ow2.asm:* + + net.kyori:adventure-api:* + net.kyori:examination-api:* + net.kyori:examination-string:* + net.kyori:adventure-text-serializer-gson:* + net.kyori:adventure-text-serializer-legacy:* + net.kyori:adventure-nbt:* diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityConfiguration.java similarity index 78% rename from bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java rename to bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityConfiguration.java index 7dc6746b0..62e600290 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,19 +23,20 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.velocity; +package org.geysermc.geyser.platform.velocity; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.proxy.ProxyServer; import lombok.Getter; -import org.geysermc.connector.FloodgateKeyLoader; -import org.geysermc.connector.configuration.GeyserJacksonConfiguration; +import org.geysermc.geyser.FloodgateKeyLoader; +import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Optional; @Getter @JsonIgnoreProperties(ignoreUnknown = true) @@ -44,7 +45,8 @@ public final class GeyserVelocityConfiguration extends GeyserJacksonConfiguratio private Path floodgateKeyPath; public void loadFloodgate(GeyserVelocityPlugin plugin, ProxyServer proxyServer, File dataFolder) { - PluginContainer floodgate = proxyServer.getPluginManager().getPlugin("floodgate").orElse(null); - floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgate, Paths.get("plugins/floodgate/"), dataFolder.toPath(), plugin.getGeyserLogger()); + Optional floodgate = proxyServer.getPluginManager().getPlugin("floodgate"); + Path floodgateDataPath = floodgate.isPresent() ? Paths.get("plugins/floodgate/") : null; + floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgateDataPath, dataFolder.toPath(), plugin.getGeyserLogger()); } } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityDumpInfo.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityDumpInfo.java similarity index 79% rename from bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityDumpInfo.java rename to bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityDumpInfo.java index f44086c59..c2d6d12a0 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityDumpInfo.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityDumpInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,13 +23,13 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.velocity; +package org.geysermc.geyser.platform.velocity; import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.proxy.ProxyServer; import lombok.Getter; -import org.geysermc.connector.common.serializer.AsteriskSerializer; -import org.geysermc.connector.dump.BootstrapDumpInfo; +import org.geysermc.geyser.text.AsteriskSerializer; +import org.geysermc.geyser.dump.BootstrapDumpInfo; import java.util.ArrayList; import java.util.List; @@ -37,13 +37,13 @@ import java.util.List; @Getter public class GeyserVelocityDumpInfo extends BootstrapDumpInfo { - private String platformName; - private String platformVersion; - private String platformVendor; - private boolean onlineMode; - private String serverIP; - private int serverPort; - private List plugins; + private final String platformName; + private final String platformVersion; + private final String platformVendor; + private final boolean onlineMode; + private final String serverIP; + private final int serverPort; + private final List plugins; GeyserVelocityDumpInfo(ProxyServer proxy) { super(); @@ -61,7 +61,7 @@ public class GeyserVelocityDumpInfo extends BootstrapDumpInfo { for (PluginContainer plugin : proxy.getPluginManager().getPlugins()) { String pluginClass = plugin.getInstance().map((pl) -> pl.getClass().getName()).orElse("unknown"); - this.plugins.add(new PluginInfo(true, plugin.getDescription().getName().get(), plugin.getDescription().getVersion().get(), pluginClass, plugin.getDescription().getAuthors())); + this.plugins.add(new PluginInfo(true, plugin.getDescription().getName().orElse(null), plugin.getDescription().getVersion().orElse(null), pluginClass, plugin.getDescription().getAuthors())); } } } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityInjector.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityInjector.java new file mode 100644 index 000000000..27cf9e815 --- /dev/null +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityInjector.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.platform.velocity; + +import com.velocitypowered.api.proxy.ProxyServer; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.*; +import io.netty.channel.local.LocalAddress; +import org.geysermc.geyser.GeyserBootstrap; +import org.geysermc.geyser.network.netty.GeyserInjector; +import org.geysermc.geyser.network.netty.LocalServerChannelWrapper; + +import java.lang.reflect.Field; +import java.util.function.Supplier; + +public class GeyserVelocityInjector extends GeyserInjector { + private final ProxyServer proxy; + + public GeyserVelocityInjector(ProxyServer proxy) { + this.proxy = proxy; + } + + @Override + @SuppressWarnings("unchecked") + protected void initializeLocalChannel0(GeyserBootstrap bootstrap) throws Exception { + Field cm = proxy.getClass().getDeclaredField("cm"); + cm.setAccessible(true); + Object connectionManager = cm.get(proxy); + Class connectionManagerClass = connectionManager.getClass(); + + Supplier> serverChannelInitializerHolder = (Supplier>) connectionManagerClass + .getMethod("getServerChannelInitializer") + .invoke(connectionManager); + ChannelInitializer channelInitializer = serverChannelInitializerHolder.get(); + + // Is set on Velocity's end for listening to Java connections - required on ours or else the initial world load process won't finish sometimes + Field serverWriteMarkField = connectionManagerClass.getDeclaredField("SERVER_WRITE_MARK"); + serverWriteMarkField.setAccessible(true); + WriteBufferWaterMark serverWriteMark = (WriteBufferWaterMark) serverWriteMarkField.get(null); + + EventLoopGroup bossGroup = (EventLoopGroup) connectionManagerClass.getMethod("getBossGroup").invoke(connectionManager); + + Field workerGroupField = connectionManagerClass.getDeclaredField("workerGroup"); + workerGroupField.setAccessible(true); + EventLoopGroup workerGroup = (EventLoopGroup) workerGroupField.get(connectionManager); + + ChannelFuture channelFuture = (new ServerBootstrap() + .channel(LocalServerChannelWrapper.class) + .childHandler(channelInitializer) + .group(bossGroup, workerGroup) // Cannot be DefaultEventLoopGroup + .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, serverWriteMark) // Required or else rare network freezes can occur + .localAddress(LocalAddress.ANY)) + .bind() + .syncUninterruptibly(); + + this.localChannel = channelFuture; + this.serverSocketAddress = channelFuture.channel().localAddress(); + } +} diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityLogger.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityLogger.java similarity index 93% rename from bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityLogger.java rename to bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityLogger.java index cb98411c8..de9ea78d5 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityLogger.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityLogger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,12 +23,12 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.velocity; +package org.geysermc.geyser.platform.velocity; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; -import org.geysermc.connector.GeyserLogger; +import org.geysermc.geyser.GeyserLogger; import org.slf4j.Logger; @AllArgsConstructor diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityMain.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityMain.java similarity index 87% rename from bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityMain.java rename to bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityMain.java index cc6c93e88..35408ce9c 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityMain.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityMain.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,11 +23,11 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.velocity; +package org.geysermc.geyser.platform.velocity; -import org.geysermc.connector.common.main.IGeyserMain; +import org.geysermc.geyser.GeyserMain; -public class GeyserVelocityMain extends IGeyserMain { +public class GeyserVelocityMain extends GeyserMain { public static void main(String[] args) { new GeyserVelocityMain().displayMessage(); diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPingPassthrough.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPingPassthrough.java similarity index 80% rename from bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPingPassthrough.java rename to bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPingPassthrough.java index ff8376e4b..a567233db 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPingPassthrough.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPingPassthrough.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.velocity; +package org.geysermc.geyser.platform.velocity; import com.velocitypowered.api.event.proxy.ProxyPingEvent; import com.velocitypowered.api.network.ProtocolVersion; @@ -31,11 +31,10 @@ import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.server.ServerPing; import lombok.AllArgsConstructor; -import net.kyori.text.serializer.legacy.LegacyComponentSerializer; -import org.geysermc.connector.common.ping.GeyserPingInfo; -import org.geysermc.connector.ping.IGeyserPingPassthrough; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.geysermc.geyser.ping.GeyserPingInfo; +import org.geysermc.geyser.ping.IGeyserPingPassthrough; -import java.net.Inet4Address; import java.net.InetSocketAddress; import java.util.Optional; import java.util.concurrent.ExecutionException; @@ -43,22 +42,20 @@ import java.util.concurrent.ExecutionException; @AllArgsConstructor public class GeyserVelocityPingPassthrough implements IGeyserPingPassthrough { - private static final GeyserInboundConnection FAKE_INBOUND_CONNECTION = new GeyserInboundConnection(); - private final ProxyServer server; @Override - public GeyserPingInfo getPingInformation() { + public GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) { ProxyPingEvent event; try { - event = server.getEventManager().fire(new ProxyPingEvent(FAKE_INBOUND_CONNECTION, ServerPing.builder() - .description(server.getConfiguration().getMotdComponent()).onlinePlayers(server.getPlayerCount()) + event = server.getEventManager().fire(new ProxyPingEvent(new GeyserInboundConnection(inetSocketAddress), ServerPing.builder() + .description(server.getConfiguration().getMotd()).onlinePlayers(server.getPlayerCount()) .maximumPlayers(server.getConfiguration().getShowMaxPlayers()).build())).get(); } catch (ExecutionException | InterruptedException e) { throw new RuntimeException(e); } GeyserPingInfo geyserPingInfo = new GeyserPingInfo( - LegacyComponentSerializer.legacy().serialize(event.getPing().getDescription(), '§'), + LegacyComponentSerializer.legacy('§').serialize(event.getPing().getDescriptionComponent()), new GeyserPingInfo.Players( event.getPing().getPlayers().orElseThrow(IllegalStateException::new).getMax(), event.getPing().getPlayers().orElseThrow(IllegalStateException::new).getOnline() @@ -74,11 +71,15 @@ public class GeyserVelocityPingPassthrough implements IGeyserPingPassthrough { private static class GeyserInboundConnection implements InboundConnection { - private static final InetSocketAddress FAKE_REMOTE = new InetSocketAddress(Inet4Address.getLoopbackAddress(), 69); + private final InetSocketAddress remote; + + public GeyserInboundConnection(InetSocketAddress remote) { + this.remote = remote; + } @Override public InetSocketAddress getRemoteAddress() { - return FAKE_REMOTE; + return this.remote; } @Override diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java similarity index 61% rename from bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java rename to bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java index b5255e623..8106192ac 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,37 +23,42 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.velocity; +package org.geysermc.geyser.platform.velocity; import com.google.inject.Inject; import com.velocitypowered.api.command.CommandManager; import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.proxy.ListenerBoundEvent; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; +import com.velocitypowered.api.network.ListenerType; import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.proxy.ProxyServer; import lombok.Getter; import org.geysermc.common.PlatformType; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.bootstrap.GeyserBootstrap; -import org.geysermc.connector.configuration.GeyserConfiguration; -import org.geysermc.connector.dump.BootstrapDumpInfo; -import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; -import org.geysermc.connector.ping.IGeyserPingPassthrough; -import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.LanguageUtils; -import org.geysermc.platform.velocity.command.GeyserVelocityCommandExecutor; -import org.geysermc.platform.velocity.command.GeyserVelocityCommandManager; +import org.geysermc.geyser.GeyserBootstrap; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.dump.BootstrapDumpInfo; +import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; +import org.geysermc.geyser.ping.IGeyserPingPassthrough; +import org.geysermc.geyser.platform.velocity.command.GeyserVelocityCommandExecutor; +import org.geysermc.geyser.platform.velocity.command.GeyserVelocityCommandManager; +import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.util.FileUtils; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.nio.file.Path; import java.nio.file.Paths; import java.util.UUID; -@Plugin(id = "geyser", name = GeyserConnector.NAME + "-Velocity", version = GeyserConnector.VERSION, url = "https://geysermc.org", authors = "GeyserMC") +@Plugin(id = "geyser", name = GeyserImpl.NAME + "-Velocity", version = GeyserImpl.VERSION, url = "https://geysermc.org", authors = "GeyserMC") public class GeyserVelocityPlugin implements GeyserBootstrap { @Inject @@ -67,25 +72,30 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { private GeyserVelocityCommandManager geyserCommandManager; private GeyserVelocityConfiguration geyserConfig; + private GeyserVelocityInjector geyserInjector; private GeyserVelocityLogger geyserLogger; private IGeyserPingPassthrough geyserPingPassthrough; - private GeyserConnector connector; + private GeyserImpl geyser; @Getter - private final Path configFolder = Paths.get("plugins/" + GeyserConnector.NAME + "-Velocity/"); + private final Path configFolder = Paths.get("plugins/" + GeyserImpl.NAME + "-Velocity/"); @Override public void onEnable() { + GeyserLocale.init(this); + try { if (!configFolder.toFile().exists()) //noinspection ResultOfMethodCallIgnored configFolder.toFile().mkdirs(); - File configFile = FileUtils.fileOrCopiedFromResource(configFolder.resolve("config.yml").toFile(), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); + File configFile = FileUtils.fileOrCopiedFromResource(configFolder.resolve("config.yml").toFile(), + "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); this.geyserConfig = FileUtils.loadConfig(configFile, GeyserVelocityConfiguration.class); } catch (IOException ex) { - logger.warn(LanguageUtils.getLocaleStringLog("geyser.config.failed"), ex); + logger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); ex.printStackTrace(); + return; } InetSocketAddress javaAddr = proxyServer.getBoundAddress(); @@ -107,23 +117,37 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { this.geyserLogger = new GeyserVelocityLogger(logger, geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - if (geyserConfig.getRemote().getAuthType().equals("floodgate") && !proxyServer.getPluginManager().getPlugin("floodgate").isPresent()) { - geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); + // Remove this in like a year + try { + // Should only exist on 1.0 + Class.forName("org.geysermc.floodgate.FloodgateAPI"); + geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.outdated", + "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/")); + return; + } catch (ClassNotFoundException ignored) { + } + + if (geyserConfig.getRemote().getAuthType() == AuthType.FLOODGATE && proxyServer.getPluginManager().getPlugin("floodgate").isEmpty()) { + geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + + GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); return; } else if (geyserConfig.isAutoconfiguredRemote() && proxyServer.getPluginManager().getPlugin("floodgate").isPresent()) { // Floodgate installed means that the user wants Floodgate authentication geyserLogger.debug("Auto-setting to Floodgate authentication."); - geyserConfig.getRemote().setAuthType("floodgate"); + geyserConfig.getRemote().setAuthType(AuthType.FLOODGATE); } geyserConfig.loadFloodgate(this, proxyServer, configFolder.toFile()); - this.connector = GeyserConnector.start(PlatformType.VELOCITY, this); + this.geyser = GeyserImpl.start(PlatformType.VELOCITY, this); - this.geyserCommandManager = new GeyserVelocityCommandManager(connector); - this.commandManager.register("geyser", new GeyserVelocityCommandExecutor(connector)); + this.geyserInjector = new GeyserVelocityInjector(proxyServer); + // Will be initialized after the proxy has been bound + + this.geyserCommandManager = new GeyserVelocityCommandManager(geyser); + this.commandManager.register("geyser", new GeyserVelocityCommandExecutor(geyser)); if (geyserConfig.isLegacyPingPassthrough()) { - this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector); + this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); } else { this.geyserPingPassthrough = new GeyserVelocityPingPassthrough(proxyServer); } @@ -131,7 +155,12 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { @Override public void onDisable() { - connector.shutdown(); + if (geyser != null) { + geyser.shutdown(); + } + if (geyserInjector != null) { + geyserInjector.shutdown(); + } } @Override @@ -145,7 +174,7 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { } @Override - public org.geysermc.connector.command.CommandManager getGeyserCommandManager() { + public org.geysermc.geyser.command.CommandManager getGeyserCommandManager() { return this.geyserCommandManager; } @@ -164,8 +193,22 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { onDisable(); } + @Subscribe + public void onProxyBound(ListenerBoundEvent event) { + if (event.getListenerType() == ListenerType.MINECRAFT && geyserInjector != null) { + // After this bound, we know that the channel initializer cannot change without it being ineffective for Velocity, too + geyserInjector.initializeLocalChannel(this); + } + } + @Override public BootstrapDumpInfo getDumpInfo() { return new GeyserVelocityDumpInfo(proxyServer); } + + @Nullable + @Override + public SocketAddress getSocketAddress() { + return this.geyserInjector.getServerSocketAddress(); + } } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandExecutor.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandExecutor.java new file mode 100644 index 000000000..1034d6062 --- /dev/null +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandExecutor.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.platform.velocity.command; + +import com.velocitypowered.api.command.SimpleCommand; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandExecutor; +import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.text.ChatColor; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.GeyserLocale; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class GeyserVelocityCommandExecutor extends CommandExecutor implements SimpleCommand { + + public GeyserVelocityCommandExecutor(GeyserImpl geyser) { + super(geyser); + } + + @Override + public void execute(Invocation invocation) { + CommandSender sender = new VelocityCommandSender(invocation.source()); + GeyserSession session = getGeyserSession(sender); + + if (invocation.arguments().length > 0) { + GeyserCommand command = getCommand(invocation.arguments()[0]); + if (command != null) { + if (!invocation.source().hasPermission(getCommand(invocation.arguments()[0]).getPermission())) { + sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.getLocale())); + return; + } + if (command.isBedrockOnly() && session == null) { + sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.getLocale())); + return; + } + command.execute(session, sender, invocation.arguments().length > 1 ? Arrays.copyOfRange(invocation.arguments(), 1, invocation.arguments().length) : new String[0]); + } + } else { + getCommand("help").execute(session, sender, new String[0]); + } + } + + @Override + public List suggest(Invocation invocation) { + // Velocity seems to do the splitting a bit differently. This results in the same behaviour in bungeecord/spigot. + if (invocation.arguments().length == 0 || invocation.arguments().length == 1) { + return tabComplete(new VelocityCommandSender(invocation.source())); + } + return Collections.emptyList(); + } +} diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandManager.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandManager.java similarity index 81% rename from bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandManager.java rename to bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandManager.java index 76655d0ac..33f0d77c2 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandManager.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,15 +23,15 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.velocity.command; +package org.geysermc.geyser.platform.velocity.command; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.CommandManager; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandManager; public class GeyserVelocityCommandManager extends CommandManager { - public GeyserVelocityCommandManager(GeyserConnector connector) { - super(connector); + public GeyserVelocityCommandManager(GeyserImpl geyser) { + super(geyser); } @Override diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/VelocityCommandSender.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSender.java similarity index 75% rename from bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/VelocityCommandSender.java rename to bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSender.java index 3a1c46033..3ef08d4e9 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/VelocityCommandSender.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSender.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,14 +23,14 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.velocity.command; +package org.geysermc.geyser.platform.velocity.command; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.ConsoleCommandSource; import com.velocitypowered.api.proxy.Player; -import net.kyori.text.TextComponent; -import org.geysermc.connector.command.CommandSender; -import org.geysermc.connector.utils.LanguageUtils; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.text.GeyserLocale; import java.util.Locale; @@ -41,11 +41,11 @@ public class VelocityCommandSender implements CommandSender { public VelocityCommandSender(CommandSource handle) { this.handle = handle; // Ensure even Java players' languages are loaded - LanguageUtils.loadGeyserLocale(getLocale()); + GeyserLocale.loadGeyserLocale(getLocale()); } @Override - public String getName() { + public String name() { if (handle instanceof Player) { return ((Player) handle).getUsername(); } else if (handle instanceof ConsoleCommandSource) { @@ -56,7 +56,7 @@ public class VelocityCommandSender implements CommandSender { @Override public void sendMessage(String message) { - handle.sendMessage(TextComponent.of(message)); + handle.sendMessage(LegacyComponentSerializer.legacy('§').deserialize(message)); } @Override @@ -68,8 +68,13 @@ public class VelocityCommandSender implements CommandSender { public String getLocale() { if (handle instanceof Player) { Locale locale = ((Player) handle).getPlayerSettings().getLocale(); - return LanguageUtils.formatLocale(locale.getLanguage() + "_" + locale.getCountry()); + return GeyserLocale.formatLocale(locale.getLanguage() + "_" + locale.getCountry()); } - return LanguageUtils.getDefaultLocale(); + return GeyserLocale.getDefaultLocale(); + } + + @Override + public boolean hasPermission(String permission) { + return handle.hasPermission(permission); } } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java deleted file mode 100644 index 30c474139..000000000 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.platform.velocity.command; - -import com.velocitypowered.api.command.CommandSource; -import com.velocitypowered.api.command.SimpleCommand; -import lombok.AllArgsConstructor; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.CommandSender; -import org.geysermc.connector.command.GeyserCommand; -import org.geysermc.connector.common.ChatColor; -import org.geysermc.connector.utils.LanguageUtils; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -@AllArgsConstructor -public class GeyserVelocityCommandExecutor implements SimpleCommand { - - private final GeyserConnector connector; - - @Override - public void execute(Invocation invocation) { - if (invocation.arguments().length > 0) { - if (getCommand(invocation.arguments()[0]) != null) { - if (!invocation.source().hasPermission(getCommand(invocation.arguments()[0]).getPermission())) { - CommandSender sender = new VelocityCommandSender(invocation.source()); - sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.getLocale())); - return; - } - getCommand(invocation.arguments()[0]).execute(new VelocityCommandSender(invocation.source()), invocation.arguments().length > 1 ? Arrays.copyOfRange(invocation.arguments(), 1, invocation.arguments().length) : new String[0]); - } - } else { - getCommand("help").execute(new VelocityCommandSender(invocation.source()), new String[0]); - } - } - - @Override - public List suggest(Invocation invocation) { - if (invocation.arguments().length == 0) { - return connector.getCommandManager().getCommandNames(); - } - return new ArrayList<>(); - } - - private GeyserCommand getCommand(String label) { - return connector.getCommandManager().getCommands().get(label); - } -} diff --git a/common/pom.xml b/common/pom.xml index 32c4b1876..8e7be26f4 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -6,22 +6,26 @@ org.geysermc geyser-parent - 1.2.0-SNAPSHOT + 2.0.0-SNAPSHOT common + + + 8 + 8 + + + + org.geysermc.cumulus + cumulus + 1.0-SNAPSHOT + com.google.code.gson gson - 2.8.2 - compile - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - 2.9.8 - compile + 2.8.6 \ No newline at end of file diff --git a/common/src/main/java/org/geysermc/common/PlatformType.java b/common/src/main/java/org/geysermc/common/PlatformType.java index 883490208..098fd3946 100644 --- a/common/src/main/java/org/geysermc/common/PlatformType.java +++ b/common/src/main/java/org/geysermc/common/PlatformType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -31,7 +31,6 @@ import lombok.Getter; @Getter @AllArgsConstructor public enum PlatformType { - ANDROID("Android"), BUNGEECORD("BungeeCord"), FABRIC("Fabric"), diff --git a/common/src/main/java/org/geysermc/common/window/CustomFormBuilder.java b/common/src/main/java/org/geysermc/common/window/CustomFormBuilder.java deleted file mode 100644 index 004b00a96..000000000 --- a/common/src/main/java/org/geysermc/common/window/CustomFormBuilder.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window; - -import lombok.Getter; -import org.geysermc.common.window.CustomFormWindow; -import org.geysermc.common.window.button.FormImage; -import org.geysermc.common.window.component.FormComponent; -import org.geysermc.common.window.response.CustomFormResponse; - -public class CustomFormBuilder { - - @Getter - private CustomFormWindow form; - - public CustomFormBuilder(String title) { - form = new CustomFormWindow(title); - } - - public CustomFormBuilder setTitle(String title) { - form.setTitle(title); - return this; - } - - public CustomFormBuilder setIcon(FormImage icon) { - form.setIcon(icon); - return this; - } - - public CustomFormBuilder setResponse(String data) { - form.setResponse(data); - return this; - } - - public CustomFormBuilder setResponse(CustomFormResponse response) { - form.setResponse(response); - return this; - } - - public CustomFormBuilder addComponent(FormComponent component) { - form.addComponent(component); - return this; - } - - public CustomFormWindow build() { - return form; - } -} diff --git a/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java b/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java deleted file mode 100644 index 045552b6e..000000000 --- a/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Getter; -import lombok.Setter; -import org.geysermc.common.window.button.FormImage; -import org.geysermc.common.window.component.*; -import org.geysermc.common.window.response.CustomFormResponse; -import org.geysermc.common.window.response.FormResponseData; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class CustomFormWindow extends FormWindow { - - @Getter - @Setter - private String title; - - @Getter - @Setter - private FormImage icon; - - @Getter - private List content; - - public CustomFormWindow(String title) { - this(title, new ArrayList<>()); - } - - public CustomFormWindow(String title, List content) { - this(title, content, (FormImage) null); - } - - public CustomFormWindow(String title, List content, String icon) { - this(title, content, new FormImage(FormImage.FormImageType.URL, icon)); - } - - public CustomFormWindow(String title, List content, FormImage icon) { - super("custom_form"); - - this.title = title; - this.content = content; - this.icon = icon; - } - - public void addComponent(FormComponent component) { - content.add(component); - } - - public String getJSONData() { - String toModify = ""; - try { - toModify = new ObjectMapper().writeValueAsString(this); - } catch (JsonProcessingException e) { } - - //We need to replace this due to Java not supporting declaring class field 'default' - return toModify.replace("defaultOptionIndex", "default") - .replace("defaultText", "default") - .replace("defaultValue", "default") - .replace("defaultStepIndex", "default"); - } - - public void setResponse(String data) { - if (data == null || data.trim().equalsIgnoreCase("null") || data.isEmpty()) { - closed = true; - return; - } - - int i = 0; - Map dropdownResponses = new HashMap(); - Map inputResponses = new HashMap(); - Map sliderResponses = new HashMap(); - Map stepSliderResponses = new HashMap(); - Map toggleResponses = new HashMap(); - Map responses = new HashMap(); - Map labelResponses = new HashMap(); - - List componentResponses = new ArrayList<>(); - try { - componentResponses = new ObjectMapper().readValue(data.trim(), new TypeReference>(){}); - } catch (IOException e) { } - - for (String response : componentResponses) { - if (i >= content.size()) { - break; - } - - FormComponent component = content.get(i); - if (component == null) - return; - - if (component instanceof LabelComponent) { - LabelComponent labelComponent = (LabelComponent) component; - labelResponses.put(i, labelComponent.getText()); - } - - if (component instanceof DropdownComponent) { - DropdownComponent dropdownComponent = (DropdownComponent) component; - String option = dropdownComponent.getOptions().get(Integer.parseInt(response)); - - dropdownResponses.put(i, new FormResponseData(Integer.parseInt(response), option)); - responses.put(i, option); - } - - if (component instanceof InputComponent) { - inputResponses.put(i, response); - responses.put(i, response); - } - - if (component instanceof SliderComponent) { - float value = Float.parseFloat(response); - sliderResponses.put(i, value); - responses.put(i, value); - } - - if (component instanceof StepSliderComponent) { - StepSliderComponent stepSliderComponent = (StepSliderComponent) component; - String step = stepSliderComponent.getSteps().get(Integer.parseInt(response)); - stepSliderResponses.put(i, new FormResponseData(Integer.parseInt(response), step)); - responses.put(i, step); - } - - if (component instanceof ToggleComponent) { - boolean answer = Boolean.parseBoolean(response); - toggleResponses.put(i, answer); - responses.put(i, answer); - } - i++; - } - - this.response = new CustomFormResponse(responses, dropdownResponses, inputResponses, - sliderResponses, stepSliderResponses, toggleResponses, labelResponses); - } -} diff --git a/common/src/main/java/org/geysermc/common/window/ModalFormWindow.java b/common/src/main/java/org/geysermc/common/window/ModalFormWindow.java deleted file mode 100644 index bfeafa1b0..000000000 --- a/common/src/main/java/org/geysermc/common/window/ModalFormWindow.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Getter; -import lombok.Setter; -import org.geysermc.common.window.response.ModalFormResponse; - -public class ModalFormWindow extends FormWindow { - - @Getter - @Setter - private String title; - - @Getter - @Setter - private String content; - - @Getter - @Setter - private String button1; - - @Getter - @Setter - private String button2; - - public ModalFormWindow(String title, String content, String button1, String button2) { - super("modal"); - - this.title = title; - this.content = content; - this.button1 = button1; - this.button2 = button2; - } - - @Override - public String getJSONData() { - try { - return new ObjectMapper().writeValueAsString(this); - } catch (JsonProcessingException e) { - return ""; - } - } - - public void setResponse(String data) { - if (data == null || data.equalsIgnoreCase("null")) { - closed = true; - return; - } - - if (Boolean.parseBoolean(data)) { - response = new ModalFormResponse(0, button1); - } else { - response = new ModalFormResponse(1, button2); - } - } -} diff --git a/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java b/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java deleted file mode 100644 index 3101f5fb3..000000000 --- a/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Getter; -import lombok.Setter; -import org.geysermc.common.window.button.FormButton; -import org.geysermc.common.window.response.SimpleFormResponse; - -import java.util.ArrayList; -import java.util.List; - - -public class SimpleFormWindow extends FormWindow { - - @Getter - @Setter - private String title; - - @Getter - @Setter - private String content; - - @Getter - @Setter - private List buttons; - - public SimpleFormWindow(String title, String content) { - this(title, content, new ArrayList()); - } - - public SimpleFormWindow(String title, String content, List buttons) { - super("form"); - - this.title = title; - this.content = content; - this.buttons = buttons; - } - - @Override - public String getJSONData() { - try { - return new ObjectMapper().writeValueAsString(this); - } catch (JsonProcessingException e) { - return ""; - } - } - - public void setResponse(String data) { - if (data == null || data.trim().equalsIgnoreCase("null")) { - closed = true; - return; - } - - int buttonID; - try { - buttonID = Integer.parseInt(data.trim()); - } catch (Exception ex) { - return; - } - - if (buttonID >= buttons.size()) { - response = new SimpleFormResponse(buttonID, null); - return; - } - - response = new SimpleFormResponse(buttonID, buttons.get(buttonID)); - } -} diff --git a/common/src/main/java/org/geysermc/common/window/component/StepSliderComponent.java b/common/src/main/java/org/geysermc/common/window/component/StepSliderComponent.java deleted file mode 100644 index 8f128d1a4..000000000 --- a/common/src/main/java/org/geysermc/common/window/component/StepSliderComponent.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window.component; - -import lombok.Getter; -import lombok.Setter; - -import java.util.ArrayList; -import java.util.List; - -public class StepSliderComponent extends FormComponent { - - @Getter - @Setter - private String text; - - @Getter - private List steps; - - @Getter - @Setter - private int defaultStepIndex; - - public StepSliderComponent(String text) { - this(text, new ArrayList()); - } - - public StepSliderComponent(String text, List steps) { - this(text, steps, 0); - } - - public StepSliderComponent(String text, List steps, int defaultStepIndex) { - super("step_slider"); - - this.text = text; - this.steps = steps; - this.defaultStepIndex = defaultStepIndex; - } - - public void addStep(String step, boolean isDefault) { - steps.add(step); - - if (isDefault) - defaultStepIndex = steps.size() - 1; - } -} diff --git a/common/src/main/java/org/geysermc/common/window/response/SimpleFormResponse.java b/common/src/main/java/org/geysermc/common/window/response/SimpleFormResponse.java deleted file mode 100644 index e80d58e78..000000000 --- a/common/src/main/java/org/geysermc/common/window/response/SimpleFormResponse.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window.response; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.geysermc.common.window.button.FormButton; -import org.geysermc.common.window.response.FormResponse; - -@Getter -@AllArgsConstructor -public class SimpleFormResponse implements FormResponse { - - private int clickedButtonId; - private FormButton clickedButton; -} diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java b/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java new file mode 100644 index 000000000..619065836 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + * + */ + +package org.geysermc.floodgate.crypto; + +import lombok.RequiredArgsConstructor; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.security.Key; +import java.security.SecureRandom; + +@RequiredArgsConstructor +public final class AesCipher implements FloodgateCipher { + public static final int IV_LENGTH = 12; + private static final int TAG_BIT_LENGTH = 128; + private static final String CIPHER_NAME = "AES/GCM/NoPadding"; + + private final SecureRandom secureRandom = new SecureRandom(); + private final Topping topping; + private SecretKey secretKey; + + public void init(Key key) { + if (!"AES".equals(key.getAlgorithm())) { + throw new RuntimeException( + "Algorithm was expected to be AES, but got " + key.getAlgorithm() + ); + } + secretKey = (SecretKey) key; + } + + public byte[] encrypt(byte[] data) throws Exception { + Cipher cipher = Cipher.getInstance(CIPHER_NAME); + + byte[] iv = new byte[IV_LENGTH]; + secureRandom.nextBytes(iv); + + GCMParameterSpec spec = new GCMParameterSpec(TAG_BIT_LENGTH, iv); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, spec); + byte[] cipherText = cipher.doFinal(data); + + if (topping != null) { + iv = topping.encode(iv); + cipherText = topping.encode(cipherText); + } + + return ByteBuffer.allocate(HEADER.length + iv.length + cipherText.length + 1) + .put(HEADER) + .put(iv) + .put((byte) 0x21) + .put(cipherText) + .array(); + } + + public byte[] decrypt(byte[] cipherTextWithIv) throws Exception { + checkHeader(cipherTextWithIv); + + Cipher cipher = Cipher.getInstance(CIPHER_NAME); + + int bufferLength = cipherTextWithIv.length - HEADER.length; + ByteBuffer buffer = ByteBuffer.wrap(cipherTextWithIv, HEADER.length, bufferLength); + + int ivLength = IV_LENGTH; + + if (topping != null) { + int mark = buffer.position(); + + // we need the first index, the second is for the actual data + boolean found = false; + while (buffer.hasRemaining() && !found) { + if (buffer.get() == 0x21) { + found = true; + } + } + + ivLength = buffer.position() - mark - 1; // don't include the splitter itself + // don't remove this cast, it'll cause problems if you remove it + ((Buffer) buffer).position(mark); // reset to the pre-while index + } + + byte[] iv = new byte[ivLength]; + buffer.get(iv); + + // don't remove this cast, it'll cause problems if you remove it + ((Buffer) buffer).position(buffer.position() + 1); // skip splitter + + byte[] cipherText = new byte[buffer.remaining()]; + buffer.get(cipherText); + + if (topping != null) { + iv = topping.decode(iv); + cipherText = topping.decode(cipherText); + } + + GCMParameterSpec spec = new GCMParameterSpec(TAG_BIT_LENGTH, iv); + cipher.init(Cipher.DECRYPT_MODE, secretKey, spec); + return cipher.doFinal(cipherText); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java b/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java new file mode 100644 index 000000000..54bfa0a4e --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + * + */ + +package org.geysermc.floodgate.crypto; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +public final class AesKeyProducer implements KeyProducer { + public static int KEY_SIZE = 128; + + @Override + public SecretKey produce() { + try { + KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); + keyGenerator.init(KEY_SIZE, secureRandom()); + return keyGenerator.generateKey(); + } catch (Exception exception) { + throw new RuntimeException(exception); + } + } + + @Override + public SecretKey produceFrom(byte[] keyFileData) { + try { + return new SecretKeySpec(keyFileData, "AES"); + } catch (Exception exception) { + throw new RuntimeException(exception); + } + } + + private SecureRandom secureRandom() throws NoSuchAlgorithmException { + // use Windows-PRNG for windows (default impl is SHA1PRNG) + if (System.getProperty("os.name").startsWith("Windows")) { + return SecureRandom.getInstance("Windows-PRNG"); + } else { + try { + // NativePRNG (which should be the default on unix-systems) can still block your + // system. Even though it isn't as bad as NativePRNGBlocking, we still try to + // prevent that if possible + return SecureRandom.getInstance("NativePRNGNonBlocking"); + } catch (NoSuchAlgorithmException ignored) { + // at this point we just have to go with the default impl even if it blocks + return new SecureRandom(); + } + } + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/Base64Topping.java b/common/src/main/java/org/geysermc/floodgate/crypto/Base64Topping.java new file mode 100644 index 000000000..18fc5a04a --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/crypto/Base64Topping.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.crypto; + +import java.util.Base64; + +public final class Base64Topping implements Topping { + @Override + public byte[] encode(byte[] data) { + return Base64.getEncoder().encode(data); + } + + @Override + public byte[] decode(byte[] data) { + return Base64.getDecoder().decode(data); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java b/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java new file mode 100644 index 000000000..23497506a --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + * + */ + +package org.geysermc.floodgate.crypto; + +import org.geysermc.floodgate.util.InvalidFormatException; + +import java.security.Key; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Responsible for both encrypting and decrypting data + */ +public interface FloodgateCipher { + int VERSION = 0; + byte[] IDENTIFIER = "^Floodgate^".getBytes(UTF_8); + byte[] HEADER = (new String(IDENTIFIER, UTF_8) + (char) (VERSION + 0x3E)).getBytes(UTF_8); + + static int version(String data) { + if (data.length() <= HEADER.length) { + return -1; + } + + for (int i = 0; i < IDENTIFIER.length; i++) { + if (IDENTIFIER[i] != data.charAt(i)) { + return -1; + } + } + + return data.charAt(IDENTIFIER.length) - 0x3E; + } + + /** + * Initializes the instance by giving it the key it needs to encrypt or decrypt data + * + * @param key the key used to encrypt and decrypt data + */ + void init(Key key); + + /** + * Encrypts the given data using the Key provided in {@link #init(Key)} + * + * @param data the data to encrypt + * @return the encrypted data + * @throws Exception when the encryption failed + */ + byte[] encrypt(byte[] data) throws Exception; + + /** + * Encrypts data from a String.
This method internally calls {@link #encrypt(byte[])} + * + * @param data the data to encrypt + * @return the encrypted data + * @throws Exception when the encryption failed + */ + default byte[] encryptFromString(String data) throws Exception { + return encrypt(data.getBytes(UTF_8)); + } + + /** + * Decrypts the given data using the Key provided in {@link #init(Key)} + * + * @param data the data to decrypt + * @return the decrypted data + * @throws Exception when the decrypting failed + */ + byte[] decrypt(byte[] data) throws Exception; + + /** + * Decrypts a byte[] and turn it into a String.
This method internally calls {@link + * #decrypt(byte[])} and converts the returned byte[] into a String. + * + * @param data the data to encrypt + * @return the decrypted data in a UTF-8 String + * @throws Exception when the decrypting failed + */ + default String decryptToString(byte[] data) throws Exception { + byte[] decrypted = decrypt(data); + if (decrypted == null) { + return null; + } + return new String(decrypted, UTF_8); + } + + /** + * Decrypts a String.
This method internally calls {@link #decrypt(byte[])} by converting + * the UTF-8 String into a byte[] + * + * @param data the data to decrypt + * @return the decrypted data in a byte[] + * @throws Exception when the decrypting failed + */ + default byte[] decryptFromString(String data) throws Exception { + return decrypt(data.getBytes(UTF_8)); + } + + /** + * Checks if the header is valid. This method will throw an InvalidFormatException when the + * header is invalid. + * + * @param data the data to check + * @throws InvalidFormatException when the header is invalid + */ + default void checkHeader(byte[] data) throws InvalidFormatException { + if (data.length <= HEADER.length) { + throw new InvalidFormatException( + "Data length is smaller then header." + + "Needed " + HEADER.length + ", got " + data.length + ); + } + + for (int i = 0; i < IDENTIFIER.length; i++) { + if (IDENTIFIER[i] != data[i]) { + String identifier = new String(IDENTIFIER, UTF_8); + String received = new String(data, 0, IDENTIFIER.length, UTF_8); + throw new InvalidFormatException( + "Expected identifier " + identifier + ", got " + received + ); + } + } + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/KeyProducer.java b/common/src/main/java/org/geysermc/floodgate/crypto/KeyProducer.java new file mode 100644 index 000000000..fc2ac512d --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/crypto/KeyProducer.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + * + */ + +package org.geysermc.floodgate.crypto; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.Key; + +public interface KeyProducer { + Key produce(); + Key produceFrom(byte[] keyFileData); + + default Key produceFrom(Path keyFileLocation) throws IOException { + return produceFrom(Files.readAllBytes(keyFileLocation)); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/Topping.java b/common/src/main/java/org/geysermc/floodgate/crypto/Topping.java new file mode 100644 index 000000000..689274269 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/crypto/Topping.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.crypto; + +public interface Topping { + byte[] encode(byte[] data); + byte[] decode(byte[] data); +} diff --git a/common/src/main/java/org/geysermc/floodgate/news/NewsItem.java b/common/src/main/java/org/geysermc/floodgate/news/NewsItem.java new file mode 100644 index 000000000..5d9cd9d27 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/NewsItem.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.news; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.geysermc.floodgate.news.data.ItemData; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public final class NewsItem { + private final int id; + private final boolean active; + private final NewsType type; + private final ItemData data; + private final String message; + private final Set actions; + private final String url; + + private NewsItem( + int id, boolean active, NewsType type, ItemData data, + String message, Set actions, String url) { + + this.id = id; + this.active = active; + this.type = type; + this.data = data; + this.message = message; + this.actions = Collections.unmodifiableSet(actions); + this.url = url; + } + + public static NewsItem readItem(JsonObject newsItem) { + NewsType newsType = NewsType.getByName(newsItem.get("type").getAsString()); + if (newsType == null) { + return null; + } + + JsonObject messageObject = newsItem.getAsJsonObject("message"); + NewsItemMessage itemMessage = NewsItemMessage.getById(messageObject.get("id").getAsInt()); + + String message = "Received an unknown news message type. Please update"; + if (itemMessage != null) { + message = itemMessage.getFormattedMessage(messageObject.getAsJsonArray("args")); + } + + Set actions = new HashSet<>(); + for (JsonElement actionElement : newsItem.getAsJsonArray("actions")) { + NewsItemAction action = NewsItemAction.getByName(actionElement.getAsString()); + if (action != null) { + actions.add(action); + } + } + + return new NewsItem( + newsItem.get("id").getAsInt(), + newsItem.get("active").getAsBoolean(), + newsType, + newsType.read(newsItem.getAsJsonObject("data")), + message, + actions, + newsItem.get("url").getAsString() + ); + } + + public int getId() { + return id; + } + + public boolean isActive() { + return active; + } + + public NewsType getType() { + return type; + } + + public ItemData getData() { + return data; + } + + @SuppressWarnings("unchecked") + public T getDataAs(Class type) { + return (T) data; + } + + public String getRawMessage() { + return message; + } + + public String getMessage() { + return message + " See " + getUrl() + " for more information."; + } + + public Set getActions() { + return actions; + } + + public String getUrl() { + return url; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/news/NewsItemAction.java b/common/src/main/java/org/geysermc/floodgate/news/NewsItemAction.java new file mode 100644 index 000000000..78a8e4ed3 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/NewsItemAction.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.news; + +public enum NewsItemAction { + ON_SERVER_STARTED, + ON_OPERATOR_JOIN, + BROADCAST_TO_CONSOLE, + BROADCAST_TO_OPERATORS; + + private static final NewsItemAction[] VALUES = values(); + + public static NewsItemAction getByName(String actionName) { + for (NewsItemAction type : VALUES) { + if (type.name().equalsIgnoreCase(actionName)) { + return type; + } + } + return null; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/news/NewsItemMessage.java b/common/src/main/java/org/geysermc/floodgate/news/NewsItemMessage.java new file mode 100644 index 000000000..b11605fb4 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/NewsItemMessage.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.news; + +import com.google.gson.JsonArray; + +// {} is used for things that have to be filled in by the server, +// {@} is for things that have to be filled in by us +public enum NewsItemMessage { + UPDATE_AVAILABLE("There is an update available for {}. The newest version is: {}"), + UPDATE_RECOMMENDED(UPDATE_AVAILABLE + ". Your version is quite old, updating is recommend."), + UPDATE_HIGHLY_RECOMMENDED(UPDATE_AVAILABLE + ". We highly recommend updating because some important changes have been made."), + UPDATE_ANCIENT_VERSION(UPDATE_AVAILABLE + ". You are running an ancient version, updating is recommended."), + + DOWNTIME_GENERIC("The {} is temporarily going down for maintenance soon."), + DOWNTIME_WITH_START("The {} is temporarily going down for maintenance on {}."), + DOWNTIME_TIMEFRAME(DOWNTIME_WITH_START + " The maintenance is expected to last till {}."); + + private static final NewsItemMessage[] VALUES = values(); + + private final String messageFormat; + private final String[] messageSplitted; + + NewsItemMessage(String messageFormat) { + this.messageFormat = messageFormat; + this.messageSplitted = messageFormat.split(" "); + } + + public static NewsItemMessage getById(int id) { + return VALUES.length > id ? VALUES[id] : null; + } + + public String getMessageFormat() { + return messageFormat; + } + + public String getFormattedMessage(JsonArray serverArguments) { + int serverArgumentsIndex = 0; + + StringBuilder message = new StringBuilder(); + for (String split : messageSplitted) { + if (message.length() > 0) { + message.append(' '); + } + + String result = split; + + if (serverArgumentsIndex < serverArguments.size()) { + String argument = serverArguments.get(serverArgumentsIndex).getAsString(); + result = result.replace("{}", argument); + if (!result.equals(split)) { + serverArgumentsIndex++; + } + } + + message.append(result); + } + return message.toString(); + } + + + @Override + public String toString() { + return getMessageFormat(); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/news/NewsType.java b/common/src/main/java/org/geysermc/floodgate/news/NewsType.java new file mode 100644 index 000000000..8976cc6b6 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/NewsType.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.news; + +import com.google.gson.JsonObject; +import org.geysermc.floodgate.news.data.*; + +import java.util.function.Function; + +public enum NewsType { + BUILD_SPECIFIC(BuildSpecificData::read), + CHECK_AFTER(CheckAfterData::read), + ANNOUNCEMENT(AnnouncementData::read), + CONFIG_SPECIFIC(ConfigSpecificData::read); + + private static final NewsType[] VALUES = values(); + + private final Function readFunction; + + NewsType(Function readFunction) { + this.readFunction = readFunction; + } + + public static NewsType getByName(String newsType) { + for (NewsType type : VALUES) { + if (type.name().equalsIgnoreCase(newsType)) { + return type; + } + } + return null; + } + + public ItemData read(JsonObject data) { + return readFunction.apply(data); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/news/data/AnnouncementData.java b/common/src/main/java/org/geysermc/floodgate/news/data/AnnouncementData.java new file mode 100644 index 000000000..5cc574a57 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/data/AnnouncementData.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.news.data; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.HashSet; +import java.util.Set; + +public final class AnnouncementData implements ItemData { + private final Set including = new HashSet<>(); + private final Set excluding = new HashSet<>(); + + private AnnouncementData() {} + + public static AnnouncementData read(JsonObject data) { + AnnouncementData announcementData = new AnnouncementData(); + + if (data.has("including")) { + JsonArray including = data.getAsJsonArray("including"); + for (JsonElement element : including) { + announcementData.including.add(element.getAsString()); + } + } + + if (data.has("excluding")) { + JsonArray including = data.getAsJsonArray("excluding"); + for (JsonElement element : including) { + announcementData.excluding.add(element.getAsString()); + } + } + return announcementData; + } + + public boolean isAffected(String project) { + return !excluding.contains(project) && (including.isEmpty() || including.contains(project)); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/news/data/BuildSpecificData.java b/common/src/main/java/org/geysermc/floodgate/news/data/BuildSpecificData.java new file mode 100644 index 000000000..f13d5fd7b --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/data/BuildSpecificData.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.news.data; + +import com.google.gson.JsonObject; + +public final class BuildSpecificData implements ItemData { + private String branch; + + private boolean allAffected; + private int affectedGreaterThan; + private int affectedLessThan; + + private BuildSpecificData() {} + + public static BuildSpecificData read(JsonObject data) { + BuildSpecificData updateData = new BuildSpecificData(); + updateData.branch = data.get("branch").getAsString(); + + JsonObject affectedBuilds = data.getAsJsonObject("affected_builds"); + if (affectedBuilds.has("all")) { + updateData.allAffected = affectedBuilds.get("all").getAsBoolean(); + } + if (!updateData.allAffected) { + updateData.affectedGreaterThan = affectedBuilds.get("gt").getAsInt(); + updateData.affectedLessThan = affectedBuilds.get("lt").getAsInt(); + } + return updateData; + } + + public boolean isAffected(String branch, int buildId) { + return this.branch.equals(branch) && + (allAffected || buildId > affectedGreaterThan && buildId < affectedLessThan); + } + + public String getBranch() { + return branch; + } +} diff --git a/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java b/common/src/main/java/org/geysermc/floodgate/news/data/CheckAfterData.java similarity index 68% rename from common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java rename to common/src/main/java/org/geysermc/floodgate/news/data/CheckAfterData.java index f972d5906..2545a97da 100644 --- a/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java +++ b/common/src/main/java/org/geysermc/floodgate/news/data/CheckAfterData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,29 +23,22 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.component; +package org.geysermc.floodgate.news.data; -import lombok.Getter; -import lombok.Setter; +import com.google.gson.JsonObject; -public class ToggleComponent extends FormComponent { +public final class CheckAfterData implements ItemData { + private long checkAfter; - @Getter - @Setter - private String text; + private CheckAfterData() {} - @Getter - @Setter - private boolean defaultValue; - - public ToggleComponent(String text) { - this(text, false); + public static CheckAfterData read(JsonObject data) { + CheckAfterData checkAfterData = new CheckAfterData(); + checkAfterData.checkAfter = data.get("check_after").getAsLong(); + return checkAfterData; } - public ToggleComponent(String text, boolean defaultValue) { - super("toggle"); - - this.text = text; - this.defaultValue = defaultValue; + public long getCheckAfter() { + return checkAfter; } } diff --git a/common/src/main/java/org/geysermc/floodgate/news/data/ConfigSpecificData.java b/common/src/main/java/org/geysermc/floodgate/news/data/ConfigSpecificData.java new file mode 100644 index 000000000..8d05c93db --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/data/ConfigSpecificData.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.news.data; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public final class ConfigSpecificData implements ItemData { + private final Map affectedKeys = new HashMap<>(); + + private ConfigSpecificData() {} + + public static ConfigSpecificData read(JsonObject data) { + ConfigSpecificData configSpecificData = new ConfigSpecificData(); + + JsonArray entries = data.getAsJsonArray("entries"); + for (JsonElement element : entries) { + JsonObject entry = element.getAsJsonObject(); + String key = entry.get("key").getAsString(); + String pattern = entry.get("pattern").getAsString(); + configSpecificData.affectedKeys.put(key, Pattern.compile(pattern)); + } + return configSpecificData; + } + + public boolean isAffected(Map config) { + for (Map.Entry entry : affectedKeys.entrySet()) { + if (config.containsKey(entry.getKey())) { + String value = config.get(entry.getKey()); + if (entry.getValue().matcher(value).matches()) { + return true; + } + } + } + return false; + } +} diff --git a/common/src/main/java/org/geysermc/common/window/response/FormResponse.java b/common/src/main/java/org/geysermc/floodgate/news/data/ItemData.java similarity index 89% rename from common/src/main/java/org/geysermc/common/window/response/FormResponse.java rename to common/src/main/java/org/geysermc/floodgate/news/data/ItemData.java index 2be646837..122ee775d 100644 --- a/common/src/main/java/org/geysermc/common/window/response/FormResponse.java +++ b/common/src/main/java/org/geysermc/floodgate/news/data/ItemData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.response; +package org.geysermc.floodgate.news.data; -public interface FormResponse { +public interface ItemData { } diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index 9775ce9f2..31696d97a 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,47 +25,88 @@ package org.geysermc.floodgate.util; -import lombok.AllArgsConstructor; +import lombok.AccessLevel; import lombok.Getter; +import lombok.RequiredArgsConstructor; -import java.util.UUID; - -@AllArgsConstructor +/** + * This class contains the raw data send by Geyser to Floodgate or from Floodgate to Floodgate. This + * class is only used internally, and you should look at FloodgatePlayer instead (FloodgatePlayer is + * present in the API module of the Floodgate repo) + */ @Getter -public class BedrockData { - public static final int EXPECTED_LENGTH = 7; - public static final String FLOODGATE_IDENTIFIER = "Geyser-Floodgate"; +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public final class BedrockData implements Cloneable { + public static final int EXPECTED_LENGTH = 12; - private String version; - private String username; - private String xuid; - private int deviceId; - private String languageCode; - private int inputMode; - private String ip; - private int dataLength; + private final String version; + private final String username; + private final String xuid; + private final int deviceOs; + private final String languageCode; + private final int uiProfile; + private final int inputMode; + private final String ip; + private final LinkedPlayer linkedPlayer; + private final boolean fromProxy; - public BedrockData(String version, String username, String xuid, int deviceId, String languageCode, int inputMode, String ip) { - this(version, username, xuid, deviceId, languageCode, inputMode, ip, EXPECTED_LENGTH); + private final int subscribeId; + private final String verifyCode; + + private final int dataLength; + + public static BedrockData of( + String version, String username, String xuid, int deviceOs, + String languageCode, int uiProfile, int inputMode, String ip, + LinkedPlayer linkedPlayer, boolean fromProxy, int subscribeId, + String verifyCode) { + return new BedrockData(version, username, xuid, deviceOs, languageCode, inputMode, + uiProfile, ip, linkedPlayer, fromProxy, subscribeId, verifyCode, EXPECTED_LENGTH); + } + + public static BedrockData of( + String version, String username, String xuid, int deviceOs, + String languageCode, int uiProfile, int inputMode, String ip, + int subscribeId, String verifyCode) { + return of(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null, + false, subscribeId, verifyCode); } public static BedrockData fromString(String data) { String[] split = data.split("\0"); - if (split.length != EXPECTED_LENGTH) return null; + if (split.length != EXPECTED_LENGTH) { + return emptyData(split.length); + } + LinkedPlayer linkedPlayer = LinkedPlayer.fromString(split[8]); + // The format is the same as the order of the fields in this class return new BedrockData( - split[0], split[1], split[2], Integer.parseInt(split[3]), - split[4], Integer.parseInt(split[5]), split[6], split.length + split[0], split[1], split[2], Integer.parseInt(split[3]), split[4], + Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], linkedPlayer, + "1".equals(split[9]), Integer.parseInt(split[10]), split[11], split.length ); } - public static BedrockData fromRawData(byte[] data) { - return fromString(new String(data)); + private static BedrockData emptyData(int dataLength) { + return new BedrockData(null, null, null, -1, null, -1, -1, null, null, false, -1, null, + dataLength); + } + + public boolean hasPlayerLink() { + return linkedPlayer != null; } @Override public String toString() { - return version +'\0'+ username +'\0'+ xuid +'\0'+ deviceId +'\0'+ languageCode +'\0'+ - inputMode +'\0'+ ip; + // The format is the same as the order of the fields in this class + return version + '\0' + username + '\0' + xuid + '\0' + deviceOs + '\0' + + languageCode + '\0' + uiProfile + '\0' + inputMode + '\0' + ip + '\0' + + (linkedPlayer != null ? linkedPlayer.toString() : "null") + '\0' + + (fromProxy ? 1 : 0) + '\0' + subscribeId + '\0' + verifyCode; + } + + @Override + public BedrockData clone() throws CloneNotSupportedException { + return (BedrockData) super.clone(); } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/DeviceOS.java b/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java similarity index 67% rename from common/src/main/java/org/geysermc/floodgate/util/DeviceOS.java rename to common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java index 93d3c121e..f56d3a8c0 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/DeviceOS.java +++ b/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,36 +25,41 @@ package org.geysermc.floodgate.util; -import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; -public enum DeviceOS { - - @JsonEnumDefaultValue +/** + * The Operation Systems where Bedrock players can connect with + */ +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public enum DeviceOs { UNKNOWN("Unknown"), - ANDROID("Android"), + GOOGLE("Android"), IOS("iOS"), OSX("macOS"), - FIREOS("FireOS"), + AMAZON("Amazon"), GEARVR("Gear VR"), HOLOLENS("Hololens"), - WIN10("Windows 10"), - WIN32("Windows"), + UWP("Windows 10"), + WIN32("Windows x86"), DEDICATED("Dedicated"), - ORBIS("PS4"), + TVOS("Apple TV"), + PS4("PS4"), NX("Switch"), - SWITCH("Switch"), - XBOX_ONE("Xbox One"), - WIN_PHONE("Windows Phone"); + XBOX("Xbox One"), + WINDOWS_PHONE("Windows Phone"); - private static final DeviceOS[] VALUES = values(); + private static final DeviceOs[] VALUES = values(); private final String displayName; - DeviceOS(final String displayName) { - this.displayName = displayName; - } - - public static DeviceOS getById(int id) { + /** + * Get the DeviceOs instance from the identifier. + * + * @param id the DeviceOs identifier + * @return The DeviceOs or {@link #UNKNOWN} if the DeviceOs wasn't found + */ + public static DeviceOs fromId(int id) { return id < VALUES.length ? VALUES[id] : VALUES[0]; } diff --git a/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java b/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java deleted file mode 100644 index 105309f9c..000000000 --- a/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.floodgate.util; - -import javax.crypto.*; -import javax.crypto.spec.SecretKeySpec; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.*; -import java.security.spec.EncodedKeySpec; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; -import java.util.Base64; - -public class EncryptionUtil { - public static String encrypt(Key key, String data) throws IllegalBlockSizeException, - InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { - KeyGenerator generator = KeyGenerator.getInstance("AES"); - generator.init(128); - SecretKey secretKey = generator.generateKey(); - - Cipher cipher = Cipher.getInstance("AES"); - cipher.init(Cipher.ENCRYPT_MODE, secretKey); - byte[] encryptedText = cipher.doFinal(data.getBytes()); - - cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); - cipher.init(key instanceof PublicKey ? Cipher.PUBLIC_KEY : Cipher.PRIVATE_KEY, key); - return Base64.getEncoder().encodeToString(cipher.doFinal(secretKey.getEncoded())) + '\0' + - Base64.getEncoder().encodeToString(encryptedText); - } - - public static String encryptBedrockData(Key key, BedrockData data) throws IllegalBlockSizeException, - InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { - return encrypt(key, data.toString()); - } - - public static byte[] decrypt(Key key, String encryptedData) throws IllegalBlockSizeException, - InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { - String[] split = encryptedData.split("\0"); - if (split.length != 2) { - throw new IllegalArgumentException("Expected two arguments, got " + split.length); - } - - Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); - cipher.init(key instanceof PublicKey ? Cipher.PUBLIC_KEY : Cipher.PRIVATE_KEY, key); - byte[] decryptedKey = cipher.doFinal(Base64.getDecoder().decode(split[0])); - - SecretKey secretKey = new SecretKeySpec(decryptedKey, 0, decryptedKey.length, "AES"); - cipher = Cipher.getInstance("AES"); - cipher.init(Cipher.DECRYPT_MODE, secretKey); - return cipher.doFinal(Base64.getDecoder().decode(split[1])); - } - - public static BedrockData decryptBedrockData(Key key, String encryptedData) throws IllegalBlockSizeException, - InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { - return BedrockData.fromRawData(decrypt(key, encryptedData)); - } - - @SuppressWarnings("unchecked") - public static T getKeyFromFile(Path fileLocation, Class keyType) throws - IOException, InvalidKeySpecException, NoSuchAlgorithmException { - boolean isPublicKey = keyType == PublicKey.class; - if (!isPublicKey && keyType != PrivateKey.class) { - throw new RuntimeException("I can only read public and private keys!"); - } - - byte[] key = Files.readAllBytes(fileLocation); - - KeyFactory keyFactory = KeyFactory.getInstance("RSA"); - EncodedKeySpec keySpec = isPublicKey ? new X509EncodedKeySpec(key) : new PKCS8EncodedKeySpec(key); - return (T) (isPublicKey ? - keyFactory.generatePublic(keySpec) : - keyFactory.generatePrivate(keySpec) - ); - } -} diff --git a/common/src/main/java/org/geysermc/common/window/component/LabelComponent.java b/common/src/main/java/org/geysermc/floodgate/util/FloodgateInfoHolder.java similarity index 81% rename from common/src/main/java/org/geysermc/common/window/component/LabelComponent.java rename to common/src/main/java/org/geysermc/floodgate/util/FloodgateInfoHolder.java index a76b313fa..c7a681f9d 100644 --- a/common/src/main/java/org/geysermc/common/window/component/LabelComponent.java +++ b/common/src/main/java/org/geysermc/floodgate/util/FloodgateInfoHolder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,20 +23,18 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.component; +package org.geysermc.floodgate.util; import lombok.Getter; import lombok.Setter; -public class LabelComponent extends FormComponent { +import java.util.Properties; +public final class FloodgateInfoHolder { @Getter @Setter - private String text; - - public LabelComponent(String text) { - super("label"); - - this.text = text; - } + private static Object config; + @Getter + @Setter + private static Properties gitProperties; } diff --git a/common/src/main/java/org/geysermc/floodgate/util/InputMode.java b/common/src/main/java/org/geysermc/floodgate/util/InputMode.java new file mode 100644 index 000000000..3ce51634d --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/InputMode.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.floodgate.util; + +public enum InputMode { + UNKNOWN, + KEYBOARD_MOUSE, + TOUCH, + CONTROLLER, + VR; + + private static final InputMode[] VALUES = values(); + + /** + * Get the InputMode instance from the identifier. + * + * @param id the InputMode identifier + * @return The InputMode or {@link #UNKNOWN} if the DeviceOs wasn't found + */ + public static InputMode fromId(int id) { + return VALUES.length > id ? VALUES[id] : VALUES[0]; + } +} \ No newline at end of file diff --git a/common/src/main/java/org/geysermc/floodgate/util/InvalidFormatException.java b/common/src/main/java/org/geysermc/floodgate/util/InvalidFormatException.java new file mode 100644 index 000000000..698e4522c --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/InvalidFormatException.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.floodgate.util; + +public class InvalidFormatException extends Exception { + public InvalidFormatException(String message) { + super(message); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java new file mode 100644 index 000000000..f91bfafbc --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.floodgate.util; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.UUID; + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public final class LinkedPlayer implements Cloneable { + /** + * The Java username of the linked player + */ + private final String javaUsername; + /** + * The Java UUID of the linked player + */ + private final UUID javaUniqueId; + /** + * The UUID of the Bedrock player + */ + private final UUID bedrockId; + /** + * If the LinkedPlayer is sent from a different platform. For example the LinkedPlayer is from + * Bungee but the data has been sent to the Bukkit server. + */ + private boolean fromDifferentPlatform = false; + + public static LinkedPlayer of(String javaUsername, UUID javaUniqueId, UUID bedrockId) { + return new LinkedPlayer(javaUsername, javaUniqueId, bedrockId); + } + + public static LinkedPlayer fromString(String data) { + String[] split = data.split(";"); + if (split.length != 3) { + return null; + } + + LinkedPlayer player = new LinkedPlayer( + split[0], UUID.fromString(split[1]), UUID.fromString(split[2]) + ); + player.fromDifferentPlatform = true; + return player; + } + + @Override + public String toString() { + return javaUsername + ';' + javaUniqueId.toString() + ';' + bedrockId.toString(); + } + + @Override + public LinkedPlayer clone() throws CloneNotSupportedException { + return (LinkedPlayer) super.clone(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/effect/SoundLevelEffect.java b/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java similarity index 54% rename from connector/src/main/java/org/geysermc/connector/network/translators/effect/SoundLevelEffect.java rename to common/src/main/java/org/geysermc/floodgate/util/UiProfile.java index 67b10bb2c..298aaea83 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/effect/SoundLevelEffect.java +++ b/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java @@ -24,33 +24,21 @@ * */ -package org.geysermc.connector.network.translators.effect; +package org.geysermc.floodgate.util; -import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerPlayEffectPacket; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.LevelEventType; -import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; -import lombok.Value; -import org.geysermc.connector.network.session.GeyserSession; +public enum UiProfile { + CLASSIC, + POCKET; -@Value -public class SoundLevelEffect implements Effect { - /** - * Bedrock level event type - */ - LevelEventType levelEventType; + private static final UiProfile[] VALUES = values(); /** - * Event data. Usually 0 + * Get the UiProfile instance from the identifier. + * + * @param id the UiProfile identifier + * @return The UiProfile or {@link #CLASSIC} if the UiProfile wasn't found */ - int data; - - @Override - public void handleEffectPacket(GeyserSession session, ServerPlayEffectPacket packet) { - LevelEventPacket eventPacket = new LevelEventPacket(); - eventPacket.setType(levelEventType); - eventPacket.setData(data); - eventPacket.setPosition(Vector3f.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ()).add(0.5f, 0.5f, 0.5f)); - session.sendUpstreamPacket(eventPacket); + public static UiProfile fromId(int id) { + return VALUES.length > id ? VALUES[id] : VALUES[0]; } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java b/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java new file mode 100644 index 000000000..b570d7b3d --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.util; + +public enum WebsocketEventType { + /** + * Sent once we successfully connected to the server + */ + SUBSCRIBER_CREATED(0), + /** + * Sent every time a subscriber got added or disconnected + */ + SUBSCRIBER_COUNT(1), + /** + * Sent once the creator disconnected. After this packet the server will automatically close the + * connection once the queue size (sent in {@link #ADDED_TO_QUEUE} and {@link #SKIN_UPLOADED} + * reaches 0. + */ + CREATOR_DISCONNECTED(4), + + /** + * Sent every time a skin got added to the upload queue + */ + ADDED_TO_QUEUE(2), + /** + * Sent every time a skin got successfully uploaded + */ + SKIN_UPLOADED(3), + + /** + * Sent every time a news item was added + */ + NEWS_ADDED(6), + + /** + * Sent when the server wants you to know something. Currently used for violations that aren't + * bad enough to close the connection + */ + LOG_MESSAGE(5); + + private static final WebsocketEventType[] VALUES; + + static { + WebsocketEventType[] values = values(); + VALUES = new WebsocketEventType[values.length]; + for (WebsocketEventType value : values) { + VALUES[value.id] = value; + } + } + + /** + * The ID is based of the time it got added. However, to keep the enum organized as time goes on, + * it looks nicer to sort the events based of categories. + */ + private final int id; + + WebsocketEventType(int id) { + this.id = id; + } + + public static WebsocketEventType fromId(int id) { + return VALUES.length > id ? VALUES[id] : null; + } + + public int id() { + return id; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java b/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java deleted file mode 100644 index ec5dd349a..000000000 --- a/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector; - -import org.geysermc.connector.configuration.GeyserJacksonConfiguration; -import org.geysermc.connector.utils.LanguageUtils; - -import java.nio.file.Files; -import java.nio.file.Path; - -public class FloodgateKeyLoader { - public static Path getKeyPath(GeyserJacksonConfiguration config, Object floodgate, Path floodgateDataFolder, Path geyserDataFolder, GeyserLogger logger) { - Path floodgateKey = geyserDataFolder.resolve(config.getFloodgateKeyFile()); - - if (!Files.exists(floodgateKey) && config.getRemote().getAuthType().equals("floodgate")) { - if (floodgate != null) { - Path autoKey = floodgateDataFolder.resolve("public-key.pem"); - if (Files.exists(autoKey)) { - logger.info(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.auto_loaded")); - floodgateKey = autoKey; - } else { - logger.error(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.missing_key")); - } - } else { - logger.error(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed")); - } - } - - return floodgateKey; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java deleted file mode 100644 index c461fa96c..000000000 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ /dev/null @@ -1,362 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.nukkitx.network.raknet.RakNetConstants; -import com.nukkitx.protocol.bedrock.BedrockServer; -import lombok.Getter; -import lombok.Setter; -import org.geysermc.common.PlatformType; -import org.geysermc.connector.bootstrap.GeyserBootstrap; -import org.geysermc.connector.command.CommandManager; -import org.geysermc.connector.common.AuthType; -import org.geysermc.connector.configuration.GeyserConfiguration; -import org.geysermc.connector.metrics.Metrics; -import org.geysermc.connector.network.ConnectorServerEventHandler; -import org.geysermc.connector.network.remote.RemoteServer; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.BiomeTranslator; -import org.geysermc.connector.network.translators.EntityIdentifierRegistry; -import org.geysermc.connector.network.translators.PacketTranslatorRegistry; -import org.geysermc.connector.network.translators.effect.EffectRegistry; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.geysermc.connector.network.translators.item.PotionMixRegistry; -import org.geysermc.connector.network.translators.item.RecipeRegistry; -import org.geysermc.connector.network.translators.sound.SoundHandlerRegistry; -import org.geysermc.connector.network.translators.sound.SoundRegistry; -import org.geysermc.connector.network.translators.world.WorldManager; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator; -import org.geysermc.connector.utils.DimensionUtils; -import org.geysermc.connector.utils.LanguageUtils; -import org.geysermc.connector.utils.LocaleUtils; -import org.geysermc.connector.utils.ResourcePack; - -import javax.naming.directory.Attribute; -import javax.naming.directory.InitialDirContext; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.UnknownHostException; -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -@Getter -public class GeyserConnector { - - public static final ObjectMapper JSON_MAPPER = new ObjectMapper().enable(JsonParser.Feature.IGNORE_UNDEFINED).enable(JsonParser.Feature.ALLOW_COMMENTS).disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - - public static final String NAME = "Geyser"; - public static final String VERSION = "DEV"; // A fallback for running in IDEs - - private static final String IP_REGEX = "\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b"; - - private final List players = new ArrayList<>(); - - private static GeyserConnector instance; - - private RemoteServer remoteServer; - @Setter - private AuthType authType; - - private boolean shuttingDown = false; - - private final ScheduledExecutorService generalThreadPool; - - private BedrockServer bedrockServer; - private PlatformType platformType; - private GeyserBootstrap bootstrap; - - private Metrics metrics; - - private GeyserConnector(PlatformType platformType, GeyserBootstrap bootstrap) { - long startupTime = System.currentTimeMillis(); - - instance = this; - - this.bootstrap = bootstrap; - - GeyserLogger logger = bootstrap.getGeyserLogger(); - GeyserConfiguration config = bootstrap.getGeyserConfig(); - - this.platformType = platformType; - - logger.info("******************************************"); - logger.info(""); - logger.info(LanguageUtils.getLocaleStringLog("geyser.core.load", NAME, VERSION)); - logger.info(""); - logger.info("******************************************"); - - this.generalThreadPool = Executors.newScheduledThreadPool(config.getGeneralThreadPool()); - - logger.setDebug(config.isDebugMode()); - - PacketTranslatorRegistry.init(); - - /* Initialize translators and registries */ - BiomeTranslator.init(); - BlockTranslator.init(); - BlockEntityTranslator.init(); - EffectRegistry.init(); - EntityIdentifierRegistry.init(); - ItemRegistry.init(); - ItemTranslator.init(); - LocaleUtils.init(); - PotionMixRegistry.init(); - RecipeRegistry.init(); - SoundRegistry.init(); - SoundHandlerRegistry.init(); - - ResourcePack.loadPacks(); - - if (platformType != PlatformType.STANDALONE && config.getRemote().getAddress().equals("auto")) { - // Set the remote address to localhost since that is where we are always connecting - try { - config.getRemote().setAddress(InetAddress.getLocalHost().getHostAddress()); - } catch (UnknownHostException ex) { - logger.debug("Unknown host when trying to find localhost."); - if (config.isDebugMode()) { - ex.printStackTrace(); - } - } - } - String remoteAddress = config.getRemote().getAddress(); - int remotePort = config.getRemote().getPort(); - // Filters whether it is not an IP address or localhost, because otherwise it is not possible to find out an SRV entry. - if ((config.isLegacyPingPassthrough() || platformType == PlatformType.STANDALONE) && !remoteAddress.matches(IP_REGEX) && !remoteAddress.equalsIgnoreCase("localhost")) { - try { - // Searches for a server address and a port from a SRV record of the specified host name - InitialDirContext ctx = new InitialDirContext(); - Attribute attr = ctx.getAttributes("dns:///_minecraft._tcp." + remoteAddress, new String[]{"SRV"}).get("SRV"); - // size > 0 = SRV entry found - if (attr != null && attr.size() > 0) { - String[] record = ((String) attr.get(0)).split(" "); - // Overwrites the existing address and port with that from the SRV record. - config.getRemote().setAddress(remoteAddress = record[3]); - config.getRemote().setPort(remotePort = Integer.parseInt(record[2])); - logger.debug("Found SRV record \"" + remoteAddress + ":" + remotePort + "\""); - } - } catch (Exception | NoClassDefFoundError ex) { // Check for a NoClassDefFoundError to prevent Android crashes - logger.debug("Exception while trying to find an SRV record for the remote host."); - if (config.isDebugMode()) - ex.printStackTrace(); // Otherwise we can get a stack trace for any domain that doesn't have an SRV record - } - } - - remoteServer = new RemoteServer(config.getRemote().getAddress(), remotePort); - authType = AuthType.getByName(config.getRemote().getAuthType()); - - DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether - - // https://github.com/GeyserMC/Geyser/issues/957 - RakNetConstants.MAXIMUM_MTU_SIZE = (short) config.getMtu(); - logger.debug("Setting MTU to " + config.getMtu()); - - bedrockServer = new BedrockServer(new InetSocketAddress(config.getBedrock().getAddress(), config.getBedrock().getPort())); - bedrockServer.setHandler(new ConnectorServerEventHandler(this)); - bedrockServer.bind().whenComplete((avoid, throwable) -> { - if (throwable == null) { - logger.info(LanguageUtils.getLocaleStringLog("geyser.core.start", config.getBedrock().getAddress(), String.valueOf(config.getBedrock().getPort()))); - } else { - logger.severe(LanguageUtils.getLocaleStringLog("geyser.core.fail", config.getBedrock().getAddress(), String.valueOf(config.getBedrock().getPort()))); - throwable.printStackTrace(); - } - }).join(); - - if (config.getMetrics().isEnabled()) { - metrics = new Metrics(this, "GeyserMC", config.getMetrics().getUniqueId(), false, java.util.logging.Logger.getLogger("")); - metrics.addCustomChart(new Metrics.SingleLineChart("servers", () -> 1)); - metrics.addCustomChart(new Metrics.SingleLineChart("players", players::size)); - // Prevent unwanted words best we can - metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> AuthType.getByName(config.getRemote().getAuthType()).toString().toLowerCase())); - metrics.addCustomChart(new Metrics.SimplePie("platform", platformType::getPlatformName)); - metrics.addCustomChart(new Metrics.SimplePie("defaultLocale", LanguageUtils::getDefaultLocale)); - metrics.addCustomChart(new Metrics.SimplePie("version", () -> GeyserConnector.VERSION)); - metrics.addCustomChart(new Metrics.AdvancedPie("playerPlatform", () -> { - Map valueMap = new HashMap<>(); - for (GeyserSession session : players) { - if (session == null) continue; - if (session.getClientData() == null) continue; - String os = session.getClientData().getDeviceOS().toString(); - if (!valueMap.containsKey(os)) { - valueMap.put(os, 1); - } else { - valueMap.put(os, valueMap.get(os) + 1); - } - } - return valueMap; - })); - metrics.addCustomChart(new Metrics.AdvancedPie("playerVersion", () -> { - Map valueMap = new HashMap<>(); - for (GeyserSession session : players) { - if (session == null) continue; - if (session.getClientData() == null) continue; - String version = session.getClientData().getGameVersion(); - if (!valueMap.containsKey(version)) { - valueMap.put(version, 1); - } else { - valueMap.put(version, valueMap.get(version) + 1); - } - } - return valueMap; - })); - } - - boolean isGui = false; - // This will check if we are in standalone and get the 'useGui' variable from there - if (platformType == PlatformType.STANDALONE) { - try { - Class cls = Class.forName("org.geysermc.platform.standalone.GeyserStandaloneBootstrap"); - isGui = (boolean) cls.getMethod("isUseGui").invoke(cls.cast(bootstrap)); - } catch (Exception e) { - logger.debug("Failed detecting if standalone is using a GUI; if this is a GeyserConnect instance this can be safely ignored."); - } - } - - double completeTime = (System.currentTimeMillis() - startupTime) / 1000D; - String message = LanguageUtils.getLocaleStringLog("geyser.core.finish.done", new DecimalFormat("#.###").format(completeTime)) + " "; - if (isGui) { - message += LanguageUtils.getLocaleStringLog("geyser.core.finish.gui"); - } else { - message += LanguageUtils.getLocaleStringLog("geyser.core.finish.console"); - } - logger.info(message); - - if (platformType == PlatformType.STANDALONE) { - logger.warning(LanguageUtils.getLocaleStringLog("geyser.core.movement_warn")); - } - } - - public void shutdown() { - bootstrap.getGeyserLogger().info(LanguageUtils.getLocaleStringLog("geyser.core.shutdown")); - shuttingDown = true; - - if (players.size() >= 1) { - bootstrap.getGeyserLogger().info(LanguageUtils.getLocaleStringLog("geyser.core.shutdown.kick.log", players.size())); - - // Make a copy to prevent ConcurrentModificationException - final List tmpPlayers = new ArrayList<>(players); - for (GeyserSession playerSession : tmpPlayers) { - playerSession.disconnect(LanguageUtils.getPlayerLocaleString("geyser.core.shutdown.kick.message", playerSession.getLocale())); - } - - CompletableFuture future = CompletableFuture.runAsync(new Runnable() { - @Override - public void run() { - // Simulate a long-running Job - try { - while (true) { - if (players.size() == 0) { - return; - } - - TimeUnit.MILLISECONDS.sleep(100); - } - } catch (InterruptedException e) { - throw new IllegalStateException(e); - } - } - }); - - // Block and wait for the future to complete - try { - future.get(); - bootstrap.getGeyserLogger().info(LanguageUtils.getLocaleStringLog("geyser.core.shutdown.kick.done")); - } catch (Exception e) { - // Quietly fail - } - } - - generalThreadPool.shutdown(); - bedrockServer.close(); - players.clear(); - remoteServer = null; - authType = null; - this.getCommandManager().getCommands().clear(); - - bootstrap.getGeyserLogger().info(LanguageUtils.getLocaleStringLog("geyser.core.shutdown.done")); - } - - public void addPlayer(GeyserSession player) { - players.add(player); - } - - public void removePlayer(GeyserSession player) { - players.remove(player); - } - - public static GeyserConnector start(PlatformType platformType, GeyserBootstrap bootstrap) { - return new GeyserConnector(platformType, bootstrap); - } - - public void reload() { - shutdown(); - bootstrap.onEnable(); - } - - public GeyserLogger getLogger() { - return bootstrap.getGeyserLogger(); - } - - public GeyserConfiguration getConfig() { - return bootstrap.getGeyserConfig(); - } - - public CommandManager getCommandManager() { - return bootstrap.getGeyserCommandManager(); - } - - public WorldManager getWorldManager() { - return bootstrap.getWorldManager(); - } - - /** - * Whether to use XML reflections in the jar or manually find the reflections. - * Will return true if the version number is not 'DEV' and the platform is not Fabric. - * On Fabric - it complains about being unable to create a default XMLReader. - * On other platforms this should only be true in compiled jars. - * - * @return whether to use XML reflections - */ - public boolean useXmlReflections() { - //noinspection ConstantConditions - return !this.getPlatformType().equals(PlatformType.FABRIC) && !"DEV".equals(GeyserConnector.VERSION); - } - - public static GeyserConnector getInstance() { - return instance; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java deleted file mode 100644 index 822b6deae..000000000 --- a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.command; - -import lombok.Getter; - -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.defaults.*; -import org.geysermc.connector.utils.LanguageUtils; - -import java.util.*; - -public abstract class CommandManager { - - @Getter - private final Map commands = Collections.synchronizedMap(new HashMap<>()); - - private final GeyserConnector connector; - - public CommandManager(GeyserConnector connector) { - this.connector = connector; - - registerCommand(new HelpCommand(connector, "help", "geyser.commands.help.desc", "geyser.command.help")); - registerCommand(new ListCommand(connector, "list", "geyser.commands.list.desc", "geyser.command.list")); - registerCommand(new ReloadCommand(connector, "reload", "geyser.commands.reload.desc", "geyser.command.reload")); - registerCommand(new StopCommand(connector, "stop", "geyser.commands.stop.desc", "geyser.command.stop")); - registerCommand(new OffhandCommand(connector, "offhand", "geyser.commands.offhand.desc", "geyser.command.offhand")); - registerCommand(new DumpCommand(connector, "dump", "geyser.commands.dump.desc", "geyser.command.dump")); - registerCommand(new VersionCommand(connector, "version", "geyser.commands.version.desc", "geyser.command.version")); - registerCommand(new StatisticsCommand(connector, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics")); - } - - public void registerCommand(GeyserCommand command) { - commands.put(command.getName(), command); - connector.getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.commands.registered", command.getName())); - - if (command.getAliases().isEmpty()) - return; - - for (String alias : command.getAliases()) - commands.put(alias, command); - } - - public void runCommand(CommandSender sender, String command) { - if (!command.startsWith("geyser ")) - return; - - command = command.trim().replace("geyser ", ""); - String label; - String[] args; - - if (!command.contains(" ")) { - label = command.toLowerCase(); - args = new String[0]; - } else { - label = command.substring(0, command.indexOf(" ")).toLowerCase(); - String argLine = command.substring(command.indexOf(" ") + 1); - args = argLine.contains(" ") ? argLine.split(" ") : new String[] { argLine }; - } - - GeyserCommand cmd = commands.get(label); - if (cmd == null) { - connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.invalid")); - return; - } - - cmd.execute(sender, args); - } - - /** - * @return a list of all subcommands under {@code /geyser}. - */ - public List getCommandNames() { - return Arrays.asList(connector.getCommandManager().getCommands().keySet().toArray(new String[0])); - } - - /** - * Returns the description of the given command - * - * @param command Command to get the description for - * @return Command description - */ - public abstract String getDescription(String command); -} diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java deleted file mode 100644 index f2c37da28..000000000 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.command.defaults; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.CommandSender; -import org.geysermc.connector.command.GeyserCommand; -import org.geysermc.connector.common.ChatColor; -import org.geysermc.connector.common.serializer.AsteriskSerializer; -import org.geysermc.connector.dump.DumpInfo; -import org.geysermc.connector.utils.LanguageUtils; -import org.geysermc.connector.utils.WebUtils; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; - -public class DumpCommand extends GeyserCommand { - - private final GeyserConnector connector; - private static final ObjectMapper MAPPER = new ObjectMapper(); - private static final String DUMP_URL = "https://dump.geysermc.org/"; - - public DumpCommand(GeyserConnector connector, String name, String description, String permission) { - super(name, description, permission); - - this.connector = connector; - } - - @Override - public void execute(CommandSender sender, String[] args) { - boolean showSensitive = false; - boolean offlineDump = false; - if (args.length >= 1) { - for (String arg : args) { - switch (arg) { - case "full": - showSensitive = true; - break; - case "offline": - offlineDump = true; - break; - - } - } - } - - AsteriskSerializer.showSensitive = showSensitive; - - sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.dump.collecting", sender.getLocale())); - String dumpData = ""; - try { - if (offlineDump) { - dumpData = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(new DumpInfo()); - } else { - dumpData = MAPPER.writeValueAsString(new DumpInfo()); - } - } catch (IOException e) { - sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.dump.collect_error", sender.getLocale())); - connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.collect_error_short"), e); - return; - } - - String uploadedDumpUrl = ""; - - if (offlineDump) { - sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.dump.writing", sender.getLocale())); - - try { - FileOutputStream outputStream = new FileOutputStream(GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("dump.json").toFile()); - outputStream.write(dumpData.getBytes()); - outputStream.close(); - } catch (IOException e) { - sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.dump.write_error", sender.getLocale())); - connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.write_error_short"), e); - return; - } - - uploadedDumpUrl = "dump.json"; - } else { - sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.dump.uploading", sender.getLocale())); - - String response; - JsonNode responseNode; - try { - response = WebUtils.post(DUMP_URL + "documents", dumpData); - responseNode = MAPPER.readTree(response); - } catch (IOException e) { - sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.dump.upload_error", sender.getLocale())); - connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error_short"), e); - return; - } - - if (!responseNode.has("key")) { - sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.dump.upload_error_short", sender.getLocale()) + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response)); - return; - } - - uploadedDumpUrl = DUMP_URL + responseNode.get("key").asText(); - } - - sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.dump.message", sender.getLocale()) + " " + ChatColor.DARK_AQUA + uploadedDumpUrl); - if (!sender.isConsole()) { - connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.commands.dump.created", sender.getName(), uploadedDumpUrl)); - } - } - - @Override - public List getSubCommands() { - return Arrays.asList("offline", "full"); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java deleted file mode 100644 index 268dc4b5b..000000000 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.command.defaults; - -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.CommandSender; -import org.geysermc.connector.command.GeyserCommand; -import org.geysermc.connector.common.ChatColor; -import org.geysermc.connector.utils.LanguageUtils; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -public class HelpCommand extends GeyserCommand { - - public GeyserConnector connector; - - public HelpCommand(GeyserConnector connector, String name, String description, String permission) { - super(name, description, permission); - this.connector = connector; - - this.setAliases(Collections.singletonList("?")); - } - - @Override - public void execute(CommandSender sender, String[] args) { - int page = 1; - int maxPage = 1; - String header = LanguageUtils.getPlayerLocaleString("geyser.commands.help.header", sender.getLocale(), page, maxPage); - - sender.sendMessage(header); - Map cmds = connector.getCommandManager().getCommands(); - List commands = connector.getCommandManager().getCommands().keySet().stream().sorted().collect(Collectors.toList()); - commands.forEach(cmd -> sender.sendMessage(ChatColor.YELLOW + "/geyser " + cmd + ChatColor.WHITE + ": " + - LanguageUtils.getPlayerLocaleString(cmds.get(cmd).getDescription(), sender.getLocale()))); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java deleted file mode 100644 index 4b6397f1e..000000000 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.command.defaults; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; -import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; -import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.CommandSender; -import org.geysermc.connector.command.GeyserCommand; -import org.geysermc.connector.network.session.GeyserSession; - -public class OffhandCommand extends GeyserCommand { - - private final GeyserConnector connector; - - public OffhandCommand(GeyserConnector connector, String name, String description, String permission) { - super(name, description, permission); - - this.connector = connector; - } - - @Override - public void execute(CommandSender sender, String[] args) { - if (sender.isConsole()) { - return; - } - - // Make sure the sender is a Bedrock edition client - if (sender instanceof GeyserSession) { - GeyserSession session = (GeyserSession) sender; - ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.SWAP_HANDS, new Position(0,0,0), - BlockFace.DOWN); - session.sendDownstreamPacket(releaseItemPacket); - return; - } - // Needed for Spigot - sender is not an instance of GeyserSession - for (GeyserSession session : connector.getPlayers()) { - if (sender.getName().equals(session.getPlayerEntity().getUsername())) { - ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.SWAP_HANDS, new Position(0,0,0), - BlockFace.DOWN); - session.sendDownstreamPacket(releaseItemPacket); - break; - } - } - } - - @Override - public boolean isExecutableOnConsole() { - return false; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java deleted file mode 100644 index 52379145a..000000000 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.command.defaults; - -import com.github.steveice10.mc.protocol.data.game.ClientRequest; -import com.github.steveice10.mc.protocol.packet.ingame.client.ClientRequestPacket; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.CommandSender; -import org.geysermc.connector.command.GeyserCommand; -import org.geysermc.connector.network.session.GeyserSession; - -public class StatisticsCommand extends GeyserCommand { - - private final GeyserConnector connector; - - public StatisticsCommand(GeyserConnector connector, String name, String description, String permission) { - super(name, description, permission); - - this.connector = connector; - } - - @Override - public void execute(CommandSender sender, String[] args) { - if (sender.isConsole()) { - return; - } - - // Make sure the sender is a Bedrock edition client - GeyserSession session = null; - if (sender instanceof GeyserSession) { - session = (GeyserSession) sender; - } else { - // Needed for Spigot - sender is not an instance of GeyserSession - for (GeyserSession otherSession : connector.getPlayers()) { - if (sender.getName().equals(otherSession.getPlayerEntity().getUsername())) { - session = otherSession; - break; - } - } - } - if (session == null) return; - session.setWaitingForStatistics(true); - ClientRequestPacket clientRequestPacket = new ClientRequestPacket(ClientRequest.STATS); - session.sendDownstreamPacket(clientRequestPacket); - } - - @Override - public boolean isExecutableOnConsole() { - return false; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java deleted file mode 100644 index 562bc9fb5..000000000 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.command.defaults; - -import com.github.steveice10.mc.protocol.MinecraftConstants; -import com.nukkitx.protocol.bedrock.BedrockPacketCodec; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.CommandSender; -import org.geysermc.connector.command.GeyserCommand; -import org.geysermc.connector.common.ChatColor; -import org.geysermc.connector.network.BedrockProtocol; -import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.LanguageUtils; -import org.geysermc.connector.utils.WebUtils; - -import java.io.IOException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Properties; - -public class VersionCommand extends GeyserCommand { - - public GeyserConnector connector; - - public VersionCommand(GeyserConnector connector, String name, String description, String permission) { - super(name, description, permission); - this.connector = connector; - } - - @Override - public void execute(CommandSender sender, String[] args) { - String bedrockVersions; - List supportedCodecs = BedrockProtocol.SUPPORTED_BEDROCK_CODECS; - if (supportedCodecs.size() > 1) { - bedrockVersions = supportedCodecs.get(0).getMinecraftVersion() + " - " + supportedCodecs.get(supportedCodecs.size() - 1).getMinecraftVersion(); - } else { - bedrockVersions = BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion(); - } - - sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.version", sender.getLocale(), GeyserConnector.NAME, GeyserConnector.VERSION, MinecraftConstants.GAME_VERSION, bedrockVersions)); - - // Disable update checking in dev mode - //noinspection ConstantConditions - changes in production - if (!GeyserConnector.VERSION.equals("DEV")) { - sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.checking", sender.getLocale())); - try { - Properties gitProp = new Properties(); - gitProp.load(FileUtils.getResource("git.properties")); - - String buildXML = WebUtils.getBody("https://ci.nukkitx.com/job/GeyserMC/job/Geyser/job/" + URLEncoder.encode(gitProp.getProperty("git.branch"), StandardCharsets.UTF_8.toString()) + "/lastSuccessfulBuild/api/xml?xpath=//buildNumber"); - if (buildXML.startsWith("")) { - int latestBuildNum = Integer.parseInt(buildXML.replaceAll("<(\\\\)?(/)?buildNumber>", "").trim()); - int buildNum = Integer.parseInt(gitProp.getProperty("git.build.number")); - if (latestBuildNum == buildNum) { - sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.no_updates", sender.getLocale())); - } else { - sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.outdated", sender.getLocale(), (latestBuildNum - buildNum), "https://ci.geysermc.org/")); - } - } else { - throw new AssertionError("buildNumber missing"); - } - } catch (IOException | AssertionError | NumberFormatException e) { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.version.failed"), e); - sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.version.failed", sender.getLocale())); - } - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java b/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java deleted file mode 100644 index 8193953a7..000000000 --- a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.dump; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.github.steveice10.mc.protocol.MinecraftConstants; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import lombok.Getter; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.common.serializer.AsteriskSerializer; -import org.geysermc.connector.configuration.GeyserConfiguration; -import org.geysermc.connector.network.BedrockProtocol; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.utils.DockerCheck; -import org.geysermc.connector.utils.FileUtils; -import org.geysermc.floodgate.util.DeviceOS; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.UnknownHostException; -import java.util.Properties; - -@Getter -public class DumpInfo { - - @JsonIgnore - private static final long MEGABYTE = 1024L * 1024L; - - private final DumpInfo.VersionInfo versionInfo; - private Properties gitInfo; - private final GeyserConfiguration config; - private Object2IntMap userPlatforms; - private RamInfo ramInfo; - private final BootstrapDumpInfo bootstrapInfo; - - public DumpInfo() { - this.versionInfo = new DumpInfo.VersionInfo(); - - try { - this.gitInfo = new Properties(); - this.gitInfo.load(FileUtils.getResource("git.properties")); - } catch (IOException ignored) { } - - this.config = GeyserConnector.getInstance().getConfig(); - - this.ramInfo = new DumpInfo.RamInfo(); - - this.userPlatforms = new Object2IntOpenHashMap(); - for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) { - DeviceOS device = session.getClientData().getDeviceOS(); - userPlatforms.put(device, userPlatforms.getOrDefault(device, 0) + 1); - } - - this.bootstrapInfo = GeyserConnector.getInstance().getBootstrap().getDumpInfo(); - } - - @Getter - public class VersionInfo { - - private final String name; - private final String version; - private final String javaVersion; - private final String architecture; - private final String operatingSystem; - private final String operatingSystemVersion; - - private final NetworkInfo network; - private final MCInfo mcInfo; - - VersionInfo() { - this.name = GeyserConnector.NAME; - this.version = GeyserConnector.VERSION; - this.javaVersion = System.getProperty("java.version"); - this.architecture = System.getProperty("os.arch"); // Usually gives Java architecture but still may be helpful. - this.operatingSystem = System.getProperty("os.name"); - this.operatingSystemVersion = System.getProperty("os.version"); - - this.network = new NetworkInfo(); - this.mcInfo = new MCInfo(); - } - } - - @Getter - public static class NetworkInfo { - - private String internalIP; - private final boolean dockerCheck; - - NetworkInfo() { - if (AsteriskSerializer.showSensitive) { - try { - // This is the most reliable for getting the main local IP - Socket socket = new Socket(); - socket.connect(new InetSocketAddress("geysermc.org", 80)); - this.internalIP = socket.getLocalAddress().getHostAddress(); - } catch (IOException e1) { - try { - // Fallback to the normal way of getting the local IP - this.internalIP = InetAddress.getLocalHost().getHostAddress(); - } catch (UnknownHostException ignored) { } - } - } else { - // Sometimes the internal IP is the external IP... - this.internalIP = "***"; - } - - this.dockerCheck = DockerCheck.checkBasic(); - } - } - - @Getter - public static class MCInfo { - - private final String bedrockVersion; - private final int bedrockProtocol; - private final String javaVersion; - private final int javaProtocol; - - MCInfo() { - this.bedrockVersion = BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion(); - this.bedrockProtocol = BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion(); - this.javaVersion = MinecraftConstants.GAME_VERSION; - this.javaProtocol = MinecraftConstants.PROTOCOL_VERSION; - } - } - - @Getter - public static class RamInfo { - - private final long free; - private final long total; - private final long max; - - RamInfo() { - this.free = Runtime.getRuntime().freeMemory() / MEGABYTE; - this.total = Runtime.getRuntime().totalMemory() / MEGABYTE; - this.max = Runtime.getRuntime().maxMemory() / MEGABYTE; - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java b/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java deleted file mode 100644 index 308d2121a..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.game.world.particle.Particle; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.effect.EffectRegistry; - -public class AreaEffectCloudEntity extends Entity { - - public AreaEffectCloudEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - - // Without this the cloud doesn't appear, - metadata.put(EntityData.AREA_EFFECT_CLOUD_DURATION, 600); - - // This disabled client side shrink of the cloud - metadata.put(EntityData.AREA_EFFECT_CLOUD_RADIUS, 0.0f); - metadata.put(EntityData.AREA_EFFECT_CLOUD_CHANGE_RATE, -0.005f); - metadata.put(EntityData.AREA_EFFECT_CLOUD_CHANGE_ON_PICKUP, -0.5f); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7) { - metadata.put(EntityData.AREA_EFFECT_CLOUD_RADIUS, entityMetadata.getValue()); - metadata.put(EntityData.BOUNDING_BOX_WIDTH, 2.0f * (float) entityMetadata.getValue()); - } else if (entityMetadata.getId() == 8) { - metadata.put(EntityData.EFFECT_COLOR, entityMetadata.getValue()); - } else if (entityMetadata.getId() == 10) { - Particle particle = (Particle) entityMetadata.getValue(); - int particleId = EffectRegistry.getParticleId(particle.getType()); - if (particleId != -1) { - metadata.put(EntityData.AREA_EFFECT_CLOUD_PARTICLE_ID, particleId); - } - } - super.updateBedrockMetadata(entityMetadata, session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java deleted file mode 100644 index c067416df..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; - -import java.util.concurrent.TimeUnit; - -public class BoatEntity extends Entity { - - private boolean isPaddlingLeft; - private float paddleTimeLeft; - private boolean isPaddlingRight; - private float paddleTimeRight; - - // Looks too fast and too choppy with 0.1f, which is how I believe the Microsoftian client handles it - private final float ROWING_SPEED = 0.05f; - - public BoatEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position.add(0d, entityType.getOffset(), 0d), motion, rotation.add(90, 0, 90)); - } - - @Override - public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { - // We don't include the rotation (y) as it causes the boat to appear sideways - super.moveAbsolute(session, position.add(0d, this.entityType.getOffset(), 0d), Vector3f.from(rotation.getX() + 90, 0, rotation.getX() + 90), isOnGround, teleported); - } - - @Override - public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) { - super.moveRelative(session, relX, relY, relZ, Vector3f.from(rotation.getX(), 0, rotation.getX()), isOnGround); - } - - @Override - public void updatePositionAndRotation(GeyserSession session, double moveX, double moveY, double moveZ, float yaw, float pitch, boolean isOnGround) { - moveRelative(session, moveX, moveY, moveZ, yaw + 90, pitch, isOnGround); - } - - @Override - public void updateRotation(GeyserSession session, float yaw, float pitch, boolean isOnGround) { - moveRelative(session, 0, 0, 0, Vector3f.from(yaw + 90, 0, 0), isOnGround); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - - // Time since last hit - if (entityMetadata.getId() == 7) { - metadata.put(EntityData.HURT_TIME, entityMetadata.getValue()); - } - - // Rocking direction - if (entityMetadata.getId() == 8) { - metadata.put(EntityData.HURT_DIRECTION, entityMetadata.getValue()); - } - - // 'Health' in Bedrock, damage taken in Java - if (entityMetadata.getId() == 9) { - // Not exactly health but it makes motion in Bedrock - metadata.put(EntityData.HEALTH, 40 - ((int) (float) entityMetadata.getValue())); - } - - if (entityMetadata.getId() == 10) { - metadata.put(EntityData.VARIANT, entityMetadata.getValue()); - } else if (entityMetadata.getId() == 11) { - isPaddlingLeft = (boolean) entityMetadata.getValue(); - if (!isPaddlingLeft) { - metadata.put(EntityData.ROW_TIME_LEFT, 0f); - } - else { - // Java sends simply "true" and "false" (is_paddling_left), Bedrock keeps sending packets as you're rowing - // This is an asynchronous method that emulates Bedrock rowing until "false" is sent. - paddleTimeLeft = 0f; - session.getConnector().getGeneralThreadPool().execute(() -> - updateLeftPaddle(session, entityMetadata) - ); - } - } - else if (entityMetadata.getId() == 12) { - isPaddlingRight = (boolean) entityMetadata.getValue(); - if (!isPaddlingRight) { - metadata.put(EntityData.ROW_TIME_RIGHT, 0f); - } else { - paddleTimeRight = 0f; - session.getConnector().getGeneralThreadPool().execute(() -> - updateRightPaddle(session, entityMetadata) - ); - } - } else if (entityMetadata.getId() == 13) { - // Possibly - I don't think this does anything? - metadata.put(EntityData.BOAT_BUBBLE_TIME, entityMetadata.getValue()); - } - - super.updateBedrockMetadata(entityMetadata, session); - } - - public void updateLeftPaddle(GeyserSession session, EntityMetadata entityMetadata) { - if (isPaddlingLeft) { - paddleTimeLeft += ROWING_SPEED; - metadata.put(EntityData.ROW_TIME_LEFT, paddleTimeLeft); - super.updateBedrockMetadata(entityMetadata, session); - session.getConnector().getGeneralThreadPool().schedule(() -> - updateLeftPaddle(session, entityMetadata), - 100, - TimeUnit.MILLISECONDS - ); - }} - - public void updateRightPaddle(GeyserSession session, EntityMetadata entityMetadata) { - if (isPaddlingRight) { - paddleTimeRight += ROWING_SPEED; - metadata.put(EntityData.ROW_TIME_RIGHT, paddleTimeRight); - super.updateBedrockMetadata(entityMetadata, session); - session.getConnector().getGeneralThreadPool().schedule(() -> - updateRightPaddle(session, entityMetadata), - 100, - TimeUnit.MILLISECONDS - ); - }} -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/DefaultBlockMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/DefaultBlockMinecartEntity.java deleted file mode 100644 index dda4577d2..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/DefaultBlockMinecartEntity.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; - -/** - * This class is used as a base for minecarts with a default block to display like furnaces and spawners - */ -public class DefaultBlockMinecartEntity extends MinecartEntity { - - public int customBlock = 0; - public int customBlockOffset = 0; - public boolean showCustomBlock = false; - - public DefaultBlockMinecartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - - updateDefaultBlockMetadata(); - metadata.put(EntityData.CUSTOM_DISPLAY, (byte) 1); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - - // Custom block - if (entityMetadata.getId() == 10) { - customBlock = (int) entityMetadata.getValue(); - - if (showCustomBlock) { - metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId(customBlock)); - } - } - - // Custom block offset - if (entityMetadata.getId() == 11) { - customBlockOffset = (int) entityMetadata.getValue(); - - if (showCustomBlock) { - metadata.put(EntityData.DISPLAY_OFFSET, customBlockOffset); - } - } - - // If the custom block should be enabled - if (entityMetadata.getId() == 12) { - if ((boolean) entityMetadata.getValue()) { - showCustomBlock = true; - metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId(customBlock)); - metadata.put(EntityData.DISPLAY_OFFSET, customBlockOffset); - } else { - showCustomBlock = false; - updateDefaultBlockMetadata(); - } - } - - super.updateBedrockMetadata(entityMetadata, session); - } - - public void updateDefaultBlockMetadata() { } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java deleted file mode 100644 index 7b1fa1cf0..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ /dev/null @@ -1,381 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; -import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; -import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; -import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; -import com.github.steveice10.mc.protocol.data.message.Message; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerUseItemPacket; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.protocol.bedrock.data.AttributeData; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityDataMap; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlags; -import com.nukkitx.protocol.bedrock.packet.*; -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import lombok.Getter; -import lombok.Setter; -import org.geysermc.connector.entity.attribute.Attribute; -import org.geysermc.connector.entity.attribute.AttributeType; -import org.geysermc.connector.entity.living.ArmorStandEntity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.utils.AttributeUtils; -import org.geysermc.connector.utils.ChunkUtils; -import org.geysermc.connector.network.translators.chat.MessageTranslator; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@Getter -@Setter -public class Entity { - protected long entityId; - protected long geyserId; - - protected Vector3f position; - protected Vector3f motion; - - /** - * x = Yaw, y = Pitch, z = HeadYaw - */ - protected Vector3f rotation; - - /** - * Saves if the entity should be on the ground. Otherwise entities like parrots are flapping when rotating - */ - protected boolean onGround; - - protected float scale = 1; - - protected EntityType entityType; - - protected boolean valid; - - protected LongOpenHashSet passengers = new LongOpenHashSet(); - protected Map attributes = new HashMap<>(); - protected EntityDataMap metadata = new EntityDataMap(); - - public Entity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - this.entityId = entityId; - this.geyserId = geyserId; - this.entityType = entityType; - this.motion = motion; - this.rotation = rotation; - - this.valid = false; - - setPosition(position); - - metadata.put(EntityData.SCALE, 1f); - metadata.put(EntityData.COLOR, 0); - metadata.put(EntityData.MAX_AIR_SUPPLY, (short) 300); - metadata.put(EntityData.AIR_SUPPLY, (short) 0); - metadata.put(EntityData.LEASH_HOLDER_EID, -1L); - metadata.put(EntityData.BOUNDING_BOX_HEIGHT, entityType.getHeight()); - metadata.put(EntityData.BOUNDING_BOX_WIDTH, entityType.getWidth()); - EntityFlags flags = new EntityFlags(); - flags.setFlag(EntityFlag.HAS_GRAVITY, true); - flags.setFlag(EntityFlag.HAS_COLLISION, true); - flags.setFlag(EntityFlag.CAN_SHOW_NAME, true); - flags.setFlag(EntityFlag.CAN_CLIMB, true); - metadata.putFlags(flags); - } - - public void spawnEntity(GeyserSession session) { - AddEntityPacket addEntityPacket = new AddEntityPacket(); - addEntityPacket.setIdentifier(entityType.getIdentifier()); - addEntityPacket.setRuntimeEntityId(geyserId); - addEntityPacket.setUniqueEntityId(geyserId); - addEntityPacket.setPosition(position); - addEntityPacket.setMotion(motion); - addEntityPacket.setRotation(getBedrockRotation()); - addEntityPacket.setEntityType(entityType.getType()); - addEntityPacket.getMetadata().putAll(metadata); - - valid = true; - session.sendUpstreamPacket(addEntityPacket); - - session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); - } - - /** - * Despawns the entity - * - * @param session The GeyserSession - * @return can be deleted - */ - public boolean despawnEntity(GeyserSession session) { - if (!valid) return true; - - for (long passenger : passengers) { // Make sure all passengers on the despawned entity are updated - Entity entity = session.getEntityCache().getEntityByJavaId(passenger); - if (entity == null) continue; - entity.getMetadata().getOrCreateFlags().setFlag(EntityFlag.RIDING, false); - entity.updateBedrockMetadata(session); - } - - RemoveEntityPacket removeEntityPacket = new RemoveEntityPacket(); - removeEntityPacket.setUniqueEntityId(geyserId); - session.sendUpstreamPacket(removeEntityPacket); - - valid = false; - return true; - } - - public void moveRelative(GeyserSession session, double relX, double relY, double relZ, float yaw, float pitch, boolean isOnGround) { - moveRelative(session, relX, relY, relZ, Vector3f.from(yaw, pitch, this.rotation.getZ()), isOnGround); - } - - public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) { - setRotation(rotation); - setOnGround(isOnGround); - this.position = Vector3f.from(position.getX() + relX, position.getY() + relY, position.getZ() + relZ); - - MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket(); - moveEntityPacket.setRuntimeEntityId(geyserId); - moveEntityPacket.setPosition(position); - moveEntityPacket.setRotation(getBedrockRotation()); - moveEntityPacket.setOnGround(isOnGround); - moveEntityPacket.setTeleported(false); - - session.sendUpstreamPacket(moveEntityPacket); - } - - public void moveAbsolute(GeyserSession session, Vector3f position, float yaw, float pitch, boolean isOnGround, boolean teleported) { - moveAbsolute(session, position, Vector3f.from(yaw, pitch, this.rotation.getZ()), isOnGround, teleported); - } - - public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { - setPosition(position); - setRotation(rotation); - setOnGround(isOnGround); - - MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket(); - moveEntityPacket.setRuntimeEntityId(geyserId); - moveEntityPacket.setPosition(position); - moveEntityPacket.setRotation(getBedrockRotation()); - moveEntityPacket.setOnGround(isOnGround); - moveEntityPacket.setTeleported(teleported); - - session.sendUpstreamPacket(moveEntityPacket); - } - - /** - * Teleports an entity to a new location. Used in JavaEntityTeleportTranslator. - * @param session GeyserSession. - * @param position The new position of the entity. - * @param yaw The new yaw of the entity. - * @param pitch The new pitch of the entity. - * @param isOnGround Whether the entity is currently on the ground. - */ - public void teleport(GeyserSession session, Vector3f position, float yaw, float pitch, boolean isOnGround) { - moveAbsolute(session, position, yaw, pitch, isOnGround, false); - } - - /** - * Updates an entity's head position. Used in JavaEntityHeadLookTranslator. - * @param session GeyserSession. - * @param headYaw The new head rotation of the entity. - */ - public void updateHeadLookRotation(GeyserSession session, float headYaw) { - moveRelative(session, 0, 0, 0, Vector3f.from(headYaw, rotation.getY(), rotation.getZ()), onGround); - } - - /** - * Updates an entity's position and rotation. Used in JavaEntityPositionRotationTranslator. - * @param session GeyserSession - * @param moveX The new X offset of the current position. - * @param moveY The new Y offset of the current position. - * @param moveZ The new Z offset of the current position. - * @param yaw The new yaw of the entity. - * @param pitch The new pitch of the entity. - * @param isOnGround Whether the entity is currently on the ground. - */ - public void updatePositionAndRotation(GeyserSession session, double moveX, double moveY, double moveZ, float yaw, float pitch, boolean isOnGround) { - moveRelative(session, moveX, moveY, moveZ, Vector3f.from(rotation.getX(), pitch, yaw), isOnGround); - } - - /** - * Updates an entity's rotation. Used in JavaEntityRotationTranslator. - * @param session GeyserSession. - * @param yaw The new yaw of the entity. - * @param pitch The new pitch of the entity. - * @param isOnGround Whether the entity is currently on the ground. - */ - public void updateRotation(GeyserSession session, float yaw, float pitch, boolean isOnGround) { - updatePositionAndRotation(session, 0, 0, 0, yaw, pitch, isOnGround); - } - - public void updateBedrockAttributes(GeyserSession session) { - if (!valid) return; - - List attributes = new ArrayList<>(); - for (Map.Entry entry : this.attributes.entrySet()) { - if (!entry.getValue().getType().isBedrockAttribute()) - continue; - - attributes.add(AttributeUtils.getBedrockAttribute(entry.getValue())); - } - - UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket(); - updateAttributesPacket.setRuntimeEntityId(geyserId); - updateAttributesPacket.setAttributes(attributes); - session.sendUpstreamPacket(updateAttributesPacket); - } - - /** - * Applies the Java metadata to the local Bedrock metadata copy - * @param entityMetadata the Java entity metadata - * @param session GeyserSession - */ - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - switch (entityMetadata.getId()) { - case 0: - if (entityMetadata.getType() == MetadataType.BYTE) { - byte xd = (byte) entityMetadata.getValue(); - metadata.getFlags().setFlag(EntityFlag.ON_FIRE, ((xd & 0x01) == 0x01) && !metadata.getFlags().getFlag(EntityFlag.FIRE_IMMUNE)); // Otherwise immune entities sometimes flicker onfire - metadata.getFlags().setFlag(EntityFlag.SNEAKING, (xd & 0x02) == 0x02); - metadata.getFlags().setFlag(EntityFlag.SPRINTING, (xd & 0x08) == 0x08); - metadata.getFlags().setFlag(EntityFlag.SWIMMING, ((xd & 0x10) == 0x10) && metadata.getFlags().getFlag(EntityFlag.SPRINTING)); // Otherwise swimming is enabled on older servers - metadata.getFlags().setFlag(EntityFlag.GLIDING, (xd & 0x80) == 0x80); - - if ((xd & 0x20) == 0x20) { - // Armour stands are handled in their own class - if (!this.is(ArmorStandEntity.class)) { - metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true); - } - } else { - metadata.getFlags().setFlag(EntityFlag.INVISIBLE, false); - } - - // Shield code - if (session.getPlayerEntity().getEntityId() == entityId && metadata.getFlags().getFlag(EntityFlag.SNEAKING)) { - if ((session.getInventory().getItemInHand() != null && session.getInventory().getItemInHand().getId() == ItemRegistry.SHIELD.getJavaId()) || - (session.getInventoryCache().getPlayerInventory().getItem(45) != null && session.getInventoryCache().getPlayerInventory().getItem(45).getId() == ItemRegistry.SHIELD.getJavaId())) { - ClientPlayerUseItemPacket useItemPacket; - metadata.getFlags().setFlag(EntityFlag.BLOCKING, true); - if (session.getInventory().getItemInHand() != null && session.getInventory().getItemInHand().getId() == ItemRegistry.SHIELD.getJavaId()) { - useItemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND); - } - // Else we just assume it's the offhand, to simplify logic and to assure the packet gets sent - else { - useItemPacket = new ClientPlayerUseItemPacket(Hand.OFF_HAND); - } - session.sendDownstreamPacket(useItemPacket); - } - } else if (session.getPlayerEntity().getEntityId() == entityId && !metadata.getFlags().getFlag(EntityFlag.SNEAKING) && metadata.getFlags().getFlag(EntityFlag.BLOCKING)) { - metadata.getFlags().setFlag(EntityFlag.BLOCKING, false); - metadata.getFlags().setFlag(EntityFlag.IS_AVOIDING_BLOCK, true); - ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, new Position(0, 0, 0), BlockFace.DOWN); - session.sendDownstreamPacket(releaseItemPacket); - } - } - break; - case 1: // Air/bubbles - if ((int) entityMetadata.getValue() == 300) { - metadata.put(EntityData.AIR_SUPPLY, (short) 0); // Otherwise the bubble counter remains in the UI - } else { - metadata.put(EntityData.AIR_SUPPLY, (short) (int) entityMetadata.getValue()); - } - break; - case 2: // custom name - if (entityMetadata.getValue() instanceof Message) { - Message message = (Message) entityMetadata.getValue(); - if (message != null) - // Always translate even if it's a TextMessage since there could be translatable parameters - metadata.put(EntityData.NAMETAG, MessageTranslator.convertMessage(message.toString(), session.getLocale())); - } - break; - case 3: // is custom name visible - if (!this.is(PlayerEntity.class)) - metadata.put(EntityData.NAMETAG_ALWAYS_SHOW, (byte) ((boolean) entityMetadata.getValue() ? 1 : 0)); - break; - case 4: // silent - metadata.getFlags().setFlag(EntityFlag.SILENT, (boolean) entityMetadata.getValue()); - break; - case 5: // no gravity - metadata.getFlags().setFlag(EntityFlag.HAS_GRAVITY, !(boolean) entityMetadata.getValue()); - break; - case 6: // Pose change - if (entityMetadata.getValue().equals(Pose.SLEEPING)) { - metadata.getFlags().setFlag(EntityFlag.SLEEPING, true); - // Has to be a byte or it does not work - metadata.put(EntityData.PLAYER_FLAGS, (byte) 2); - metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.2f); - metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.2f); - } else if (metadata.getFlags().getFlag(EntityFlag.SLEEPING)) { - metadata.getFlags().setFlag(EntityFlag.SLEEPING, false); - metadata.put(EntityData.BOUNDING_BOX_WIDTH, getEntityType().getWidth()); - metadata.put(EntityData.BOUNDING_BOX_HEIGHT, getEntityType().getHeight()); - metadata.put(EntityData.PLAYER_FLAGS, (byte) 0); - } - break; - } - } - - /** - * Sends the Bedrock metadata to the client - * @param session GeyserSession - */ - public void updateBedrockMetadata(GeyserSession session) { - if (!valid) return; - - SetEntityDataPacket entityDataPacket = new SetEntityDataPacket(); - entityDataPacket.setRuntimeEntityId(geyserId); - entityDataPacket.getMetadata().putAll(metadata); - session.sendUpstreamPacket(entityDataPacket); - } - - /** - * x = Pitch, y = HeadYaw, z = Yaw - * - * @return the bedrock rotation - */ - public Vector3f getBedrockRotation() { - return Vector3f.from(rotation.getY(), rotation.getZ(), rotation.getX()); - } - - @SuppressWarnings("unchecked") - public I as(Class entityClass) { - return entityClass.isInstance(this) ? (I) this : null; - } - - public boolean is(Class entityClass) { - return entityClass.isInstance(this); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java deleted file mode 100644 index c100c6f37..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.github.steveice10.opennbt.tag.builtin.ListTag; -import com.github.steveice10.opennbt.tag.builtin.Tag; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.nbt.NbtMapBuilder; -import com.nukkitx.nbt.NbtType; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.utils.FireworkColor; -import org.geysermc.connector.utils.MathUtils; -import org.geysermc.floodgate.util.DeviceOS; - -import java.util.ArrayList; -import java.util.List; -import java.util.OptionalInt; - -public class FireworkEntity extends Entity { - - public FireworkEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7) { - ItemStack item = (ItemStack) entityMetadata.getValue(); - if (item == null) { - return; - } - CompoundTag tag = item.getNbt(); - - if (tag == null) { - return; - } - - // TODO: Remove once Mojang fixes bugs with fireworks crashing clients on these specific devices. - // https://bugs.mojang.com/browse/MCPE-89115 - if (session.getClientData().getDeviceOS() == DeviceOS.XBOX_ONE || session.getClientData().getDeviceOS() == DeviceOS.ORBIS) { - return; - } - - CompoundTag fireworks = tag.get("Fireworks"); - if (fireworks == null) { - // Thank you Mineplex very cool - return; - } - - NbtMapBuilder fireworksBuilder = NbtMap.builder(); - if (fireworks.get("Flight") != null) { - fireworksBuilder.putByte("Flight", MathUtils.convertByte(fireworks.get("Flight").getValue())); - } - - List explosions = new ArrayList<>(); - if (fireworks.get("Explosions") != null) { - for (Tag effect : ((ListTag) fireworks.get("Explosions")).getValue()) { - CompoundTag effectData = (CompoundTag) effect; - NbtMapBuilder effectBuilder = NbtMap.builder(); - - if (effectData.get("Type") != null) { - effectBuilder.putByte("FireworkType", MathUtils.convertByte(effectData.get("Type").getValue())); - } - - if (effectData.get("Colors") != null) { - int[] oldColors = (int[]) effectData.get("Colors").getValue(); - byte[] colors = new byte[oldColors.length]; - - int i = 0; - for (int color : oldColors) { - colors[i++] = FireworkColor.fromJavaID(color).getBedrockID(); - } - - effectBuilder.putByteArray("FireworkColor", colors); - } - - if (effectData.get("FadeColors") != null) { - int[] oldColors = (int[]) effectData.get("FadeColors").getValue(); - byte[] colors = new byte[oldColors.length]; - - int i = 0; - for (int color : oldColors) { - colors[i++] = FireworkColor.fromJavaID(color).getBedrockID(); - } - - effectBuilder.putByteArray("FireworkFade", colors); - } - - if (effectData.get("Trail") != null) { - effectBuilder.putByte("FireworkTrail", MathUtils.convertByte(effectData.get("Trail").getValue())); - } - - if (effectData.get("Flicker") != null) { - effectBuilder.putByte("FireworkFlicker", MathUtils.convertByte(effectData.get("Flicker").getValue())); - } - - explosions.add(effectBuilder.build()); - } - } - - fireworksBuilder.putList("Explosions", NbtType.COMPOUND, explosions); - - NbtMapBuilder builder = NbtMap.builder(); - builder.put("Fireworks", fireworksBuilder.build()); - metadata.put(EntityData.DISPLAY_ITEM, builder.build()); - } else if (entityMetadata.getId() == 8 && !entityMetadata.getValue().equals(OptionalInt.empty()) && ((OptionalInt) entityMetadata.getValue()).getAsInt() == session.getPlayerEntity().getEntityId()) { - //Checks if the firework has an entity ID (used when a player is gliding) and checks to make sure the player that is gliding is the one getting sent the packet or else every player near the gliding player will boost too. - PlayerEntity entity = session.getPlayerEntity(); - float yaw = entity.getRotation().getX(); - float pitch = entity.getRotation().getY(); - //Uses math from NukkitX - entity.setMotion(Vector3f.from( - -Math.sin(Math.toRadians(yaw)) * Math.cos(Math.toRadians(pitch)) * 2, - -Math.sin(Math.toRadians(pitch)) * 2, - Math.cos(Math.toRadians(yaw)) * Math.cos(Math.toRadians(pitch)) * 2)); - //Need to update the EntityMotionPacket or else the player won't boost - SetEntityMotionPacket entityMotionPacket = new SetEntityMotionPacket(); - entityMotionPacket.setRuntimeEntityId(entity.getGeyserId()); - entityMotionPacket.setMotion(entity.getMotion()); - - session.sendUpstreamPacket(entityMotionPacket); - } - - super.updateBedrockMetadata(entityMetadata, session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java deleted file mode 100644 index 2949b5735..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.game.entity.object.ProjectileData; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; - -public class FishingHookEntity extends Entity { - public FishingHookEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, ProjectileData data) { - super(entityId, geyserId, entityType, position, motion, rotation); - - for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) { - Entity entity = session.getEntityCache().getEntityByJavaId(data.getOwnerId()); - if (entity == null && session.getPlayerEntity().getEntityId() == data.getOwnerId()) { - entity = session.getPlayerEntity(); - } - - if (entity != null) { - this.metadata.put(EntityData.OWNER_EID, entity.getGeyserId()); - return; - } - } - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7) { - Entity entity = session.getEntityCache().getEntityByJavaId((Integer) entityMetadata.getValue() - 1); - if (entity == null && session.getPlayerEntity().getEntityId() == (Integer) entityMetadata.getValue() - 1) { - entity = session.getPlayerEntity(); - } - - if (entity != null) { - metadata.put(EntityData.TARGET_EID, entity.getGeyserId()); - } - } - - super.updateBedrockMetadata(entityMetadata, session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemEntity.java deleted file mode 100644 index 41308a0de..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemEntity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.packet.AddItemEntityPacket; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.item.ItemTranslator; - -public class ItemEntity extends Entity { - - public ItemEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7) { - AddItemEntityPacket itemPacket = new AddItemEntityPacket(); - itemPacket.setRuntimeEntityId(geyserId); - itemPacket.setPosition(position); - itemPacket.setMotion(motion); - itemPacket.setUniqueEntityId(geyserId); - itemPacket.setFromFishing(false); - itemPacket.getMetadata().putAll(metadata); - itemPacket.setItemInHand(ItemTranslator.translateToBedrock(session, (ItemStack) entityMetadata.getValue())); - session.sendUpstreamPacket(itemPacket); - } - - super.updateBedrockMetadata(entityMetadata, session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java deleted file mode 100644 index 1544f767a..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity; - -import com.nukkitx.math.vector.Vector3f; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; - -public class ItemedFireballEntity extends ThrowableEntity { - private final Vector3f acceleration; - - public ItemedFireballEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, Vector3f.ZERO, rotation); - acceleration = motion; - } - - @Override - protected void updatePosition(GeyserSession session) { - position = position.add(motion); - // TODO: While this reduces latency in position updating (needed for better fireball reflecting), - // TODO: movement is incredibly stiff. See if the MoveEntityDeltaPacket in 1.16.100 fixes this, and if not, - // TODO: only use this laggy movement for fireballs that be reflected - moveAbsoluteImmediate(session, position, rotation, false, true); - float drag = getDrag(session); - motion = motion.add(acceleration).mul(drag); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java b/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java deleted file mode 100644 index 345c19dea..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.protocol.bedrock.data.AttributeData; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; -import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import com.nukkitx.protocol.bedrock.packet.MobArmorEquipmentPacket; -import com.nukkitx.protocol.bedrock.packet.MobEquipmentPacket; -import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; -import lombok.Getter; -import lombok.Setter; -import org.geysermc.connector.entity.attribute.AttributeType; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.utils.AttributeUtils; -import org.geysermc.connector.utils.ChunkUtils; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -@Getter -@Setter -public class LivingEntity extends Entity { - - protected ItemData helmet = ItemData.AIR; - protected ItemData chestplate = ItemData.AIR; - protected ItemData leggings = ItemData.AIR; - protected ItemData boots = ItemData.AIR; - protected ItemData hand = ItemData.AIR; - protected ItemData offHand = ItemData.AIR; - - public LivingEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - switch (entityMetadata.getId()) { - case 7: // blocking - byte xd = (byte) entityMetadata.getValue(); - - //blocking gets triggered when using a bow, but if we set USING_ITEM for all items, it may look like - //you're "mining" with ex. a shield. - boolean isUsingShield = (getHand().getId() == ItemRegistry.SHIELD.getBedrockId() || - getHand().equals(ItemData.AIR) && getOffHand().getId() == ItemRegistry.SHIELD.getBedrockId()); - metadata.getFlags().setFlag(EntityFlag.USING_ITEM, (xd & 0x01) == 0x01 && !isUsingShield); - metadata.getFlags().setFlag(EntityFlag.BLOCKING, (xd & 0x01) == 0x01); - break; - case 8: - metadata.put(EntityData.HEALTH, entityMetadata.getValue()); - break; - case 9: - metadata.put(EntityData.EFFECT_COLOR, entityMetadata.getValue()); - break; - case 10: - metadata.put(EntityData.EFFECT_AMBIENT, (byte) ((boolean) entityMetadata.getValue() ? 1 : 0)); - break; - case 13: // Bed Position - Position bedPosition = (Position) entityMetadata.getValue(); - if (bedPosition != null) { - metadata.put(EntityData.BED_POSITION, Vector3i.from(bedPosition.getX(), bedPosition.getY(), bedPosition.getZ())); - if (session.getConnector().getConfig().isCacheChunks()) { - int bed = session.getConnector().getWorldManager().getBlockAt(session, bedPosition); - // Bed has to be updated, or else player is floating in the air - ChunkUtils.updateBlock(session, bed, bedPosition); - } - } - break; - } - - super.updateBedrockMetadata(entityMetadata, session); - } - - public void updateEquipment(GeyserSession session) { - if (!valid) - return; - - MobArmorEquipmentPacket armorEquipmentPacket = new MobArmorEquipmentPacket(); - armorEquipmentPacket.setRuntimeEntityId(geyserId); - armorEquipmentPacket.setHelmet(helmet); - armorEquipmentPacket.setChestplate(chestplate); - armorEquipmentPacket.setLeggings(leggings); - armorEquipmentPacket.setBoots(boots); - - MobEquipmentPacket handPacket = new MobEquipmentPacket(); - handPacket.setRuntimeEntityId(geyserId); - handPacket.setItem(hand); - handPacket.setHotbarSlot(-1); - handPacket.setInventorySlot(0); - handPacket.setContainerId(ContainerId.INVENTORY); - - MobEquipmentPacket offHandPacket = new MobEquipmentPacket(); - offHandPacket.setRuntimeEntityId(geyserId); - offHandPacket.setItem(offHand); - offHandPacket.setHotbarSlot(-1); - offHandPacket.setInventorySlot(0); - offHandPacket.setContainerId(ContainerId.OFFHAND); - - session.sendUpstreamPacket(armorEquipmentPacket); - session.sendUpstreamPacket(handPacket); - session.sendUpstreamPacket(offHandPacket); - } - - @Override - public void updateBedrockAttributes(GeyserSession session) { - if (!valid) return; - - float maxHealth = this.attributes.containsKey(AttributeType.MAX_HEALTH) ? this.attributes.get(AttributeType.MAX_HEALTH).getValue() : getDefaultMaxHealth(); - - List attributes = new ArrayList<>(); - for (Map.Entry entry : this.attributes.entrySet()) { - if (!entry.getValue().getType().isBedrockAttribute()) - continue; - if (entry.getValue().getType() == AttributeType.HEALTH) { - // Add health attribute to properly show hearts when mounting - // TODO: Not a perfect system, since it led to respawn bugs - attributes.add(new AttributeData("minecraft:health", 0.0f, maxHealth, metadata.getFloat(EntityData.HEALTH, 20f), maxHealth)); - continue; - } - - attributes.add(AttributeUtils.getBedrockAttribute(entry.getValue())); - } - - UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket(); - updateAttributesPacket.setRuntimeEntityId(geyserId); - updateAttributesPacket.setAttributes(attributes); - session.sendUpstreamPacket(updateAttributesPacket); - } - - /** - * Used for the health visual when mounting an entity. - * @return the default maximum health for the entity. - */ - protected float getDefaultMaxHealth() { - return 20f; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java deleted file mode 100644 index 72b5ee820..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; - -public class MinecartEntity extends Entity { - - public MinecartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position.add(0d, entityType.getOffset(), 0d), motion, rotation); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - - if (entityMetadata.getId() == 7) { - metadata.put(EntityData.HEALTH, entityMetadata.getValue()); - } - - // Direction in which the minecart is shaking - if (entityMetadata.getId() == 8) { - metadata.put(EntityData.HURT_DIRECTION, entityMetadata.getValue()); - } - - // Power in Java, time in Bedrock - if (entityMetadata.getId() == 9) { - metadata.put(EntityData.HURT_TIME, Math.min((int) (float) entityMetadata.getValue(), 15)); - } - - if (!(this instanceof DefaultBlockMinecartEntity)) { // Handled in the DefaultBlockMinecartEntity class - // Custom block - if (entityMetadata.getId() == 10) { - metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId((int) entityMetadata.getValue())); - } - - // Custom block offset - if (entityMetadata.getId() == 11) { - metadata.put(EntityData.DISPLAY_OFFSET, entityMetadata.getValue()); - } - - // If the custom block should be enabled - if (entityMetadata.getId() == 12) { - // Needs a byte based off of Java's boolean - metadata.put(EntityData.CUSTOM_DISPLAY, (byte) ((boolean) entityMetadata.getValue() ? 1 : 0)); - } - } - - super.updateBedrockMetadata(entityMetadata, session); - } - - @Override - public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { - super.moveAbsolute(session, position.add(0d, this.entityType.getOffset(), 0d), rotation, isOnGround, teleported); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java deleted file mode 100644 index be65525cb..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ /dev/null @@ -1,330 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity; - -import com.github.steveice10.mc.auth.data.GameProfile; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.message.TextMessage; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.protocol.bedrock.data.AttributeData; -import com.nukkitx.protocol.bedrock.data.PlayerPermission; -import com.nukkitx.protocol.bedrock.data.command.CommandPermission; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData; -import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket; -import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; -import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket; -import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; -import lombok.Getter; -import lombok.Setter; -import org.geysermc.connector.entity.attribute.Attribute; -import org.geysermc.connector.entity.attribute.AttributeType; -import org.geysermc.connector.entity.living.animal.tameable.ParrotEntity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.session.cache.EntityEffectCache; -import org.geysermc.connector.scoreboard.Team; -import org.geysermc.connector.utils.AttributeUtils; -import org.geysermc.connector.network.translators.chat.MessageTranslator; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -@Getter @Setter -public class PlayerEntity extends LivingEntity { - private GameProfile profile; - private UUID uuid; - private String username; - private long lastSkinUpdate = -1; - private boolean playerList = true; // Player is in the player list - private final EntityEffectCache effectCache; - - /** - * Saves the parrot currently on the player's left shoulder; otherwise null - */ - private ParrotEntity leftParrot; - /** - * Saves the parrot currently on the player's right shoulder; otherwise null - */ - private ParrotEntity rightParrot; - - public PlayerEntity(GameProfile gameProfile, long entityId, long geyserId, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, EntityType.PLAYER, position, motion, rotation); - - profile = gameProfile; - uuid = gameProfile.getId(); - username = gameProfile.getName(); - effectCache = new EntityEffectCache(); - if (geyserId == 1) valid = true; - } - - @Override - public void spawnEntity(GeyserSession session) { - if (geyserId == 1) return; - - AddPlayerPacket addPlayerPacket = new AddPlayerPacket(); - addPlayerPacket.setUuid(uuid); - addPlayerPacket.setUsername(username); - addPlayerPacket.setRuntimeEntityId(geyserId); - addPlayerPacket.setUniqueEntityId(geyserId); - addPlayerPacket.setPosition(position.clone().sub(0, EntityType.PLAYER.getOffset(), 0)); - addPlayerPacket.setRotation(getBedrockRotation()); - addPlayerPacket.setMotion(motion); - addPlayerPacket.setHand(hand); - addPlayerPacket.getAdventureSettings().setCommandPermission(CommandPermission.NORMAL); - addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.MEMBER); - addPlayerPacket.setDeviceId(""); - addPlayerPacket.setPlatformChatId(""); - addPlayerPacket.getMetadata().putAll(metadata); - - long linkedEntityId = session.getEntityCache().getCachedPlayerEntityLink(entityId); - if (linkedEntityId != -1) { - addPlayerPacket.getEntityLinks().add(new EntityLinkData(session.getEntityCache().getEntityByJavaId(linkedEntityId).getGeyserId(), geyserId, EntityLinkData.Type.RIDER, false)); - } - - valid = true; - session.sendUpstreamPacket(addPlayerPacket); - - updateEquipment(session); - updateBedrockAttributes(session); - } - - public void sendPlayer(GeyserSession session) { - if (session.getEntityCache().getPlayerEntity(uuid) == null) - return; - - if (session.getUpstream().isInitialized() && session.getEntityCache().getEntityByGeyserId(geyserId) == null) { - session.getEntityCache().spawnEntity(this); - } else { - spawnEntity(session); - } - } - - @Override - public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { - setPosition(position); - setRotation(rotation); - - setOnGround(isOnGround); - - MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); - movePlayerPacket.setRuntimeEntityId(geyserId); - movePlayerPacket.setPosition(this.position); - movePlayerPacket.setRotation(getBedrockRotation()); - movePlayerPacket.setOnGround(isOnGround); - movePlayerPacket.setMode(teleported ? MovePlayerPacket.Mode.TELEPORT : MovePlayerPacket.Mode.NORMAL); - - if (teleported) { - movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.UNKNOWN); - } - - session.sendUpstreamPacket(movePlayerPacket); - if (leftParrot != null) { - leftParrot.moveAbsolute(session, position, rotation, true, teleported); - } - if (rightParrot != null) { - rightParrot.moveAbsolute(session, position, rotation, true, teleported); - } - } - - @Override - public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) { - setRotation(rotation); - this.position = Vector3f.from(position.getX() + relX, position.getY() + relY, position.getZ() + relZ); - - setOnGround(isOnGround); - - MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); - movePlayerPacket.setRuntimeEntityId(geyserId); - movePlayerPacket.setPosition(position); - movePlayerPacket.setRotation(getBedrockRotation()); - movePlayerPacket.setOnGround(isOnGround); - movePlayerPacket.setMode(MovePlayerPacket.Mode.NORMAL); - // If the player is moved while sleeping, we have to adjust their y, so it appears - // correctly on Bedrock. This fixes GSit's lay. - if (metadata.getFlags().getFlag(EntityFlag.SLEEPING)) { - Vector3i bedPosition = metadata.getPos(EntityData.BED_POSITION); - if (bedPosition != null && (bedPosition.getY() == 0 || bedPosition.distanceSquared(position.toInt()) > 4)) { - // Force the player movement by using a teleport - movePlayerPacket.setPosition(Vector3f.from(position.getX(), position.getY() - entityType.getOffset() + 0.2f, position.getZ())); - movePlayerPacket.setMode(MovePlayerPacket.Mode.TELEPORT); - movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.UNKNOWN); - } - } - session.sendUpstreamPacket(movePlayerPacket); - if (leftParrot != null) { - leftParrot.moveRelative(session, relX, relY, relZ, rotation, true); - } - if (rightParrot != null) { - rightParrot.moveRelative(session, relX, relY, relZ, rotation, true); - } - } - - @Override - public void updateHeadLookRotation(GeyserSession session, float headYaw) { - moveRelative(session, 0, 0, 0, Vector3f.from(rotation.getX(), rotation.getY(), headYaw), onGround); - MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); - movePlayerPacket.setRuntimeEntityId(geyserId); - movePlayerPacket.setPosition(position); - movePlayerPacket.setRotation(getBedrockRotation()); - movePlayerPacket.setMode(MovePlayerPacket.Mode.HEAD_ROTATION); - session.sendUpstreamPacket(movePlayerPacket); - } - - @Override - public void updatePositionAndRotation(GeyserSession session, double moveX, double moveY, double moveZ, float yaw, float pitch, boolean isOnGround) { - moveRelative(session, moveX, moveY, moveZ, yaw, pitch, isOnGround); - if (leftParrot != null) { - leftParrot.moveRelative(session, moveX, moveY, moveZ, yaw, pitch, isOnGround); - } - if (rightParrot != null) { - rightParrot.moveRelative(session, moveX, moveY, moveZ, yaw, pitch, isOnGround); - } - } - - @Override - public void updateRotation(GeyserSession session, float yaw, float pitch, boolean isOnGround) { - super.updateRotation(session, yaw, pitch, isOnGround); - // Both packets need to be sent or else player head rotation isn't correctly updated - MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); - movePlayerPacket.setRuntimeEntityId(geyserId); - movePlayerPacket.setPosition(position); - movePlayerPacket.setRotation(getBedrockRotation()); - movePlayerPacket.setOnGround(isOnGround); - movePlayerPacket.setMode(MovePlayerPacket.Mode.HEAD_ROTATION); - session.sendUpstreamPacket(movePlayerPacket); - if (leftParrot != null) { - leftParrot.updateRotation(session, yaw, pitch, isOnGround); - } - if (rightParrot != null) { - rightParrot.updateRotation(session, yaw, pitch, isOnGround); - } - } - - @Override - public void setPosition(Vector3f position) { - this.position = position.add(0, entityType.getOffset(), 0); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - super.updateBedrockMetadata(entityMetadata, session); - - if (entityMetadata.getId() == 2) { - String username = this.username; - TextMessage name = (TextMessage) entityMetadata.getValue(); - if (name != null) { - username = MessageTranslator.convertMessage(name.toString()); - } - Team team = session.getWorldCache().getScoreboard().getTeamFor(username); - if (team != null) { - String displayName = ""; - if (team.isVisibleFor(session.getPlayerEntity().getUsername())) { - displayName = MessageTranslator.toChatColor(team.getColor()) + username; - displayName = team.getCurrentData().getDisplayName(displayName); - } - metadata.put(EntityData.NAMETAG, displayName); - } - } - - // Extra hearts - is not metadata but an attribute on Bedrock - if (entityMetadata.getId() == 14) { - UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); - attributesPacket.setRuntimeEntityId(geyserId); - List attributes = new ArrayList<>(); - // Setting to a higher maximum since plugins/datapacks can probably extend the Bedrock soft limit - attributes.add(new AttributeData("minecraft:absorption", 0.0f, 1024f, (float) entityMetadata.getValue(), 0.0f)); - attributesPacket.setAttributes(attributes); - session.sendUpstreamPacket(attributesPacket); - } - - // Parrot occupying shoulder - if (entityMetadata.getId() == 18 || entityMetadata.getId() == 19) { - CompoundTag tag = (CompoundTag) entityMetadata.getValue(); - if (tag != null && !tag.isEmpty()) { - if ((entityMetadata.getId() == 18 && leftParrot != null) || (entityMetadata.getId() == 19 && rightParrot != null)) { - // No need to update a parrot's data when it already exists - return; - } - // The parrot is a separate entity in Bedrock, but part of the player entity in Java - ParrotEntity parrot = new ParrotEntity(0, session.getEntityCache().getNextEntityId().incrementAndGet(), - EntityType.PARROT, position, motion, rotation); - parrot.spawnEntity(session); - parrot.getMetadata().put(EntityData.VARIANT, tag.get("Variant").getValue()); - // Different position whether the parrot is left or right - float offset = (entityMetadata.getId() == 18) ? 0.4f : -0.4f; - parrot.getMetadata().put(EntityData.RIDER_SEAT_POSITION, Vector3f.from(offset, -0.22, -0.1)); - parrot.getMetadata().put(EntityData.RIDER_ROTATION_LOCKED, 1); - parrot.updateBedrockMetadata(session); - SetEntityLinkPacket linkPacket = new SetEntityLinkPacket(); - EntityLinkData.Type type = (entityMetadata.getId() == 18) ? EntityLinkData.Type.RIDER : EntityLinkData.Type.PASSENGER; - linkPacket.setEntityLink(new EntityLinkData(geyserId, parrot.getGeyserId(), type, false)); - // Delay, or else spawned-in players won't get the link - // TODO: Find a better solution. This problem also exists with item frames - session.getConnector().getGeneralThreadPool().schedule(() -> session.sendUpstreamPacket(linkPacket), 500, TimeUnit.MILLISECONDS); - if (entityMetadata.getId() == 18) { - leftParrot = parrot; - } else { - rightParrot = parrot; - } - } else { - Entity parrot = (entityMetadata.getId() == 18 ? leftParrot : rightParrot); - if (parrot != null) { - parrot.despawnEntity(session); - if (entityMetadata.getId() == 18) { - leftParrot = null; - } else { - rightParrot = null; - } - } - } - } - } - - @Override - public void updateBedrockAttributes(GeyserSession session) { // TODO: Don't use duplicated code - if (!valid) return; - - List attributes = new ArrayList<>(); - for (Map.Entry entry : this.attributes.entrySet()) { - if (!entry.getValue().getType().isBedrockAttribute()) - continue; - - attributes.add(AttributeUtils.getBedrockAttribute(entry.getValue())); - } - - UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket(); - updateAttributesPacket.setRuntimeEntityId(geyserId); - updateAttributesPacket.setAttributes(attributes); - session.sendUpstreamPacket(updateAttributesPacket); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/TNTEntity.java b/connector/src/main/java/org/geysermc/connector/entity/TNTEntity.java deleted file mode 100644 index 068cd2a1f..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/TNTEntity.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; - -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -public class TNTEntity extends Entity { - - private int currentTick; - - public TNTEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7) { - currentTick = (int) entityMetadata.getValue(); - metadata.getFlags().setFlag(EntityFlag.IGNITED, true); - metadata.put(EntityData.FUSE_LENGTH, currentTick); - ScheduledFuture future = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> { - if (currentTick % 5 == 0) { - metadata.put(EntityData.FUSE_LENGTH, currentTick); - } - currentTick--; - super.updateBedrockMetadata(entityMetadata, session); - }, 50, 50, TimeUnit.MILLISECONDS); // 5 ticks - session.getConnector().getGeneralThreadPool().schedule(() -> future.cancel(true), (int) entityMetadata.getValue() / 20, TimeUnit.SECONDS); - } - - super.updateBedrockMetadata(entityMetadata, session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java deleted file mode 100644 index 5b7ba5c03..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity; - -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.LevelEventType; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; - -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -/** - * Used as a class for any object-like entity that moves as a projectile - */ -public class ThrowableEntity extends Entity { - - private Vector3f lastPosition; - /** - * Updates the position for the Bedrock client. - * - * Java clients assume the next positions of moving items. Bedrock needs to be explicitly told positions - */ - protected ScheduledFuture positionUpdater; - - public ThrowableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - this.lastPosition = position; - } - - @Override - public void spawnEntity(GeyserSession session) { - super.spawnEntity(session); - positionUpdater = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> { - if (session.isClosed()) { - positionUpdater.cancel(true); - return; - } - updatePosition(session); - }, 0, 50, TimeUnit.MILLISECONDS); - } - - protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { - super.moveAbsolute(session, position, rotation, isOnGround, teleported); - } - - protected void updatePosition(GeyserSession session) { - super.moveRelative(session, motion.getX(), motion.getY(), motion.getZ(), rotation, onGround); - float drag = getDrag(session); - float gravity = getGravity(); - motion = motion.mul(drag).down(gravity); - } - - /** - * Get the gravity of this entity type. Used for applying gravity while the entity is in motion. - * - * @return the amount of gravity to apply to this entity while in motion. - */ - protected float getGravity() { - if (metadata.getFlags().getFlag(EntityFlag.HAS_GRAVITY)) { - switch (entityType) { - case THROWN_POTION: - case LINGERING_POTION: - return 0.05f; - case THROWN_EXP_BOTTLE: - return 0.07f; - case FIREBALL: - return 0; - case SNOWBALL: - case THROWN_EGG: - case THROWN_ENDERPEARL: - return 0.03f; - } - } - return 0; - } - - /** - * @param session the session of the Bedrock client. - * @return the drag that should be multiplied to the entity's motion - */ - protected float getDrag(GeyserSession session) { - if (isInWater(session)) { - return 0.8f; - } else { - switch (entityType) { - case THROWN_POTION: - case LINGERING_POTION: - case THROWN_EXP_BOTTLE: - case SNOWBALL: - case THROWN_EGG: - case THROWN_ENDERPEARL: - return 0.99f; - case FIREBALL: - case SMALL_FIREBALL: - case DRAGON_FIREBALL: - return 0.95f; - } - } - return 1; - } - - /** - * @param session the session of the Bedrock client. - * @return true if this entity is currently in water. - */ - protected boolean isInWater(GeyserSession session) { - if (session.getConnector().getConfig().isCacheChunks()) { - int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt()); - return block == BlockTranslator.BEDROCK_WATER_ID; - } - return false; - } - - @Override - public boolean despawnEntity(GeyserSession session) { - positionUpdater.cancel(true); - if (entityType == EntityType.THROWN_ENDERPEARL) { - LevelEventPacket particlePacket = new LevelEventPacket(); - particlePacket.setType(LevelEventType.PARTICLE_TELEPORT); - particlePacket.setPosition(position); - session.sendUpstreamPacket(particlePacket); - } - return super.despawnEntity(session); - } - - @Override - public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) { - position = lastPosition; - super.moveRelative(session, relX, relY, relZ, rotation, isOnGround); - lastPosition = position; - } - - @Override - public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { - super.moveAbsolute(session, position, rotation, isOnGround, teleported); - lastPosition = position; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java deleted file mode 100644 index b61aeda92..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity.living; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import lombok.Getter; -import org.geysermc.connector.entity.LivingEntity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; - -public class ArmorStandEntity extends LivingEntity { - - // These are used to store the state of the armour stand for use when handling invisibility - @Getter - private boolean isMarker = false; - private boolean isInvisible = false; - private boolean isSmall = false; - - public ArmorStandEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } - - @Override - public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { - // Fake the height to be above where it is so the nametag appears in the right location for invisible non-marker armour stands - if (!isMarker && isInvisible && passengers.isEmpty()) { - position = position.add(0d, entityType.getHeight() * (isSmall ? 0.55d : 1d), 0d); - } - - super.moveAbsolute(session, position, rotation, isOnGround, teleported); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 0 && entityMetadata.getType() == MetadataType.BYTE) { - byte xd = (byte) entityMetadata.getValue(); - - // Check if the armour stand is invisible and store accordingly - if ((xd & 0x20) == 0x20) { - metadata.put(EntityData.SCALE, 0.0f); - isInvisible = true; - } - } else if (entityMetadata.getId() == 14 && entityMetadata.getType() == MetadataType.BYTE) { - byte xd = (byte) entityMetadata.getValue(); - - // isSmall - if ((xd & 0x01) == 0x01) { - isSmall = true; - - if (metadata.getFloat(EntityData.SCALE) != 0.55f && metadata.getFloat(EntityData.SCALE) != 0.0f) { - metadata.put(EntityData.SCALE, 0.55f); - } - - if (metadata.get(EntityData.BOUNDING_BOX_WIDTH) != null && metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.5f)) { - metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.25f); - metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.9875f); - } - } else if (metadata.get(EntityData.BOUNDING_BOX_WIDTH) != null && metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.25f)) { - metadata.put(EntityData.BOUNDING_BOX_WIDTH, entityType.getWidth()); - metadata.put(EntityData.BOUNDING_BOX_HEIGHT, entityType.getHeight()); - } - - // setMarker - if ((xd & 0x10) == 0x10 && (metadata.get(EntityData.BOUNDING_BOX_WIDTH) == null || !metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.0f))) { - metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.0f); - metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.0f); - isMarker = true; - } - } - super.updateBedrockMetadata(entityMetadata, session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java deleted file mode 100644 index ee17e2a2c..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity.living.animal; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; - -public class BeeEntity extends AnimalEntity { - - public BeeEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { - byte xd = (byte) entityMetadata.getValue(); - // Bee is performing sting attack; trigger animation - if ((xd & 0x02) == 0x02) { - EntityEventPacket packet = new EntityEventPacket(); - packet.setRuntimeEntityId(geyserId); - packet.setType(EntityEventType.ATTACK_START); - packet.setData(0); - session.sendUpstreamPacket(packet); - } - // If the bee has stung - metadata.put(EntityData.MARK_VARIANT, (xd & 0x04) == 0x04 ? 1 : 0); - // If the bee has nectar or not - metadata.getFlags().setFlag(EntityFlag.POWERED, (xd & 0x08) == 0x08); - } - if (entityMetadata.getId() == 17) { - // Converting "anger time" to a boolean - metadata.getFlags().setFlag(EntityFlag.ANGRY, (int) entityMetadata.getValue() > 0); - } - super.updateBedrockMetadata(entityMetadata, session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java deleted file mode 100644 index bbc2d7def..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity.living.animal; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; - -public class FoxEntity extends AnimalEntity { - - public FoxEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { - metadata.put(EntityData.VARIANT, entityMetadata.getValue()); - } - if (entityMetadata.getId() == 17) { - byte xd = (byte) entityMetadata.getValue(); - metadata.getFlags().setFlag(EntityFlag.SITTING, (xd & 0x01) == 0x01); - metadata.getFlags().setFlag(EntityFlag.SNEAKING, (xd & 0x04) == 0x04); - metadata.getFlags().setFlag(EntityFlag.INTERESTED, (xd & 0x08) == 0x08); - metadata.getFlags().setFlag(EntityFlag.SLEEPING, (xd & 0x20) == 0x20); - } - super.updateBedrockMetadata(entityMetadata, session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/OcelotEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/OcelotEntity.java deleted file mode 100644 index 36a67dbb7..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/OcelotEntity.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity.living.animal; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; - -public class OcelotEntity extends AnimalEntity { - - public OcelotEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { - metadata.getFlags().setFlag(EntityFlag.TRUSTING, (boolean) entityMetadata.getValue()); - } - super.updateBedrockMetadata(entityMetadata, session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java deleted file mode 100644 index ed3ed80b7..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity.living.animal; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.item.ItemRegistry; - -public class PandaEntity extends AnimalEntity { - - private int mainGene; - private int hiddenGene; - - public PandaEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 18) { - metadata.getFlags().setFlag(EntityFlag.EATING, (int) entityMetadata.getValue() > 0); - metadata.put(EntityData.EATING_COUNTER, entityMetadata.getValue()); - if ((int) entityMetadata.getValue() != 0) { - // Particles and sound - EntityEventPacket packet = new EntityEventPacket(); - packet.setRuntimeEntityId(geyserId); - packet.setType(EntityEventType.EATING_ITEM); - packet.setData(ItemRegistry.BAMBOO.getBedrockId() << 16); - session.sendUpstreamPacket(packet); - } - } - if (entityMetadata.getId() == 19) { - mainGene = (int) (byte) entityMetadata.getValue(); - updateAppearance(); - } - if (entityMetadata.getId() == 20) { - hiddenGene = (int) (byte) entityMetadata.getValue(); - updateAppearance(); - } - if (entityMetadata.getId() == 21) { - byte xd = (byte) entityMetadata.getValue(); - metadata.getFlags().setFlag(EntityFlag.SNEEZING, (xd & 0x02) == 0x02); - metadata.getFlags().setFlag(EntityFlag.ROLLING, (xd & 0x04) == 0x04); - metadata.getFlags().setFlag(EntityFlag.SITTING, (xd & 0x08) == 0x08); - // Required to put these both for sitting to actually show - metadata.put(EntityData.SITTING_AMOUNT, (xd & 0x08) == 0x08 ? 1f : 0f); - metadata.put(EntityData.SITTING_AMOUNT_PREVIOUS, (xd & 0x08) == 0x08 ? 1f : 0f); - metadata.getFlags().setFlag(EntityFlag.LAYING_DOWN, (xd & 0x10) == 0x10); - } - super.updateBedrockMetadata(entityMetadata, session); - } - - /** - * Update the panda's appearance, and take into consideration the recessive brown and weak traits that only show up - * when both main and hidden genes match - */ - private void updateAppearance() { - if (mainGene == 4 || mainGene == 5) { - // Main gene is a recessive trait - if (mainGene == hiddenGene) { - // Main and hidden genes match; this is what the panda looks like. - metadata.put(EntityData.VARIANT, mainGene); - } else { - // Genes have no effect on appearance - metadata.put(EntityData.VARIANT, 0); - } - } else { - // No need to worry about hidden gene - metadata.put(EntityData.VARIANT, mainGene); - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PolarBearEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PolarBearEntity.java deleted file mode 100644 index 2b09ca912..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PolarBearEntity.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity.living.animal; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; - -public class PolarBearEntity extends AnimalEntity { - - public PolarBearEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { - metadata.getFlags().setFlag(EntityFlag.STANDING, (boolean) entityMetadata.getValue()); - } - super.updateBedrockMetadata(entityMetadata, session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java deleted file mode 100644 index 792027926..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity.living.animal; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; - -public class RabbitEntity extends AnimalEntity { - - public RabbitEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - super.updateBedrockMetadata(entityMetadata, session); - if (entityMetadata.getId() == 15) { - metadata.put(EntityData.SCALE, .55f); - boolean isBaby = (boolean) entityMetadata.getValue(); - if(isBaby) { - metadata.put(EntityData.SCALE, .35f); - metadata.getFlags().setFlag(EntityFlag.BABY, true); - } - } else if (entityMetadata.getId() == 16) { - int variant = (int) entityMetadata.getValue(); - - // Change the killer bunny to display as white since it only exists on Java Edition - if (variant == 99) { - variant = 1; - } - - metadata.put(EntityData.VARIANT, variant); - } - } -} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/TropicalFishEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/TropicalFishEntity.java deleted file mode 100644 index eadc3db0c..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/TropicalFishEntity.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity.living.animal; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.geysermc.connector.entity.living.AbstractFishEntity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; - -public class TropicalFishEntity extends AbstractFishEntity { - - public TropicalFishEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { - TropicalFishVariant variant = TropicalFishVariant.fromVariantNumber((int) entityMetadata.getValue()); - - metadata.put(EntityData.VARIANT, variant.getShape()); // Shape 0-1 - metadata.put(EntityData.MARK_VARIANT, variant.getPattern()); // Pattern 0-5 - metadata.put(EntityData.COLOR, variant.getBaseColor()); // Base color 0-15 - metadata.put(EntityData.COLOR_2, variant.getPatternColor()); // Pattern color 0-15 - } - super.updateBedrockMetadata(entityMetadata, session); - } - - @Getter - @AllArgsConstructor - private static class TropicalFishVariant { - private int shape; - private int pattern; - private byte baseColor; - private byte patternColor; - - /** - * Convert the variant number from Java into separate values - * - * @param varNumber Variant number from Java edition - * - * @return The variant converted into TropicalFishVariant - */ - public static TropicalFishVariant fromVariantNumber(int varNumber) { - return new TropicalFishVariant((varNumber & 0xFF), ((varNumber >> 8) & 0xFF), (byte) ((varNumber >> 16) & 0xFF), (byte) ((varNumber >> 24) & 0xFF)); - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java deleted file mode 100644 index cf9f84b42..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity.living.animal.horse; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; -import org.geysermc.connector.entity.living.animal.AnimalEntity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.item.ItemRegistry; - -public class AbstractHorseEntity extends AnimalEntity { - - public AbstractHorseEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - - if (entityMetadata.getId() == 16) { - byte xd = (byte) entityMetadata.getValue(); - metadata.getFlags().setFlag(EntityFlag.TAMED, (xd & 0x02) == 0x02); - metadata.getFlags().setFlag(EntityFlag.SADDLED, (xd & 0x04) == 0x04); - metadata.getFlags().setFlag(EntityFlag.EATING, (xd & 0x10) == 0x10); - metadata.getFlags().setFlag(EntityFlag.STANDING, (xd & 0x20) == 0x20); - - // HorseFlags - // Bred 0x10 - // Eating 0x20 - // Open mouth 0x80 - int horseFlags = 0x0; - horseFlags = (xd & 0x40) == 0x40 ? horseFlags | 0x80 : horseFlags; - - // Only set eating when we don't have mouth open so a player interaction doesn't trigger the eating animation - horseFlags = (xd & 0x10) == 0x10 && (xd & 0x40) != 0x40 ? horseFlags | 0x20 : horseFlags; - - // Set the flags into the display item - metadata.put(EntityData.DISPLAY_ITEM, horseFlags); - - // Send the eating particles - // We use the wheat metadata as static particles since Java - // doesn't send over what item was used to feed the horse - if ((xd & 0x40) == 0x40) { - EntityEventPacket entityEventPacket = new EntityEventPacket(); - entityEventPacket.setRuntimeEntityId(geyserId); - entityEventPacket.setType(EntityEventType.EATING_ITEM); - entityEventPacket.setData(ItemRegistry.WHEAT.getBedrockId() << 16); - session.sendUpstreamPacket(entityEventPacket); - } - } - - // Needed to control horses - metadata.getFlags().setFlag(EntityFlag.CAN_POWER_JUMP, true); - metadata.getFlags().setFlag(EntityFlag.WASD_CONTROLLED, true); - - super.updateBedrockMetadata(entityMetadata, session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java deleted file mode 100644 index ddac4a63f..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity.living.animal.horse; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import com.nukkitx.protocol.bedrock.packet.MobArmorEquipmentPacket; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; - -public class LlamaEntity extends ChestedHorseEntity { - - public LlamaEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - // Strength - if (entityMetadata.getId() == 19) { - metadata.put(EntityData.STRENGTH, entityMetadata.getValue()); - } - // Color equipped on the llama - if (entityMetadata.getId() == 20) { - // Bedrock treats llama decoration as armor - MobArmorEquipmentPacket equipmentPacket = new MobArmorEquipmentPacket(); - equipmentPacket.setRuntimeEntityId(getGeyserId()); - // -1 means no armor - if ((int) entityMetadata.getValue() != -1) { - // The damage value is the dye color that Java sends us - // Always going to be a carpet so we can hardcode 171 in BlockTranslator - // The int then short conversion is required or we get a ClassCastException - equipmentPacket.setChestplate(ItemData.of(BlockTranslator.CARPET, (short)((int) entityMetadata.getValue()), 1)); - } else { - equipmentPacket.setChestplate(ItemData.AIR); - } - // Required to fill out the rest of the equipment or Bedrock ignores it, including above else statement if removing armor - equipmentPacket.setBoots(ItemData.AIR); - equipmentPacket.setHelmet(ItemData.AIR); - equipmentPacket.setLeggings(ItemData.AIR); - - session.sendUpstreamPacket(equipmentPacket); - } - // Color of the llama - if (entityMetadata.getId() == 21) { - metadata.put(EntityData.VARIANT, entityMetadata.getValue()); - } - super.updateBedrockMetadata(entityMetadata, session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java deleted file mode 100644 index 5c5de5466..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity.living.animal.tameable; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; - -public class CatEntity extends TameableEntity { - - private byte collarColor; - - public CatEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } - - @Override - public void updateRotation(GeyserSession session, float yaw, float pitch, boolean isOnGround) { - moveRelative(session, 0, 0, 0, Vector3f.from(this.rotation.getX(), pitch, yaw), isOnGround); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - super.updateBedrockMetadata(entityMetadata, session); - if (entityMetadata.getId() == 16) { - // Update collar color if tamed - if (metadata.getFlags().getFlag(EntityFlag.TAMED)) { - metadata.put(EntityData.COLOR, collarColor); - } - } - if (entityMetadata.getId() == 18) { - // Different colors in Java and Bedrock for some reason - int variantColor; - switch ((int) entityMetadata.getValue()) { - case 0: - variantColor = 8; - break; - case 8: - variantColor = 0; - break; - case 9: - variantColor = 10; - break; - case 10: - variantColor = 9; - break; - default: - variantColor = (int) entityMetadata.getValue(); - } - metadata.put(EntityData.VARIANT, variantColor); - } - if (entityMetadata.getId() == 21) { - collarColor = (byte) (int) entityMetadata.getValue(); - // Needed or else wild cats are a red color - if (metadata.getFlags().getFlag(EntityFlag.TAMED)) { - metadata.put(EntityData.COLOR, collarColor); - } - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/ParrotEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/ParrotEntity.java deleted file mode 100644 index a867517a8..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/ParrotEntity.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity.living.animal.tameable; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; - -public class ParrotEntity extends TameableEntity { - - public ParrotEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - // Parrot color - if (entityMetadata.getId() == 18) { - metadata.put(EntityData.VARIANT, entityMetadata.getValue()); - } - super.updateBedrockMetadata(entityMetadata, session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java deleted file mode 100644 index 9e73ebe57..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity.living.animal.tameable; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.living.animal.AnimalEntity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; - -import java.util.UUID; - -public class TameableEntity extends AnimalEntity { - - public TameableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { - byte xd = (byte) entityMetadata.getValue(); - metadata.getFlags().setFlag(EntityFlag.SITTING, (xd & 0x01) == 0x01); - metadata.getFlags().setFlag(EntityFlag.ANGRY, (xd & 0x02) == 0x02); - metadata.getFlags().setFlag(EntityFlag.TAMED, (xd & 0x04) == 0x04); - } - - // Note: Must be set for wolf collar color to work - if (entityMetadata.getId() == 17) { - if (entityMetadata.getValue() != null) { - // Owner UUID of entity - Entity entity = session.getEntityCache().getPlayerEntity((UUID) entityMetadata.getValue()); - // Used as both a check since the player isn't in the entity cache and a normal fallback - if (entity == null) { - entity = session.getPlayerEntity(); - } - // Translate to entity ID - metadata.put(EntityData.OWNER_EID, entity.getGeyserId()); - } else { - metadata.put(EntityData.OWNER_EID, 0L); // Reset - } - } - super.updateBedrockMetadata(entityMetadata, session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java deleted file mode 100644 index 6fe9e5927..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity.living.animal.tameable; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; - -public class WolfEntity extends TameableEntity { - - private byte collarColor; - - public WolfEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - //Reset wolf color - if (entityMetadata.getId() == 16) { - byte xd = (byte) entityMetadata.getValue(); - boolean angry = (xd & 0x02) == 0x02; - if (angry) { - metadata.put(EntityData.COLOR, (byte) 0); - } - } - - // "Begging" on wiki.vg, "Interested" in Nukkit - the tilt of the head - if (entityMetadata.getId() == 18) { - metadata.getFlags().setFlag(EntityFlag.INTERESTED, (boolean) entityMetadata.getValue()); - } - - // Wolf collar color - // Relies on EntityData.OWNER_EID being set in TameableEntity.java - if (entityMetadata.getId() == 19 && !metadata.getFlags().getFlag(EntityFlag.ANGRY)) { - metadata.put(EntityData.COLOR, collarColor = (byte) (int) entityMetadata.getValue()); - } - - // Wolf anger (1.16+) - if (entityMetadata.getId() == 20) { - metadata.getFlags().setFlag(EntityFlag.ANGRY, (int) entityMetadata.getValue() != 0); - metadata.put(EntityData.COLOR, (int) entityMetadata.getValue() != 0 ? (byte) 0 : collarColor); - } - - super.updateBedrockMetadata(entityMetadata, session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java deleted file mode 100644 index 028d18312..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity.living.merchant; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; -import it.unimi.dsi.fastutil.ints.Int2IntMap; -import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class VillagerEntity extends AbstractMerchantEntity { - - /** - * A map of Java profession IDs to Bedrock IDs - */ - private static final Int2IntMap VILLAGER_VARIANTS = new Int2IntOpenHashMap(); - /** - * A map of all Java region IDs (plains, savanna...) to Bedrock - */ - public static final Int2IntMap VILLAGER_REGIONS = new Int2IntOpenHashMap(); - - static { - // Java villager profession IDs -> Bedrock - VILLAGER_VARIANTS.put(0, 0); - VILLAGER_VARIANTS.put(1, 8); - VILLAGER_VARIANTS.put(2, 11); - VILLAGER_VARIANTS.put(3, 6); - VILLAGER_VARIANTS.put(4, 7); - VILLAGER_VARIANTS.put(5, 1); - VILLAGER_VARIANTS.put(6, 2); - VILLAGER_VARIANTS.put(7, 4); - VILLAGER_VARIANTS.put(8, 12); - VILLAGER_VARIANTS.put(9, 5); - VILLAGER_VARIANTS.put(10, 13); - VILLAGER_VARIANTS.put(11, 14); - VILLAGER_VARIANTS.put(12, 3); - VILLAGER_VARIANTS.put(13, 10); - VILLAGER_VARIANTS.put(14, 9); - - VILLAGER_REGIONS.put(0, 1); - VILLAGER_REGIONS.put(1, 2); - VILLAGER_REGIONS.put(2, 0); - VILLAGER_REGIONS.put(3, 3); - VILLAGER_REGIONS.put(4, 4); - VILLAGER_REGIONS.put(5, 5); - VILLAGER_REGIONS.put(6, 6); - } - - public VillagerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 17) { - VillagerData villagerData = (VillagerData) entityMetadata.getValue(); - // Profession - metadata.put(EntityData.VARIANT, VILLAGER_VARIANTS.get(villagerData.getProfession())); - //metadata.put(EntityData.SKIN_ID, villagerData.getType()); Looks like this is modified but for any reason? - // Region - metadata.put(EntityData.MARK_VARIANT, VILLAGER_REGIONS.get(villagerData.getType())); - // Trade tier - different indexing in Bedrock - metadata.put(EntityData.TRADE_TIER, villagerData.getLevel() - 1); - } - super.updateBedrockMetadata(entityMetadata, session); - } - - @Override - public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) { - int z = 0; - int bedId = 0; - float bedPositionSubtractorW = 0; - float bedPositionSubtractorN = 0; - Vector3i bedPosition = metadata.getPos(EntityData.BED_POSITION); - if (session.getConnector().getConfig().isCacheChunks() && bedPosition != null) { - bedId = session.getConnector().getWorldManager().getBlockAt(session, bedPosition); - } - String bedRotationZ = BlockTranslator.getJavaIdBlockMap().inverse().get(bedId); - setRotation(rotation); - setOnGround(isOnGround); - this.position = Vector3f.from(position.getX() + relX, position.getY() + relY, position.getZ() + relZ); - - MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket(); - moveEntityPacket.setRuntimeEntityId(geyserId); - //Sets Villager position and rotation when sleeping - if (!metadata.getFlags().getFlag(EntityFlag.SLEEPING)) { - moveEntityPacket.setPosition(position); - moveEntityPacket.setRotation(getBedrockRotation()); - } else { - //String Setup - Pattern r = Pattern.compile("facing=([a-z]+)"); - Matcher m = r.matcher(bedRotationZ); - if (m.find()) { - switch (m.group(0)){ - case "facing=south": - //bed is facing south - z = 180; - bedPositionSubtractorW = -.5f; - break; - case "facing=east": - //bed is facing east - z = 90; - bedPositionSubtractorW = -.5f; - break; - case "facing=west": - //bed is facing west - z = 270; - bedPositionSubtractorW = .5f; - break; - case "facing=north": - //rotation does not change because north is 0 - bedPositionSubtractorN = .5f; - break; - } - } - moveEntityPacket.setRotation(Vector3f.from(0, 0, z)); - moveEntityPacket.setPosition(Vector3f.from(position.getX() + bedPositionSubtractorW, position.getY(), position.getZ() + bedPositionSubtractorN)); - } - moveEntityPacket.setOnGround(isOnGround); - moveEntityPacket.setTeleported(false); - session.sendUpstreamPacket(moveEntityPacket); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/BasePiglinEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/BasePiglinEntity.java deleted file mode 100644 index b83a2ca73..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/BasePiglinEntity.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.geysermc.connector.entity.living.monster; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.utils.DimensionUtils; - -public class BasePiglinEntity extends MonsterEntity { - - public BasePiglinEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { - // Immune to zombification? - // Apply shaking effect if not in the nether and zombification is possible - metadata.getFlags().setFlag(EntityFlag.SHAKING, !((boolean) entityMetadata.getValue()) && !session.getDimension().equals(DimensionUtils.NETHER)); - } - super.updateBedrockMetadata(entityMetadata, session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java deleted file mode 100644 index 14466dda2..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity.living.monster; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.AttributeData; -import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; -import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; -import lombok.Data; -import org.geysermc.connector.entity.living.InsentientEntity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; - -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -public class EnderDragonEntity extends InsentientEntity { - /** - * The Ender Dragon has multiple hit boxes, which - * are each its own invisible entity - */ - private EnderDragonPartEntity head; - private EnderDragonPartEntity neck; - private EnderDragonPartEntity body; - private EnderDragonPartEntity leftWing; - private EnderDragonPartEntity rightWing; - private EnderDragonPartEntity[] tail; - - private EnderDragonPartEntity[] allParts; - - /** - * A circular buffer that stores a history of - * y and yaw values. - */ - private final Segment[] segmentHistory = new Segment[19]; - private int latestSegment = -1; - - private boolean hovering; - - private ScheduledFuture partPositionUpdater; - - public EnderDragonEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - - metadata.getFlags().setFlag(EntityFlag.FIRE_IMMUNE, true); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - // Phase - if (entityMetadata.getId() == 15) { - int value = (int) entityMetadata.getValue(); - if (value == 5) { - // Performing breath attack - EntityEventPacket entityEventPacket = new EntityEventPacket(); - entityEventPacket.setType(EntityEventType.DRAGON_FLAMING); - entityEventPacket.setRuntimeEntityId(geyserId); - entityEventPacket.setData(0); - session.sendUpstreamPacket(entityEventPacket); - } - metadata.getFlags().setFlag(EntityFlag.SITTING, value == 5 || value == 6 || value == 7); - hovering = value == 10; - } - super.updateBedrockMetadata(entityMetadata, session); - } - - @Override - public void spawnEntity(GeyserSession session) { - AddEntityPacket addEntityPacket = new AddEntityPacket(); - addEntityPacket.setIdentifier("minecraft:" + entityType.name().toLowerCase()); - addEntityPacket.setRuntimeEntityId(geyserId); - addEntityPacket.setUniqueEntityId(geyserId); - addEntityPacket.setPosition(position); - addEntityPacket.setMotion(motion); - addEntityPacket.setRotation(getBedrockRotation()); - addEntityPacket.setEntityType(entityType.getType()); - addEntityPacket.getMetadata().putAll(metadata); - - // Otherwise dragon is always 'dying' - addEntityPacket.getAttributes().add(new AttributeData("minecraft:health", 0.0f, 200f, 200f, 200f)); - - valid = true; - session.sendUpstreamPacket(addEntityPacket); - - head = new EnderDragonPartEntity(entityId + 1, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 1, 1); - neck = new EnderDragonPartEntity(entityId + 2, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 3, 3); - body = new EnderDragonPartEntity(entityId + 3, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 5, 3); - leftWing = new EnderDragonPartEntity(entityId + 4, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 4, 2); - rightWing = new EnderDragonPartEntity(entityId + 5, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 4, 2); - tail = new EnderDragonPartEntity[3]; - for (int i = 0; i < 3; i++) { - tail[i] = new EnderDragonPartEntity(entityId + 6 + i, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 2, 2); - } - - allParts = new EnderDragonPartEntity[]{head, neck, body, leftWing, rightWing, tail[0], tail[1], tail[2]}; - - for (EnderDragonPartEntity part : allParts) { - session.getEntityCache().spawnEntity(part); - } - - for (int i = 0; i < segmentHistory.length; i++) { - segmentHistory[i] = new Segment(); - segmentHistory[i].yaw = rotation.getZ(); - segmentHistory[i].y = position.getY(); - } - - partPositionUpdater = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> { - pushSegment(); - updateBoundingBoxes(session); - }, 0, 50, TimeUnit.MILLISECONDS); - - session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); - } - - @Override - public boolean despawnEntity(GeyserSession session) { - partPositionUpdater.cancel(true); - - for (EnderDragonPartEntity part : allParts) { - part.despawnEntity(session); - } - return super.despawnEntity(session); - } - - /** - * Updates the positions of the Ender Dragon's multiple bounding boxes - * - * @param session GeyserSession. - */ - private void updateBoundingBoxes(GeyserSession session) { - Vector3f facingDir = Vector3f.createDirectionDeg(0, rotation.getZ()); - Segment baseSegment = getSegment(5); - // Used to angle the head, neck, and tail when the dragon flies up/down - float pitch = (float) Math.toRadians(10 * (baseSegment.getY() - getSegment(10).getY())); - float pitchXZ = (float) Math.cos(pitch); - float pitchY = (float) Math.sin(pitch); - - // Lowers the head when the dragon sits/hovers - float headDuck; - if (hovering || metadata.getFlags().getFlag(EntityFlag.SITTING)) { - headDuck = -1f; - } else { - headDuck = baseSegment.y - getSegment(0).y; - } - - head.setPosition(facingDir.up(pitchY).mul(pitchXZ, 1, -pitchXZ).mul(6.5f).up(headDuck)); - neck.setPosition(facingDir.up(pitchY).mul(pitchXZ, 1, -pitchXZ).mul(5.5f).up(headDuck)); - body.setPosition(facingDir.mul(0.5f, 0f, -0.5f)); - - Vector3f wingPos = Vector3f.createDirectionDeg(0, 90f - rotation.getZ()).mul(4.5f).up(2f); - rightWing.setPosition(wingPos); - leftWing.setPosition(wingPos.mul(-1, 1, -1)); // Mirror horizontally - - Vector3f tailBase = facingDir.mul(1.5f); - for (int i = 0; i < tail.length; i++) { - float distance = (i + 1) * 2f; - // Curls the tail when the dragon turns - Segment targetSegment = getSegment(12 + 2 * i); - float angle = rotation.getZ() + targetSegment.yaw - baseSegment.yaw; - - float tailYOffset = targetSegment.y - baseSegment.y - (distance + 1.5f) * pitchY + 1.5f; - tail[i].setPosition(Vector3f.createDirectionDeg(0, angle).mul(distance).add(tailBase).mul(-pitchXZ, 1, pitchXZ).up(tailYOffset)); - } - // Send updated positions - for (EnderDragonPartEntity part : allParts) { - part.moveAbsolute(session, part.getPosition().add(position), Vector3f.ZERO, false, false); - } - } - - /** - * Store the current yaw and y into the circular buffer - */ - private void pushSegment() { - latestSegment = (latestSegment + 1) % segmentHistory.length; - segmentHistory[latestSegment].yaw = rotation.getZ(); - segmentHistory[latestSegment].y = position.getY(); - } - - /** - * Gets the previous yaw and y - * Used to curl the tail and pitch the head and tail up/down - * - * @param index Number of ticks in the past - * @return Segment with the yaw and y - */ - private Segment getSegment(int index) { - index = (latestSegment - index) % segmentHistory.length; - if (index < 0) { - index += segmentHistory.length; - } - return segmentHistory[index]; - } - - @Data - private static class Segment { - private float yaw; - private float y; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java deleted file mode 100644 index dfca08041..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity.living.monster; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; - -public class EndermanEntity extends MonsterEntity { - - public EndermanEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - // Held block - if (entityMetadata.getId() == 15) { - metadata.put(EntityData.CARRIED_BLOCK, BlockTranslator.getBedrockBlockId((int) entityMetadata.getValue())); - } - // 'Angry' - mouth open - if (entityMetadata.getId() == 16) { - metadata.getFlags().setFlag(EntityFlag.ANGRY, (boolean) entityMetadata.getValue()); - } - // TODO: ID 17 is stared at but I don't believe it's used - maybe only for the sound effect. Check after particle merge - super.updateBedrockMetadata(entityMetadata, session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java deleted file mode 100644 index e0b443d3b..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity.living.monster; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.item.ItemRegistry; - -public class PiglinEntity extends BasePiglinEntity { - - public PiglinEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { - boolean isBaby = (boolean) entityMetadata.getValue(); - if (isBaby) { - metadata.put(EntityData.SCALE, .55f); - metadata.getFlags().setFlag(EntityFlag.BABY, true); - } - } - if (entityMetadata.getId() == 17) { - metadata.getFlags().setFlag(EntityFlag.CHARGING, (boolean) entityMetadata.getValue()); - } - if (entityMetadata.getId() == 18) { - metadata.getFlags().setFlag(EntityFlag.DANCING, (boolean) entityMetadata.getValue()); - } - - super.updateBedrockMetadata(entityMetadata, session); - } - - @Override - public void updateEquipment(GeyserSession session) { - // Check if the Piglin is holding Gold and set the ADMIRING flag accordingly - metadata.getFlags().setFlag(EntityFlag.ADMIRING, offHand.getId() == ItemRegistry.GOLD.getBedrockId()); - super.updateBedrockMetadata(session); - - super.updateEquipment(session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java deleted file mode 100644 index a0bd5bc2b..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity.living.monster; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; -import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.entity.living.GolemEntity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; - -public class ShulkerEntity extends GolemEntity { - - public ShulkerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { - BlockFace blockFace = (BlockFace) entityMetadata.getValue(); - metadata.put(EntityData.SHULKER_ATTACH_FACE, (byte) blockFace.ordinal()); - } - if (entityMetadata.getId() == 16) { - Position position = (Position) entityMetadata.getValue(); - if (position != null) { - metadata.put(EntityData.SHULKER_ATTACH_POS, Vector3i.from(position.getX(), position.getY(), position.getZ())); - } - } - - if (entityMetadata.getId() == 17) { - int height = (byte) entityMetadata.getValue(); - metadata.put(EntityData.SHULKER_PEEK_ID, height); - } - - if (entityMetadata.getId() == 18) { - byte color = (byte) entityMetadata.getValue(); - if (color == 16) { - // 16 is default on both editions - metadata.put(EntityData.VARIANT, 16); - } else { - // Every other shulker color is offset 15 in bedrock edition - metadata.put(EntityData.VARIANT, Math.abs(color - 15)); - } - } - super.updateBedrockMetadata(entityMetadata, session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/WitherEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/WitherEntity.java deleted file mode 100644 index 8b864525f..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/WitherEntity.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity.living.monster; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; - -public class WitherEntity extends MonsterEntity { - - public WitherEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - - metadata.put(EntityData.WITHER_AERIAL_ATTACK, (short) 1); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - long targetID = 0; - - if (entityMetadata.getId() >= 15 && entityMetadata.getId() <= 17) { - Entity entity = session.getEntityCache().getEntityByJavaId((int) entityMetadata.getValue()); - if (entity == null && session.getPlayerEntity().getEntityId() == (Integer) entityMetadata.getValue()) { - entity = session.getPlayerEntity(); - } - - if (entity != null) { - targetID = entity.getGeyserId(); - } - } - - if (entityMetadata.getId() == 15) { - metadata.put(EntityData.WITHER_TARGET_1, targetID); - } else if (entityMetadata.getId() == 16) { - metadata.put(EntityData.WITHER_TARGET_2, targetID); - } else if (entityMetadata.getId() == 17) { - metadata.put(EntityData.WITHER_TARGET_3, targetID); - } else if (entityMetadata.getId() == 18) { - metadata.put(EntityData.WITHER_INVULNERABLE_TICKS, (int) entityMetadata.getValue()); - - // Show the shield for the first few seconds of spawning (like Java) - if ((int) entityMetadata.getValue() >= 165) { - metadata.put(EntityData.WITHER_AERIAL_ATTACK, (short) 0); - } else { - metadata.put(EntityData.WITHER_AERIAL_ATTACK, (short) 1); - } - } - - super.updateBedrockMetadata(entityMetadata, session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/PillagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/PillagerEntity.java deleted file mode 100644 index c7a8f24d0..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/PillagerEntity.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity.living.monster.raid; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; - -public class PillagerEntity extends AbstractIllagerEntity { - - public PillagerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { - // Java Edition always has the Pillager entity as positioning the crossbow - metadata.getFlags().setFlag(EntityFlag.USING_ITEM, true); - metadata.getFlags().setFlag(EntityFlag.CHARGED, true); - } - super.updateBedrockMetadata(entityMetadata, session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/SpellcasterIllagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/SpellcasterIllagerEntity.java deleted file mode 100644 index 6a998ddfe..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/SpellcasterIllagerEntity.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity.living.monster.raid; - -import com.nukkitx.math.vector.Vector3f; -import org.geysermc.connector.entity.living.monster.raid.AbstractIllagerEntity; -import org.geysermc.connector.entity.type.EntityType; - -public class SpellcasterIllagerEntity extends AbstractIllagerEntity { - - public SpellcasterIllagerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java deleted file mode 100644 index 05447760d..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity.type; - -import lombok.Getter; -import org.geysermc.connector.entity.*; -import org.geysermc.connector.entity.living.*; -import org.geysermc.connector.entity.living.animal.*; -import org.geysermc.connector.entity.living.animal.horse.*; -import org.geysermc.connector.entity.living.animal.tameable.*; -import org.geysermc.connector.entity.living.merchant.*; -import org.geysermc.connector.entity.living.monster.*; -import org.geysermc.connector.entity.living.monster.raid.AbstractIllagerEntity; -import org.geysermc.connector.entity.living.monster.raid.PillagerEntity; -import org.geysermc.connector.entity.living.monster.raid.RaidParticipantEntity; -import org.geysermc.connector.entity.living.monster.raid.SpellcasterIllagerEntity; - -@Getter -public enum EntityType { - - CHICKEN(AnimalEntity.class, 10, 0.7f, 0.4f), - COW(AnimalEntity.class, 11, 1.4f, 0.9f), - PIG(PigEntity.class, 12, 0.9f), - SHEEP(SheepEntity.class, 13, 1.3f, 0.9f), - WOLF(WolfEntity.class, 14, 0.85f, 0.6f), - VILLAGER(VillagerEntity.class, 15, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:villager_v2"), - MOOSHROOM(MooshroomEntity.class, 16, 1.4f, 0.9f), - SQUID(SquidEntity.class, 17, 0.8f), - RABBIT(RabbitEntity.class, 18, 0.5f, 0.4f), - BAT(BatEntity.class, 19, 0.9f, 0.5f), - IRON_GOLEM(GolemEntity.class, 20, 2.7f, 1.4f), - SNOW_GOLEM(SnowGolemEntity.class, 21, 1.9f, 0.7f), - OCELOT(OcelotEntity.class, 22, 0.35f, 0.3f), - HORSE(HorseEntity.class, 23, 1.6f, 1.3965f), - DONKEY(ChestedHorseEntity.class, 24, 1.6f, 1.3965f), - MULE(ChestedHorseEntity.class, 25, 1.6f, 1.3965f), - SKELETON_HORSE(AbstractHorseEntity.class, 26, 1.6f, 1.3965f), - ZOMBIE_HORSE(AbstractHorseEntity.class, 27, 1.6f, 1.3965f), - POLAR_BEAR(PolarBearEntity.class, 28, 1.4f, 1.3f), - LLAMA(LlamaEntity.class, 29, 1.87f, 0.9f), - TRADER_LLAMA(TraderLlamaEntity.class, 29, 1.187f, 0.9f, 0f, 0f, "minecraft:llama"), - PARROT(ParrotEntity.class, 30, 0.9f, 0.5f), - DOLPHIN(WaterEntity.class, 31, 0.6f, 0.9f), - ZOMBIE(ZombieEntity.class, 32, 1.8f, 0.6f, 0.6f, 1.62f), - GIANT(GiantEntity.class, 32, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:zombie"), - CREEPER(CreeperEntity.class, 33, 1.7f, 0.6f, 0.6f, 1.62f), - SKELETON(AbstractSkeletonEntity.class, 34, 1.8f, 0.6f, 0.6f, 1.62f), - SPIDER(SpiderEntity.class, 35, 0.9f, 1.4f, 1.4f, 1f), - ZOMBIFIED_PIGLIN(ZombifiedPiglinEntity.class, 36, 1.95f, 0.6f, 0.6f, 1.62f, "minecraft:zombie_pigman"), - SLIME(SlimeEntity.class, 37, 0.51f), - ENDERMAN(EndermanEntity.class, 38, 2.9f, 0.6f), - SILVERFISH(MonsterEntity.class, 39, 0.3f, 0.4f), - CAVE_SPIDER(MonsterEntity.class, 40, 0.5f, 0.7f), - GHAST(GhastEntity.class, 41, 4.0f), - MAGMA_CUBE(MagmaCubeEntity.class, 42, 0.51f), - BLAZE(BlazeEntity.class, 43, 1.8f, 0.6f), - ZOMBIE_VILLAGER(ZombieVillagerEntity.class, 44, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:zombie_villager_v2"), - WITCH(RaidParticipantEntity.class, 45, 1.8f, 0.6f, 0.6f, 1.62f), - STRAY(AbstractSkeletonEntity.class, 46, 1.8f, 0.6f, 0.6f, 1.62f), - HUSK(ZombieEntity.class, 47, 1.8f, 0.6f, 0.6f, 1.62f), - WITHER_SKELETON(AbstractSkeletonEntity.class, 48, 2.4f, 0.7f), - GUARDIAN(GuardianEntity.class, 49, 0.85f), - ELDER_GUARDIAN(ElderGuardianEntity.class, 50, 1.9975f), - NPC(PlayerEntity.class, 51, 1.8f, 0.6f, 0.6f, 1.62f), - WITHER(WitherEntity.class, 52, 3.5f, 0.9f), - ENDER_DRAGON(EnderDragonEntity.class, 53, 0f, 0f), - SHULKER(ShulkerEntity.class, 54, 1f, 1f), - ENDERMITE(MonsterEntity.class, 55, 0.3f, 0.4f), - AGENT(Entity.class, 56, 0f), - VINDICATOR(AbstractIllagerEntity.class, 57, 1.8f, 0.6f, 0.6f, 1.62f), - PILLAGER(PillagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f), - WANDERING_TRADER(AbstractMerchantEntity.class, 118, 1.8f, 0.6f, 0.6f, 1.62f), - PHANTOM(FlyingEntity.class, 58, 0.5f, 0.9f, 0.9f, 0.6f), - RAVAGER(RaidParticipantEntity.class, 59, 1.9f, 1.2f), - - ARMOR_STAND(ArmorStandEntity.class, 61, 1.975f, 0.5f), - TRIPOD_CAMERA(Entity.class, 62, 0f), - PLAYER(PlayerEntity.class, 63, 1.8f, 0.6f, 0.6f, 1.62f), - ITEM(ItemEntity.class, 64, 0.25f, 0.25f), - PRIMED_TNT(TNTEntity.class, 65, 0.98f, 0.98f, 0.98f, 0f, "minecraft:tnt"), - FALLING_BLOCK(FallingBlockEntity.class, 66, 0.98f, 0.98f), - MOVING_BLOCK(Entity.class, 67, 0f), - THROWN_EXP_BOTTLE(ThrowableEntity.class, 68, 0.25f, 0.25f, 0f, 0f, "minecraft:xp_bottle"), - EXPERIENCE_ORB(ExpOrbEntity.class, 69, 0f, 0f, 0f, 0f, "minecraft:xp_orb"), - EYE_OF_ENDER(Entity.class, 70, 0.25f, 0.25f, 0f, 0f, "minecraft:eye_of_ender_signal"), - END_CRYSTAL(EnderCrystalEntity.class, 71, 2.0f, 2.0f, 2.0f, 0f, "minecraft:ender_crystal"), - FIREWORK_ROCKET(FireworkEntity.class, 72, 0.25f, 0.25f, 0.25f, 0f, "minecraft:fireworks_rocket"), - TRIDENT(TridentEntity.class, 73, 0f, 0f, 0f, 0f, "minecraft:thrown_trident"), - TURTLE(TurtleEntity.class, 74, 0.4f, 1.2f), - CAT(CatEntity.class, 75, 0.35f, 0.3f), - SHULKER_BULLET(Entity.class, 76, 0.3125f), - FISHING_BOBBER(FishingHookEntity.class, 77, 0f, 0f, 0f, 0f, "minecraft:fishing_hook"), - CHALKBOARD(Entity.class, 78, 0f), - DRAGON_FIREBALL(ItemedFireballEntity.class, 79, 1.0f), - ARROW(TippedArrowEntity.class, 80, 0.25f, 0.25f), - SPECTRAL_ARROW(AbstractArrowEntity.class, 80, 0.25f, 0.25f, 0.25f, 0f, "minecraft:arrow"), - SNOWBALL(ThrowableEntity.class, 81, 0.25f), - THROWN_EGG(ThrowableEntity.class, 82, 0.25f, 0.25f, 0.25f, 0f, "minecraft:egg"), - PAINTING(PaintingEntity.class, 83, 0f), - MINECART(MinecartEntity.class, 84, 0.7f, 0.98f, 0.98f, 0.35f), - FIREBALL(ItemedFireballEntity.class, 85, 1.0f), - THROWN_POTION(ThrowableEntity.class, 86, 0.25f, 0.25f, 0.25f, 0f, "minecraft:splash_potion"), - THROWN_ENDERPEARL(ThrowableEntity.class, 87, 0.25f, 0.25f, 0.25f, 0f, "minecraft:ender_pearl"), - LEASH_KNOT(LeashKnotEntity.class, 88, 0.5f, 0.375f), - WITHER_SKULL(WitherSkullEntity.class, 89, 0.3125f), - BOAT(BoatEntity.class, 90, 0.7f, 1.6f, 1.6f, 0.35f), - WITHER_SKULL_DANGEROUS(WitherSkullEntity.class, 91, 0f), - LIGHTNING_BOLT(Entity.class, 93, 0f), - SMALL_FIREBALL(ItemedFireballEntity.class, 94, 0.3125f), - AREA_EFFECT_CLOUD(AreaEffectCloudEntity.class, 95, 0.5f, 1.0f), - MINECART_HOPPER(MinecartEntity.class, 96, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:hopper_minecart"), - MINECART_TNT(MinecartEntity.class, 97, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:tnt_minecart"), - MINECART_CHEST(MinecartEntity.class, 98, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:chest_minecart"), - MINECART_FURNACE(FurnaceMinecartEntity.class, 98, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:minecart"), - MINECART_SPAWNER(SpawnerMinecartEntity.class, 98, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:minecart"), - MINECART_COMMAND_BLOCK(CommandBlockMinecartEntity.class, 100, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:command_block_minecart"), - LINGERING_POTION(ThrowableEntity.class, 101, 0f), - LLAMA_SPIT(Entity.class, 102, 0.25f), - EVOKER_FANGS(Entity.class, 103, 0.8f, 0.5f, 0.5f, 0f, "minecraft:evocation_fang"), - EVOKER(SpellcasterIllagerEntity.class, 104, 1.95f, 0.6f, 0.6f, 0f, "minecraft:evocation_illager"), - VEX(VexEntity.class, 105, 0.8f, 0.4f), - ICE_BOMB(Entity.class, 106, 0f), - BALLOON(Entity.class, 107, 0f), //TODO - PUFFERFISH(PufferFishEntity.class, 108, 0.7f, 0.7f), - SALMON(AbstractFishEntity.class, 109, 0.5f, 0.7f), - DROWNED(ZombieEntity.class, 110, 1.95f, 0.6f), - TROPICAL_FISH(TropicalFishEntity.class, 111, 0.6f, 0.6f, 0f, 0f, "minecraft:tropicalfish"), - COD(AbstractFishEntity.class, 112, 0.25f, 0.5f), - PANDA(PandaEntity.class, 113, 1.25f, 1.125f, 1.825f), - FOX(FoxEntity.class, 121, 0.5f, 1.25f), - BEE(BeeEntity.class, 122, 0.6f, 0.6f), - STRIDER(StriderEntity.class, 125, 1.7f, 0.9f, 0f, 0f, "minecraft:strider"), - HOGLIN(HoglinEntity.class, 124, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:hoglin"), - ZOGLIN(ZoglinEntity.class, 126, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:zoglin"), - PIGLIN(PiglinEntity.class, 123, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin"), - PIGLIN_BRUTE(BasePiglinEntity.class, 127, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin_brute"), - - /** - * Item frames are handled differently since they are a block in Bedrock. - */ - ITEM_FRAME(ItemFrameEntity.class, 0, 0, 0), - - /** - * Not an entity in Bedrock, so we replace it with a Pillager - */ - ILLUSIONER(AbstractIllagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:pillager"), - - /** - * Not an entity in Bedrock, but used for the Ender Dragon's multiple hitboxes - */ - ENDER_DRAGON_PART(EnderDragonPartEntity.class, 32, 0, 0, 0, 0, "minecraft:armor_stand"); - - private static final EntityType[] VALUES = values(); - - private Class entityClass; - private final int type; - private final float height; - private final float width; - private final float length; - private final float offset; - private String identifier; - - EntityType(Class entityClass, int type, float height) { - this(entityClass, type, height, height); - } - - EntityType(Class entityClass, int type, float height, float width) { - this(entityClass, type, height, width, width); - } - - EntityType(Class entityClass, int type, float height, float width, float length) { - this(entityClass, type, height, width, length, 0f); - } - - EntityType(Class entityClass, int type, float height, float width, float length, float offset) { - this(entityClass, type, height, width, length, offset, null); - - this.identifier = "minecraft:" + name().toLowerCase(); - } - - EntityType(Class entityClass, int type, float height, float width, float length, float offset, String identifier) { - this.entityClass = entityClass; - this.type = type; - this.height = height; - this.width = width; - this.length = length; - this.offset = offset + 0.00001f; - this.identifier = identifier; - } - - public static EntityType getFromIdentifier(String identifier) { - for (EntityType type : VALUES) { - if (type.identifier.equals(identifier)) { - return type; - } - } - - return null; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java deleted file mode 100644 index 539fe1e26..000000000 --- a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.inventory; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.data.game.window.WindowType; -import com.nukkitx.math.vector.Vector3i; -import lombok.Getter; -import lombok.Setter; - -import java.util.concurrent.atomic.AtomicInteger; - -public class Inventory { - - @Getter - protected int id; - - @Getter - @Setter - protected boolean open; - - @Getter - protected WindowType windowType; - - @Getter - protected final int size; - - @Getter - @Setter - protected String title; - - @Setter - protected ItemStack[] items; - - @Getter - @Setter - protected Vector3i holderPosition = Vector3i.ZERO; - - @Getter - @Setter - protected long holderId = -1; - - @Getter - protected AtomicInteger transactionId = new AtomicInteger(1); - - public Inventory(int id, WindowType windowType, int size) { - this("Inventory", id, windowType, size); - } - - public Inventory(String title, int id, WindowType windowType, int size) { - this.title = title; - this.id = id; - this.windowType = windowType; - this.size = size; - this.items = new ItemStack[size]; - } - - public ItemStack getItem(int slot) { - return items[slot]; - } - - public void setItem(int slot, ItemStack item) { - if (item != null && (item.getId() == 0 || item.getAmount() < 1)) - item = null; - items[slot] = item; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java deleted file mode 100644 index 85043d378..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network; - -import com.nukkitx.protocol.bedrock.BedrockPacketCodec; -import com.nukkitx.protocol.bedrock.v419.Bedrock_v419; - -import java.util.ArrayList; -import java.util.List; - -/** - * Contains information about the supported Bedrock protocols in Geyser. - */ -public class BedrockProtocol { - /** - * Default Bedrock codec that should act as a fallback. Should represent the latest available - * release of the game that Geyser supports. - */ - public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v419.V419_CODEC; - /** - * A list of all supported Bedrock versions that can join Geyser - */ - public static final List SUPPORTED_BEDROCK_CODECS = new ArrayList<>(); - - static { - SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); - } - - /** - * Gets the {@link BedrockPacketCodec} of the given protocol version. - * @param protocolVersion The protocol version to attempt to find - * @return The packet codec, or null if the client's protocol is unsupported - */ - public static BedrockPacketCodec getBedrockCodec(int protocolVersion) { - for (BedrockPacketCodec packetCodec : SUPPORTED_BEDROCK_CODECS) { - if (packetCodec.getProtocolVersion() == protocolVersion) { - return packetCodec; - } - } - return null; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java deleted file mode 100644 index 150d298c7..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network; - -import com.nukkitx.protocol.bedrock.BedrockPong; -import com.nukkitx.protocol.bedrock.BedrockServerEventHandler; -import com.nukkitx.protocol.bedrock.BedrockServerSession; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.socket.DatagramPacket; -import org.geysermc.connector.common.ping.GeyserPingInfo; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.configuration.GeyserConfiguration; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.ping.IGeyserPingPassthrough; -import org.geysermc.connector.network.translators.chat.MessageTranslator; -import org.geysermc.connector.utils.LanguageUtils; - -import java.net.InetSocketAddress; - -public class ConnectorServerEventHandler implements BedrockServerEventHandler { - - private final GeyserConnector connector; - - public ConnectorServerEventHandler(GeyserConnector connector) { - this.connector = connector; - } - - @Override - public boolean onConnectionRequest(InetSocketAddress inetSocketAddress) { - connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.attempt_connect", inetSocketAddress)); - return true; - } - - @Override - public BedrockPong onQuery(InetSocketAddress inetSocketAddress) { - connector.getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.network.pinged", inetSocketAddress)); - - GeyserConfiguration config = connector.getConfig(); - - GeyserPingInfo pingInfo = null; - if (config.isPassthroughMotd() || config.isPassthroughPlayerCounts()) { - IGeyserPingPassthrough pingPassthrough = connector.getBootstrap().getGeyserPingPassthrough(); - pingInfo = pingPassthrough.getPingInformation(); - } - - BedrockPong pong = new BedrockPong(); - pong.setEdition("MCPE"); - pong.setGameType("Default"); - pong.setNintendoLimited(false); - pong.setProtocolVersion(BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()); - pong.setVersion(null); // Server tries to connect either way and it looks better - pong.setIpv4Port(config.getBedrock().getPort()); - - if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) { - String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n"); - String mainMotd = motd[0]; // First line of the motd. - String subMotd = (motd.length != 1) ? motd[1] : ""; // Second line of the motd if present, otherwise blank. - - pong.setMotd(mainMotd.trim()); - pong.setSubMotd(subMotd.trim()); // Trimmed to shift it to the left, prevents the universe from collapsing on us just because we went 2 characters over the text box's limit. - } else { - pong.setMotd(config.getBedrock().getMotd1()); - pong.setSubMotd(config.getBedrock().getMotd2()); - } - - if (config.isPassthroughPlayerCounts() && pingInfo != null) { - pong.setPlayerCount(pingInfo.getPlayers().getOnline()); - pong.setMaximumPlayerCount(pingInfo.getPlayers().getMax()); - } else { - pong.setPlayerCount(connector.getPlayers().size()); - pong.setMaximumPlayerCount(config.getMaxPlayers()); - } - - //Bedrock will not even attempt a connection if the client thinks the server is full - //so we have to fake it not being full - if (pong.getPlayerCount() >= pong.getMaximumPlayerCount()) { - pong.setMaximumPlayerCount(pong.getPlayerCount() + 1); - } - - return pong; - } - - @Override - public void onSessionCreation(BedrockServerSession bedrockServerSession) { - bedrockServerSession.setLogging(true); - bedrockServerSession.setPacketHandler(new UpstreamPacketHandler(connector, new GeyserSession(connector, bedrockServerSession))); - // Set the packet codec to default just in case we need to send disconnect packets. - bedrockServerSession.setPacketCodec(BedrockProtocol.DEFAULT_BEDROCK_CODEC); - } - - @Override - public void onUnhandledDatagram(ChannelHandlerContext ctx, DatagramPacket packet) { - new QueryPacketHandler(connector, packet.sender(), packet.content()); - } -} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java deleted file mode 100644 index b87221221..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ /dev/null @@ -1,835 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.session; - -import com.github.steveice10.mc.auth.data.GameProfile; -import com.github.steveice10.mc.auth.exception.request.InvalidCredentialsException; -import com.github.steveice10.mc.auth.exception.request.RequestException; -import com.github.steveice10.mc.protocol.MinecraftConstants; -import com.github.steveice10.mc.protocol.MinecraftProtocol; -import com.github.steveice10.mc.protocol.data.SubProtocol; -import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; -import com.github.steveice10.mc.protocol.data.game.statistic.Statistic; -import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade; -import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket; -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket; -import com.github.steveice10.mc.protocol.packet.login.server.LoginSuccessPacket; -import com.github.steveice10.packetlib.Client; -import com.github.steveice10.packetlib.event.session.*; -import com.github.steveice10.packetlib.packet.Packet; -import com.github.steveice10.packetlib.tcp.TcpSessionFactory; -import com.nukkitx.math.GenericMath; -import com.nukkitx.math.vector.*; -import com.nukkitx.protocol.bedrock.BedrockPacket; -import com.nukkitx.protocol.bedrock.BedrockServerSession; -import com.nukkitx.protocol.bedrock.data.*; -import com.nukkitx.protocol.bedrock.data.command.CommandPermission; -import com.nukkitx.protocol.bedrock.packet.*; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2LongMap; -import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; -import lombok.Getter; -import lombok.NonNull; -import lombok.Setter; -import org.geysermc.common.window.CustomFormWindow; -import org.geysermc.common.window.FormWindow; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.CommandSender; -import org.geysermc.connector.common.AuthType; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.PlayerEntity; -import org.geysermc.connector.inventory.PlayerInventory; -import org.geysermc.connector.network.translators.chat.MessageTranslator; -import org.geysermc.connector.network.remote.RemoteServer; -import org.geysermc.connector.network.session.auth.AuthData; -import org.geysermc.connector.network.session.auth.BedrockClientData; -import org.geysermc.connector.network.session.cache.*; -import org.geysermc.connector.network.translators.BiomeTranslator; -import org.geysermc.connector.network.translators.EntityIdentifierRegistry; -import org.geysermc.connector.network.translators.PacketTranslatorRegistry; -import org.geysermc.connector.network.translators.inventory.EnchantmentInventoryTranslator; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.utils.*; -import org.geysermc.floodgate.util.BedrockData; -import org.geysermc.floodgate.util.EncryptionUtil; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.spec.InvalidKeySpecException; -import java.util.*; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.atomic.AtomicInteger; - -@Getter -public class GeyserSession implements CommandSender { - - private final GeyserConnector connector; - private final UpstreamSession upstream; - private RemoteServer remoteServer; - private Client downstream; - @Setter - private AuthData authData; - @Setter - private BedrockClientData clientData; - - private PlayerEntity playerEntity; - private PlayerInventory inventory; - - private ChunkCache chunkCache; - private EntityCache entityCache; - private InventoryCache inventoryCache; - private WorldCache worldCache; - private WindowCache windowCache; - @Setter - private TeleportCache teleportCache; - - @Getter - private final Long2ObjectMap storedMaps = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); - - /** - * A map of Vector3i positions to Java entity IDs. - * Used for translating Bedrock block actions to Java entity actions. - */ - private final Object2LongMap itemFrameCache = new Object2LongOpenHashMap<>(); - - @Setter - private Vector2i lastChunkPosition = null; - private int renderDistance; - - private boolean loggedIn; - private boolean loggingIn; - - @Setter - private boolean spawned; - private boolean closed; - - @Setter - private GameMode gameMode = GameMode.SURVIVAL; - - private final AtomicInteger pendingDimSwitches = new AtomicInteger(0); - - @Setter - private boolean sneaking; - - @Setter - private boolean sprinting; - - @Setter - private boolean jumping; - - /** - * The dimension of the player. - * As all entities are in the same world, this can be safely applied to all other entities. - */ - @Setter - private String dimension = DimensionUtils.OVERWORLD; - - @Setter - private int breakingBlock; - - @Setter - private Vector3i lastBlockPlacePosition; - - @Setter - private String lastBlockPlacedId; - - @Setter - private boolean interacting; - - /** - * Stores the last position of the block the player interacted with. This can either be a block that the client - * placed or an existing block the player interacted with (for example, a chest).
- * Initialized as (0, 0, 0) so it is always not-null. - */ - @Setter - private Vector3i lastInteractionPosition = Vector3i.ZERO; - - private boolean manyDimPackets = false; - private ServerRespawnPacket lastDimPacket = null; - - @Setter - private Entity ridingVehicleEntity; - - @Setter - private int craftSlot = 0; - - @Setter - private long lastWindowCloseTime = 0; - - /** - * Saves the timestamp of the last keep alive packet - */ - @Setter - private long lastKeepAliveTimestamp = 0; - - @Setter - private VillagerTrade[] villagerTrades; - @Setter - private long lastInteractedVillagerEid; - - /** - * Stores the enchantment information the client has received if they are in an enchantment table GUI - */ - private final EnchantmentInventoryTranslator.EnchantmentSlotData[] enchantmentSlotData = new EnchantmentInventoryTranslator.EnchantmentSlotData[3]; - - /** - * The current attack speed of the player. Used for sending proper cooldown timings. - * Setting a default fixes cooldowns not showing up on a fresh world. - */ - @Setter - private double attackSpeed = 4.0d; - /** - * The time of the last hit. Used to gauge how long the cooldown is taking. - * This is a session variable in order to prevent more scheduled threads than necessary. - */ - @Setter - private long lastHitTime; - - /** - * Store the last time the player interacted. Used to fix a right-click spam bug. - * See https://github.com/GeyserMC/Geyser/issues/503 for context. - */ - @Setter - private long lastInteractionTime; - - /** - * Stores a future interaction to place a bucket. Will be cancelled if the client instead intended to - * interact with a block. - */ - @Setter - private ScheduledFuture bucketScheduledFuture; - - /** - * Sends a movement packet every three seconds if the player hasn't moved. Prevents timeouts when AFK in certain instances. - */ - @Setter - private ScheduledFuture movementSendIfIdle; - - private boolean reducedDebugInfo = false; - - @Setter - private CustomFormWindow settingsForm; - - /** - * The op permission level set by the server - */ - @Setter - private int opPermissionLevel = 0; - - /** - * If the current player can fly - */ - @Setter - private boolean canFly = false; - - /** - * If the current player is flying - */ - @Setter - private boolean flying = false; - - /** - * If the current player is in noclip - */ - @Setter - private boolean noClip = false; - - /** - * If the current player can not interact with the world - */ - @Setter - private boolean worldImmutable = false; - - /** - * Caches current rain status. - */ - @Setter - private boolean raining = false; - - /** - * Caches current thunder status. - */ - @Setter - private boolean thunder = false; - - /** - * Stores the last text inputted into a sign. - * - * Bedrock sends packets every time you update the sign, Java only wants the final packet. - * Until we determine that the user has finished editing, we save the sign's current status. - */ - @Setter - private String lastSignMessage; - - /** - * Stores a map of all statistics sent from the server. - * The server only sends new statistics back to us, so in order to show all statistics we need to cache existing ones. - */ - private final Map statistics = new HashMap<>(); - - /** - * Whether we're expecting statistics to be sent back to us. - */ - @Setter - private boolean waitingForStatistics = false; - - @Setter - private List selectedEmotes = new ArrayList<>(); - private final Set emotes = new HashSet<>(); - - private MinecraftProtocol protocol; - - public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) { - this.connector = connector; - this.upstream = new UpstreamSession(bedrockServerSession); - - this.chunkCache = new ChunkCache(this); - this.entityCache = new EntityCache(this); - this.inventoryCache = new InventoryCache(this); - this.worldCache = new WorldCache(this); - this.windowCache = new WindowCache(this); - - this.playerEntity = new PlayerEntity(new GameProfile(UUID.randomUUID(), "unknown"), 1, 1, Vector3f.ZERO, Vector3f.ZERO, Vector3f.ZERO); - this.inventory = new PlayerInventory(); - - this.spawned = false; - this.loggedIn = false; - - this.inventoryCache.getInventories().put(0, inventory); - - connector.getPlayers().forEach(player -> this.emotes.addAll(player.getEmotes())); - - bedrockServerSession.addDisconnectHandler(disconnectReason -> { - connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.disconnect", bedrockServerSession.getAddress().getAddress(), disconnectReason)); - - disconnect(disconnectReason.name()); - connector.removePlayer(this); - }); - } - - public void connect(RemoteServer remoteServer) { - startGame(); - this.remoteServer = remoteServer; - - ChunkUtils.sendEmptyChunks(this, playerEntity.getPosition().toInt(), 0, false); - - BiomeDefinitionListPacket biomeDefinitionListPacket = new BiomeDefinitionListPacket(); - biomeDefinitionListPacket.setDefinitions(BiomeTranslator.BIOMES); - upstream.sendPacket(biomeDefinitionListPacket); - - AvailableEntityIdentifiersPacket entityPacket = new AvailableEntityIdentifiersPacket(); - entityPacket.setIdentifiers(EntityIdentifierRegistry.ENTITY_IDENTIFIERS); - upstream.sendPacket(entityPacket); - - CreativeContentPacket creativePacket = new CreativeContentPacket(); - creativePacket.setContents(ItemRegistry.CREATIVE_ITEMS); - upstream.sendPacket(creativePacket); - - PlayStatusPacket playStatusPacket = new PlayStatusPacket(); - playStatusPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN); - upstream.sendPacket(playStatusPacket); - - UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); - attributesPacket.setRuntimeEntityId(getPlayerEntity().getGeyserId()); - List attributes = new ArrayList<>(); - // Default move speed - // Bedrock clients move very fast by default until they get an attribute packet correcting the speed - attributes.add(new AttributeData("minecraft:movement", 0.0f, 1024f, 0.1f, 0.1f)); - attributesPacket.setAttributes(attributes); - upstream.sendPacket(attributesPacket); - - GameRulesChangedPacket gamerulePacket = new GameRulesChangedPacket(); - // Only allow the server to send health information - // Setting this to false allows natural regeneration to work false but doesn't break it being true - gamerulePacket.getGameRules().add(new GameRuleData<>("naturalregeneration", false)); - // Don't let the client modify the inventory on death - // Setting this to true allows keep inventory to work if enabled but doesn't break functionality being false - gamerulePacket.getGameRules().add(new GameRuleData<>("keepinventory", true)); - upstream.sendPacket(gamerulePacket); - } - - public void login() { - if (connector.getAuthType() != AuthType.ONLINE) { - if (connector.getAuthType() == AuthType.OFFLINE) { - connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.offline")); - } else { - connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.floodgate")); - } - authenticate(authData.getName()); - } - } - - public void authenticate(String username) { - authenticate(username, ""); - } - - public void authenticate(String username, String password) { - if (loggedIn) { - connector.getLogger().severe(LanguageUtils.getLocaleStringLog("geyser.auth.already_loggedin", username)); - return; - } - - loggingIn = true; - // new thread so clients don't timeout - new Thread(() -> { - try { - if (password != null && !password.isEmpty()) { - protocol = new MinecraftProtocol(username, password); - } else { - protocol = new MinecraftProtocol(username); - } - - boolean floodgate = connector.getAuthType() == AuthType.FLOODGATE; - final PublicKey publicKey; - - if (floodgate) { - PublicKey key = null; - try { - key = EncryptionUtil.getKeyFromFile( - connector.getConfig().getFloodgateKeyPath(), - PublicKey.class - ); - } catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) { - connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), e); - } - publicKey = key; - } else publicKey = null; - - if (publicKey != null) { - connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key")); - } - - downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory()); - // Let Geyser handle sending the keep alive - downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false); - downstream.getSession().addListener(new SessionAdapter() { - @Override - public void packetSending(PacketSendingEvent event) { - //todo move this somewhere else - if (event.getPacket() instanceof HandshakePacket && floodgate) { - String encrypted = ""; - try { - encrypted = EncryptionUtil.encryptBedrockData(publicKey, new BedrockData( - clientData.getGameVersion(), - authData.getName(), - authData.getXboxUUID(), - clientData.getDeviceOS().ordinal(), - clientData.getLanguageCode(), - clientData.getCurrentInputMode().ordinal(), - upstream.getSession().getAddress().getAddress().getHostAddress() - )); - } catch (Exception e) { - connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e); - } - - HandshakePacket handshakePacket = event.getPacket(); - event.setPacket(new HandshakePacket( - handshakePacket.getProtocolVersion(), - handshakePacket.getHostname() + '\0' + BedrockData.FLOODGATE_IDENTIFIER + '\0' + encrypted, - handshakePacket.getPort(), - handshakePacket.getIntent() - )); - } - } - - @Override - public void connected(ConnectedEvent event) { - loggingIn = false; - loggedIn = true; - if (protocol.getProfile() == null) { - // Java account is offline - disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode())); - return; - } - connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.connect", authData.getName(), protocol.getProfile().getName(), remoteServer.getAddress())); - playerEntity.setUuid(protocol.getProfile().getId()); - playerEntity.setUsername(protocol.getProfile().getName()); - - String locale = clientData.getLanguageCode(); - - // Let the user know there locale may take some time to download - // as it has to be extracted from a JAR - if (locale.toLowerCase().equals("en_us") && !LocaleUtils.LOCALE_MAPPINGS.containsKey("en_us")) { - // This should probably be left hardcoded as it will only show for en_us clients - sendMessage("Loading your locale (en_us); if this isn't already downloaded, this may take some time"); - } - - // Download and load the language for the player - LocaleUtils.downloadAndLoadLocale(locale); - } - - @Override - public void disconnected(DisconnectedEvent event) { - loggingIn = false; - loggedIn = false; - connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.disconnect", authData.getName(), remoteServer.getAddress(), event.getReason())); - if (event.getCause() != null) { - event.getCause().printStackTrace(); - } - - upstream.disconnect(MessageTranslator.convertMessageLenient(event.getReason())); - } - - @Override - public void packetReceived(PacketReceivedEvent event) { - if (!closed) { - //handle consecutive respawn packets - if (event.getPacket().getClass().equals(ServerRespawnPacket.class)) { - manyDimPackets = lastDimPacket != null; - lastDimPacket = event.getPacket(); - return; - } else if (lastDimPacket != null) { - PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(lastDimPacket.getClass(), lastDimPacket, GeyserSession.this); - lastDimPacket = null; - } - - // Required, or else Floodgate players break with Bukkit chunk caching - if (event.getPacket() instanceof LoginSuccessPacket) { - GameProfile profile = ((LoginSuccessPacket) event.getPacket()).getProfile(); - playerEntity.setUsername(profile.getName()); - playerEntity.setUuid(profile.getId()); - - // Check if they are not using a linked account - if (connector.getAuthType() == AuthType.OFFLINE || playerEntity.getUuid().getMostSignificantBits() == 0) { - SkinUtils.handleBedrockSkin(playerEntity, clientData); - } - } - - PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this); - } - } - - @Override - public void packetError(PacketErrorEvent event) { - connector.getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.network.downstream_error", event.getCause().getMessage())); - if (connector.getConfig().isDebugMode()) - event.getCause().printStackTrace(); - event.setSuppress(true); - } - }); - - downstream.getSession().connect(); - connector.addPlayer(this); - } catch (InvalidCredentialsException | IllegalArgumentException e) { - connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.invalid", username)); - disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.invalid.kick", getClientData().getLanguageCode())); - } catch (RequestException ex) { - ex.printStackTrace(); - } - }).start(); - } - - public void disconnect(String reason) { - if (!closed) { - loggedIn = false; - if (downstream != null && downstream.getSession() != null) { - downstream.getSession().disconnect(reason); - } - if (upstream != null && !upstream.isClosed()) { - connector.getPlayers().remove(this); - upstream.disconnect(reason); - } - } - - this.chunkCache = null; - this.entityCache = null; - this.worldCache = null; - this.inventoryCache = null; - this.windowCache = null; - - closed = true; - } - - public void close() { - disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.close", getClientData().getLanguageCode())); - } - - public void setAuthenticationData(AuthData authData) { - this.authData = authData; - } - - @Override - public String getName() { - return authData.getName(); - } - - @Override - public void sendMessage(String message) { - TextPacket textPacket = new TextPacket(); - textPacket.setPlatformChatId(""); - textPacket.setSourceName(""); - textPacket.setXuid(""); - textPacket.setType(TextPacket.Type.CHAT); - textPacket.setNeedsTranslation(false); - textPacket.setMessage(message); - - upstream.sendPacket(textPacket); - } - - @Override - public boolean isConsole() { - return false; - } - - @Override - public String getLocale() { - return clientData.getLanguageCode(); - } - - public void sendForm(FormWindow window, int id) { - windowCache.showWindow(window, id); - } - - public void setRenderDistance(int renderDistance) { - renderDistance = GenericMath.ceil(++renderDistance * MathUtils.SQRT_OF_TWO); //square to circle - if (renderDistance > 32) renderDistance = 32; // <3 u ViaVersion but I don't like crashing clients x) - this.renderDistance = renderDistance; - - ChunkRadiusUpdatedPacket chunkRadiusUpdatedPacket = new ChunkRadiusUpdatedPacket(); - chunkRadiusUpdatedPacket.setRadius(renderDistance); - upstream.sendPacket(chunkRadiusUpdatedPacket); - } - - public InetSocketAddress getSocketAddress() { - return this.upstream.getAddress(); - } - - public void sendForm(FormWindow window) { - windowCache.showWindow(window); - } - - private void startGame() { - StartGamePacket startGamePacket = new StartGamePacket(); - startGamePacket.setUniqueEntityId(playerEntity.getGeyserId()); - startGamePacket.setRuntimeEntityId(playerEntity.getGeyserId()); - startGamePacket.setPlayerGameType(GameType.SURVIVAL); - startGamePacket.setPlayerPosition(Vector3f.from(0, 69, 0)); - startGamePacket.setRotation(Vector2f.from(1, 1)); - - startGamePacket.setSeed(-1); - startGamePacket.setDimensionId(DimensionUtils.javaToBedrock(dimension)); - startGamePacket.setGeneratorId(1); - startGamePacket.setLevelGameType(GameType.SURVIVAL); - startGamePacket.setDifficulty(1); - startGamePacket.setDefaultSpawn(Vector3i.ZERO); - startGamePacket.setAchievementsDisabled(!connector.getConfig().isXboxAchievementsEnabled()); - startGamePacket.setCurrentTick(-1); - startGamePacket.setEduEditionOffers(0); - startGamePacket.setEduFeaturesEnabled(false); - startGamePacket.setRainLevel(0); - startGamePacket.setLightningLevel(0); - startGamePacket.setMultiplayerGame(true); - startGamePacket.setBroadcastingToLan(true); - startGamePacket.getGamerules().add(new GameRuleData<>("showcoordinates", true)); - startGamePacket.setPlatformBroadcastMode(GamePublishSetting.PUBLIC); - startGamePacket.setXblBroadcastMode(GamePublishSetting.PUBLIC); - startGamePacket.setCommandsEnabled(!connector.getConfig().isXboxAchievementsEnabled()); - startGamePacket.setTexturePacksRequired(false); - startGamePacket.setBonusChestEnabled(false); - startGamePacket.setStartingWithMap(false); - startGamePacket.setTrustingPlayers(true); - startGamePacket.setDefaultPlayerPermission(PlayerPermission.MEMBER); - startGamePacket.setServerChunkTickRange(4); - startGamePacket.setBehaviorPackLocked(false); - startGamePacket.setResourcePackLocked(false); - startGamePacket.setFromLockedWorldTemplate(false); - startGamePacket.setUsingMsaGamertagsOnly(false); - startGamePacket.setFromWorldTemplate(false); - startGamePacket.setWorldTemplateOptionLocked(false); - - String serverName = connector.getConfig().getBedrock().getServerName(); - startGamePacket.setLevelId(serverName); - startGamePacket.setLevelName(serverName); - - startGamePacket.setPremiumWorldTemplateId("00000000-0000-0000-0000-000000000000"); - // startGamePacket.setCurrentTick(0); - startGamePacket.setEnchantmentSeed(0); - startGamePacket.setMultiplayerCorrelationId(""); - startGamePacket.setItemEntries(ItemRegistry.ITEMS); - startGamePacket.setVanillaVersion("*"); - startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT); - upstream.sendPacket(startGamePacket); - } - - public boolean confirmTeleport(Vector3d position) { - if (teleportCache != null) { - if (!teleportCache.canConfirm(position)) { - GeyserConnector.getInstance().getLogger().debug("Unconfirmed Teleport " + teleportCache.getTeleportConfirmId() - + " Ignore movement " + position + " expected " + teleportCache); - return false; - } - int teleportId = teleportCache.getTeleportConfirmId(); - teleportCache = null; - ClientTeleportConfirmPacket teleportConfirmPacket = new ClientTeleportConfirmPacket(teleportId); - sendDownstreamPacket(teleportConfirmPacket); - } - return true; - } - - /** - * Queue a packet to be sent to player. - * - * @param packet the bedrock packet from the NukkitX protocol lib - */ - public void sendUpstreamPacket(BedrockPacket packet) { - if (upstream != null) { - upstream.sendPacket(packet); - } else { - connector.getLogger().debug("Tried to send upstream packet " + packet.getClass().getSimpleName() + " but the session was null"); - } - } - - /** - * Send a packet immediately to the player. - * - * @param packet the bedrock packet from the NukkitX protocol lib - */ - public void sendUpstreamPacketImmediately(BedrockPacket packet) { - if (upstream != null) { - upstream.sendPacketImmediately(packet); - } else { - connector.getLogger().debug("Tried to send upstream packet " + packet.getClass().getSimpleName() + " immediately but the session was null"); - } - } - - /** - * Send a packet to the remote server. - * - * @param packet the java edition packet from MCProtocolLib - */ - public void sendDownstreamPacket(Packet packet) { - if (downstream != null && downstream.getSession() != null && protocol.getSubProtocol().equals(SubProtocol.GAME)) { - downstream.getSession().send(packet); - } else { - connector.getLogger().debug("Tried to send downstream packet " + packet.getClass().getSimpleName() + " before connected to the server"); - } - } - - /** - * Update the cached value for the reduced debug info gamerule. - * This also toggles the coordinates display - * - * @param value The new value for reducedDebugInfo - */ - public void setReducedDebugInfo(boolean value) { - worldCache.setShowCoordinates(!value); - reducedDebugInfo = value; - } - - /** - * Send a gamerule value to the client - * - * @param gameRule The gamerule to send - * @param value The value of the gamerule - */ - public void sendGameRule(String gameRule, Object value) { - GameRulesChangedPacket gameRulesChangedPacket = new GameRulesChangedPacket(); - gameRulesChangedPacket.getGameRules().add(new GameRuleData<>(gameRule, value)); - upstream.sendPacket(gameRulesChangedPacket); - } - - /** - * Checks if the given session's player has a permission - * - * @param permission The permission node to check - * @return true if the player has the requested permission, false if not - */ - public Boolean hasPermission(String permission) { - return connector.getWorldManager().hasPermission(this, permission); - } - - /** - * Send an AdventureSettingsPacket to the client with the latest flags - */ - public void sendAdventureSettings() { - AdventureSettingsPacket adventureSettingsPacket = new AdventureSettingsPacket(); - adventureSettingsPacket.setUniqueEntityId(playerEntity.getGeyserId()); - // Set command permission if OP permission level is high enough - // This allows mobile players access to a GUI for doing commands. The commands there do not change above OPERATOR - // and all commands there are accessible with OP permission level 2 - adventureSettingsPacket.setCommandPermission(opPermissionLevel >= 2 ? CommandPermission.OPERATOR : CommandPermission.NORMAL); - // Required to make command blocks destroyable - adventureSettingsPacket.setPlayerPermission(opPermissionLevel >= 2 ? PlayerPermission.OPERATOR : PlayerPermission.MEMBER); - - // Update the noClip and worldImmutable values based on the current gamemode - noClip = gameMode == GameMode.SPECTATOR; - worldImmutable = gameMode == GameMode.ADVENTURE || gameMode == GameMode.SPECTATOR; - - Set flags = new HashSet<>(); - if (canFly) { - flags.add(AdventureSetting.MAY_FLY); - } - - if (flying) { - flags.add(AdventureSetting.FLYING); - } - - if (worldImmutable) { - flags.add(AdventureSetting.WORLD_IMMUTABLE); - } - - if (noClip) { - flags.add(AdventureSetting.NO_CLIP); - } - - flags.add(AdventureSetting.AUTO_JUMP); - - adventureSettingsPacket.getSettings().addAll(flags); - sendUpstreamPacket(adventureSettingsPacket); - } - - /** - * Used for updating statistic values since we only get changes from the server - * - * @param statistics Updated statistics values - */ - public void updateStatistics(@NonNull Map statistics) { - this.statistics.putAll(statistics); - } - - public void refreshEmotes(List emotes) { - this.selectedEmotes = emotes; - this.emotes.addAll(emotes); - for (GeyserSession player : connector.getPlayers()) { - List pieces = new ArrayList<>(); - for (UUID piece : emotes) { - if (!player.getEmotes().contains(piece)) { - pieces.add(piece); - } - player.getEmotes().add(piece); - } - EmoteListPacket emoteList = new EmoteListPacket(); - emoteList.setRuntimeEntityId(player.getPlayerEntity().getGeyserId()); - emoteList.getPieceIds().addAll(pieces); - player.sendUpstreamPacket(emoteList); - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java deleted file mode 100644 index cbf3721f7..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.session.cache; - -import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; -import com.github.steveice10.mc.protocol.data.game.chunk.Column; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import org.geysermc.connector.bootstrap.GeyserBootstrap; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.utils.MathUtils; - -public class ChunkCache { - - private final boolean cache; - - private final Long2ObjectMap chunks = new Long2ObjectOpenHashMap<>(); - - public ChunkCache(GeyserSession session) { - if (session.getConnector().getWorldManager().getClass() == GeyserBootstrap.DEFAULT_CHUNK_MANAGER.getClass()) { - this.cache = session.getConnector().getConfig().isCacheChunks(); - } else { - this.cache = false; // To prevent Spigot from initializing - } - } - - public Column addToCache(Column chunk) { - if (!cache) { - return chunk; - } - - long chunkPosition = MathUtils.chunkPositionToLong(chunk.getX(), chunk.getZ()); - Column existingChunk; - if (chunk.getBiomeData() == null // Only consider merging columns if the new chunk isn't a full chunk - && (existingChunk = chunks.getOrDefault(chunkPosition, null)) != null) { // Column is already present in cache, we can merge with existing - boolean changed = false; - for (int i = 0; i < chunk.getChunks().length; i++) { // The chunks member is final, so chunk.getChunks() will probably be inlined and then completely optimized away - if (chunk.getChunks()[i] != null) { - existingChunk.getChunks()[i] = chunk.getChunks()[i]; - changed = true; - } - } - return changed ? existingChunk : null; - } else { - chunks.put(chunkPosition, chunk); - return chunk; - } - } - - public Column getChunk(int chunkX, int chunkZ) { - long chunkPosition = MathUtils.chunkPositionToLong(chunkX, chunkZ); - return chunks.getOrDefault(chunkPosition, null); - } - - public void updateBlock(int x, int y, int z, int block) { - if (!cache) { - return; - } - - Column column = this.getChunk(x >> 4, z >> 4); - if (column == null) { - return; - } - - Chunk chunk = column.getChunks()[y >> 4]; - if (chunk != null) { - chunk.set(x & 0xF, y & 0xF, z & 0xF, block); - } - } - - public int getBlockAt(int x, int y, int z) { - if (!cache) { - return BlockTranslator.JAVA_AIR_ID; - } - - Column column = this.getChunk(x >> 4, z >> 4); - if (column == null) { - return BlockTranslator.JAVA_AIR_ID; - } - - Chunk chunk = column.getChunks()[y >> 4]; - if (chunk != null) { - return chunk.get(x & 0xF, y & 0xF, z & 0xF); - } - - return BlockTranslator.JAVA_AIR_ID; - } - - public void removeChunk(int chunkX, int chunkZ) { - if (!cache) { - return; - } - - long chunkPosition = MathUtils.chunkPositionToLong(chunkX, chunkZ); - chunks.remove(chunkPosition); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/InventoryCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/InventoryCache.java deleted file mode 100644 index 032f64024..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/InventoryCache.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.session.cache; - -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import lombok.Getter; -import lombok.Setter; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; - -public class InventoryCache { - - private GeyserSession session; - - @Getter - @Setter - private Inventory openInventory; - - @Getter - private Int2ObjectMap inventories = new Int2ObjectOpenHashMap<>(); - - public InventoryCache(GeyserSession session) { - this.session = session; - } - - public Inventory getPlayerInventory() { - return inventories.get(0); - } - - public void cacheInventory(Inventory inventory) { - inventories.put(inventory.getId(), inventory); - } - - public void uncacheInventory(int id) { - inventories.remove(id); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java deleted file mode 100644 index 15b9a7705..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.session.cache; - -import com.nukkitx.protocol.bedrock.packet.ModalFormRequestPacket; - -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; - -import lombok.Getter; - -import org.geysermc.common.window.FormWindow; -import org.geysermc.connector.network.session.GeyserSession; - -public class WindowCache { - - private GeyserSession session; - - @Getter - private Int2ObjectMap windows = new Int2ObjectOpenHashMap<>(); - - public WindowCache(GeyserSession session) { - this.session = session; - } - - public void addWindow(FormWindow window) { - windows.put(windows.size() + 1, window); - } - - public void addWindow(FormWindow window, int id) { - windows.put(id, window); - } - - public void showWindow(FormWindow window) { - showWindow(window, windows.size() + 1); - } - - public void showWindow(int id) { - if (!windows.containsKey(id)) - return; - - ModalFormRequestPacket formRequestPacket = new ModalFormRequestPacket(); - formRequestPacket.setFormId(id); - formRequestPacket.setFormData(windows.get(id).getJSONData()); - - session.sendUpstreamPacket(formRequestPacket); - } - - public void showWindow(FormWindow window, int id) { - ModalFormRequestPacket formRequestPacket = new ModalFormRequestPacket(); - formRequestPacket.setFormId(id); - formRequestPacket.setFormData(window.getJSONData()); - - session.sendUpstreamPacket(formRequestPacket); - - addWindow(window, id); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java deleted file mode 100644 index 680a2d0c1..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators; - -import com.nukkitx.nbt.NBTInputStream; -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.nbt.NbtUtils; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.LanguageUtils; - -import java.io.InputStream; -import java.util.Arrays; - -// Based off of ProtocolSupport's LegacyBiomeData.java: -// https://github.com/ProtocolSupport/ProtocolSupport/blob/b2cad35977f3fcb65bee57b9e14fc9c975f71d32/src/protocolsupport/protocol/typeremapper/legacy/LegacyBiomeData.java -// Array index formula by https://wiki.vg/Chunk_Format -public class BiomeTranslator { - - public static final NbtMap BIOMES; - - private BiomeTranslator() { - } - - public static void init() { - // no-op - } - - static { - /* Load biomes */ - InputStream stream = FileUtils.getResource("bedrock/biome_definitions.dat"); - - NbtMap biomesTag; - - try (NBTInputStream biomenbtInputStream = NbtUtils.createNetworkReader(stream)) { - biomesTag = (NbtMap) biomenbtInputStream.readTag(); - BIOMES = biomesTag; - } catch (Exception ex) { - GeyserConnector.getInstance().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.biome_read")); - throw new AssertionError(ex); - } - } - - public static byte[] toBedrockBiome(int[] biomeData) { - byte[] bedrockData = new byte[256]; - if (biomeData == null) { - return bedrockData; - } - - for (int y = 0; y < 16; y += 4) { - for (int z = 0; z < 16; z += 4) { - for (int x = 0; x < 16; x += 4) { - byte biomeId = biomeID(biomeData, x, y, z); - int offset = ((z + (y / 4)) << 4) | x; - Arrays.fill(bedrockData, offset, offset + 4, biomeId); - } - } - } - return bedrockData; - } - - private static byte biomeID(int[] biomeData, int x, int y, int z) { - int biomeId = biomeData[((y >> 2) & 63) << 4 | ((z >> 2) & 3) << 2 | ((x >> 2) & 3)]; - if (biomeId == 0) { - biomeId = 42; // Ocean - } else if (biomeId >= 40 && biomeId <= 43) { // Java has multiple End dimensions that Bedrock doesn't recognize - biomeId = 9; - } else if (biomeId >= 170) { // Nether biomes. Dunno why it's like this :microjang: - biomeId += 8; - } - return (byte) biomeId; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java deleted file mode 100644 index b6c6f3aec..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators; - -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPlayerListDataPacket; -import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdateLightPacket; -import com.github.steveice10.packetlib.packet.Packet; -import com.nukkitx.protocol.bedrock.BedrockPacket; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.LanguageUtils; -import org.reflections.Reflections; - -import java.util.HashMap; -import java.util.Map; - -public class PacketTranslatorRegistry { - private final Map, PacketTranslator> translators = new HashMap<>(); - - public static final PacketTranslatorRegistry JAVA_TRANSLATOR = new PacketTranslatorRegistry<>(); - public static final PacketTranslatorRegistry BEDROCK_TRANSLATOR = new PacketTranslatorRegistry<>(); - - private static final ObjectArrayList> IGNORED_PACKETS = new ObjectArrayList<>(); - - static { - Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators") : new Reflections("org.geysermc.connector.network.translators"); - - for (Class clazz : ref.getTypesAnnotatedWith(Translator.class)) { - Class packet = clazz.getAnnotation(Translator.class).packet(); - - GeyserConnector.getInstance().getLogger().debug("Found annotated translator: " + clazz.getCanonicalName() + " : " + packet.getSimpleName()); - - try { - if (Packet.class.isAssignableFrom(packet)) { - Class targetPacket = (Class) packet; - PacketTranslator translator = (PacketTranslator) clazz.newInstance(); - - JAVA_TRANSLATOR.translators.put(targetPacket, translator); - } else if (BedrockPacket.class.isAssignableFrom(packet)) { - Class targetPacket = (Class) packet; - PacketTranslator translator = (PacketTranslator) clazz.newInstance(); - - BEDROCK_TRANSLATOR.translators.put(targetPacket, translator); - } else { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.invalid_target", clazz.getCanonicalName())); - } - } catch (InstantiationException | IllegalAccessException e) { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.failed", clazz.getCanonicalName())); - } - } - - IGNORED_PACKETS.add(ServerUpdateLightPacket.class); // Light is handled on Bedrock for us - IGNORED_PACKETS.add(ServerPlayerListDataPacket.class); // Cant be implemented in bedrock - } - - private PacketTranslatorRegistry() { - } - - public static void init() { - // no-op - } - - @SuppressWarnings("unchecked") - public

boolean translate(Class clazz, P packet, GeyserSession session) { - if (!session.getUpstream().isClosed() && !session.isClosed()) { - try { - if (translators.containsKey(clazz)) { - ((PacketTranslator

) translators.get(clazz)).translate(packet, session); - return true; - } else { - if (!IGNORED_PACKETS.contains(clazz)) - GeyserConnector.getInstance().getLogger().debug("Could not find packet for " + (packet.toString().length() > 25 ? packet.getClass().getSimpleName() : packet)); - } - } catch (Throwable ex) { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.packet.failed", packet.getClass().getSimpleName()), ex); - ex.printStackTrace(); - } - } - return false; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java deleted file mode 100644 index 3e40ddd6f..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.bedrock; - -import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.protocol.bedrock.packet.BlockPickRequestPacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.utils.InventoryUtils; - -@Translator(packet = BlockPickRequestPacket.class) -public class BedrockBlockPickRequestTranslator extends PacketTranslator { - - @Override - public void translate(BlockPickRequestPacket packet, GeyserSession session) { - Vector3i vector = packet.getBlockPosition(); - int blockToPick = session.getConnector().getWorldManager().getBlockAt(session, vector.getX(), vector.getY(), vector.getZ()); - - // Block is air - chunk caching is probably off - if (blockToPick == BlockTranslator.JAVA_AIR_ID) { - return; - } - - String targetIdentifier = BlockTranslator.getJavaIdBlockMap().inverse().get(blockToPick).split("\\[")[0]; - InventoryUtils.findOrCreatePickedBlock(session, targetIdentifier); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java deleted file mode 100644 index 00905f6d9..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.bedrock; - -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCloseWindowPacket; -import com.nukkitx.protocol.bedrock.packet.ContainerClosePacket; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.InventoryUtils; - -@Translator(packet = ContainerClosePacket.class) -public class BedrockContainerCloseTranslator extends PacketTranslator { - - @Override - public void translate(ContainerClosePacket packet, GeyserSession session) { - session.setLastWindowCloseTime(0); - byte windowId = packet.getId(); - Inventory openInventory = session.getInventoryCache().getOpenInventory(); - if (windowId == -1) { //player inventory or crafting table - if (openInventory != null) { - windowId = (byte) openInventory.getId(); - } else { - windowId = 0; - } - } - - if (windowId == 0 || (openInventory != null && openInventory.getId() == windowId)) { - ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(windowId); - session.getDownstream().getSession().send(closeWindowPacket); - InventoryUtils.closeInventory(session, windowId); - } - - //Client wants close confirmation - session.sendUpstreamPacket(packet); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEntityPickRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEntityPickRequestTranslator.java deleted file mode 100644 index 4aa044ee4..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEntityPickRequestTranslator.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.bedrock; - -import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.packet.EntityPickRequestPacket; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.utils.InventoryUtils; - -/** - * Called when the Bedrock user uses the pick block button on an entity - */ -@Translator(packet = EntityPickRequestPacket.class) -public class BedrockEntityPickRequestTranslator extends PacketTranslator { - - @Override - public void translate(EntityPickRequestPacket packet, GeyserSession session) { - if (session.getGameMode() != GameMode.CREATIVE) return; // Apparently Java behavior - Entity entity = session.getEntityCache().getEntityByGeyserId(packet.getRuntimeEntityId()); - if (entity == null) return; - - // Get the corresponding item - String itemName; - switch (entity.getEntityType()) { - case BOAT: - // Include type of boat in the name - int variant = entity.getMetadata().getInt(EntityData.VARIANT); - String typeOfBoat; - switch (variant) { - case 1: - typeOfBoat = "spruce"; - break; - case 2: - typeOfBoat = "birch"; - break; - case 3: - typeOfBoat = "jungle"; - break; - case 4: - typeOfBoat = "acacia"; - break; - case 5: - typeOfBoat = "dark_oak"; - break; - default: - typeOfBoat = "oak"; - break; - } - itemName = typeOfBoat + "_boat"; - break; - case LEASH_KNOT: - itemName = "lead"; - break; - case MINECART_CHEST: - case MINECART_COMMAND_BLOCK: - case MINECART_FURNACE: - case MINECART_HOPPER: - case MINECART_TNT: - // Move MINECART to the end of the name - itemName = entity.getEntityType().toString().toLowerCase().replace("minecart_", "") + "_minecart"; - break; - case MINECART_SPAWNER: - // Turns into a normal minecart - itemName = "minecart"; - break; - case ARMOR_STAND: - case END_CRYSTAL: - case ITEM_FRAME: - case MINECART: - case PAINTING: - // No spawn egg, just an item - itemName = entity.getEntityType().toString().toLowerCase(); - break; - default: - itemName = entity.getEntityType().toString().toLowerCase() + "_spawn_egg"; - break; - } - - String fullItemName = "minecraft:" + itemName; - ItemEntry entry = ItemRegistry.getItemEntry(fullItemName); - // Verify it is, indeed, an item - if (entry == null) return; - - InventoryUtils.findOrCreatePickedBlock(session, fullItemName); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java deleted file mode 100644 index 5b0fbb222..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.bedrock; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; -import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; -import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; -import com.github.steveice10.mc.protocol.data.game.entity.player.InteractAction; -import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; -import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerInteractEntityPacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPlaceBlockPacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerUseItemPacket; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.protocol.bedrock.data.LevelEventType; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; -import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; -import com.nukkitx.protocol.bedrock.packet.InventoryTransactionPacket; -import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; -import org.geysermc.connector.entity.CommandBlockMinecartEntity; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.ItemFrameEntity; -import org.geysermc.connector.entity.living.merchant.AbstractMerchantEntity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.inventory.InventoryTranslator; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.sound.EntitySoundInteractionHandler; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.utils.BlockUtils; -import org.geysermc.connector.utils.InventoryUtils; - -import java.util.concurrent.TimeUnit; - -@Translator(packet = InventoryTransactionPacket.class) -public class BedrockInventoryTransactionTranslator extends PacketTranslator { - - @Override - public void translate(InventoryTransactionPacket packet, GeyserSession session) { - switch (packet.getTransactionType()) { - case NORMAL: - Inventory inventory = session.getInventoryCache().getOpenInventory(); - if (inventory == null) inventory = session.getInventory(); - InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()).translateActions(session, inventory, packet.getActions()); - break; - case INVENTORY_MISMATCH: - Inventory inv = session.getInventoryCache().getOpenInventory(); - if (inv == null) inv = session.getInventory(); - InventoryTranslator.INVENTORY_TRANSLATORS.get(inv.getWindowType()).updateInventory(session, inv); - InventoryUtils.updateCursor(session); - break; - case ITEM_USE: - switch (packet.getActionType()) { - case 0: - // Check to make sure the client isn't spamming interaction - // Based on Nukkit 1.0, with changes to ensure holding down still works - boolean hasAlreadyClicked = System.currentTimeMillis() - session.getLastInteractionTime() < 110.0 && - packet.getBlockPosition().distanceSquared(session.getLastInteractionPosition()) < 0.00001; - session.setLastInteractionPosition(packet.getBlockPosition()); - if (hasAlreadyClicked) { - break; - } else { - // Only update the interaction time if it's valid - that way holding down still works. - session.setLastInteractionTime(System.currentTimeMillis()); - } - - // Bedrock sends block interact code for a Java entity so we send entity code back to Java - if (BlockTranslator.isItemFrame(packet.getBlockRuntimeId()) && - session.getEntityCache().getEntityByJavaId(ItemFrameEntity.getItemFrameEntityId(session, packet.getBlockPosition())) != null) { - Vector3f vector = packet.getClickPosition(); - ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) ItemFrameEntity.getItemFrameEntityId(session, packet.getBlockPosition()), - InteractAction.INTERACT, Hand.MAIN_HAND, session.isSneaking()); - ClientPlayerInteractEntityPacket interactAtPacket = new ClientPlayerInteractEntityPacket((int) ItemFrameEntity.getItemFrameEntityId(session, packet.getBlockPosition()), - InteractAction.INTERACT_AT, vector.getX(), vector.getY(), vector.getZ(), Hand.MAIN_HAND, session.isSneaking()); - session.sendDownstreamPacket(interactPacket); - session.sendDownstreamPacket(interactAtPacket); - break; - } - - ClientPlayerPlaceBlockPacket blockPacket = new ClientPlayerPlaceBlockPacket( - new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()), - BlockFace.values()[packet.getBlockFace()], - Hand.MAIN_HAND, - packet.getClickPosition().getX(), packet.getClickPosition().getY(), packet.getClickPosition().getZ(), - false); - session.sendDownstreamPacket(blockPacket); - - // Otherwise boats will not be able to be placed in survival and buckets won't work on mobile - if (packet.getItemInHand() != null && ItemRegistry.BOATS.contains(packet.getItemInHand().getId())) { - ClientPlayerUseItemPacket itemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND); - session.sendDownstreamPacket(itemPacket); - } - // Check actions, otherwise buckets may be activated when block inventories are accessed - else if (packet.getItemInHand() != null && ItemRegistry.BUCKETS.contains(packet.getItemInHand().getId())) { - // Let the server decide if the bucket item should change, not the client, and revert the changes the client made - InventorySlotPacket slotPacket = new InventorySlotPacket(); - slotPacket.setContainerId(ContainerId.INVENTORY); - slotPacket.setSlot(packet.getHotbarSlot()); - slotPacket.setItem(packet.getItemInHand()); - session.sendUpstreamPacket(slotPacket); - // Delay the interaction in case the client doesn't intend to actually use the bucket - // See BedrockActionTranslator.java - session.setBucketScheduledFuture(session.getConnector().getGeneralThreadPool().schedule(() -> { - ClientPlayerUseItemPacket itemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND); - session.sendDownstreamPacket(itemPacket); - }, 5, TimeUnit.MILLISECONDS)); - } - - if (packet.getActions().isEmpty()) { - if (session.getOpPermissionLevel() >= 2 && session.getGameMode() == GameMode.CREATIVE) { - // Otherwise insufficient permissions - int blockState = BlockTranslator.getJavaBlockState(packet.getBlockRuntimeId()); - String blockName = BlockTranslator.getJavaIdBlockMap().inverse().getOrDefault(blockState, ""); - // In the future this can be used for structure blocks too, however not all elements - // are available in each GUI - if (blockName.contains("jigsaw")) { - ContainerOpenPacket openPacket = new ContainerOpenPacket(); - openPacket.setBlockPosition(packet.getBlockPosition()); - openPacket.setId((byte) 1); - openPacket.setType(ContainerType.JIGSAW_EDITOR); - openPacket.setUniqueEntityId(-1); - session.sendUpstreamPacket(openPacket); - } - } - } - - Vector3i blockPos = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getBlockFace()); - ItemEntry handItem = ItemRegistry.getItem(packet.getItemInHand()); - if (handItem.isBlock()) { - session.setLastBlockPlacePosition(blockPos); - session.setLastBlockPlacedId(handItem.getJavaIdentifier()); - } - session.setInteracting(true); - break; - case 1: - ItemStack shieldSlot = session.getInventory().getItem(session.getInventory().getHeldItemSlot() + 36); - // Handled in Entity.java - if (shieldSlot != null && shieldSlot.getId() == ItemRegistry.SHIELD.getJavaId()) { - break; - } - - // Handled in ITEM_USE if the item is not milk - if (packet.getItemInHand() != null && ItemRegistry.BUCKETS.contains(packet.getItemInHand().getId()) && - packet.getItemInHand().getId() != ItemRegistry.MILK_BUCKET.getBedrockId()) { - break; - } - - ClientPlayerUseItemPacket useItemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND); - session.sendDownstreamPacket(useItemPacket); - break; - case 2: - int blockState = session.getConnector().getWorldManager().getBlockAt(session, packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()); - double blockHardness = BlockTranslator.JAVA_RUNTIME_ID_TO_HARDNESS.get(blockState); - if (session.getGameMode() == GameMode.CREATIVE || (session.getConnector().getConfig().isCacheChunks() && blockHardness == 0)) { - session.setLastBlockPlacedId(null); - session.setLastBlockPlacePosition(null); - - LevelEventPacket blockBreakPacket = new LevelEventPacket(); - blockBreakPacket.setType(LevelEventType.PARTICLE_DESTROY_BLOCK); - blockBreakPacket.setPosition(packet.getBlockPosition().toFloat()); - blockBreakPacket.setData(BlockTranslator.getBedrockBlockId(blockState)); - session.sendUpstreamPacket(blockBreakPacket); - } - - long frameEntityId = ItemFrameEntity.getItemFrameEntityId(session, packet.getBlockPosition()); - if (frameEntityId != -1 && session.getEntityCache().getEntityByJavaId(frameEntityId) != null) { - ClientPlayerInteractEntityPacket attackPacket = new ClientPlayerInteractEntityPacket((int) frameEntityId, InteractAction.ATTACK, session.isSneaking()); - session.sendDownstreamPacket(attackPacket); - break; - } - - PlayerAction action = session.getGameMode() == GameMode.CREATIVE ? PlayerAction.START_DIGGING : PlayerAction.FINISH_DIGGING; - Position pos = new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()); - ClientPlayerActionPacket breakPacket = new ClientPlayerActionPacket(action, pos, BlockFace.values()[packet.getBlockFace()]); - session.sendDownstreamPacket(breakPacket); - break; - } - break; - case ITEM_RELEASE: - if (packet.getActionType() == 0) { - // Followed to the Minecraft Protocol specification outlined at wiki.vg - ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, new Position(0,0,0), - BlockFace.DOWN); - session.sendDownstreamPacket(releaseItemPacket); - } - break; - case ITEM_USE_ON_ENTITY: - Entity entity = session.getEntityCache().getEntityByGeyserId(packet.getRuntimeEntityId()); - if (entity == null) - return; - - //https://wiki.vg/Protocol#Interact_Entity - switch (packet.getActionType()) { - case 0: //Interact - if (entity instanceof CommandBlockMinecartEntity) { - // The UI is handled client-side on Java Edition - // Ensure OP permission level and gamemode is appropriate - if (session.getOpPermissionLevel() < 2 || session.getGameMode() != GameMode.CREATIVE) return; - ContainerOpenPacket openPacket = new ContainerOpenPacket(); - openPacket.setBlockPosition(Vector3i.ZERO); - openPacket.setId((byte) 1); - openPacket.setType(ContainerType.COMMAND_BLOCK); - openPacket.setUniqueEntityId(entity.getGeyserId()); - session.sendUpstreamPacket(openPacket); - break; - } - Vector3f vector = packet.getClickPosition(); - ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(), - InteractAction.INTERACT, Hand.MAIN_HAND, session.isSneaking()); - ClientPlayerInteractEntityPacket interactAtPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(), - InteractAction.INTERACT_AT, vector.getX(), vector.getY(), vector.getZ(), Hand.MAIN_HAND, session.isSneaking()); - session.sendDownstreamPacket(interactPacket); - session.sendDownstreamPacket(interactAtPacket); - - EntitySoundInteractionHandler.handleEntityInteraction(session, vector, entity); - - if (entity instanceof AbstractMerchantEntity) { - session.setLastInteractedVillagerEid(packet.getRuntimeEntityId()); - } - break; - case 1: //Attack - if (entity.getEntityType() == EntityType.ENDER_DRAGON) { - // Redirects the attack to its body entity, this only happens when - // attacking the underbelly of the ender dragon - ClientPlayerInteractEntityPacket attackPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId() + 3, - InteractAction.ATTACK, session.isSneaking()); - session.sendDownstreamPacket(attackPacket); - } else { - ClientPlayerInteractEntityPacket attackPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(), - InteractAction.ATTACK, session.isSneaking()); - session.sendDownstreamPacket(attackPacket); - } - break; - } - break; - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java deleted file mode 100644 index a220e389f..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.bedrock; - -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerChangeHeldItemPacket; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; -import com.nukkitx.protocol.bedrock.packet.MobEquipmentPacket; -import org.geysermc.connector.utils.CooldownUtils; - -@Translator(packet = MobEquipmentPacket.class) -public class BedrockMobEquipmentTranslator extends PacketTranslator { - - @Override - public void translate(MobEquipmentPacket packet, GeyserSession session) { - if (!session.isSpawned() || packet.getHotbarSlot() > 8 || - packet.getContainerId() != ContainerId.INVENTORY || session.getInventory().getHeldItemSlot() == packet.getHotbarSlot()) { - // For the last condition - Don't update the slot if the slot is the same - not Java Edition behavior and messes with plugins such as Grief Prevention - return; - } - - session.getInventory().setHeldItemSlot(packet.getHotbarSlot()); - - ClientPlayerChangeHeldItemPacket changeHeldItemPacket = new ClientPlayerChangeHeldItemPacket(packet.getHotbarSlot()); - session.sendDownstreamPacket(changeHeldItemPacket); - - // Java sends a cooldown indicator whenever you switch an item - CooldownUtils.sendCooldown(session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMoveEntityAbsoluteTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMoveEntityAbsoluteTranslator.java deleted file mode 100644 index 26d7f1d46..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMoveEntityAbsoluteTranslator.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.bedrock; - -import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientVehicleMovePacket; -import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - -// Used for horses -@Translator(packet = MoveEntityAbsolutePacket.class) -public class BedrockMoveEntityAbsoluteTranslator extends PacketTranslator { - - @Override - public void translate(MoveEntityAbsolutePacket packet, GeyserSession session) { - - ClientVehicleMovePacket clientVehicleMovePacket = new ClientVehicleMovePacket( - packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ(), - packet.getRotation().getY() - 90, packet.getRotation().getX() - ); - session.sendDownstreamPacket(clientVehicleMovePacket); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java deleted file mode 100644 index d480b526f..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.bedrock; - -import com.github.steveice10.mc.protocol.packet.ingame.client.ClientKeepAlivePacket; -import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - -/** - * Used to send the keep alive packet back to the server - */ -@Translator(packet = NetworkStackLatencyPacket.class) -public class BedrockNetworkStackLatencyTranslator extends PacketTranslator { - - @Override - public void translate(NetworkStackLatencyPacket packet, GeyserSession session) { - // The client sends a timestamp back but it's rounded and therefore unreliable when we need the exact number - ClientKeepAlivePacket keepAlivePacket = new ClientKeepAlivePacket(session.getLastKeepAliveTimestamp()); - session.sendDownstreamPacket(keepAlivePacket); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPlayerInputTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPlayerInputTranslator.java deleted file mode 100644 index 2bd46c7ce..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPlayerInputTranslator.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.bedrock; - -import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientSteerVehiclePacket; -import com.nukkitx.protocol.bedrock.packet.PlayerInputPacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - -// Makes minecarts respond to player input -@Translator(packet = PlayerInputPacket.class) -public class BedrockPlayerInputTranslator extends PacketTranslator { - - @Override - public void translate(PlayerInputPacket packet, GeyserSession session) { - ClientSteerVehiclePacket clientSteerVehiclePacket = new ClientSteerVehiclePacket( - packet.getInputMotion().getX(), packet.getInputMotion().getY(), packet.isJumping(), packet.isSneaking() - ); - - session.sendDownstreamPacket(clientSteerVehiclePacket); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/BedrockEntityEventTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/BedrockEntityEventTranslator.java deleted file mode 100644 index 18fd6614e..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/BedrockEntityEventTranslator.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.bedrock.entity; - -import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade; -import com.github.steveice10.mc.protocol.data.game.window.WindowType; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientSelectTradePacket; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - -@Translator(packet = EntityEventPacket.class) -public class BedrockEntityEventTranslator extends PacketTranslator { - - @Override - public void translate(EntityEventPacket packet, GeyserSession session) { - switch (packet.getType()) { - // Resend the packet so we get the eating sounds - case EATING_ITEM: - session.sendUpstreamPacket(packet); - return; - case COMPLETE_TRADE: - ClientSelectTradePacket selectTradePacket = new ClientSelectTradePacket(packet.getData()); - session.getDownstream().getSession().send(selectTradePacket); - - Entity villager = session.getPlayerEntity(); - Inventory openInventory = session.getInventoryCache().getOpenInventory(); - if (openInventory != null && openInventory.getWindowType() == WindowType.MERCHANT) { - VillagerTrade[] trades = session.getVillagerTrades(); - if (trades != null && packet.getData() >= 0 && packet.getData() < trades.length) { - VillagerTrade trade = session.getVillagerTrades()[packet.getData()]; - openInventory.setItem(2, trade.getOutput()); - villager.getMetadata().put(EntityData.TRADE_XP, trade.getXp() + villager.getMetadata().getInt(EntityData.TRADE_XP)); - villager.updateBedrockMetadata(session); - } - } - return; - } - session.getConnector().getLogger().debug("Did not translate incoming EntityEventPacket: " + packet.toString()); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java deleted file mode 100644 index e3bcbce9f..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.bedrock.entity.player; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; -import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; -import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerState; -import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerAbilitiesPacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerStatePacket; -import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.protocol.bedrock.data.LevelEventType; -import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; -import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; -import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; -import com.nukkitx.protocol.bedrock.packet.PlayStatusPacket; -import com.nukkitx.protocol.bedrock.packet.PlayerActionPacket; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.utils.BlockUtils; - -import java.util.concurrent.TimeUnit; - -@Translator(packet = PlayerActionPacket.class) -public class BedrockActionTranslator extends PacketTranslator { - - @Override - public void translate(PlayerActionPacket packet, GeyserSession session) { - Entity entity = session.getPlayerEntity(); - if (entity == null) - return; - - Vector3i vector = packet.getBlockPosition(); - Position position = new Position(vector.getX(), vector.getY(), vector.getZ()); - - switch (packet.getAction()) { - case RESPAWN: - // Respawn process is finished and the server and client are both OK with respawning. - EntityEventPacket eventPacket = new EntityEventPacket(); - eventPacket.setRuntimeEntityId(entity.getGeyserId()); - eventPacket.setType(EntityEventType.RESPAWN); - eventPacket.setData(0); - session.sendUpstreamPacket(eventPacket); - // Resend attributes or else in rare cases the user can think they're not dead when they are, upon joining the server - entity.updateBedrockAttributes(session); - break; - case START_SWIMMING: - ClientPlayerStatePacket startSwimPacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.START_SPRINTING); - session.sendDownstreamPacket(startSwimPacket); - break; - case STOP_SWIMMING: - ClientPlayerStatePacket stopSwimPacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.STOP_SPRINTING); - session.sendDownstreamPacket(stopSwimPacket); - break; - case START_GLIDE: - // Otherwise gliding will not work in creative - ClientPlayerAbilitiesPacket playerAbilitiesPacket = new ClientPlayerAbilitiesPacket(false); - session.sendDownstreamPacket(playerAbilitiesPacket); - case STOP_GLIDE: - ClientPlayerStatePacket glidePacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.START_ELYTRA_FLYING); - session.sendDownstreamPacket(glidePacket); - break; - case START_SNEAK: - ClientPlayerStatePacket startSneakPacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.START_SNEAKING); - session.sendDownstreamPacket(startSneakPacket); - session.setSneaking(true); - break; - case STOP_SNEAK: - ClientPlayerStatePacket stopSneakPacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.STOP_SNEAKING); - session.sendDownstreamPacket(stopSneakPacket); - session.setSneaking(false); - break; - case START_SPRINT: - ClientPlayerStatePacket startSprintPacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.START_SPRINTING); - session.sendDownstreamPacket(startSprintPacket); - session.setSprinting(true); - break; - case STOP_SPRINT: - ClientPlayerStatePacket stopSprintPacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.STOP_SPRINTING); - session.sendDownstreamPacket(stopSprintPacket); - session.setSprinting(false); - break; - case DROP_ITEM: - ClientPlayerActionPacket dropItemPacket = new ClientPlayerActionPacket(PlayerAction.DROP_ITEM, position, BlockFace.values()[packet.getFace()]); - session.sendDownstreamPacket(dropItemPacket); - break; - case STOP_SLEEP: - ClientPlayerStatePacket stopSleepingPacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.LEAVE_BED); - session.sendDownstreamPacket(stopSleepingPacket); - break; - case BLOCK_INTERACT: - // Client means to interact with a block; cancel bucket interaction, if any - if (session.getBucketScheduledFuture() != null) { - session.getBucketScheduledFuture().cancel(true); - session.setBucketScheduledFuture(null); - } - // Otherwise handled in BedrockInventoryTransactionTranslator - break; - case START_BREAK: - if (session.getConnector().getConfig().isCacheChunks()) { - // Account for fire - the client likes to hit the block behind. - Vector3i fireBlockPos = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getFace()); - int blockUp = session.getConnector().getWorldManager().getBlockAt(session, fireBlockPos); - String identifier = BlockTranslator.getJavaIdBlockMap().inverse().get(blockUp); - if (identifier.startsWith("minecraft:fire") || identifier.startsWith("minecraft:soul_fire")) { - ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, new Position(fireBlockPos.getX(), - fireBlockPos.getY(), fireBlockPos.getZ()), BlockFace.values()[packet.getFace()]); - session.sendDownstreamPacket(startBreakingPacket); - break; - } - } - ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, new Position(packet.getBlockPosition().getX(), - packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()), BlockFace.values()[packet.getFace()]); - session.sendDownstreamPacket(startBreakingPacket); - break; - case CONTINUE_BREAK: - LevelEventPacket continueBreakPacket = new LevelEventPacket(); - continueBreakPacket.setType(LevelEventType.PARTICLE_CRACK_BLOCK); - continueBreakPacket.setData(BlockTranslator.getBedrockBlockId(session.getBreakingBlock())); - continueBreakPacket.setPosition(packet.getBlockPosition().toFloat()); - session.sendUpstreamPacket(continueBreakPacket); - break; - case ABORT_BREAK: - ClientPlayerActionPacket abortBreakingPacket = new ClientPlayerActionPacket(PlayerAction.CANCEL_DIGGING, new Position(packet.getBlockPosition().getX(), - packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()), BlockFace.DOWN); - session.sendDownstreamPacket(abortBreakingPacket); - break; - case STOP_BREAK: - // Handled in BedrockInventoryTransactionTranslator - break; - case DIMENSION_CHANGE_SUCCESS: - if (session.getPendingDimSwitches().decrementAndGet() == 0) { - //sometimes the client doesn't feel like loading - PlayStatusPacket spawnPacket = new PlayStatusPacket(); - spawnPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN); - session.sendUpstreamPacket(spawnPacket); - entity.updateBedrockAttributes(session); - session.getEntityCache().updateBossBars(); - } - break; - case JUMP: - session.setJumping(true); - session.getConnector().getGeneralThreadPool().schedule(() -> { - session.setJumping(false); - }, 1, TimeUnit.SECONDS); - break; - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockEmoteTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockEmoteTranslator.java deleted file mode 100644 index 813c5594c..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockEmoteTranslator.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.bedrock.entity.player; - -import com.nukkitx.protocol.bedrock.packet.EmotePacket; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - -@Translator(packet = EmotePacket.class) -public class BedrockEmoteTranslator extends PacketTranslator { - - @Override - public void translate(EmotePacket packet, GeyserSession session) { - long javaId = session.getPlayerEntity().getEntityId(); - for (GeyserSession otherSession : session.getConnector().getPlayers()) { - if (otherSession != session) { - if (otherSession.isClosed()) continue; - Entity otherEntity = otherSession.getEntityCache().getEntityByJavaId(javaId); - if (otherEntity == null) continue; - packet.setRuntimeEntityId(otherEntity.getGeyserId()); - otherSession.sendUpstreamPacket(packet); - } - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java deleted file mode 100644 index 6c03cd033..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java +++ /dev/null @@ -1,431 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.bedrock.entity.player; - -import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; -import com.github.steveice10.mc.protocol.data.game.entity.player.InteractAction; -import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerState; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerInteractEntityPacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerStatePacket; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityDataMap; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; -import com.nukkitx.protocol.bedrock.packet.InteractPacket; -import lombok.Getter; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.ItemRegistry; - -import java.util.Arrays; -import java.util.List; - -@Translator(packet = InteractPacket.class) -public class BedrockInteractTranslator extends PacketTranslator { - - /** - * A list of all foods a horse/donkey can eat on Java Edition. - * Used to display interactive tag if needed. - */ - private static final List DONKEY_AND_HORSE_FOODS = Arrays.asList("golden_apple", "enchanted_golden_apple", - "golden_carrot", "sugar", "apple", "wheat", "hay_block"); - - /** - * A list of all flowers. Used for feeding bees. - */ - private static final List FLOWERS = Arrays.asList("dandelion", "poppy", "blue_orchid", "allium", "azure_bluet", - "red_tulip", "pink_tulip", "white_tulip", "orange_tulip", "cornflower", "lily_of_the_valley", "wither_rose", - "sunflower", "lilac", "rose_bush", "peony"); - - /** - * All entity types that can be leashed on Java Edition - */ - private static final List LEASHABLE_MOB_TYPES = Arrays.asList(EntityType.BEE, EntityType.CAT, EntityType.CHICKEN, - EntityType.COW, EntityType.DOLPHIN, EntityType.DONKEY, EntityType.FOX, EntityType.HOGLIN, EntityType.HORSE, EntityType.SKELETON_HORSE, - EntityType.ZOMBIE_HORSE, EntityType.IRON_GOLEM, EntityType.LLAMA, EntityType.TRADER_LLAMA, EntityType.MOOSHROOM, - EntityType.MULE, EntityType.OCELOT, EntityType.PARROT, EntityType.PIG, EntityType.POLAR_BEAR, EntityType.RABBIT, - EntityType.SHEEP, EntityType.SNOW_GOLEM, EntityType.STRIDER, EntityType.WOLF, EntityType.ZOGLIN); - - private static final List SADDLEABLE_WHEN_TAMED_MOB_TYPES = Arrays.asList(EntityType.DONKEY, EntityType.HORSE, - EntityType.ZOMBIE_HORSE, EntityType.MULE); - /** - * A list of all foods a wolf can eat on Java Edition. - * Used to display interactive tag if needed. - */ - private static final List WOLF_FOODS = Arrays.asList("pufferfish", "tropical_fish", "chicken", "cooked_chicken", - "porkchop", "beef", "rabbit", "cooked_porkchop", "cooked_beef", "rotten_flesh", "mutton", "cooked_mutton", - "cooked_rabbit"); - - @Override - public void translate(InteractPacket packet, GeyserSession session) { - Entity entity; - if (packet.getRuntimeEntityId() == session.getPlayerEntity().getGeyserId()) { - //Player is not in entity cache - entity = session.getPlayerEntity(); - } else { - entity = session.getEntityCache().getEntityByGeyserId(packet.getRuntimeEntityId()); - } - if (entity == null) - return; - - switch (packet.getAction()) { - case INTERACT: - if (session.getInventory().getItem(session.getInventory().getHeldItemSlot() + 36).getId() == ItemRegistry.SHIELD.getJavaId()) { - break; - } - ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(), - InteractAction.INTERACT, Hand.MAIN_HAND, session.isSneaking()); - session.sendDownstreamPacket(interactPacket); - break; - case DAMAGE: - ClientPlayerInteractEntityPacket attackPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(), - InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking()); - session.sendDownstreamPacket(attackPacket); - break; - case LEAVE_VEHICLE: - ClientPlayerStatePacket sneakPacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.START_SNEAKING); - session.sendDownstreamPacket(sneakPacket); - session.setRidingVehicleEntity(null); - break; - case MOUSEOVER: - // Handle the buttons for mobile - "Mount", etc; and the suggestions for console - "ZL: Mount", etc - if (packet.getRuntimeEntityId() != 0) { - Entity interactEntity = session.getEntityCache().getEntityByGeyserId(packet.getRuntimeEntityId()); - if (interactEntity == null) - return; - EntityDataMap entityMetadata = interactEntity.getMetadata(); - ItemEntry itemEntry = session.getInventory().getItemInHand() == null ? ItemEntry.AIR : ItemRegistry.getItem(session.getInventory().getItemInHand()); - String javaIdentifierStripped = itemEntry.getJavaIdentifier().replace("minecraft:", ""); - - // TODO - in the future, update these in the metadata? So the client doesn't have to wiggle their cursor around for it to happen - // TODO - also, might be good to abstract out the eating thing. I know there will need to be food tracked for https://github.com/GeyserMC/Geyser/issues/1005 but not all food is breeding food - InteractiveTag interactiveTag = InteractiveTag.NONE; - if (entityMetadata.getLong(EntityData.LEASH_HOLDER_EID) == session.getPlayerEntity().getGeyserId()) { - // Unleash the entity - interactiveTag = InteractiveTag.REMOVE_LEASH; - } else if (javaIdentifierStripped.equals("saddle") && !entityMetadata.getFlags().getFlag(EntityFlag.SADDLED) && - ((SADDLEABLE_WHEN_TAMED_MOB_TYPES.contains(interactEntity.getEntityType()) && entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) || - interactEntity.getEntityType() == EntityType.PIG || interactEntity.getEntityType() == EntityType.STRIDER)) { - // Entity can be saddled and the conditions meet (entity can be saddled and, if needed, is tamed) - interactiveTag = InteractiveTag.SADDLE; - } else if (javaIdentifierStripped.equals("name_tag") && session.getInventory().getItemInHand().getNbt() != null && - session.getInventory().getItemInHand().getNbt().contains("display")) { - // Holding a named name tag - interactiveTag = InteractiveTag.NAME; - } else if (javaIdentifierStripped.equals("lead") && LEASHABLE_MOB_TYPES.contains(interactEntity.getEntityType()) && - entityMetadata.getLong(EntityData.LEASH_HOLDER_EID) == -1L) { - // Holding a leash and the mob is leashable for sure - // (Plugins can change this behavior so that's something to look into in the far far future) - interactiveTag = InteractiveTag.LEASH; - } else { - switch (interactEntity.getEntityType()) { - case BEE: - if (FLOWERS.contains(javaIdentifierStripped)) { - interactiveTag = InteractiveTag.FEED; - } - break; - case BOAT: - interactiveTag = InteractiveTag.BOARD_BOAT; - break; - case CAT: - if (javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon")) { - interactiveTag = InteractiveTag.FEED; - } else if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && - entityMetadata.getLong(EntityData.OWNER_EID) == session.getPlayerEntity().getGeyserId()) { - // Tamed and owned by player - can sit/stand - interactiveTag = entityMetadata.getFlags().getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; - break; - } - break; - case CHICKEN: - if (javaIdentifierStripped.contains("seeds")) { - interactiveTag = InteractiveTag.FEED; - } - break; - case MOOSHROOM: - // Shear the mooshroom - if (javaIdentifierStripped.equals("shears")) { - interactiveTag = InteractiveTag.MOOSHROOM_SHEAR; - break; - } - // Bowls are acceptable here - else if (javaIdentifierStripped.equals("bowl")) { - interactiveTag = InteractiveTag.MOOSHROOM_MILK_STEW; - break; - } - // Fall down to COW as this works on mooshrooms - case COW: - if (javaIdentifierStripped.equals("wheat")) { - interactiveTag = InteractiveTag.FEED; - } else if (javaIdentifierStripped.equals("bucket")) { - // Milk the cow - interactiveTag = InteractiveTag.MILK; - } - break; - case CREEPER: - if (javaIdentifierStripped.equals("flint_and_steel")) { - // Today I learned that you can ignite a creeper with flint and steel! Huh. - interactiveTag = InteractiveTag.IGNITE_CREEPER; - } - break; - case DONKEY: - case LLAMA: - case MULE: - if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && !entityMetadata.getFlags().getFlag(EntityFlag.CHESTED) - && javaIdentifierStripped.equals("chest")) { - // Can attach a chest - interactiveTag = InteractiveTag.ATTACH_CHEST; - break; - } - // Intentional fall-through - case HORSE: - case SKELETON_HORSE: - case TRADER_LLAMA: - case ZOMBIE_HORSE: - // have another switch statement as, while these share mount attributes they don't share food - switch (interactEntity.getEntityType()) { - case LLAMA: - case TRADER_LLAMA: - if (javaIdentifierStripped.equals("wheat") || javaIdentifierStripped.equals("hay_block")) { - interactiveTag = InteractiveTag.FEED; - break; - } - case DONKEY: - case HORSE: - // Undead can't eat - if (DONKEY_AND_HORSE_FOODS.contains(javaIdentifierStripped)) { - interactiveTag = InteractiveTag.FEED; - break; - } - } - if (!entityMetadata.getFlags().getFlag(EntityFlag.BABY)) { - // Can't ride a baby - if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) { - interactiveTag = InteractiveTag.RIDE_HORSE; - } else if (!entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && itemEntry.equals(ItemEntry.AIR)) { - // Can't hide an untamed entity without having your hand empty - interactiveTag = InteractiveTag.MOUNT; - } - } - break; - case FOX: - if (javaIdentifierStripped.equals("sweet_berries")) { - interactiveTag = InteractiveTag.FEED; - } - break; - case HOGLIN: - if (javaIdentifierStripped.equals("crimson_fungus")) { - interactiveTag = InteractiveTag.FEED; - } - break; - case MINECART: - interactiveTag = InteractiveTag.RIDE_MINECART; - break; - case MINECART_CHEST: - case MINECART_COMMAND_BLOCK: - case MINECART_HOPPER: - interactiveTag = InteractiveTag.OPEN_CONTAINER; - break; - case OCELOT: - if (javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon")) { - interactiveTag = InteractiveTag.FEED; - } - break; - case PANDA: - if (javaIdentifierStripped.equals("bamboo")) { - interactiveTag = InteractiveTag.FEED; - } - break; - case PARROT: - if (javaIdentifierStripped.contains("seeds") || javaIdentifierStripped.equals("cookie")) { - interactiveTag = InteractiveTag.FEED; - } - break; - case PIG: - if (javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("potato") || javaIdentifierStripped.equals("beetroot")) { - interactiveTag = InteractiveTag.FEED; - } else if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) { - interactiveTag = InteractiveTag.MOUNT; - } - break; - case PIGLIN: - if (!entityMetadata.getFlags().getFlag(EntityFlag.BABY) && javaIdentifierStripped.equals("gold_ingot")) { - interactiveTag = InteractiveTag.BARTER; - } - break; - case RABBIT: - if (javaIdentifierStripped.equals("dandelion") || javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("golden_carrot")) { - interactiveTag = InteractiveTag.FEED; - } - break; - case SHEEP: - if (javaIdentifierStripped.equals("wheat")) { - interactiveTag = InteractiveTag.FEED; - } else if (!entityMetadata.getFlags().getFlag(EntityFlag.SHEARED)) { - if (javaIdentifierStripped.equals("shears")) { - // Shear the sheep - interactiveTag = InteractiveTag.SHEAR; - } else if (javaIdentifierStripped.contains("_dye")) { - // Dye the sheep - interactiveTag = InteractiveTag.DYE; - } - } - break; - case STRIDER: - if (javaIdentifierStripped.equals("warped_fungus")) { - interactiveTag = InteractiveTag.FEED; - } else if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) { - interactiveTag = InteractiveTag.RIDE_STRIDER; - } - break; - case TURTLE: - if (javaIdentifierStripped.equals("seagrass")) { - interactiveTag = InteractiveTag.FEED; - } - break; - case VILLAGER: - if (entityMetadata.getInt(EntityData.VARIANT) != 14 && entityMetadata.getInt(EntityData.VARIANT) != 0 - && entityMetadata.getFloat(EntityData.SCALE) >= 0.75f) { // Not a nitwit, has a profession and is not a baby - interactiveTag = InteractiveTag.TRADE; - } - break; - case WANDERING_TRADER: - interactiveTag = InteractiveTag.TRADE; // Since you can always trade with a wandering villager, presumably. - break; - case WOLF: - if (javaIdentifierStripped.equals("bone") && !entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) { - // Bone and untamed - can tame - interactiveTag = InteractiveTag.TAME; - } else if (WOLF_FOODS.contains(javaIdentifierStripped)) { - // Compatible food in hand - feed - // Sometimes just sits/stands when the wolf isn't hungry - there doesn't appear to be a way to fix this - interactiveTag = InteractiveTag.FEED; - } else if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && - entityMetadata.getLong(EntityData.OWNER_EID) == session.getPlayerEntity().getGeyserId()) { - // Tamed and owned by player - can sit/stand - interactiveTag = entityMetadata.getFlags().getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; - } - break; - case ZOMBIE_VILLAGER: - // We can't guarantee the existence of the weakness effect so we just always show it. - if (javaIdentifierStripped.equals("golden_apple")) { - interactiveTag = InteractiveTag.CURE; - } - break; - default: - break; - } - } - session.getPlayerEntity().getMetadata().put(EntityData.INTERACTIVE_TAG, interactiveTag.getValue()); - session.getPlayerEntity().updateBedrockMetadata(session); - } else { - if (!session.getPlayerEntity().getMetadata().getString(EntityData.INTERACTIVE_TAG).isEmpty()) { - // No interactive tag should be sent - session.getPlayerEntity().getMetadata().remove(EntityData.INTERACTIVE_TAG); - session.getPlayerEntity().updateBedrockMetadata(session); - } - } - break; - case OPEN_INVENTORY: - if (!session.getInventory().isOpen()) { - ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket(); - containerOpenPacket.setId((byte) 0); - containerOpenPacket.setType(ContainerType.INVENTORY); - containerOpenPacket.setUniqueEntityId(-1); - containerOpenPacket.setBlockPosition(entity.getPosition().toInt()); - session.sendUpstreamPacket(containerOpenPacket); - session.getInventory().setOpen(true); - } - break; - } - } - - /** - * All interactive tags in enum form. For potential API usage. - */ - public enum InteractiveTag { - NONE(true), - IGNITE_CREEPER("creeper"), - EDIT, - LEAVE_BOAT("exit.boat"), - FEED, - FISH("fishing"), - MILK, - MOOSHROOM_SHEAR("mooshear"), - MOOSHROOM_MILK_STEW("moostew"), - BOARD_BOAT("ride.boat"), - RIDE_MINECART("ride.minecart"), - RIDE_HORSE("ride.horse"), - RIDE_STRIDER("ride.strider"), - SHEAR, - SIT, - STAND, - TALK, - TAME, - DYE, - CURE, - OPEN_CONTAINER("opencontainer"), - CREATE_MAP("createMap"), - TAKE_PICTURE("takepicture"), - SADDLE, - MOUNT, - BOOST, - WRITE, - LEASH, - REMOVE_LEASH("unleash"), - NAME, - ATTACH_CHEST("attachchest"), - TRADE, - POSE_ARMOR_STAND("armorstand.pose"), - EQUIP_ARMOR_STAND("armorstand.equip"), - READ, - WAKE_VILLAGER("wakevillager"), - BARTER; - - /** - * The full string that should be passed on to the client. - */ - @Getter - private final String value; - - InteractiveTag(boolean isNone) { - this.value = ""; - } - - InteractiveTag(String value) { - this.value = "action.interact." + value; - } - - InteractiveTag() { - this.value = "action.interact." + name().toLowerCase(); - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java deleted file mode 100644 index c5988bf0b..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.bedrock.entity.player; - -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionPacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionRotationPacket; -import com.nukkitx.math.vector.Vector3d; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; -import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; -import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; -import org.geysermc.connector.common.ChatColor; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.PlayerEntity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - -import java.util.concurrent.TimeUnit; - -@Translator(packet = MovePlayerPacket.class) -public class BedrockMovePlayerTranslator extends PacketTranslator { - - @Override - public void translate(MovePlayerPacket packet, GeyserSession session) { - PlayerEntity entity = session.getPlayerEntity(); - if (entity == null || !session.isSpawned() || session.getPendingDimSwitches().get() > 0) return; - - if (!session.getUpstream().isInitialized()) { - MoveEntityAbsolutePacket moveEntityBack = new MoveEntityAbsolutePacket(); - moveEntityBack.setRuntimeEntityId(entity.getGeyserId()); - moveEntityBack.setPosition(entity.getPosition()); - moveEntityBack.setRotation(entity.getBedrockRotation()); - moveEntityBack.setTeleported(true); - moveEntityBack.setOnGround(true); - session.sendUpstreamPacketImmediately(moveEntityBack); - return; - } - - if (session.getMovementSendIfIdle() != null) { - session.getMovementSendIfIdle().cancel(true); - } - - Vector3d position = adjustBedrockPosition(packet.getPosition(), packet.isOnGround()); - - if(!session.confirmTeleport(position)){ - return; - } - - if (!isValidMove(session, packet.getMode(), entity.getPosition(), packet.getPosition())) { - session.getConnector().getLogger().debug("Recalculating position..."); - recalculatePosition(session, entity, entity.getPosition()); - return; - } - - ClientPlayerPositionRotationPacket playerPositionRotationPacket = new ClientPlayerPositionRotationPacket( - packet.isOnGround(), position.getX(), position.getY(), position.getZ(), packet.getRotation().getY(), packet.getRotation().getX() - ); - - // head yaw, pitch, head yaw - Vector3f rotation = Vector3f.from(packet.getRotation().getY(), packet.getRotation().getX(), packet.getRotation().getY()); - entity.setPosition(packet.getPosition().sub(0, EntityType.PLAYER.getOffset(), 0)); - entity.setRotation(rotation); - entity.setOnGround(packet.isOnGround()); - // Move parrots to match if applicable - if (entity.getLeftParrot() != null) { - entity.getLeftParrot().moveAbsolute(session, entity.getPosition(), entity.getRotation(), true, false); - } - if (entity.getRightParrot() != null) { - entity.getRightParrot().moveAbsolute(session, entity.getPosition(), entity.getRotation(), true, false); - } - - /* - boolean colliding = false; - Position position = new Position((int) packet.getPosition().getX(), - (int) Math.ceil(javaY * 2) / 2, (int) packet.getPosition().getZ()); - - BlockEntry block = session.getChunkCache().getBlockAt(position); - if (!block.getJavaIdentifier().contains("air")) - colliding = true; - - if (!colliding) - */ - session.sendDownstreamPacket(playerPositionRotationPacket); - - // Schedule a position send loop if the player is idle - session.setMovementSendIfIdle(session.getConnector().getGeneralThreadPool().schedule(() -> sendPositionIfIdle(session), - 3, TimeUnit.SECONDS)); - } - - /** - * Adjust the Bedrock position before sending to the Java server to account for inaccuracies in movement between - * the two versions. - * - * @param position the current Bedrock position of the client - * @param onGround whether the Bedrock player is on the ground - * @return the position to send to the Java server. - */ - private Vector3d adjustBedrockPosition(Vector3f position, boolean onGround) { - // We need to parse the float as a string since casting a float to a double causes us to - // lose precision and thus, causes players to get stuck when walking near walls - double javaY = position.getY() - EntityType.PLAYER.getOffset(); - if (onGround) javaY = Math.ceil(javaY * 2) / 2; - - return Vector3d.from(Double.parseDouble(Float.toString(position.getX())), javaY, - Double.parseDouble(Float.toString(position.getZ()))); - } - - public boolean isValidMove(GeyserSession session, MovePlayerPacket.Mode mode, Vector3f currentPosition, Vector3f newPosition) { - if (mode != MovePlayerPacket.Mode.NORMAL) - return true; - - double xRange = newPosition.getX() - currentPosition.getX(); - double yRange = newPosition.getY() - currentPosition.getY(); - double zRange = newPosition.getZ() - currentPosition.getZ(); - - if (xRange < 0) - xRange = -xRange; - if (yRange < 0) - yRange = -yRange; - if (zRange < 0) - zRange = -zRange; - - if ((xRange + yRange + zRange) > 100) { - session.getConnector().getLogger().debug(ChatColor.RED + session.getName() + " moved too quickly." + - " current position: " + currentPosition + ", new position: " + newPosition); - - return false; - } - - return true; - } - - public void recalculatePosition(GeyserSession session, Entity entity, Vector3f currentPosition) { - // Gravity might need to be reset... - SetEntityDataPacket entityDataPacket = new SetEntityDataPacket(); - entityDataPacket.setRuntimeEntityId(entity.getGeyserId()); - entityDataPacket.getMetadata().putAll(entity.getMetadata()); - session.sendUpstreamPacket(entityDataPacket); - - MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); - movePlayerPacket.setRuntimeEntityId(entity.getGeyserId()); - movePlayerPacket.setPosition(entity.getPosition()); - movePlayerPacket.setRotation(entity.getBedrockRotation()); - movePlayerPacket.setMode(MovePlayerPacket.Mode.RESPAWN); - session.sendUpstreamPacket(movePlayerPacket); - } - - private void sendPositionIfIdle(GeyserSession session) { - if (session.isClosed()) return; - PlayerEntity entity = session.getPlayerEntity(); - // Recalculate in case something else changed position - Vector3d position = adjustBedrockPosition(entity.getPosition(), entity.isOnGround()); - ClientPlayerPositionPacket packet = new ClientPlayerPositionPacket(session.getPlayerEntity().isOnGround(), - position.getX(), position.getY(), position.getZ()); - session.sendDownstreamPacket(packet); - session.setMovementSendIfIdle(session.getConnector().getGeneralThreadPool().schedule(() -> sendPositionIfIdle(session), - 3, TimeUnit.SECONDS)); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java deleted file mode 100644 index b7e6838e4..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.chat; - -import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; -import com.github.steveice10.mc.protocol.data.message.style.ChatColor; -import com.github.steveice10.mc.protocol.data.message.style.ChatFormat; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.renderer.TranslatableComponentRenderer; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -import net.kyori.adventure.translation.TranslationRegistry; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.utils.LanguageUtils; - -import java.util.*; - -public class MessageTranslator { - - // These are used for handling the translations of the messages - private static final TranslationRegistry REGISTRY = new MinecraftTranslationRegistry(); - private static final TranslatableComponentRenderer RENDERER = TranslatableComponentRenderer.usingTranslationSource(REGISTRY); - - // Store team colors for player names - private static final Map TEAM_COLORS = new HashMap<>(); - - static { - TEAM_COLORS.put(TeamColor.BLACK, getColor(ChatColor.BLACK)); - TEAM_COLORS.put(TeamColor.DARK_BLUE, getColor(ChatColor.DARK_BLUE)); - TEAM_COLORS.put(TeamColor.DARK_GREEN, getColor(ChatColor.DARK_GREEN)); - TEAM_COLORS.put(TeamColor.DARK_AQUA, getColor(ChatColor.DARK_AQUA)); - TEAM_COLORS.put(TeamColor.DARK_RED, getColor(ChatColor.DARK_RED)); - TEAM_COLORS.put(TeamColor.DARK_PURPLE, getColor(ChatColor.DARK_PURPLE)); - TEAM_COLORS.put(TeamColor.GOLD, getColor(ChatColor.GOLD)); - TEAM_COLORS.put(TeamColor.GRAY, getColor(ChatColor.GRAY)); - TEAM_COLORS.put(TeamColor.DARK_GRAY, getColor(ChatColor.DARK_GRAY)); - TEAM_COLORS.put(TeamColor.BLUE, getColor(ChatColor.BLUE)); - TEAM_COLORS.put(TeamColor.GREEN, getColor(ChatColor.GREEN)); - TEAM_COLORS.put(TeamColor.AQUA, getColor(ChatColor.AQUA)); - TEAM_COLORS.put(TeamColor.RED, getColor(ChatColor.RED)); - TEAM_COLORS.put(TeamColor.LIGHT_PURPLE, getColor(ChatColor.LIGHT_PURPLE)); - TEAM_COLORS.put(TeamColor.YELLOW, getColor(ChatColor.YELLOW)); - TEAM_COLORS.put(TeamColor.WHITE, getColor(ChatColor.WHITE)); - TEAM_COLORS.put(TeamColor.OBFUSCATED, getFormat(ChatFormat.OBFUSCATED)); - TEAM_COLORS.put(TeamColor.BOLD, getFormat(ChatFormat.BOLD)); - TEAM_COLORS.put(TeamColor.STRIKETHROUGH, getFormat(ChatFormat.STRIKETHROUGH)); - TEAM_COLORS.put(TeamColor.ITALIC, getFormat(ChatFormat.ITALIC)); - } - - /** - * Convert a Java message to the legacy format ready for bedrock - * - * @param message Java message - * @param locale Locale to use for translation strings - * @return Parsed and formatted message for bedrock - */ - public static String convertMessage(String message, String locale) { - Component component = GsonComponentSerializer.gson().deserialize(message); - - // Get a Locale from the given locale string - Locale localeCode = Locale.forLanguageTag(locale.replace('_', '-')); - component = RENDERER.render(component, localeCode); - - String legacy = LegacyComponentSerializer.legacySection().serialize(component); - - // Strip strikethrough and underline as they are not supported on bedrock - legacy = legacy.replaceAll("\u00a7[mn]", ""); - - // Make color codes reset formatting like Java - // See https://minecraft.gamepedia.com/Formatting_codes#Usage - legacy = legacy.replaceAll("\u00a7([0-9a-f])", "\u00a7r\u00a7$1"); - legacy = legacy.replaceAll("\u00a7r\u00a7r", "\u00a7r"); - - return legacy; - } - - public static String convertMessage(String message) { - return convertMessage(message, LanguageUtils.getDefaultLocale()); - } - - /** - * Verifies the message is valid JSON in case it's plaintext. Works around GsonComponentSeraializer not using lenient mode. - * See https://wiki.vg/Chat for messages sent in lenient mode, and for a description on leniency. - * - * @param message Potentially lenient JSON message - * @param locale Locale to use for translation strings - * @return Bedrock formatted message - */ - public static String convertMessageLenient(String message, String locale) { - if (isMessage(message)) { - return convertMessage(message, locale); - } else { - String convertedMessage = convertMessage(convertToJavaMessage(message), locale); - - // We have to do this since Adventure strips the starting reset character - if (message.startsWith(getColor(ChatColor.RESET)) && !convertedMessage.startsWith(getColor(ChatColor.RESET))) { - convertedMessage = getColor(ChatColor.RESET) + convertedMessage; - } - - return convertedMessage; - } - } - - public static String convertMessageLenient(String message) { - return convertMessageLenient(message, LanguageUtils.getDefaultLocale()); - } - - /** - * Convert a Bedrock message string back to a format Java can understand - * - * @param message Message to convert - * @return The formatted JSON string - */ - public static String convertToJavaMessage(String message) { - Component component = LegacyComponentSerializer.legacySection().deserialize(message); - return GsonComponentSerializer.gson().serialize(component); - } - - /** - * Checks if the given text string is a JSON message - * - * @param text String to test - * @return True if its a valid message JSON string, false if not - */ - public static boolean isMessage(String text) { - if (text.trim().isEmpty()) { - return false; - } - - try { - GsonComponentSerializer.gson().deserialize(text); - } catch (Exception ex) { - return false; - } - - return true; - } - - /** - * Convert a {@link ChatColor} into a string for inserting into messages - * - * @param color {@link ChatColor} to convert - * @return The converted color string - */ - private static String getColor(String color) { - String base = "\u00a7"; - switch (color) { - case ChatColor.BLACK: - base += "0"; - break; - case ChatColor.DARK_BLUE: - base += "1"; - break; - case ChatColor.DARK_GREEN: - base += "2"; - break; - case ChatColor.DARK_AQUA: - base += "3"; - break; - case ChatColor.DARK_RED: - base += "4"; - break; - case ChatColor.DARK_PURPLE: - base += "5"; - break; - case ChatColor.GOLD: - base += "6"; - break; - case ChatColor.GRAY: - base += "7"; - break; - case ChatColor.DARK_GRAY: - base += "8"; - break; - case ChatColor.BLUE: - base += "9"; - break; - case ChatColor.GREEN: - base += "a"; - break; - case ChatColor.AQUA: - base += "b"; - break; - case ChatColor.RED: - base += "c"; - break; - case ChatColor.LIGHT_PURPLE: - base += "d"; - break; - case ChatColor.YELLOW: - base += "e"; - break; - case ChatColor.WHITE: - base += "f"; - break; - case ChatColor.RESET: - base += "r"; - break; - default: - return ""; - } - - return base; - } - - /** - * Convert a {@link ChatFormat} into a string for inserting into messages - * - * @param format {@link ChatFormat} to convert - * @return The converted chat formatting string - */ - private static String getFormat(ChatFormat format) { - StringBuilder str = new StringBuilder(); - String base = "\u00a7"; - switch (format) { - case OBFUSCATED: - base += "k"; - break; - case BOLD: - base += "l"; - break; - case STRIKETHROUGH: - base += "m"; - break; - case UNDERLINED: - base += "n"; - break; - case ITALIC: - base += "o"; - break; - default: - return ""; - } - - str.append(base); - - return str.toString(); - } - - /** - * Convert a team color to a chat color - * - * @param teamColor - * @return The chat color character - */ - public static String toChatColor(TeamColor teamColor) { - return TEAM_COLORS.getOrDefault(teamColor, ""); - } - - /** - * Checks if the given message is over 256 characters (Java edition server chat limit) and sends a message to the user if it is - * - * @param message Message to check - * @param session {@link GeyserSession} for the user - * @return True if the message is too long, false if not - */ - public static boolean isTooLong(String message, GeyserSession session) { - if (message.length() > 256) { - session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.chat.too_long", session.getLocale(), message.length())); - return true; - } - - return false; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/effect/EffectRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/effect/EffectRegistry.java deleted file mode 100644 index 75cab1521..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/effect/EffectRegistry.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.effect; - -import com.fasterxml.jackson.databind.JsonNode; -import com.github.steveice10.mc.protocol.data.game.world.effect.SoundEffect; -import com.github.steveice10.mc.protocol.data.game.world.particle.ParticleType; -import com.nukkitx.protocol.bedrock.data.LevelEventType; -import com.nukkitx.protocol.bedrock.data.SoundEvent; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import lombok.NonNull; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.utils.FileUtils; - -import java.io.InputStream; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -/** - * Registry for particles and effects. - */ -public class EffectRegistry { - - public static final Map SOUND_EFFECTS = new HashMap<>(); - public static final Int2ObjectMap RECORDS = new Int2ObjectOpenHashMap<>(); - - /** - * Java particle type to Bedrock particle ID - * Used for area effect clouds. - */ - private static final Object2IntMap PARTICLE_TO_ID = new Object2IntOpenHashMap<>(); - /** - * Java particle type to Bedrock level event - */ - private static final Map PARTICLE_TO_LEVEL_EVENT = new HashMap<>(); - /** - * Java particle type to Bedrock namespaced string ID - */ - private static final Map PARTICLE_TO_STRING = new HashMap<>(); - - public static void init() { - // no-op - } - - static { - /* Load particles */ - InputStream particleStream = FileUtils.getResource("mappings/particles.json"); - JsonNode particleEntries; - try { - particleEntries = GeyserConnector.JSON_MAPPER.readTree(particleStream); - } catch (Exception e) { - throw new AssertionError("Unable to load particle map", e); - } - - Iterator> particlesIterator = particleEntries.fields(); - try { - while (particlesIterator.hasNext()) { - Map.Entry entry = particlesIterator.next(); - JsonNode bedrockId = entry.getValue().get("bedrockId"); - JsonNode bedrockIdNumeric = entry.getValue().get("bedrockNumericId"); - JsonNode eventType = entry.getValue().get("eventType"); - if (bedrockIdNumeric != null) { - PARTICLE_TO_ID.put(ParticleType.valueOf(entry.getKey().toUpperCase()), bedrockIdNumeric.asInt()); - } - if (bedrockId != null) { - PARTICLE_TO_STRING.put(ParticleType.valueOf(entry.getKey().toUpperCase()), bedrockId.asText()); - } - if (eventType != null) { - PARTICLE_TO_LEVEL_EVENT.put(ParticleType.valueOf(entry.getKey().toUpperCase()), LevelEventType.valueOf(eventType.asText().toUpperCase())); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - - /* Load effects */ - InputStream effectsStream = FileUtils.getResource("mappings/effects.json"); - JsonNode effects; - try { - effects = GeyserConnector.JSON_MAPPER.readTree(effectsStream); - } catch (Exception e) { - throw new AssertionError("Unable to load effects mappings", e); - } - - Iterator> effectsIterator = effects.fields(); - while (effectsIterator.hasNext()) { - Map.Entry entry = effectsIterator.next(); - JsonNode node = entry.getValue(); - try { - String type = node.get("type").asText(); - SoundEffect javaEffect = null; - Effect effect = null; - switch (type) { - case "soundLevel": { - javaEffect = SoundEffect.valueOf(entry.getKey()); - LevelEventType levelEventType = LevelEventType.valueOf(node.get("name").asText()); - int data = node.has("data") ? node.get("data").intValue() : 0; - effect = new SoundLevelEffect(levelEventType, data); - break; - } - case "soundEvent": { - javaEffect = SoundEffect.valueOf(entry.getKey()); - SoundEvent soundEvent = SoundEvent.valueOf(node.get("name").asText()); - String identifier = node.has("identifier") ? node.get("identifier").asText() : ""; - int extraData = node.has("extraData") ? node.get("extraData").intValue() : -1; - effect = new SoundEventEffect(soundEvent, identifier, extraData); - break; - } - case "playSound": { - javaEffect = SoundEffect.valueOf(entry.getKey()); - String name = node.get("name").asText(); - float volume = node.has("volume") ? node.get("volume").floatValue() : 1.0f; - boolean pitchSub = node.has("pitch_sub") ? node.get("pitch_sub").booleanValue() : false; - float pitchMul = node.has("pitch_mul") ? node.get("pitch_mul").floatValue() : 1.0f; - float pitchAdd = node.has("pitch_add") ? node.get("pitch_add").floatValue() : 0.0f; - boolean relative = node.has("relative") ? node.get("relative").booleanValue() : true; - effect = new PlaySoundEffect(name, volume, pitchSub, pitchMul, pitchAdd, relative); - break; - } - case "record": { - JsonNode records = entry.getValue().get("records"); - Iterator> recordsIterator = records.fields(); - while (recordsIterator.hasNext()) { - Map.Entry recordEntry = recordsIterator.next(); - RECORDS.put(Integer.parseInt(recordEntry.getKey()), SoundEvent.valueOf(recordEntry.getValue().asText())); - } - break; - } - } - if (javaEffect != null) { - SOUND_EFFECTS.put(javaEffect, effect); - } - } catch (Exception e) { - GeyserConnector.getInstance().getLogger().warning("Failed to map sound effect " + entry.getKey() + " : " + e.toString()); - } - } - } - - /** - * @param type the Java particle to search for - * @return the Bedrock integer ID of the particle, or -1 if it does not exist - */ - public static int getParticleId(@NonNull ParticleType type) { - return PARTICLE_TO_ID.getOrDefault(type, -1); - } - - /** - * @param type the Java particle to search for - * @return the level event equivalent Bedrock particle - */ - public static LevelEventType getParticleLevelEventType(@NonNull ParticleType type) { - return PARTICLE_TO_LEVEL_EVENT.getOrDefault(type, null); - } - - /** - * @param type the Java particle to search for - * @return the namespaced ID equivalent for Bedrock - */ - public static String getParticleString(@NonNull ParticleType type) { - return PARTICLE_TO_STRING.getOrDefault(type, null); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/effect/PlaySoundEffect.java b/connector/src/main/java/org/geysermc/connector/network/translators/effect/PlaySoundEffect.java deleted file mode 100644 index c82bca332..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/effect/PlaySoundEffect.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - * - */ - -package org.geysermc.connector.network.translators.effect; - -import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerPlayEffectPacket; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.packet.PlaySoundPacket; -import lombok.Value; -import org.geysermc.connector.network.session.GeyserSession; - -import java.util.Random; -import java.util.concurrent.ThreadLocalRandom; - -@Value -public class PlaySoundEffect implements Effect { - /** - * Bedrock playsound identifier - */ - String name; - - /** - * Volume of the sound - */ - float volume; - - /** - * If true, the initial value used for random pitch is the difference between two random floats. - * If false, it is a single random float - */ - boolean pitchSub; - - /** - * Multiplier for random pitch value - */ - float pitchMul; - - /** - * Constant addition to random pitch value after multiplier - */ - float pitchAdd; - - /** - * True if the sound is meant to be played in 3d space - */ - boolean relative; - - @Override - public void handleEffectPacket(GeyserSession session, ServerPlayEffectPacket packet) { - Random rand = ThreadLocalRandom.current(); - PlaySoundPacket playSoundPacket = new PlaySoundPacket(); - playSoundPacket.setSound(name); - playSoundPacket.setPosition(!relative ? session.getPlayerEntity().getPosition() : Vector3f.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ()).add(0.5f, 0.5f, 0.5f)); - playSoundPacket.setVolume(volume); - playSoundPacket.setPitch((pitchSub ? (rand.nextFloat() - rand.nextFloat()) : rand.nextFloat()) * pitchMul + pitchAdd); //replicates java client randomness - session.sendUpstreamPacket(playSoundPacket); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/effect/SoundEventEffect.java b/connector/src/main/java/org/geysermc/connector/network/translators/effect/SoundEventEffect.java deleted file mode 100644 index 77641e37f..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/effect/SoundEventEffect.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - * - */ - -package org.geysermc.connector.network.translators.effect; - -import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerPlayEffectPacket; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.SoundEvent; -import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; -import lombok.Value; -import org.geysermc.connector.network.session.GeyserSession; - -@Value -public class SoundEventEffect implements Effect { - /** - * Bedrock sound event - */ - SoundEvent soundEvent; - - /** - * Entity identifier. Usually an empty string - */ - String identifier; - - /** - * Extra data. Usually -1 - */ - int extraData; - - @Override - public void handleEffectPacket(GeyserSession session, ServerPlayEffectPacket packet) { - LevelSoundEventPacket levelSoundEvent = new LevelSoundEventPacket(); - levelSoundEvent.setSound(soundEvent); - levelSoundEvent.setIdentifier(identifier); - levelSoundEvent.setExtraData(extraData); - levelSoundEvent.setRelativeVolumeDisabled(packet.isBroadcast()); - levelSoundEvent.setPosition(Vector3f.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ()).add(0.5f, 0.5f, 0.5f)); - levelSoundEvent.setBabySound(false); - session.sendUpstreamPacket(levelSoundEvent); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java deleted file mode 100644 index ab266faef..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.inventory; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientRenameItemPacket; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.google.gson.JsonSyntaxException; -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.protocol.bedrock.data.inventory.*; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater; - -import java.util.List; -import java.util.stream.Collectors; - -public class AnvilInventoryTranslator extends BlockInventoryTranslator { - public AnvilInventoryTranslator() { - super(3, "minecraft:anvil[facing=north]", ContainerType.ANVIL, new CursorInventoryUpdater()); - } - - @Override - public int bedrockSlotToJava(InventoryActionData action) { - if (action.getSource().getContainerId() == ContainerId.UI) { - switch (action.getSlot()) { - case 1: - return 0; - case 2: - return 1; - case 50: - return 2; - } - } - if (action.getSource().getContainerId() == ContainerId.ANVIL_RESULT) { - return 2; - } - return super.bedrockSlotToJava(action); - } - - @Override - public int javaSlotToBedrock(int slot) { - switch (slot) { - case 0: - return 1; - case 1: - return 2; - case 2: - return 50; - } - return super.javaSlotToBedrock(slot); - } - - @Override - public SlotType getSlotType(int javaSlot) { - if (javaSlot == 2) - return SlotType.OUTPUT; - return SlotType.NORMAL; - } - - @Override - public void translateActions(GeyserSession session, Inventory inventory, List actions) { - InventoryActionData anvilResult = null; - InventoryActionData anvilInput = null; - for (InventoryActionData action : actions) { - if (action.getSource().getContainerId() == ContainerId.ANVIL_MATERIAL) { - //useless packet - return; - } else if (action.getSource().getContainerId() == ContainerId.ANVIL_RESULT) { - anvilResult = action; - } else if (bedrockSlotToJava(action) == 0) { - anvilInput = action; - } - } - ItemData itemName = null; - if (anvilResult != null) { - itemName = anvilResult.getFromItem(); - } else if (anvilInput != null) { - itemName = anvilInput.getToItem(); - } - if (itemName != null) { - String rename; - NbtMap tag = itemName.getTag(); - if (tag != null && tag.containsKey("display")) { - String name = tag.getCompound("display").getString("Name"); - try { - Component component = GsonComponentSerializer.gson().deserialize(name); - rename = LegacyComponentSerializer.legacySection().serialize(component); - } catch (JsonSyntaxException e) { - rename = name; - } - } else { - rename = ""; - } - ClientRenameItemPacket renameItemPacket = new ClientRenameItemPacket(rename); - session.sendDownstreamPacket(renameItemPacket); - } - if (anvilResult != null) { - //Strip unnecessary actions - List strippedActions = actions.stream() - .filter(action -> action.getSource().getContainerId() == ContainerId.ANVIL_RESULT - || (action.getSource().getType() == InventorySource.Type.CONTAINER - && !(action.getSource().getContainerId() == ContainerId.UI && action.getSlot() != 0))) - .collect(Collectors.toList()); - super.translateActions(session, inventory, strippedActions); - return; - } - - super.translateActions(session, inventory, actions); - } - - @Override - public void updateSlot(GeyserSession session, Inventory inventory, int slot) { - if (slot == 0) { - ItemStack item = inventory.getItem(slot); - if (item != null) { - String rename; - CompoundTag tag = item.getNbt(); - if (tag != null) { - CompoundTag displayTag = tag.get("display"); - if (displayTag != null && displayTag.contains("Name")) { - String itemName = displayTag.get("Name").getValue().toString(); - try { - Component component = GsonComponentSerializer.gson().deserialize(itemName); - rename = LegacyComponentSerializer.legacySection().serialize(component); - } catch (JsonSyntaxException e) { - rename = itemName; - } - } else { - rename = ""; - } - } else { - rename = ""; - } - ClientRenameItemPacket renameItemPacket = new ClientRenameItemPacket(rename); - session.sendDownstreamPacket(renameItemPacket); - } - } - super.updateSlot(session, inventory, slot); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java deleted file mode 100644 index b260565b8..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.inventory; - -import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; -import com.nukkitx.protocol.bedrock.data.inventory.InventorySource; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater; -import org.geysermc.connector.utils.InventoryUtils; - -import java.util.List; - -public class CraftingInventoryTranslator extends BlockInventoryTranslator { - public CraftingInventoryTranslator() { - super(10, "minecraft:crafting_table", ContainerType.WORKBENCH, new CursorInventoryUpdater()); - } - - @Override - public int bedrockSlotToJava(InventoryActionData action) { - if (action.getSlot() == 50) { - // Slot 50 is used for crafting with a controller. - return 0; - } - - if (action.getSource().getContainerId() == ContainerId.UI) { - int slotnum = action.getSlot(); - if (slotnum >= 32 && 42 >= slotnum) { - return slotnum - 31; - } - } - return super.bedrockSlotToJava(action); - } - - @Override - public int javaSlotToBedrock(int slot) { - if (slot < size) { - return slot == 0 ? 50 : slot + 31; - } - return super.javaSlotToBedrock(slot); - } - - @Override - public SlotType getSlotType(int javaSlot) { - if (javaSlot == 0) - return SlotType.OUTPUT; - return SlotType.NORMAL; - } - - @Override - public void translateActions(GeyserSession session, Inventory inventory, List actions) { - if (session.getGameMode() == GameMode.CREATIVE) { - for (InventoryActionData action : actions) { - if (action.getSource().getType() == InventorySource.Type.CREATIVE) { - updateInventory(session, inventory); - InventoryUtils.updateCursor(session); - return; - } - } - } - super.translateActions(session, inventory, actions); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java deleted file mode 100644 index 20a47fd02..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.inventory; - -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket; -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.nbt.NbtMapBuilder; -import com.nukkitx.nbt.NbtType; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; -import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; -import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.ToString; -import org.geysermc.connector.common.ChatColor; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.geysermc.connector.utils.InventoryUtils; -import org.geysermc.connector.utils.LocaleUtils; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * A temporary reconstruction of the enchantment table UI until our inventory rewrite is complete. - * The enchantment table on Bedrock without server authoritative inventories doesn't tell us which button is pressed - * when selecting an enchantment. - */ -public class EnchantmentInventoryTranslator extends BlockInventoryTranslator { - - private static final int DYE_ID = ItemRegistry.getItemEntry("minecraft:lapis_lazuli").getBedrockId(); - private static final int ENCHANTED_BOOK_ID = ItemRegistry.getItemEntry("minecraft:enchanted_book").getBedrockId(); - - public EnchantmentInventoryTranslator(InventoryUpdater updater) { - super(2, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER, updater); - } - - @Override - public void translateActions(GeyserSession session, Inventory inventory, List actions) { - for (InventoryActionData action : actions) { - if (action.getSource().getContainerId() == inventory.getId()) { - // This is the hopper UI - switch (action.getSlot()) { - case 1: - // Don't allow the slot to be put through if the item isn't lapis - if ((action.getToItem().getId() != DYE_ID) && action.getToItem() != ItemData.AIR) { - updateInventory(session, inventory); - InventoryUtils.updateCursor(session); - return; - } - break; - case 2: - case 3: - case 4: - // The books here act as buttons - ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), action.getSlot() - 2); - session.sendDownstreamPacket(packet); - updateInventory(session, inventory); - InventoryUtils.updateCursor(session); - return; - default: - break; - } - } - } - - super.translateActions(session, inventory, actions); - } - - @Override - public void updateInventory(GeyserSession session, Inventory inventory) { - super.updateInventory(session, inventory); - ItemData[] items = new ItemData[5]; - items[0] = ItemTranslator.translateToBedrock(session, inventory.getItem(0)); - items[1] = ItemTranslator.translateToBedrock(session, inventory.getItem(1)); - for (int i = 0; i < 3; i++) { - items[i + 2] = session.getEnchantmentSlotData()[i].getItem() != null ? session.getEnchantmentSlotData()[i].getItem() : createEnchantmentBook(); - } - - InventoryContentPacket contentPacket = new InventoryContentPacket(); - contentPacket.setContainerId(inventory.getId()); - contentPacket.setContents(items); - session.sendUpstreamPacket(contentPacket); - } - - @Override - public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { - int bookSlotToUpdate; - switch (key) { - case 0: - case 1: - case 2: - // Experience required - bookSlotToUpdate = key; - session.getEnchantmentSlotData()[bookSlotToUpdate].setExperienceRequired(value); - break; - case 4: - case 5: - case 6: - // Enchantment name - bookSlotToUpdate = key - 4; - if (value != -1) { - session.getEnchantmentSlotData()[bookSlotToUpdate].setEnchantmentType(EnchantmentTableEnchantments.values()[value - 1]); - } else { - // -1 means no enchantment specified - session.getEnchantmentSlotData()[bookSlotToUpdate].setEnchantmentType(null); - } - break; - case 7: - case 8: - case 9: - // Enchantment level - bookSlotToUpdate = key - 7; - session.getEnchantmentSlotData()[bookSlotToUpdate].setEnchantmentLevel(value); - break; - default: - return; - } - updateEnchantmentBook(session, inventory, bookSlotToUpdate); - } - - @Override - public void openInventory(GeyserSession session, Inventory inventory) { - super.openInventory(session, inventory); - for (int i = 0; i < session.getEnchantmentSlotData().length; i++) { - session.getEnchantmentSlotData()[i] = new EnchantmentSlotData(); - } - } - - @Override - public void closeInventory(GeyserSession session, Inventory inventory) { - super.closeInventory(session, inventory); - Arrays.fill(session.getEnchantmentSlotData(), null); - } - - private ItemData createEnchantmentBook() { - NbtMapBuilder root = NbtMap.builder(); - NbtMapBuilder display = NbtMap.builder(); - - display.putString("Name", ChatColor.RESET + "No Enchantment"); - - root.put("display", display.build()); - return ItemData.of(ENCHANTED_BOOK_ID, (short) 0, 1, root.build()); - } - - private void updateEnchantmentBook(GeyserSession session, Inventory inventory, int slot) { - NbtMapBuilder root = NbtMap.builder(); - NbtMapBuilder display = NbtMap.builder(); - EnchantmentSlotData data = session.getEnchantmentSlotData()[slot]; - if (data.getEnchantmentType() != null) { - display.putString("Name", ChatColor.ITALIC + data.getEnchantmentType().toString(session) + - (data.getEnchantmentLevel() != -1 ? " " + toRomanNumeral(session, data.getEnchantmentLevel()) : "") + "?"); - } else { - display.putString("Name", ChatColor.RESET + "No Enchantment"); - } - - display.putList("Lore", NbtType.STRING, Collections.singletonList(ChatColor.DARK_GRAY + data.getExperienceRequired() + "xp")); - root.put("display", display.build()); - ItemData book = ItemData.of(ENCHANTED_BOOK_ID, (short) 0, 1, root.build()); - - InventorySlotPacket slotPacket = new InventorySlotPacket(); - slotPacket.setContainerId(inventory.getId()); - slotPacket.setSlot(slot + 2); - slotPacket.setItem(book); - session.sendUpstreamPacket(slotPacket); - data.setItem(book); - } - - private String toRomanNumeral(GeyserSession session, int level) { - return LocaleUtils.getLocaleString("enchantment.level." + level, - session.getLocale()); - } - - /** - * Stores the data of each slot in an enchantment table - */ - @NoArgsConstructor - @Getter - @Setter - @ToString - public static class EnchantmentSlotData { - private EnchantmentTableEnchantments enchantmentType = null; - private int enchantmentLevel = 0; - private int experienceRequired = 0; - private ItemData item; - } - - /** - * Classifies enchantments by Java order - */ - public enum EnchantmentTableEnchantments { - PROTECTION, - FIRE_PROTECTION, - FEATHER_FALLING, - BLAST_PROTECTION, - PROJECTILE_PROTECTION, - RESPIRATION, - AQUA_AFFINITY, - THORNS, - DEPTH_STRIDER, - FROST_WALKER, - BINDING_CURSE, - SHARPNESS, - SMITE, - BANE_OF_ARTHROPODS, - KNOCKBACK, - FIRE_ASPECT, - LOOTING, - SWEEPING, - EFFICIENCY, - SILK_TOUCH, - UNBREAKING, - FORTUNE, - POWER, - PUNCH, - FLAME, - INFINITY, - LUCK_OF_THE_SEA, - LURE, - LOYALTY, - IMPALING, - RIPTIDE, - CHANNELING, - MENDING, - VANISHING_CURSE, // After this is not documented - MULTISHOT, - PIERCING, - QUICK_CHARGE, - SOUL_SPEED; - - public String toString(GeyserSession session) { - return LocaleUtils.getLocaleString("enchantment.minecraft." + this.toString().toLowerCase(), - session.getLocale()); - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/GrindstoneInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/GrindstoneInventoryTranslator.java deleted file mode 100644 index 4b4a12465..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/GrindstoneInventoryTranslator.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.inventory; - -import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; -import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater; - -public class GrindstoneInventoryTranslator extends BlockInventoryTranslator { - - public GrindstoneInventoryTranslator() { - super(3, "minecraft:grindstone[face=floor,facing=north]", ContainerType.GRINDSTONE, new CursorInventoryUpdater()); - } - - @Override - public int bedrockSlotToJava(InventoryActionData action) { - final int slot = super.bedrockSlotToJava(action); - if (action.getSource().getContainerId() == ContainerId.UI) { - switch (slot) { - case 16: - return 0; - case 17: - return 1; - case 50: - return 2; - default: - return slot; - } - } return slot; - } - - @Override - public int javaSlotToBedrock(int slot) { - switch (slot) { - case 0: - return 16; - case 1: - return 17; - case 2: - return 50; - } - return super.javaSlotToBedrock(slot); - } - -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java deleted file mode 100644 index 3016871f0..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.inventory; - -import com.github.steveice10.mc.protocol.data.game.window.WindowType; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; -import lombok.AllArgsConstructor; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater; -import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@AllArgsConstructor -public abstract class InventoryTranslator { - - public static final Map INVENTORY_TRANSLATORS = new HashMap() { - { - put(null, new PlayerInventoryTranslator()); //player inventory - put(WindowType.GENERIC_9X1, new SingleChestInventoryTranslator(9)); - put(WindowType.GENERIC_9X2, new SingleChestInventoryTranslator(18)); - put(WindowType.GENERIC_9X3, new SingleChestInventoryTranslator(27)); - put(WindowType.GENERIC_9X4, new DoubleChestInventoryTranslator(36)); - put(WindowType.GENERIC_9X5, new DoubleChestInventoryTranslator(45)); - put(WindowType.GENERIC_9X6, new DoubleChestInventoryTranslator(54)); - put(WindowType.BREWING_STAND, new BrewingInventoryTranslator()); - put(WindowType.ANVIL, new AnvilInventoryTranslator()); - put(WindowType.CRAFTING, new CraftingInventoryTranslator()); - //put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator()); //FIXME - put(WindowType.MERCHANT, new MerchantInventoryTranslator()); - //put(WindowType.SMITHING, new SmithingInventoryTranslator()); //TODO for server authoritative inventories - - InventoryTranslator furnace = new FurnaceInventoryTranslator(); - put(WindowType.FURNACE, furnace); - put(WindowType.BLAST_FURNACE, furnace); - put(WindowType.SMOKER, furnace); - - InventoryUpdater containerUpdater = new ContainerInventoryUpdater(); - put(WindowType.ENCHANTMENT, new EnchantmentInventoryTranslator(containerUpdater)); //TODO - put(WindowType.GENERIC_3X3, new BlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER, containerUpdater)); - put(WindowType.HOPPER, new BlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER, containerUpdater)); - put(WindowType.SHULKER_BOX, new BlockInventoryTranslator(27, "minecraft:shulker_box[facing=north]", ContainerType.CONTAINER, containerUpdater)); - //put(WindowType.BEACON, new BlockInventoryTranslator(1, "minecraft:beacon", ContainerType.BEACON)); //TODO - } - }; - - public final int size; - - public abstract void prepareInventory(GeyserSession session, Inventory inventory); - public abstract void openInventory(GeyserSession session, Inventory inventory); - public abstract void closeInventory(GeyserSession session, Inventory inventory); - public abstract void updateProperty(GeyserSession session, Inventory inventory, int key, int value); - public abstract void updateInventory(GeyserSession session, Inventory inventory); - public abstract void updateSlot(GeyserSession session, Inventory inventory, int slot); - public abstract int bedrockSlotToJava(InventoryActionData action); - public abstract int javaSlotToBedrock(int slot); - public abstract SlotType getSlotType(int javaSlot); - public abstract void translateActions(GeyserSession session, Inventory inventory, List actions); -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/MerchantInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/MerchantInventoryTranslator.java deleted file mode 100644 index 25fdae688..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/MerchantInventoryTranslator.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.inventory; - -import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; -import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; -import com.nukkitx.protocol.bedrock.data.inventory.InventorySource; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater; -import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; - -import java.util.List; - -public class MerchantInventoryTranslator extends BaseInventoryTranslator { - - private final InventoryUpdater updater; - - public MerchantInventoryTranslator() { - super(3); - this.updater = new CursorInventoryUpdater(); - } - - @Override - public int javaSlotToBedrock(int slot) { - switch (slot) { - case 0: - return 4; - case 1: - return 5; - case 2: - return 50; - } - return super.javaSlotToBedrock(slot); - } - - @Override - public int bedrockSlotToJava(InventoryActionData action) { - switch (action.getSource().getContainerId()) { - case ContainerId.UI: - switch (action.getSlot()) { - case 4: - return 0; - case 5: - return 1; - case 50: - return 2; - } - break; - case -28: // Trading 1? - return 0; - case -29: // Trading 2? - return 1; - case -30: // Trading Output? - return 2; - } - return super.bedrockSlotToJava(action); - } - - @Override - public SlotType getSlotType(int javaSlot) { - if (javaSlot == 2) { - return SlotType.OUTPUT; - } - return SlotType.NORMAL; - } - - @Override - public void prepareInventory(GeyserSession session, Inventory inventory) { - - } - - @Override - public void openInventory(GeyserSession session, Inventory inventory) { - - } - - @Override - public void closeInventory(GeyserSession session, Inventory inventory) { - session.setLastInteractedVillagerEid(-1); - session.setVillagerTrades(null); - } - - @Override - public void updateInventory(GeyserSession session, Inventory inventory) { - updater.updateInventory(this, session, inventory); - } - - @Override - public void updateSlot(GeyserSession session, Inventory inventory, int slot) { - updater.updateSlot(this, session, inventory, slot); - } - - @Override - public void translateActions(GeyserSession session, Inventory inventory, List actions) { - if (actions.stream().anyMatch(a -> a.getSource().getContainerId() == -31)) { - return; - } - - super.translateActions(session, inventory, actions); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/PlayerInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/PlayerInventoryTranslator.java deleted file mode 100644 index 7d7673c4e..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/PlayerInventoryTranslator.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.inventory; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCreativeInventoryActionPacket; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; -import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; -import com.nukkitx.protocol.bedrock.data.inventory.InventorySource; -import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; -import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.action.InventoryActionDataTranslator; -import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.geysermc.connector.utils.InventoryUtils; -import org.geysermc.connector.utils.LanguageUtils; - -import java.util.List; - -public class PlayerInventoryTranslator extends InventoryTranslator { - private static final ItemData UNUSUABLE_CRAFTING_SPACE_BLOCK = InventoryUtils.createUnusableSpaceBlock(LanguageUtils.getLocaleStringLog("geyser.inventory.unusable_item.creative")); - - public PlayerInventoryTranslator() { - super(46); - } - - @Override - public void updateInventory(GeyserSession session, Inventory inventory) { - updateCraftingGrid(session, inventory); - - InventoryContentPacket inventoryContentPacket = new InventoryContentPacket(); - inventoryContentPacket.setContainerId(ContainerId.INVENTORY); - ItemData[] contents = new ItemData[36]; - // Inventory - for (int i = 9; i < 36; i++) { - contents[i] = ItemTranslator.translateToBedrock(session, inventory.getItem(i)); - } - // Hotbar - for (int i = 36; i < 45; i++) { - contents[i - 36] = ItemTranslator.translateToBedrock(session, inventory.getItem(i)); - } - inventoryContentPacket.setContents(contents); - session.sendUpstreamPacket(inventoryContentPacket); - - // Armor - InventoryContentPacket armorContentPacket = new InventoryContentPacket(); - armorContentPacket.setContainerId(ContainerId.ARMOR); - contents = new ItemData[4]; - for (int i = 5; i < 9; i++) { - contents[i - 5] = ItemTranslator.translateToBedrock(session, inventory.getItem(i)); - } - armorContentPacket.setContents(contents); - session.sendUpstreamPacket(armorContentPacket); - - // Offhand - InventoryContentPacket offhandPacket = new InventoryContentPacket(); - offhandPacket.setContainerId(ContainerId.OFFHAND); - offhandPacket.setContents(new ItemData[]{ItemTranslator.translateToBedrock(session, inventory.getItem(45))}); - session.sendUpstreamPacket(offhandPacket); - } - - /** - * Update the crafting grid for the player to hide/show the barriers in the creative inventory - * @param session Session of the player - * @param inventory Inventory of the player - */ - public static void updateCraftingGrid(GeyserSession session, Inventory inventory) { - // Crafting grid - for (int i = 1; i < 5; i++) { - InventorySlotPacket slotPacket = new InventorySlotPacket(); - slotPacket.setContainerId(ContainerId.UI); - slotPacket.setSlot(i + 27); - - if (session.getGameMode() == GameMode.CREATIVE) { - slotPacket.setItem(UNUSUABLE_CRAFTING_SPACE_BLOCK); - }else{ - slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(i))); - } - - session.sendUpstreamPacket(slotPacket); - } - } - - @Override - public void updateSlot(GeyserSession session, Inventory inventory, int slot) { - if (slot >= 1 && slot <= 44) { - InventorySlotPacket slotPacket = new InventorySlotPacket(); - if (slot >= 9) { - slotPacket.setContainerId(ContainerId.INVENTORY); - if (slot >= 36) { - slotPacket.setSlot(slot - 36); - } else { - slotPacket.setSlot(slot); - } - } else if (slot >= 5) { - slotPacket.setContainerId(ContainerId.ARMOR); - slotPacket.setSlot(slot - 5); - } else { - slotPacket.setContainerId(ContainerId.UI); - slotPacket.setSlot(slot + 27); - } - slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(slot))); - session.sendUpstreamPacket(slotPacket); - } else if (slot == 45) { - InventoryContentPacket offhandPacket = new InventoryContentPacket(); - offhandPacket.setContainerId(ContainerId.OFFHAND); - offhandPacket.setContents(new ItemData[]{ItemTranslator.translateToBedrock(session, inventory.getItem(slot))}); - session.sendUpstreamPacket(offhandPacket); - } - } - - @Override - public int bedrockSlotToJava(InventoryActionData action) { - int slotnum = action.getSlot(); - switch (action.getSource().getContainerId()) { - case ContainerId.INVENTORY: - // Inventory - if (slotnum >= 9 && slotnum <= 35) { - return slotnum; - } - // Hotbar - if (slotnum >= 0 && slotnum <= 8) { - return slotnum + 36; - } - break; - case ContainerId.ARMOR: - if (slotnum >= 0 && slotnum <= 3) { - return slotnum + 5; - } - break; - case ContainerId.OFFHAND: - return 45; - case ContainerId.UI: - if (slotnum >= 28 && 31 >= slotnum) { - return slotnum - 27; - } - break; - case ContainerId.CRAFTING_RESULT: - return 0; - } - return slotnum; - } - - @Override - public int javaSlotToBedrock(int slot) { - return slot; - } - - @Override - public SlotType getSlotType(int javaSlot) { - if (javaSlot == 0) - return SlotType.OUTPUT; - return SlotType.NORMAL; - } - - @Override - public void translateActions(GeyserSession session, Inventory inventory, List actions) { - if (session.getGameMode() == GameMode.CREATIVE) { - //crafting grid is not visible in creative mode in java edition - for (InventoryActionData action : actions) { - if (action.getSource().getContainerId() == ContainerId.UI && (action.getSlot() >= 28 && 31 >= action.getSlot())) { - updateInventory(session, inventory); - InventoryUtils.updateCursor(session); - return; - } - } - - ItemStack javaItem; - for (InventoryActionData action : actions) { - switch (action.getSource().getContainerId()) { - case ContainerId.INVENTORY: - case ContainerId.ARMOR: - case ContainerId.OFFHAND: - int javaSlot = bedrockSlotToJava(action); - if (action.getToItem().getId() == 0) { - javaItem = new ItemStack(-1, 0, null); - } else { - javaItem = ItemTranslator.translateToJava(action.getToItem()); - } - ClientCreativeInventoryActionPacket creativePacket = new ClientCreativeInventoryActionPacket(javaSlot, javaItem); - session.sendDownstreamPacket(creativePacket); - inventory.setItem(javaSlot, javaItem); - break; - case ContainerId.UI: - if (action.getSlot() == 0) { - session.getInventory().setCursor(ItemTranslator.translateToJava(action.getToItem())); - } - break; - case ContainerId.NONE: - if (action.getSource().getType() == InventorySource.Type.WORLD_INTERACTION - && action.getSource().getFlag() == InventorySource.Flag.DROP_ITEM) { - javaItem = ItemTranslator.translateToJava(action.getToItem()); - ClientCreativeInventoryActionPacket creativeDropPacket = new ClientCreativeInventoryActionPacket(-1, javaItem); - session.sendDownstreamPacket(creativeDropPacket); - } - break; - } - } - return; - } - - InventoryActionDataTranslator.translate(this, session, inventory, actions); - } - - @Override - public void prepareInventory(GeyserSession session, Inventory inventory) { - } - - @Override - public void openInventory(GeyserSession session, Inventory inventory) { - } - - @Override - public void closeInventory(GeyserSession session, Inventory inventory) { - } - - @Override - public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SmithingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SmithingInventoryTranslator.java deleted file mode 100644 index f7f0acd8c..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SmithingInventoryTranslator.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.inventory; - -import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; -import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater; - -public class SmithingInventoryTranslator extends BlockInventoryTranslator { - - public SmithingInventoryTranslator() { - super(3, "minecraft:smithing_table", ContainerType.SMITHING_TABLE, new CursorInventoryUpdater()); - } - - @Override - public int bedrockSlotToJava(InventoryActionData action) { - final int slot = super.bedrockSlotToJava(action); - if (action.getSource().getContainerId() == ContainerId.UI) { - switch (slot) { - case 51: - return 0; - case 52: - return 1; - case 50: - return 2; - default: - return slot; - } - } return slot; - } - - @Override - public int javaSlotToBedrock(int slot) { - switch (slot) { - case 0: - return 51; - case 1: - return 52; - case 2: - return 50; - } - return super.javaSlotToBedrock(slot); - } - -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/Click.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/Click.java deleted file mode 100644 index 1fdfa3640..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/Click.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.inventory.action; - -import com.github.steveice10.mc.protocol.data.game.window.ClickItemParam; -import com.github.steveice10.mc.protocol.data.game.window.WindowActionParam; -import lombok.AllArgsConstructor; - -@AllArgsConstructor -enum Click { - LEFT(ClickItemParam.LEFT_CLICK), - RIGHT(ClickItemParam.RIGHT_CLICK); - - public final WindowActionParam actionParam; -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/ClickPlan.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/ClickPlan.java deleted file mode 100644 index a9c1eddca..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/ClickPlan.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.inventory.action; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.data.game.window.WindowAction; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientConfirmTransactionPacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientWindowActionPacket; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.inventory.PlayerInventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.InventoryTranslator; -import org.geysermc.connector.network.translators.inventory.SlotType; -import org.geysermc.connector.utils.InventoryUtils; - -import java.util.ArrayList; -import java.util.List; -import java.util.ListIterator; - -class ClickPlan { - private final List plan = new ArrayList<>(); - - public void add(Click click, int slot) { - plan.add(new ClickAction(click, slot)); - } - - public void execute(GeyserSession session, InventoryTranslator translator, Inventory inventory, boolean refresh) { - PlayerInventory playerInventory = session.getInventory(); - ListIterator planIter = plan.listIterator(); - while (planIter.hasNext()) { - final ClickAction action = planIter.next(); - final ItemStack cursorItem = playerInventory.getCursor(); - final ItemStack clickedItem = inventory.getItem(action.slot); - final short actionId = (short) inventory.getTransactionId().getAndIncrement(); - - //TODO: stop relying on refreshing the inventory for crafting to work properly - if (translator.getSlotType(action.slot) != SlotType.NORMAL) - refresh = true; - - ClientWindowActionPacket clickPacket = new ClientWindowActionPacket(inventory.getId(), - actionId, action.slot, !planIter.hasNext() && refresh ? InventoryUtils.REFRESH_ITEM : clickedItem, - WindowAction.CLICK_ITEM, action.click.actionParam); - - if (translator.getSlotType(action.slot) == SlotType.OUTPUT) { - if (cursorItem == null && clickedItem != null) { - playerInventory.setCursor(clickedItem); - } else if (InventoryUtils.canStack(cursorItem, clickedItem)) { - playerInventory.setCursor(new ItemStack(cursorItem.getId(), - cursorItem.getAmount() + clickedItem.getAmount(), cursorItem.getNbt())); - } - } else { - switch (action.click) { - case LEFT: - if (!InventoryUtils.canStack(cursorItem, clickedItem)) { - playerInventory.setCursor(clickedItem); - inventory.setItem(action.slot, cursorItem); - } else { - playerInventory.setCursor(null); - inventory.setItem(action.slot, new ItemStack(clickedItem.getId(), - clickedItem.getAmount() + cursorItem.getAmount(), clickedItem.getNbt())); - } - break; - case RIGHT: - if (cursorItem == null && clickedItem != null) { - ItemStack halfItem = new ItemStack(clickedItem.getId(), - clickedItem.getAmount() / 2, clickedItem.getNbt()); - inventory.setItem(action.slot, halfItem); - playerInventory.setCursor(new ItemStack(clickedItem.getId(), - clickedItem.getAmount() - halfItem.getAmount(), clickedItem.getNbt())); - } else if (cursorItem != null && clickedItem == null) { - playerInventory.setCursor(new ItemStack(cursorItem.getId(), - cursorItem.getAmount() - 1, cursorItem.getNbt())); - inventory.setItem(action.slot, new ItemStack(cursorItem.getId(), - 1, cursorItem.getNbt())); - } else if (InventoryUtils.canStack(cursorItem, clickedItem)) { - playerInventory.setCursor(new ItemStack(cursorItem.getId(), - cursorItem.getAmount() - 1, cursorItem.getNbt())); - inventory.setItem(action.slot, new ItemStack(clickedItem.getId(), - clickedItem.getAmount() + 1, clickedItem.getNbt())); - } - break; - } - } - session.sendDownstreamPacket(clickPacket); - session.sendDownstreamPacket(new ClientConfirmTransactionPacket(inventory.getId(), actionId, true)); - } - - /*if (refresh) { - translator.updateInventory(session, inventory); - InventoryUtils.updateCursor(session); - }*/ - } - - private static class ClickAction { - final Click click; - final int slot; - ClickAction(Click click, int slot) { - this.click = click; - this.slot = slot; - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/InventoryActionDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/InventoryActionDataTranslator.java deleted file mode 100644 index 96cbd61fb..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/InventoryActionDataTranslator.java +++ /dev/null @@ -1,338 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.inventory.action; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; -import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; -import com.github.steveice10.mc.protocol.data.game.window.*; -import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientWindowActionPacket; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; -import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; -import com.nukkitx.protocol.bedrock.data.inventory.InventorySource; -import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.InventoryTranslator; -import org.geysermc.connector.network.translators.inventory.SlotType; -import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.geysermc.connector.utils.InventoryUtils; - -import java.util.*; - -public class InventoryActionDataTranslator { - public static void translate(InventoryTranslator translator, GeyserSession session, Inventory inventory, List actions) { - if (actions.size() != 2) - return; - - InventoryActionData worldAction = null; - InventoryActionData cursorAction = null; - InventoryActionData containerAction = null; - boolean refresh = false; - for (InventoryActionData action : actions) { - if (action.getSource().getContainerId() == ContainerId.CRAFTING_USE_INGREDIENT) { - return; - } else if (action.getSource().getType() == InventorySource.Type.WORLD_INTERACTION) { - worldAction = action; - } else if (action.getSource().getContainerId() == ContainerId.UI && action.getSlot() == 0) { - cursorAction = action; - ItemData translatedCursor = ItemTranslator.translateToBedrock(session, session.getInventory().getCursor()); - if (!translatedCursor.equals(action.getFromItem())) { - refresh = true; - } - } else { - containerAction = action; - ItemData translatedItem = ItemTranslator.translateToBedrock(session, inventory.getItem(translator.bedrockSlotToJava(action))); - if (!translatedItem.equals(action.getFromItem())) { - refresh = true; - } - } - } - - final int craftSlot = session.getCraftSlot(); - session.setCraftSlot(0); - - if (worldAction != null) { - InventoryActionData sourceAction; - if (cursorAction != null) { - sourceAction = cursorAction; - } else { - sourceAction = containerAction; - } - - if (sourceAction != null) { - if (worldAction.getSource().getFlag() == InventorySource.Flag.DROP_ITEM) { - //quick dropping from hotbar? - if (session.getInventoryCache().getOpenInventory() == null && sourceAction.getSource().getContainerId() == ContainerId.INVENTORY) { - int heldSlot = session.getInventory().getHeldItemSlot(); - if (sourceAction.getSlot() == heldSlot) { - ClientPlayerActionPacket actionPacket = new ClientPlayerActionPacket( - sourceAction.getToItem().getCount() == 0 ? PlayerAction.DROP_ITEM_STACK : PlayerAction.DROP_ITEM, - new Position(0, 0, 0), BlockFace.DOWN); - session.sendDownstreamPacket(actionPacket); - ItemStack item = session.getInventory().getItem(heldSlot); - if (item != null) { - session.getInventory().setItem(heldSlot, new ItemStack(item.getId(), item.getAmount() - 1, item.getNbt())); - } - return; - } - } - int dropAmount = sourceAction.getFromItem().getCount() - sourceAction.getToItem().getCount(); - if (sourceAction != cursorAction) { //dropping directly from inventory - int javaSlot = translator.bedrockSlotToJava(sourceAction); - if (dropAmount == sourceAction.getFromItem().getCount()) { - ClientWindowActionPacket dropPacket = new ClientWindowActionPacket(inventory.getId(), - inventory.getTransactionId().getAndIncrement(), - javaSlot, null, WindowAction.DROP_ITEM, - DropItemParam.DROP_SELECTED_STACK); - session.sendDownstreamPacket(dropPacket); - } else { - for (int i = 0; i < dropAmount; i++) { - ClientWindowActionPacket dropPacket = new ClientWindowActionPacket(inventory.getId(), - inventory.getTransactionId().getAndIncrement(), - javaSlot, null, WindowAction.DROP_ITEM, - DropItemParam.DROP_FROM_SELECTED); - session.sendDownstreamPacket(dropPacket); - } - } - ItemStack item = inventory.getItem(javaSlot); - if (item != null) { - inventory.setItem(javaSlot, new ItemStack(item.getId(), item.getAmount() - dropAmount, item.getNbt())); - } - return; - } else { //clicking outside of inventory - ClientWindowActionPacket dropPacket = new ClientWindowActionPacket(inventory.getId(), inventory.getTransactionId().getAndIncrement(), - -999, null, WindowAction.CLICK_ITEM, - dropAmount > 1 ? ClickItemParam.LEFT_CLICK : ClickItemParam.RIGHT_CLICK); - session.sendDownstreamPacket(dropPacket); - ItemStack cursor = session.getInventory().getCursor(); - if (cursor != null) { - session.getInventory().setCursor(new ItemStack(cursor.getId(), dropAmount > 1 ? 0 : cursor.getAmount() - 1, cursor.getNbt())); - } - return; - } - } - } - } else if (cursorAction != null && containerAction != null) { - //left/right click - ClickPlan plan = new ClickPlan(); - int javaSlot = translator.bedrockSlotToJava(containerAction); - if (cursorAction.getFromItem().equals(containerAction.getToItem()) - && containerAction.getFromItem().equals(cursorAction.getToItem()) - && !InventoryUtils.canStack(cursorAction.getFromItem(), containerAction.getFromItem())) { //simple swap - plan.add(Click.LEFT, javaSlot); - } else if (cursorAction.getFromItem().getCount() > cursorAction.getToItem().getCount()) { //release - if (cursorAction.getToItem().getCount() == 0) { - plan.add(Click.LEFT, javaSlot); - } else { - int difference = cursorAction.getFromItem().getCount() - cursorAction.getToItem().getCount(); - for (int i = 0; i < difference; i++) { - plan.add(Click.RIGHT, javaSlot); - } - } - } else { //pickup - if (cursorAction.getFromItem().getCount() == 0) { - if (containerAction.getToItem().getCount() == 0) { //pickup all - plan.add(Click.LEFT, javaSlot); - } else { //pickup some - if (translator.getSlotType(javaSlot) == SlotType.FURNACE_OUTPUT - || containerAction.getToItem().getCount() == containerAction.getFromItem().getCount() / 2) { //right click - plan.add(Click.RIGHT, javaSlot); - } else { - plan.add(Click.LEFT, javaSlot); - int difference = containerAction.getFromItem().getCount() - cursorAction.getToItem().getCount(); - for (int i = 0; i < difference; i++) { - plan.add(Click.RIGHT, javaSlot); - } - } - } - } else { //pickup into non-empty cursor - if (translator.getSlotType(javaSlot) == SlotType.FURNACE_OUTPUT) { - if (containerAction.getToItem().getCount() == 0) { - plan.add(Click.LEFT, javaSlot); - } else { - ClientWindowActionPacket shiftClickPacket = new ClientWindowActionPacket(inventory.getId(), - inventory.getTransactionId().getAndIncrement(), - javaSlot, InventoryUtils.REFRESH_ITEM, WindowAction.SHIFT_CLICK_ITEM, - ShiftClickItemParam.LEFT_CLICK); - session.sendDownstreamPacket(shiftClickPacket); - translator.updateInventory(session, inventory); - return; - } - } else if (translator.getSlotType(javaSlot) == SlotType.OUTPUT) { - plan.add(Click.LEFT, javaSlot); - } else { - int cursorSlot = findTempSlot(inventory, session.getInventory().getCursor(), Collections.singletonList(javaSlot), false); - if (cursorSlot != -1) { - plan.add(Click.LEFT, cursorSlot); - } else { - translator.updateInventory(session, inventory); - InventoryUtils.updateCursor(session); - return; - } - plan.add(Click.LEFT, javaSlot); - int difference = cursorAction.getToItem().getCount() - cursorAction.getFromItem().getCount(); - for (int i = 0; i < difference; i++) { - plan.add(Click.RIGHT, cursorSlot); - } - plan.add(Click.LEFT, javaSlot); - plan.add(Click.LEFT, cursorSlot); - } - } - } - plan.execute(session, translator, inventory, refresh); - return; - } else { - ClickPlan plan = new ClickPlan(); - InventoryActionData fromAction; - InventoryActionData toAction; - if (actions.get(0).getFromItem().getCount() >= actions.get(0).getToItem().getCount()) { - fromAction = actions.get(0); - toAction = actions.get(1); - } else { - fromAction = actions.get(1); - toAction = actions.get(0); - } - int fromSlot = translator.bedrockSlotToJava(fromAction); - int toSlot = translator.bedrockSlotToJava(toAction); - - if (translator.getSlotType(fromSlot) == SlotType.OUTPUT) { - if ((craftSlot != 0 && craftSlot != -2) && (inventory.getItem(toSlot) == null - || InventoryUtils.canStack(session.getInventory().getCursor(), inventory.getItem(toSlot)))) { - if (fromAction.getToItem().getCount() == 0) { - refresh = true; - plan.add(Click.LEFT, toSlot); - if (craftSlot != -1) { - plan.add(Click.LEFT, craftSlot); - } - } else { - int difference = toAction.getToItem().getCount() - toAction.getFromItem().getCount(); - for (int i = 0; i < difference; i++) { - plan.add(Click.RIGHT, toSlot); - } - session.setCraftSlot(craftSlot); - } - plan.execute(session, translator, inventory, refresh); - return; - } else { - session.setCraftSlot(-2); - } - } - - int cursorSlot = -1; - if (session.getInventory().getCursor() != null) { //move cursor contents to a temporary slot - cursorSlot = findTempSlot(inventory, - session.getInventory().getCursor(), - Arrays.asList(fromSlot, toSlot), - translator.getSlotType(fromSlot) == SlotType.OUTPUT); - if (cursorSlot != -1) { - plan.add(Click.LEFT, cursorSlot); - } else { - translator.updateInventory(session, inventory); - InventoryUtils.updateCursor(session); - return; - } - } - if ((fromAction.getFromItem().equals(toAction.getToItem()) && !InventoryUtils.canStack(fromAction.getFromItem(), toAction.getFromItem())) - || fromAction.getToItem().getId() == 0) { //slot swap - plan.add(Click.LEFT, fromSlot); - plan.add(Click.LEFT, toSlot); - if (fromAction.getToItem().getId() != 0) { - plan.add(Click.LEFT, fromSlot); - } - } else if (InventoryUtils.canStack(fromAction.getFromItem(), toAction.getToItem())) { //partial item move - if (translator.getSlotType(fromSlot) == SlotType.FURNACE_OUTPUT) { - ClientWindowActionPacket shiftClickPacket = new ClientWindowActionPacket(inventory.getId(), - inventory.getTransactionId().getAndIncrement(), - fromSlot, InventoryUtils.REFRESH_ITEM, WindowAction.SHIFT_CLICK_ITEM, - ShiftClickItemParam.LEFT_CLICK); - session.sendDownstreamPacket(shiftClickPacket); - translator.updateInventory(session, inventory); - return; - } else if (translator.getSlotType(fromSlot) == SlotType.OUTPUT) { - session.setCraftSlot(cursorSlot); - plan.add(Click.LEFT, fromSlot); - int difference = toAction.getToItem().getCount() - toAction.getFromItem().getCount(); - for (int i = 0; i < difference; i++) { - plan.add(Click.RIGHT, toSlot); - } - //client will send additional packets later to finish transferring crafting output - //translator will know how to handle this using the craftSlot variable - } else { - plan.add(Click.LEFT, fromSlot); - int difference = toAction.getToItem().getCount() - toAction.getFromItem().getCount(); - for (int i = 0; i < difference; i++) { - plan.add(Click.RIGHT, toSlot); - } - plan.add(Click.LEFT, fromSlot); - } - } - if (cursorSlot != -1) { - plan.add(Click.LEFT, cursorSlot); - } - plan.execute(session, translator, inventory, refresh); - return; - } - - translator.updateInventory(session, inventory); - InventoryUtils.updateCursor(session); - } - - private static int findTempSlot(Inventory inventory, ItemStack item, List slotBlacklist, boolean emptyOnly) { - /*try and find a slot that can temporarily store the given item - only look in the main inventory and hotbar - only slots that are empty or contain a different type of item are valid*/ - int offset = inventory.getId() == 0 ? 1 : 0; //offhand is not a viable slot (some servers disable it) - List itemBlacklist = new ArrayList<>(slotBlacklist.size() + 1); - itemBlacklist.add(item); - for (int slot : slotBlacklist) { - ItemStack blacklistItem = inventory.getItem(slot); - if (blacklistItem != null) - itemBlacklist.add(blacklistItem); - } - for (int i = inventory.getSize() - (36 + offset); i < inventory.getSize() - offset; i++) { - ItemStack testItem = inventory.getItem(i); - boolean acceptable = true; - if (testItem != null) { - if (emptyOnly) { - continue; - } - for (ItemStack blacklistItem : itemBlacklist) { - if (InventoryUtils.canStack(testItem, blacklistItem)) { - acceptable = false; - break; - } - } - } - if (acceptable && !slotBlacklist.contains(i)) - return i; - } - //could not find a viable temp slot - return -1; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java deleted file mode 100644 index 6afdb25dd..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.inventory.holder; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; -import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; -import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; -import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; -import lombok.AllArgsConstructor; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.InventoryTranslator; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; - -@AllArgsConstructor -public class BlockInventoryHolder extends InventoryHolder { - private final int blockId; - private final ContainerType containerType; - - @Override - public void prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { - Vector3i position = session.getPlayerEntity().getPosition().toInt(); - position = position.add(Vector3i.UP); - UpdateBlockPacket blockPacket = new UpdateBlockPacket(); - blockPacket.setDataLayer(0); - blockPacket.setBlockPosition(position); - blockPacket.setRuntimeId(blockId); - blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); - session.sendUpstreamPacket(blockPacket); - inventory.setHolderPosition(position); - - NbtMap tag = NbtMap.builder() - .putInt("x", position.getX()) - .putInt("y", position.getY()) - .putInt("z", position.getZ()) - .putString("CustomName", inventory.getTitle()).build(); - BlockEntityDataPacket dataPacket = new BlockEntityDataPacket(); - dataPacket.setData(tag); - dataPacket.setBlockPosition(position); - session.sendUpstreamPacket(dataPacket); - } - - @Override - public void openInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { - ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket(); - containerOpenPacket.setId((byte) inventory.getId()); - containerOpenPacket.setType(containerType); - containerOpenPacket.setBlockPosition(inventory.getHolderPosition()); - containerOpenPacket.setUniqueEntityId(inventory.getHolderId()); - session.sendUpstreamPacket(containerOpenPacket); - } - - @Override - public void closeInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { - Vector3i holderPos = inventory.getHolderPosition(); - Position pos = new Position(holderPos.getX(), holderPos.getY(), holderPos.getZ()); - int realBlock = session.getConnector().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ()); - UpdateBlockPacket blockPacket = new UpdateBlockPacket(); - blockPacket.setDataLayer(0); - blockPacket.setBlockPosition(holderPos); - blockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(realBlock)); - session.sendUpstreamPacket(blockPacket); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java deleted file mode 100644 index a4a2fb750..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.item; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.nbt.NbtUtils; -import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import com.nukkitx.protocol.bedrock.packet.StartGamePacket; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.ints.IntList; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.LanguageUtils; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.*; - -/** - * Registry for anything item related. - */ -public class ItemRegistry { - - private static final Map JAVA_IDENTIFIER_MAP = new HashMap<>(); - - public static final ItemData[] CREATIVE_ITEMS; - - public static final List ITEMS = new ArrayList<>(); - public static final Int2ObjectMap ITEM_ENTRIES = new Int2ObjectOpenHashMap<>(); - - /** - * Bamboo item entry, used in PandaEntity.java - */ - public static ItemEntry BAMBOO; - /** - * Boat item entries, used in BedrockInventoryTransactionTranslator.java - */ - public static IntList BOATS = new IntArrayList(); - /** - * Bucket item entries (excluding the milk bucket), used in BedrockInventoryTransactionTranslator.java - */ - public static IntList BUCKETS = new IntArrayList(); - /** - * Empty item bucket, used in BedrockInventoryTransactionTranslator.java - */ - public static ItemEntry MILK_BUCKET; - /** - * Egg item entry, used in JavaEntityStatusTranslator.java - */ - public static ItemEntry EGG; - /** - * Gold item entry, used in PiglinEntity.java - */ - public static ItemEntry GOLD; - /** - * Shield item entry, used in Entity.java and LivingEntity.java - */ - public static ItemEntry SHIELD; - /** - * Wheat item entry, used in AbstractHorseEntity.java - */ - public static ItemEntry WHEAT; - - public static int BARRIER_INDEX = 0; - - public static void init() { - // no-op - } - - static { - /* Load item palette */ - InputStream stream = FileUtils.getResource("bedrock/runtime_item_states.json"); - - TypeReference> itemEntriesType = new TypeReference>() { - }; - - // Used to get the Bedrock namespaced ID (in instances where there are small differences) - Int2ObjectMap bedrockIdToIdentifier = new Int2ObjectOpenHashMap<>(); - - List itemEntries; - try { - itemEntries = GeyserConnector.JSON_MAPPER.readValue(stream, itemEntriesType); - } catch (Exception e) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_bedrock"), e); - } - - int lodestoneCompassId = 0; - - for (JsonNode entry : itemEntries) { - ITEMS.add(new StartGamePacket.ItemEntry(entry.get("name").textValue(), (short) entry.get("id").intValue())); - bedrockIdToIdentifier.put(entry.get("id").intValue(), entry.get("name").textValue()); - if (entry.get("name").textValue().equals("minecraft:lodestone_compass")) { - lodestoneCompassId = entry.get("id").intValue(); - } - } - - stream = FileUtils.getResource("mappings/items.json"); - - JsonNode items; - try { - items = GeyserConnector.JSON_MAPPER.readTree(stream); - } catch (Exception e) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e); - } - - int itemIndex = 0; - Iterator> iterator = items.fields(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - int bedrockId = entry.getValue().get("bedrock_id").intValue(); - String bedrockIdentifier = bedrockIdToIdentifier.get(bedrockId); - if (bedrockIdentifier == null) { - throw new RuntimeException("Missing Bedrock ID in mappings!: " + bedrockId); - } - if (entry.getValue().has("tool_type")) { - if (entry.getValue().has("tool_tier")) { - ITEM_ENTRIES.put(itemIndex, new ToolItemEntry( - entry.getKey(), bedrockIdentifier, itemIndex, bedrockId, - entry.getValue().get("bedrock_data").intValue(), - entry.getValue().get("tool_type").textValue(), - entry.getValue().get("tool_tier").textValue(), - entry.getValue().get("is_block") != null && entry.getValue().get("is_block").booleanValue())); - } else { - ITEM_ENTRIES.put(itemIndex, new ToolItemEntry( - entry.getKey(), bedrockIdentifier, itemIndex, bedrockId, - entry.getValue().get("bedrock_data").intValue(), - entry.getValue().get("tool_type").textValue(), - "", entry.getValue().get("is_block").booleanValue())); - } - } else { - ITEM_ENTRIES.put(itemIndex, new ItemEntry( - entry.getKey(), bedrockIdentifier, itemIndex, bedrockId, - entry.getValue().get("bedrock_data").intValue(), - entry.getValue().get("is_block") != null && entry.getValue().get("is_block").booleanValue())); - } - switch (entry.getKey()) { - case "minecraft:barrier": - BARRIER_INDEX = itemIndex; - break; - case "minecraft:bamboo": - BAMBOO = ITEM_ENTRIES.get(itemIndex); - break; - case "minecraft:egg": - EGG = ITEM_ENTRIES.get(itemIndex); - break; - case "minecraft:gold_ingot": - GOLD = ITEM_ENTRIES.get(itemIndex); - break; - case "minecraft:shield": - SHIELD = ITEM_ENTRIES.get(itemIndex); - break; - case "minecraft:milk_bucket": - MILK_BUCKET = ITEM_ENTRIES.get(itemIndex); - break; - case "minecraft:wheat": - WHEAT = ITEM_ENTRIES.get(itemIndex); - break; - default: - break; - } - - if (entry.getKey().contains("boat")) { - BOATS.add(entry.getValue().get("bedrock_id").intValue()); - } else if (entry.getKey().contains("bucket") && !entry.getKey().contains("milk")) { - BUCKETS.add(entry.getValue().get("bedrock_id").intValue()); - } - - itemIndex++; - } - - if (lodestoneCompassId == 0) { - throw new RuntimeException("Lodestone compass not found in item palette!"); - } - - // Add the loadstone compass since it doesn't exist on java but we need it for item conversion - ITEM_ENTRIES.put(itemIndex, new ItemEntry("minecraft:lodestone_compass", "minecraft:lodestone_compass", itemIndex, - lodestoneCompassId, 0, false)); - - /* Load creative items */ - stream = FileUtils.getResource("bedrock/creative_items.json"); - - JsonNode creativeItemEntries; - try { - creativeItemEntries = GeyserConnector.JSON_MAPPER.readTree(stream).get("items"); - } catch (Exception e) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.creative"), e); - } - - int netId = 1; - List creativeItems = new ArrayList<>(); - for (JsonNode itemNode : creativeItemEntries) { - ItemData item = getBedrockItemFromJson(itemNode); - creativeItems.add(ItemData.fromNet(netId++, item.getId(), item.getDamage(), item.getCount(), item.getTag())); - } - CREATIVE_ITEMS = creativeItems.toArray(new ItemData[0]); - } - - /** - * Gets an {@link ItemEntry} from the given {@link ItemStack}. - * - * @param stack the item stack - * @return an item entry from the given item stack - */ - public static ItemEntry getItem(ItemStack stack) { - return ITEM_ENTRIES.get(stack.getId()); - } - - /** - * Gets an {@link ItemEntry} from the given {@link ItemData}. - * - * @param data the item data - * @return an item entry from the given item data - */ - public static ItemEntry getItem(ItemData data) { - for (ItemEntry itemEntry : ITEM_ENTRIES.values()) { - if (itemEntry.getBedrockId() == data.getId() && (itemEntry.getBedrockData() == data.getDamage() || - // Make exceptions for potions and tipped arrows, whose damage values can vary - (itemEntry.getJavaIdentifier().endsWith("potion") || itemEntry.getJavaIdentifier().equals("minecraft:arrow")))) { - return itemEntry; - } - } - - // This will hide the message when the player clicks with an empty hand - if (data.getId() != 0 && data.getDamage() != 0) { - GeyserConnector.getInstance().getLogger().debug("Missing mapping for bedrock item " + data.getId() + ":" + data.getDamage()); - } - return ItemEntry.AIR; - } - - /** - * Gets an {@link ItemEntry} from the given Minecraft: Java Edition - * block state identifier. - * - * @param javaIdentifier the block state identifier - * @return an item entry from the given java edition identifier - */ - public static ItemEntry getItemEntry(String javaIdentifier) { - return JAVA_IDENTIFIER_MAP.computeIfAbsent(javaIdentifier, key -> { - for (ItemEntry entry : ITEM_ENTRIES.values()) { - if (entry.getJavaIdentifier().equals(key)) { - return entry; - } - } - return null; - }); - } - - /** - * Gets a Bedrock {@link ItemData} from a {@link JsonNode} - * @param itemNode the JSON node that contains ProxyPass-compatible Bedrock item data - * @return - */ - public static ItemData getBedrockItemFromJson(JsonNode itemNode) { - int count = 1; - short damage = 0; - NbtMap tag = null; - if (itemNode.has("damage")) { - damage = itemNode.get("damage").numberValue().shortValue(); - } - if (itemNode.has("count")) { - count = itemNode.get("count").asInt(); - } - if (itemNode.has("nbt_b64")) { - byte[] bytes = Base64.getDecoder().decode(itemNode.get("nbt_b64").asText()); - ByteArrayInputStream bais = new ByteArrayInputStream(bytes); - try { - tag = (NbtMap) NbtUtils.createReaderLE(bais).readTag(); - } catch (IOException e) { - e.printStackTrace(); - } - } - return ItemData.of(itemNode.get("id").asInt(), damage, count, tag); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java deleted file mode 100644 index 411c0295b..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.item; - -import com.fasterxml.jackson.databind.JsonNode; -import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; -import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.LanguageUtils; - -import java.io.InputStream; -import java.util.*; - -/** - * Manages any recipe-related storing - */ -public class RecipeRegistry { - - /** - * A list of all possible leather armor dyeing recipes. - * Created manually. - */ - public static final List LEATHER_DYEING_RECIPES = new ObjectArrayList<>(); - /** - * A list of all possible firework rocket recipes, including the base rocket. - * Obtained from a ProxyPass dump of protocol v407 - */ - public static final List FIREWORK_ROCKET_RECIPES = new ObjectArrayList<>(); - /** - * A list of all possible firework star recipes. - * Obtained from a ProxyPass dump of protocol v407 - */ - public static final List FIREWORK_STAR_RECIPES = new ObjectArrayList<>(); - /** - * A list of all possible shulker box dyeing options. - * Obtained from a ProxyPass dump of protocol v407 - */ - public static final List SHULKER_BOX_DYEING_RECIPES = new ObjectArrayList<>(); - /** - * A list of all possible suspicious stew recipes. - * Obtained from a ProxyPass dump of protocol v407 - */ - public static final List SUSPICIOUS_STEW_RECIPES = new ObjectArrayList<>(); - /** - * A list of all possible tipped arrow recipes. - * Obtained from a ProxyPass dump of protocol v407 - */ - public static final List TIPPED_ARROW_RECIPES = new ObjectArrayList<>(); - - // TODO: These are the other "multi" UUIDs that supposedly enable various recipes. Find out what each enables. - // 442d85ed-8272-4543-a6f1-418f90ded05d 8b36268c-1829-483c-a0f1-993b7156a8f2 602234e4-cac1-4353-8bb7-b1ebff70024b 98c84b38-1085-46bd-b1ce-dd38c159e6cc - // d81aaeaf-e172-4440-9225-868df030d27b b5c5d105-75a2-4076-af2b-923ea2bf4bf0 00000000-0000-0000-0000-000000000002 85939755-ba10-4d9d-a4cc-efb7a8e943c4 - // d392b075-4ba1-40ae-8789-af868d56f6ce aecd2294-4b94-434b-8667-4499bb2c9327 - /** - * Recipe data that, when sent to the client, enables book cloning - */ - public static final CraftingData BOOK_CLONING_RECIPE_DATA = CraftingData.fromMulti(UUID.fromString("d1ca6b84-338e-4f2f-9c6b-76cc8b4bd98d")); - /** - * Recipe data that, when sent to the client, enables tool repairing in a crafting table - */ - public static final CraftingData TOOL_REPAIRING_RECIPE_DATA = CraftingData.fromMulti(UUID.fromString("00000000-0000-0000-0000-000000000001")); - - static { - // Get all recipes that are not directly sent from a Java server - InputStream stream = FileUtils.getResource("mappings/recipes.json"); - - JsonNode items; - try { - items = GeyserConnector.JSON_MAPPER.readTree(stream); - } catch (Exception e) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e); - } - - for (JsonNode entry: items.get("leather_armor")) { - // This won't be perfect, as we can't possibly send every leather input for every kind of color - // But it does display the correct output from a base leather armor, and besides visuals everything works fine - LEATHER_DYEING_RECIPES.add(getCraftingDataFromJsonNode(entry)); - } - for (JsonNode entry : items.get("firework_rockets")) { - FIREWORK_ROCKET_RECIPES.add(getCraftingDataFromJsonNode(entry)); - } - for (JsonNode entry : items.get("firework_stars")) { - FIREWORK_STAR_RECIPES.add(getCraftingDataFromJsonNode(entry)); - } - for (JsonNode entry : items.get("shulker_boxes")) { - SHULKER_BOX_DYEING_RECIPES.add(getCraftingDataFromJsonNode(entry)); - } - for (JsonNode entry : items.get("suspicious_stew")) { - SUSPICIOUS_STEW_RECIPES.add(getCraftingDataFromJsonNode(entry)); - } - for (JsonNode entry : items.get("tipped_arrows")) { - TIPPED_ARROW_RECIPES.add(getCraftingDataFromJsonNode(entry)); - } - } - - /** - * Computes a Bedrock crafting recipe from the given JSON data. - * @param node the JSON data to compute - * @return the {@link CraftingData} to send to the Bedrock client. - */ - private static CraftingData getCraftingDataFromJsonNode(JsonNode node) { - ItemData output = ItemRegistry.getBedrockItemFromJson(node.get("output").get(0)); - UUID uuid = UUID.randomUUID(); - if (node.get("type").asInt() == 1) { - // Shaped recipe - List shape = new ArrayList<>(); - // Get the shape of the recipe - for (JsonNode chars : node.get("shape")) { - shape.add(chars.asText()); - } - - // In recipes.json each recipe is mapped by a letter - Map letterToRecipe = new HashMap<>(); - Iterator> iterator = node.get("input").fields(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - letterToRecipe.put(entry.getKey(), ItemRegistry.getBedrockItemFromJson(entry.getValue())); - } - - ItemData[] inputs = new ItemData[shape.size() * shape.get(0).length()]; - int i = 0; - // Create a linear array of items from the "cube" of the shape - for (int j = 0; i < shape.size() * shape.get(0).length(); j++) { - for (char c : shape.get(j).toCharArray()) { - ItemData data = letterToRecipe.getOrDefault(String.valueOf(c), ItemData.AIR); - inputs[i] = data; - i++; - } - } - - return CraftingData.fromShaped(uuid.toString(), shape.get(0).length(), shape.size(), - inputs, new ItemData[]{output}, uuid, "crafting_table", 0); - } - List inputs = new ObjectArrayList<>(); - for (JsonNode entry : node.get("input")) { - inputs.add(ItemRegistry.getBedrockItemFromJson(entry)); - } - if (node.get("type").asInt() == 5) { - // Shulker box - return CraftingData.fromShulkerBox(uuid.toString(), - inputs.toArray(new ItemData[0]), new ItemData[]{output}, uuid, "crafting_table", 0); - } - return CraftingData.fromShapeless(uuid.toString(), - inputs.toArray(new ItemData[0]), new ItemData[]{output}, uuid, "crafting_table", 0); - } - - public static void init() { - // no-op - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java deleted file mode 100644 index dde577005..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.item; - -import lombok.Getter; - -@Getter -public class ToolItemEntry extends ItemEntry { - private final String toolType; - private final String toolTier; - - public ToolItemEntry(String javaIdentifier, String bedrockIdentifier, int javaId, int bedrockId, int bedrockData, String toolType, String toolTier, boolean isBlock) { - super(javaIdentifier, bedrockIdentifier, javaId, bedrockId, bedrockData, isBlock); - this.toolType = toolType; - this.toolTier = toolTier; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java deleted file mode 100644 index 92ec67dd4..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.item.translators; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.opennbt.tag.builtin.*; -import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import org.geysermc.connector.network.translators.ItemRemapper; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.geysermc.connector.utils.LoadstoneTracker; - -import java.util.List; -import java.util.stream.Collectors; - -@ItemRemapper -public class CompassTranslator extends ItemTranslator { - - private final List appliedItems; - - public CompassTranslator() { - appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry -> entry.getJavaIdentifier().endsWith("compass")).collect(Collectors.toList()); - } - - @Override - public ItemData translateToBedrock(ItemStack itemStack, ItemEntry itemEntry) { - if (itemStack.getNbt() == null) return super.translateToBedrock(itemStack, itemEntry); - - Tag lodestoneTag = itemStack.getNbt().get("LodestoneTracked"); - if (lodestoneTag instanceof ByteTag) { - // Get the fake lodestonecompass entry - itemEntry = ItemRegistry.getItemEntry("minecraft:lodestone_compass"); - - // Get the loadstone pos - CompoundTag loadstonePos = itemStack.getNbt().get("LodestonePos"); - if (loadstonePos != null) { - // Get all info needed for tracking - int x = ((IntTag) loadstonePos.get("X")).getValue(); - int y = ((IntTag) loadstonePos.get("Y")).getValue(); - int z = ((IntTag) loadstonePos.get("Z")).getValue(); - String dim = ((StringTag) itemStack.getNbt().get("LodestoneDimension")).getValue(); - - // Store the info - int trackID = LoadstoneTracker.store(x, y, z, dim); - - // Set the bedrock tracking id - itemStack.getNbt().put(new IntTag("trackingHandle", trackID)); - } else { - // The loadstone was removed just set the tracking id to 0 - itemStack.getNbt().put(new IntTag("trackingHandle", 0)); - } - } - - ItemData itemData = super.translateToBedrock(itemStack, itemEntry); - - return itemData; - } - - @Override - public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { - boolean isLoadstone = false; - if (itemEntry.getBedrockIdentifier().equals("minecraft:lodestone_compass")) { - // Revert the entry back to the compass - itemEntry = ItemRegistry.getItemEntry("minecraft:compass"); - - isLoadstone = true; - } - - ItemStack itemStack = super.translateToJava(itemData, itemEntry); - - if (isLoadstone) { - // Get the tracking id - int trackingID = ((IntTag) itemStack.getNbt().get("trackingHandle")).getValue(); - - // Fetch the tracking info from the id - LoadstoneTracker.LoadstonePos pos = LoadstoneTracker.getPos(trackingID); - if (pos != null) { - // Build the new NBT data for the fetched tracking info - itemStack.getNbt().put(new StringTag("LodestoneDimension", pos.getDimension())); - - CompoundTag posTag = new CompoundTag("LodestonePos"); - posTag.put(new IntTag("X", pos.getX())); - posTag.put(new IntTag("Y", pos.getY())); - posTag.put(new IntTag("Z", pos.getZ())); - - itemStack.getNbt().put(posTag); - } - } - - return itemStack; - } - - @Override - public List getAppliedItems() { - return appliedItems; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java deleted file mode 100644 index 3fd9df8a0..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.item.translators.nbt; - -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.github.steveice10.opennbt.tag.builtin.ListTag; -import com.github.steveice10.opennbt.tag.builtin.StringTag; -import com.github.steveice10.opennbt.tag.builtin.Tag; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.TextComponent; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.ItemRemapper; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; - -import java.util.ArrayList; -import java.util.List; - -@ItemRemapper(priority = -1) -public class BasicItemTranslator extends NbtItemStackTranslator { - - @Override - public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { - if (!itemTag.contains("display")) { - return; - } - CompoundTag displayTag = itemTag.get("display"); - if (displayTag.contains("Name")) { - StringTag nameTag = displayTag.get("Name"); - try { - displayTag.put(new StringTag("Name", toBedrockMessage(nameTag))); - } catch (Exception ex) { - } - } - - if (displayTag.contains("Lore")) { - ListTag loreTag = displayTag.get("Lore"); - List lore = new ArrayList<>(); - for (Tag tag : loreTag.getValue()) { - if (!(tag instanceof StringTag)) return; - try { - lore.add(new StringTag("", toBedrockMessage((StringTag) tag))); - } catch (Exception ex) { - } - } - displayTag.put(new ListTag("Lore", lore)); - } - } - - @Override - public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { - if (!itemTag.contains("display")) { - return; - } - CompoundTag displayTag = itemTag.get("display"); - if (displayTag.contains("Name")) { - StringTag nameTag = displayTag.get("Name"); - displayTag.put(new StringTag("Name", toJavaMessage(nameTag))); - } - - if (displayTag.contains("Lore")) { - ListTag loreTag = displayTag.get("Lore"); - List lore = new ArrayList<>(); - for (Tag tag : loreTag.getValue()) { - if (!(tag instanceof StringTag)) return; - lore.add(new StringTag("", "§r" + toJavaMessage((StringTag) tag))); - } - displayTag.put(new ListTag("Lore", lore)); - } - } - - private String toJavaMessage(StringTag tag) { - String message = tag.getValue(); - if (message == null) return null; - if (message.startsWith("§r")) { - message = message.replaceFirst("§r", ""); - } - Component component = Component.text(message); - return GsonComponentSerializer.gson().serialize(component); - } - - private String toBedrockMessage(StringTag tag) { - String message = tag.getValue(); - if (message == null) return null; - TextComponent component = (TextComponent) GsonComponentSerializer.gson().deserialize(message); - String legacy = LegacyComponentSerializer.legacySection().serialize(component); - if (hasFormatting(LegacyComponentSerializer.legacySection().deserialize(legacy))) { - return "§r" + legacy; - } - return legacy; - } - - private boolean hasFormatting(Component component) { - if (component.hasStyling()) return true; - for (Component child : component.children()) { - if (hasFormatting(child)) { - return true; - } - } - return false; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java deleted file mode 100644 index 3b453ea18..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.item.translators.nbt; - -import com.github.steveice10.opennbt.tag.builtin.*; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.ItemRemapper; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; -import org.geysermc.connector.utils.FireworkColor; -import org.geysermc.connector.utils.MathUtils; - -@ItemRemapper -public class FireworkTranslator extends NbtItemStackTranslator { - - @Override - public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { - if (!itemTag.contains("Fireworks")) { - return; - } - - CompoundTag fireworks = itemTag.get("Fireworks"); - if (fireworks.get("Flight") != null) { - fireworks.put(new ByteTag("Flight", MathUtils.convertByte(fireworks.get("Flight").getValue()))); - } - - ListTag explosions = fireworks.get("Explosions"); - if (explosions == null) { - return; - } - for (Tag effect : explosions.getValue()) { - CompoundTag effectData = (CompoundTag) effect; - - CompoundTag newEffectData = new CompoundTag(""); - - if (effectData.get("Type") != null) { - newEffectData.put(new ByteTag("FireworkType", MathUtils.convertByte(effectData.get("Type").getValue()))); - } - - if (effectData.get("Colors") != null) { - int[] oldColors = (int[]) effectData.get("Colors").getValue(); - byte[] colors = new byte[oldColors.length]; - - int i = 0; - for (int color : oldColors) { - colors[i++] = FireworkColor.fromJavaID(color).getBedrockID(); - } - - newEffectData.put(new ByteArrayTag("FireworkColor", colors)); - } - - if (effectData.get("FadeColors") != null) { - int[] oldColors = (int[]) effectData.get("FadeColors").getValue(); - byte[] colors = new byte[oldColors.length]; - - int i = 0; - for (int color : oldColors) { - colors[i++] = FireworkColor.fromJavaID(color).getBedrockID(); - } - - newEffectData.put(new ByteArrayTag("FireworkFade", colors)); - } - - if (effectData.get("Trail") != null) { - newEffectData.put(new ByteTag("FireworkTrail", MathUtils.convertByte(effectData.get("Trail").getValue()))); - } - - if (effectData.get("Flicker") != null) { - newEffectData.put(new ByteTag("FireworkFlicker", MathUtils.convertByte(effectData.get("Flicker").getValue()))); - } - - explosions.remove(effect); - explosions.add(newEffectData); - } - } - - @Override - public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { - if (!itemTag.contains("Fireworks")) { - return; - } - CompoundTag fireworks = itemTag.get("Fireworks"); - if (fireworks.contains("Flight")) { - fireworks.put(new ByteTag("Flight", MathUtils.convertByte(fireworks.get("Flight").getValue()))); - } - - if (!itemTag.contains("Explosions")) { - return; - } - ListTag explosions = fireworks.get("Explosions"); - for (Tag effect : explosions.getValue()) { - CompoundTag effectData = (CompoundTag) effect; - - CompoundTag newEffectData = new CompoundTag(""); - - if (effectData.get("FireworkType") != null) { - newEffectData.put(new ByteTag("Type", MathUtils.convertByte(effectData.get("FireworkType").getValue()))); - } - - if (effectData.get("FireworkColor") != null) { - byte[] oldColors = (byte[]) effectData.get("FireworkColor").getValue(); - int[] colors = new int[oldColors.length]; - - int i = 0; - for (byte color : oldColors) { - colors[i++] = FireworkColor.fromBedrockID(color).getJavaID(); - } - - newEffectData.put(new IntArrayTag("Colors", colors)); - } - - if (effectData.get("FireworkFade") != null) { - byte[] oldColors = (byte[]) effectData.get("FireworkFade").getValue(); - int[] colors = new int[oldColors.length]; - - int i = 0; - for (byte color : oldColors) { - colors[i++] = FireworkColor.fromBedrockID(color).getJavaID(); - } - - newEffectData.put(new IntArrayTag("FadeColors", colors)); - } - - if (effectData.get("FireworkTrail") != null) { - newEffectData.put(new ByteTag("Trail", MathUtils.convertByte(effectData.get("FireworkTrail").getValue()))); - } - - if (effectData.get("FireworkFlicker") != null) { - newEffectData.put(new ByteTag("Flicker", MathUtils.convertByte(effectData.get("FireworkFlicker").getValue()))); - } - - explosions.remove(effect); - explosions.add(newEffectData); - } - } - - @Override - public boolean acceptItem(ItemEntry itemEntry) { - return "minecraft:firework_rocket".equals(itemEntry.getJavaIdentifier()); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareCommandsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareCommandsTranslator.java deleted file mode 100644 index 8f5243366..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareCommandsTranslator.java +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java; - -import com.github.steveice10.mc.protocol.data.game.command.CommandNode; -import com.github.steveice10.mc.protocol.data.game.command.CommandParser; -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerDeclareCommandsPacket; -import com.nukkitx.protocol.bedrock.data.command.CommandData; -import com.nukkitx.protocol.bedrock.data.command.CommandEnumData; -import com.nukkitx.protocol.bedrock.data.command.CommandParamData; -import com.nukkitx.protocol.bedrock.data.command.CommandParamType; -import com.nukkitx.protocol.bedrock.packet.AvailableCommandsPacket; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import lombok.Getter; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -@Translator(packet = ServerDeclareCommandsPacket.class) -public class JavaDeclareCommandsTranslator extends PacketTranslator { - @Override - public void translate(ServerDeclareCommandsPacket packet, GeyserSession session) { - // Don't send command suggestions if they are disabled - if (!session.getConnector().getConfig().isCommandSuggestions()) { - session.getConnector().getLogger().debug("Not sending command suggestions as they are disabled."); - return; - } - List commandData = new ArrayList<>(); - Int2ObjectMap commands = new Int2ObjectOpenHashMap<>(); - Int2ObjectMap> commandArgs = new Int2ObjectOpenHashMap<>(); - - // Get the first node, it should be a root node - CommandNode rootNode = packet.getNodes()[packet.getFirstNodeIndex()]; - - // Loop through the root nodes to get all commands - for (int nodeIndex : rootNode.getChildIndices()) { - CommandNode node = packet.getNodes()[nodeIndex]; - - // Make sure we don't have duplicated commands (happens if there is more than 1 root node) - if (commands.containsKey(nodeIndex)) { continue; } - if (commands.containsValue(node.getName())) { continue; } - - // Get and update the commandArgs list with the found arguments - if (node.getChildIndices().length >= 1) { - for (int childIndex : node.getChildIndices()) { - commandArgs.putIfAbsent(nodeIndex, new ArrayList<>()); - commandArgs.get(nodeIndex).add(packet.getNodes()[childIndex]); - } - } - - // Insert the command name into the list - commands.put(nodeIndex, node.getName()); - } - - // The command flags, not sure what these do apart from break things - List flags = new ArrayList<>(); - - // Loop through all the found commands - for (int commandID : commands.keySet()) { - String commandName = commands.get(commandID); - - // Create a basic alias - CommandEnumData aliases = new CommandEnumData( commandName + "Aliases", new String[] { commandName.toLowerCase() }, false); - - // Get and parse all params - CommandParamData[][] params = getParams(packet.getNodes()[commandID], packet.getNodes()); - - // Build the completed command and add it to the final list - CommandData data = new CommandData(commandName, session.getConnector().getCommandManager().getDescription(commandName), flags, (byte) 0, aliases, params); - commandData.add(data); - } - - // Add our commands to the AvailableCommandsPacket for the bedrock client - AvailableCommandsPacket availableCommandsPacket = new AvailableCommandsPacket(); - for (CommandData data : commandData) { - availableCommandsPacket.getCommands().add(data); - } - - GeyserConnector.getInstance().getLogger().debug("Sending command packet of " + commandData.size() + " commands"); - - // Finally, send the commands to the client - session.sendUpstreamPacket(availableCommandsPacket); - } - - /** - * Build the command parameter array for the given command - * - * @param commandNode The command to build the parameters for - * @param allNodes Every command node - * - * @return An array of parameter option arrays - */ - private CommandParamData[][] getParams(CommandNode commandNode, CommandNode[] allNodes) { - // Check if the command is an alias and redirect it - if (commandNode.getRedirectIndex() != -1) { - GeyserConnector.getInstance().getLogger().debug("Redirecting command " + commandNode.getName() + " to " + allNodes[commandNode.getRedirectIndex()].getName()); - commandNode = allNodes[commandNode.getRedirectIndex()]; - } - - if (commandNode.getChildIndices().length >= 1) { - // Create the root param node and build all the children - ParamInfo rootParam = new ParamInfo(commandNode, null); - rootParam.buildChildren(allNodes); - - List treeData = rootParam.getTree(); - CommandParamData[][] params = new CommandParamData[treeData.size()][]; - - // Fill the nested params array - int i = 0; - for (CommandParamData[] tree : treeData) { - params[i] = tree; - i++; - } - - return params; - } - - return new CommandParamData[0][0]; - } - - /** - * Convert Java edition command types to Bedrock edition - * - * @param parser Command type to convert - * - * @return Bedrock parameter data type - */ - private CommandParamType mapCommandType(CommandParser parser) { - if (parser == null) { return CommandParamType.STRING; } - - switch (parser) { - case FLOAT: - return CommandParamType.FLOAT; - - case INTEGER: - return CommandParamType.INT; - - case ENTITY: - case GAME_PROFILE: - return CommandParamType.TARGET; - - case BLOCK_POS: - return CommandParamType.BLOCK_POSITION; - - case COLUMN_POS: - case VEC3: - return CommandParamType.POSITION; - - case MESSAGE: - return CommandParamType.MESSAGE; - - case NBT: - case NBT_COMPOUND_TAG: - case NBT_TAG: - case NBT_PATH: - return CommandParamType.JSON; - - case RESOURCE_LOCATION: - return CommandParamType.FILE_PATH; - - case INT_RANGE: - return CommandParamType.INT_RANGE; - - case BOOL: - case DOUBLE: - case STRING: - case VEC2: - case BLOCK_STATE: - case BLOCK_PREDICATE: - case ITEM_STACK: - case ITEM_PREDICATE: - case COLOR: - case COMPONENT: - case OBJECTIVE: - case OBJECTIVE_CRITERIA: - case OPERATION: // Possibly OPERATOR - case PARTICLE: - case ROTATION: - case SCOREBOARD_SLOT: - case SCORE_HOLDER: - case SWIZZLE: - case TEAM: - case ITEM_SLOT: - case MOB_EFFECT: - case FUNCTION: - case ENTITY_ANCHOR: - case RANGE: - case FLOAT_RANGE: - case ITEM_ENCHANTMENT: - case ENTITY_SUMMON: - case DIMENSION: - case TIME: - default: - return CommandParamType.STRING; - } - } - - @Getter - private class ParamInfo { - private CommandNode paramNode; - private CommandParamData paramData; - private List children; - - /** - * Create a new parameter info object - * - * @param paramNode CommandNode the parameter is for - * @param paramData The existing parameters for the command - */ - public ParamInfo(CommandNode paramNode, CommandParamData paramData) { - this.paramNode = paramNode; - this.paramData = paramData; - this.children = new ArrayList<>(); - } - - /** - * Build the array of all the child parameters (recursive) - * - * @param allNodes Every command node - */ - public void buildChildren(CommandNode[] allNodes) { - int enumIndex = -1; - - for (int paramID : paramNode.getChildIndices()) { - CommandNode paramNode = allNodes[paramID]; - - if (paramNode.getParser() == null) { - if (enumIndex == -1) { - enumIndex = children.size(); - - // Create the new enum command - CommandEnumData enumData = new CommandEnumData(paramNode.getName(), new String[] { paramNode.getName() }, false); - children.add(new ParamInfo(paramNode, new CommandParamData(paramNode.getName(), false, enumData, mapCommandType(paramNode.getParser()), null, Collections.emptyList()))); - } else { - // Get the existing enum - ParamInfo enumParamInfo = children.get(enumIndex); - - // Extend the current list of enum values - String[] enumOptions = Arrays.copyOf(enumParamInfo.getParamData().getEnumData().getValues(), enumParamInfo.getParamData().getEnumData().getValues().length + 1); - enumOptions[enumOptions.length - 1] = paramNode.getName(); - - // Re-create the command using the updated values - CommandEnumData enumData = new CommandEnumData(enumParamInfo.getParamData().getEnumData().getName(), enumOptions, false); - children.set(enumIndex, new ParamInfo(enumParamInfo.getParamNode(), new CommandParamData(enumParamInfo.getParamData().getName(), false, enumData, enumParamInfo.getParamData().getType(), null, Collections.emptyList()))); - } - }else{ - // Put the non-enum param into the list - children.add(new ParamInfo(paramNode, new CommandParamData(paramNode.getName(), false, null, mapCommandType(paramNode.getParser()), null, Collections.emptyList()))); - } - } - - // Recursively build all child options - for (ParamInfo child : children) { - child.buildChildren(allNodes); - } - } - - /** - * Get the tree of every parameter node (recursive) - * - * @return List of parameter options arrays for the command - */ - public List getTree() { - List treeParamData = new ArrayList<>(); - - for (ParamInfo child : children) { - // Get the tree from the child - List childTree = child.getTree(); - - // Un-pack the tree append the child node to it and push into the list - for (CommandParamData[] subchild : childTree) { - CommandParamData[] tmpTree = new ArrayList() { - { - add(child.getParamData()); - addAll(Arrays.asList(subchild)); - } - }.toArray(new CommandParamData[0]); - - treeParamData.add(tmpTree); - } - - // If we have no more child parameters just the child - if (childTree.size() == 0) { - treeParamData.add(new CommandParamData[] { child.getParamData() }); - } - } - - return treeParamData; - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java deleted file mode 100644 index 70303baa0..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java; - -import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; -import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; -import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; -import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerDeclareRecipesPacket; -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; -import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket; -import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -import it.unimi.dsi.fastutil.ints.IntSet; -import lombok.AllArgsConstructor; -import lombok.EqualsAndHashCode; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.item.*; - -import java.util.*; -import java.util.stream.Collectors; - -@Translator(packet = ServerDeclareRecipesPacket.class) -public class JavaDeclareRecipesTranslator extends PacketTranslator { - - @Override - public void translate(ServerDeclareRecipesPacket packet, GeyserSession session) { - CraftingDataPacket craftingDataPacket = new CraftingDataPacket(); - craftingDataPacket.setCleanRecipes(true); - for (Recipe recipe : packet.getRecipes()) { - switch (recipe.getType()) { - case CRAFTING_SHAPELESS: { - ShapelessRecipeData shapelessRecipeData = (ShapelessRecipeData) recipe.getData(); - ItemData output = ItemTranslator.translateToBedrock(session, shapelessRecipeData.getResult()); - output = ItemData.of(output.getId(), output.getDamage(), output.getCount()); //strip NBT - ItemData[][] inputCombinations = combinations(session, shapelessRecipeData.getIngredients()); - for (ItemData[] inputs : inputCombinations) { - UUID uuid = UUID.randomUUID(); - craftingDataPacket.getCraftingData().add(CraftingData.fromShapeless(uuid.toString(), - inputs, new ItemData[]{output}, uuid, "crafting_table", 0)); - } - break; - } - case CRAFTING_SHAPED: { - ShapedRecipeData shapedRecipeData = (ShapedRecipeData) recipe.getData(); - ItemData output = ItemTranslator.translateToBedrock(session, shapedRecipeData.getResult()); - output = ItemData.of(output.getId(), output.getDamage(), output.getCount()); //strip NBT - ItemData[][] inputCombinations = combinations(session, shapedRecipeData.getIngredients()); - for (ItemData[] inputs : inputCombinations) { - UUID uuid = UUID.randomUUID(); - craftingDataPacket.getCraftingData().add(CraftingData.fromShaped(uuid.toString(), - shapedRecipeData.getWidth(), shapedRecipeData.getHeight(), inputs, - new ItemData[]{output}, uuid, "crafting_table", 0)); - } - break; - } - - // These recipes are enabled by sending a special recipe - case CRAFTING_SPECIAL_BOOKCLONING: { - craftingDataPacket.getCraftingData().add(RecipeRegistry.BOOK_CLONING_RECIPE_DATA); - break; - } - case CRAFTING_SPECIAL_REPAIRITEM: { - craftingDataPacket.getCraftingData().add(RecipeRegistry.TOOL_REPAIRING_RECIPE_DATA); - break; - } - - // Java doesn't actually tell us the recipes so we need to calculate this ahead of time. - case CRAFTING_SPECIAL_FIREWORK_ROCKET: { - craftingDataPacket.getCraftingData().addAll(RecipeRegistry.FIREWORK_ROCKET_RECIPES); - break; - } - case CRAFTING_SPECIAL_FIREWORK_STAR: { - craftingDataPacket.getCraftingData().addAll(RecipeRegistry.FIREWORK_STAR_RECIPES); - break; - } - case CRAFTING_SPECIAL_SHULKERBOXCOLORING: { - craftingDataPacket.getCraftingData().addAll(RecipeRegistry.SHULKER_BOX_DYEING_RECIPES); - break; - } - case CRAFTING_SPECIAL_SUSPICIOUSSTEW: { - craftingDataPacket.getCraftingData().addAll(RecipeRegistry.SUSPICIOUS_STEW_RECIPES); - break; - } - case CRAFTING_SPECIAL_TIPPEDARROW: { - craftingDataPacket.getCraftingData().addAll(RecipeRegistry.TIPPED_ARROW_RECIPES); - break; - } - case CRAFTING_SPECIAL_ARMORDYE: { - // This one's even worse since it's not actually on Bedrock, but it still works! - craftingDataPacket.getCraftingData().addAll(RecipeRegistry.LEATHER_DYEING_RECIPES); - break; - } - } - } - craftingDataPacket.getPotionMixData().addAll(PotionMixRegistry.POTION_MIXES); - session.sendUpstreamPacket(craftingDataPacket); - } - - //TODO: rewrite - private ItemData[][] combinations(GeyserSession session, Ingredient[] ingredients) { - Map, IntSet> squashedOptions = new HashMap<>(); - for (int i = 0; i < ingredients.length; i++) { - if (ingredients[i].getOptions().length == 0) { - squashedOptions.computeIfAbsent(Collections.singleton(ItemData.AIR), k -> new IntOpenHashSet()).add(i); - continue; - } - Ingredient ingredient = ingredients[i]; - Map> groupedByIds = Arrays.stream(ingredient.getOptions()) - .map(item -> ItemTranslator.translateToBedrock(session, item)) - .collect(Collectors.groupingBy(item -> new GroupedItem(item.getId(), item.getCount(), item.getTag()))); - Set optionSet = new HashSet<>(groupedByIds.size()); - for (Map.Entry> entry : groupedByIds.entrySet()) { - if (entry.getValue().size() > 1) { - GroupedItem groupedItem = entry.getKey(); - int idCount = 0; - //not optimal - for (ItemEntry itemEntry : ItemRegistry.ITEM_ENTRIES.values()) { - if (itemEntry.getBedrockId() == groupedItem.id) { - idCount++; - } - } - if (entry.getValue().size() < idCount) { - optionSet.addAll(entry.getValue()); - } else { - optionSet.add(ItemData.of(groupedItem.id, Short.MAX_VALUE, groupedItem.count, groupedItem.tag)); - } - } else { - ItemData item = entry.getValue().get(0); - optionSet.add(item); - } - } - squashedOptions.computeIfAbsent(optionSet, k -> new IntOpenHashSet()).add(i); - } - int totalCombinations = 1; - for (Set optionSet : squashedOptions.keySet()) { - totalCombinations *= optionSet.size(); - } - if (totalCombinations > 500) { - ItemData[] translatedItems = new ItemData[ingredients.length]; - for (int i = 0; i < ingredients.length; i++) { - if (ingredients[i].getOptions().length > 0) { - translatedItems[i] = ItemTranslator.translateToBedrock(session, ingredients[i].getOptions()[0]); - } else { - translatedItems[i] = ItemData.AIR; - } - } - return new ItemData[][]{translatedItems}; - } - List> sortedSets = new ArrayList<>(squashedOptions.keySet()); - sortedSets.sort(Comparator.comparing(Set::size, Comparator.reverseOrder())); - ItemData[][] combinations = new ItemData[totalCombinations][ingredients.length]; - int x = 1; - for (Set set : sortedSets) { - IntSet slotSet = squashedOptions.get(set); - int i = 0; - for (ItemData item : set) { - for (int j = 0; j < totalCombinations / set.size(); j++) { - final int comboIndex = (i * x) + (j % x) + ((j / x) * set.size() * x); - for (int slot : slotSet) { - combinations[comboIndex][slot] = item; - } - } - i++; - } - x *= set.size(); - } - return combinations; - } - - @EqualsAndHashCode - @AllArgsConstructor - private static class GroupedItem { - int id; - int count; - NbtMap tag; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java deleted file mode 100644 index 35ca79286..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java; - -import com.github.steveice10.mc.protocol.data.game.entity.player.HandPreference; -import com.github.steveice10.mc.protocol.data.game.setting.ChatVisibility; -import com.github.steveice10.mc.protocol.data.game.setting.SkinPart; -import com.github.steveice10.mc.protocol.packet.ingame.client.ClientPluginMessagePacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.ClientSettingsPacket; -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerJoinGamePacket; -import com.nukkitx.protocol.bedrock.data.GameRuleData; -import com.nukkitx.protocol.bedrock.data.PlayerPermission; -import com.nukkitx.protocol.bedrock.packet.*; -import org.geysermc.connector.entity.PlayerEntity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.DimensionUtils; -import org.geysermc.connector.utils.PluginMessageUtils; - -import java.util.Arrays; -import java.util.List; - -@Translator(packet = ServerJoinGamePacket.class) -public class JavaJoinGameTranslator extends PacketTranslator { - - @Override - public void translate(ServerJoinGamePacket packet, GeyserSession session) { - PlayerEntity entity = session.getPlayerEntity(); - entity.setEntityId(packet.getEntityId()); - // If the player is already initialized and a join game packet is sent, they - // are swapping servers - String newDimension = DimensionUtils.getNewDimension(packet.getDimension()); - if (session.isSpawned()) { - String fakeDim = session.getDimension().equals(DimensionUtils.OVERWORLD) ? DimensionUtils.NETHER : DimensionUtils.OVERWORLD; - DimensionUtils.switchDimension(session, fakeDim); - DimensionUtils.switchDimension(session, newDimension); - - session.getWorldCache().removeScoreboard(); - } - - AdventureSettingsPacket bedrockPacket = new AdventureSettingsPacket(); - bedrockPacket.setUniqueEntityId(session.getPlayerEntity().getGeyserId()); - bedrockPacket.setPlayerPermission(PlayerPermission.MEMBER); - session.sendUpstreamPacket(bedrockPacket); - - PlayStatusPacket playStatus = new PlayStatusPacket(); - playStatus.setStatus(PlayStatusPacket.Status.LOGIN_SUCCESS); - // session.sendPacket(playStatus); - - SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket(); - playerGameTypePacket.setGamemode(packet.getGameMode().ordinal()); - session.sendUpstreamPacket(playerGameTypePacket); - session.setGameMode(packet.getGameMode()); - - SetEntityDataPacket entityDataPacket = new SetEntityDataPacket(); - entityDataPacket.setRuntimeEntityId(entity.getGeyserId()); - entityDataPacket.getMetadata().putAll(entity.getMetadata()); - session.sendUpstreamPacket(entityDataPacket); - - // Send if client should show respawn screen - GameRulesChangedPacket gamerulePacket = new GameRulesChangedPacket(); - gamerulePacket.getGameRules().add(new GameRuleData<>("doimmediaterespawn", !packet.isEnableRespawnScreen())); - session.sendUpstreamPacket(gamerulePacket); - - session.setRenderDistance(packet.getViewDistance()); - - // We need to send our skin parts to the server otherwise java sees us with no hat, jacket etc - String locale = session.getLocale(); - List skinParts = Arrays.asList(SkinPart.values()); - ClientSettingsPacket clientSettingsPacket = new ClientSettingsPacket(locale, (byte) session.getRenderDistance(), ChatVisibility.FULL, true, skinParts, HandPreference.RIGHT_HAND); - session.sendDownstreamPacket(clientSettingsPacket); - - session.sendDownstreamPacket(new ClientPluginMessagePacket("minecraft:brand", PluginMessageUtils.getGeyserBrandData())); - - if (!newDimension.equals(session.getDimension())) { - DimensionUtils.switchDimension(session, newDimension); - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java deleted file mode 100644 index 64029ed4f..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java; - -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.LevelEventType; -import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; -import com.nukkitx.protocol.bedrock.packet.SetPlayerGameTypePacket; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.attribute.AttributeType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.DimensionUtils; - -@Translator(packet = ServerRespawnPacket.class) -public class JavaRespawnTranslator extends PacketTranslator { - - @Override - public void translate(ServerRespawnPacket packet, GeyserSession session) { - Entity entity = session.getPlayerEntity(); - if (entity == null) - return; - - float maxHealth = entity.getAttributes().containsKey(AttributeType.MAX_HEALTH) ? entity.getAttributes().get(AttributeType.MAX_HEALTH).getValue() : 20f; - // Max health must be divisible by two in bedrock - entity.getAttributes().put(AttributeType.HEALTH, AttributeType.HEALTH.getAttribute(maxHealth, (maxHealth % 2 == 1 ? maxHealth + 1 : maxHealth))); - - session.getInventoryCache().setOpenInventory(null); - - SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket(); - playerGameTypePacket.setGamemode(packet.getGamemode().ordinal()); - session.sendUpstreamPacket(playerGameTypePacket); - session.setGameMode(packet.getGamemode()); - - if (session.isRaining()) { - LevelEventPacket stopRainPacket = new LevelEventPacket(); - stopRainPacket.setType(LevelEventType.STOP_RAINING); - stopRainPacket.setData(0); - stopRainPacket.setPosition(Vector3f.ZERO); - session.sendUpstreamPacket(stopRainPacket); - session.setRaining(false); - } - - String newDimension = DimensionUtils.getNewDimension(packet.getDimension()); - if (!session.getDimension().equals(newDimension)) { - DimensionUtils.switchDimension(session, newDimension); - } else { - if (session.isManyDimPackets()) { //reloading world - String fakeDim = session.getDimension().equals(DimensionUtils.OVERWORLD) ? DimensionUtils.NETHER : DimensionUtils.OVERWORLD; - DimensionUtils.switchDimension(session, fakeDim); - DimensionUtils.switchDimension(session, newDimension); - } else { - // Handled in JavaPlayerPositionRotationTranslator - session.setSpawned(false); - } - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaTitleTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaTitleTranslator.java deleted file mode 100644 index 7fe4d7b31..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaTitleTranslator.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java; - -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.chat.MessageTranslator; - -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerTitlePacket; -import com.nukkitx.protocol.bedrock.packet.SetTitlePacket; - -@Translator(packet = ServerTitlePacket.class) -public class JavaTitleTranslator extends PacketTranslator { - - @Override - public void translate(ServerTitlePacket packet, GeyserSession session) { - SetTitlePacket titlePacket = new SetTitlePacket(); - String locale = session.getLocale(); - - String text; - if (packet.getTitle() == null) { - text = " "; - } else { - text = MessageTranslator.convertMessage(packet.getTitle().toString(), locale); - } - - switch (packet.getAction()) { - case TITLE: - titlePacket.setType(SetTitlePacket.Type.TITLE); - titlePacket.setText(text); - break; - case SUBTITLE: - titlePacket.setType(SetTitlePacket.Type.SUBTITLE); - titlePacket.setText(text); - break; - case CLEAR: - case RESET: - titlePacket.setType(SetTitlePacket.Type.CLEAR); - titlePacket.setText(""); - break; - case ACTION_BAR: - titlePacket.setType(SetTitlePacket.Type.ACTIONBAR); - titlePacket.setText(text); - break; - case TIMES: - titlePacket.setType(SetTitlePacket.Type.TIMES); - titlePacket.setFadeInTime(packet.getFadeIn()); - titlePacket.setFadeOutTime(packet.getFadeOut()); - titlePacket.setStayTime(packet.getStay()); - break; - } - - session.sendUpstreamPacket(titlePacket); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAnimationTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAnimationTranslator.java deleted file mode 100644 index 4f2fe0225..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAnimationTranslator.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.entity; - -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityAnimationPacket; -import com.nukkitx.protocol.bedrock.packet.AnimatePacket; - -@Translator(packet = ServerEntityAnimationPacket.class) -public class JavaEntityAnimationTranslator extends PacketTranslator { - - @Override - public void translate(ServerEntityAnimationPacket packet, GeyserSession session) { - Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); - if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { - entity = session.getPlayerEntity(); - } - if (entity == null) - return; - - AnimatePacket animatePacket = new AnimatePacket(); - animatePacket.setRuntimeEntityId(entity.getGeyserId()); - switch (packet.getAnimation()) { - case SWING_ARM: - animatePacket.setAction(AnimatePacket.Action.SWING_ARM); - break; - case CRITICAL_HIT: - animatePacket.setAction(AnimatePacket.Action.CRITICAL_HIT); - break; - case ENCHANTMENT_CRITICAL_HIT: - animatePacket.setAction(AnimatePacket.Action.MAGIC_CRITICAL_HIT); - break; - case LEAVE_BED: - animatePacket.setAction(AnimatePacket.Action.WAKE_UP); - break; - } - - session.sendUpstreamPacket(animatePacket); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEquipmentTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEquipmentTranslator.java deleted file mode 100644 index 96d4c8366..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEquipmentTranslator.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.entity; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Equipment; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityEquipmentPacket; -import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.LivingEntity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.item.ItemTranslator; - -@Translator(packet = ServerEntityEquipmentPacket.class) -public class JavaEntityEquipmentTranslator extends PacketTranslator { - - @Override - public void translate(ServerEntityEquipmentPacket packet, GeyserSession session) { - Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); - if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { - entity = session.getPlayerEntity(); - } - - if (entity == null) - return; - - if (!(entity instanceof LivingEntity)) { - session.getConnector().getLogger().debug("Attempted to add armor to a non-living entity type (" + - entity.getEntityType().name() + ")."); - return; - } - - LivingEntity livingEntity = (LivingEntity) entity; - for (Equipment equipment : packet.getEquipment()) { - ItemData item = ItemTranslator.translateToBedrock(session, equipment.getItem()); - switch (equipment.getSlot()) { - case HELMET: - livingEntity.setHelmet(item); - break; - case CHESTPLATE: - livingEntity.setChestplate(item); - break; - case LEGGINGS: - livingEntity.setLeggings(item); - break; - case BOOTS: - livingEntity.setBoots(item); - break; - case MAIN_HAND: - livingEntity.setHand(item); - break; - case OFF_HAND: - livingEntity.setOffHand(item); - break; - } - } - livingEntity.updateEquipment(session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityMetadataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityMetadataTranslator.java deleted file mode 100644 index 97160250d..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityMetadataTranslator.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.entity; - -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityMetadataPacket; -import org.geysermc.connector.utils.LanguageUtils; - -@Translator(packet = ServerEntityMetadataPacket.class) -public class JavaEntityMetadataTranslator extends PacketTranslator { - - @Override - public void translate(ServerEntityMetadataPacket packet, GeyserSession session) { - Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); - if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { - entity = session.getPlayerEntity(); - } - if (entity == null) return; - - for (EntityMetadata metadata : packet.getMetadata()) { - try { - entity.updateBedrockMetadata(metadata, session); - } catch (ClassCastException e) { - // Class cast exceptions are really the only ones we're going to get in normal gameplay - // Because some entity rewriters forget about some values - // Any other errors are actual bugs - session.getConnector().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.network.translator.metadata.failed", metadata, entity.getEntityType())); - session.getConnector().getLogger().debug("Entity Java ID: " + entity.getEntityId() + ", Geyser ID: " + entity.getGeyserId()); - if (session.getConnector().getConfig().isDebugMode()) { - e.printStackTrace(); - } - } - } - - entity.updateBedrockMetadata(session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPositionRotationTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPositionRotationTranslator.java deleted file mode 100644 index c4bb799e6..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPositionRotationTranslator.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.entity; - -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityPositionRotationPacket; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - -@Translator(packet = ServerEntityPositionRotationPacket.class) -public class JavaEntityPositionRotationTranslator extends PacketTranslator { - - @Override - public void translate(ServerEntityPositionRotationPacket packet, GeyserSession session) { - Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); - if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { - entity = session.getPlayerEntity(); - } - if (entity == null) return; - - entity.updatePositionAndRotation(session, packet.getMoveX(), packet.getMoveY(), packet.getMoveZ(), packet.getYaw(), packet.getPitch(), packet.isOnGround()); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPropertiesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPropertiesTranslator.java deleted file mode 100644 index 744b11e96..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPropertiesTranslator.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.entity; - -import com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityPropertiesPacket; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.attribute.AttributeType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.AttributeUtils; - -@Translator(packet = ServerEntityPropertiesPacket.class) -public class JavaEntityPropertiesTranslator extends PacketTranslator { - - @Override - public void translate(ServerEntityPropertiesPacket packet, GeyserSession session) { - boolean isSessionPlayer = false; - Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); - if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { - entity = session.getPlayerEntity(); - isSessionPlayer = true; - } - if (entity == null) return; - - for (Attribute attribute : packet.getAttributes()) { - switch (attribute.getType()) { - case GENERIC_MAX_HEALTH: - entity.getAttributes().put(AttributeType.MAX_HEALTH, AttributeType.MAX_HEALTH.getAttribute((float) AttributeUtils.calculateValue(attribute))); - break; - case GENERIC_ATTACK_DAMAGE: - entity.getAttributes().put(AttributeType.ATTACK_DAMAGE, AttributeType.ATTACK_DAMAGE.getAttribute((float) AttributeUtils.calculateValue(attribute))); - break; - case GENERIC_ATTACK_SPEED: - if (isSessionPlayer) { - // Get attack speed value for use in sending the faux cooldown - double attackSpeed = AttributeUtils.calculateValue(attribute); - session.setAttackSpeed(attackSpeed); - } - break; - case GENERIC_FLYING_SPEED: - entity.getAttributes().put(AttributeType.FLYING_SPEED, AttributeType.FLYING_SPEED.getAttribute((float) AttributeUtils.calculateValue(attribute))); - entity.getAttributes().put(AttributeType.MOVEMENT_SPEED, AttributeType.MOVEMENT_SPEED.getAttribute((float) AttributeUtils.calculateValue(attribute))); - break; - case GENERIC_MOVEMENT_SPEED: - entity.getAttributes().put(AttributeType.MOVEMENT_SPEED, AttributeType.MOVEMENT_SPEED.getAttribute((float) AttributeUtils.calculateValue(attribute))); - break; - case GENERIC_FOLLOW_RANGE: - entity.getAttributes().put(AttributeType.FOLLOW_RANGE, AttributeType.FOLLOW_RANGE.getAttribute((float) AttributeUtils.calculateValue(attribute))); - break; - case GENERIC_KNOCKBACK_RESISTANCE: - entity.getAttributes().put(AttributeType.KNOCKBACK_RESISTANCE, AttributeType.KNOCKBACK_RESISTANCE.getAttribute((float) AttributeUtils.calculateValue(attribute))); - break; - case HORSE_JUMP_STRENGTH: - entity.getAttributes().put(AttributeType.HORSE_JUMP_STRENGTH, AttributeType.HORSE_JUMP_STRENGTH.getAttribute((float) AttributeUtils.calculateValue(attribute))); - break; - } - } - - entity.updateBedrockAttributes(session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java deleted file mode 100644 index 0fecf118e..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.entity; - -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntitySetPassengersPacket; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData; -import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket; -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.living.ArmorStandEntity; -import org.geysermc.connector.entity.living.animal.AnimalEntity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - -import java.util.Arrays; - -@Translator(packet = ServerEntitySetPassengersPacket.class) -public class JavaEntitySetPassengersTranslator extends PacketTranslator { - - @Override - public void translate(ServerEntitySetPassengersPacket packet, GeyserSession session) { - Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); - if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { - entity = session.getPlayerEntity(); - } - - if (entity == null) return; - - LongOpenHashSet passengers = entity.getPassengers().clone(); - boolean rider = true; - for (long passengerId : packet.getPassengerIds()) { - Entity passenger = session.getEntityCache().getEntityByJavaId(passengerId); - if (passengerId == session.getPlayerEntity().getEntityId()) { - passenger = session.getPlayerEntity(); - session.setRidingVehicleEntity(entity); - } - // Passenger hasn't loaded in and entity link needs to be set later - if (passenger == null && passengerId != 0) { - session.getEntityCache().addCachedPlayerEntityLink(passengerId, packet.getEntityId()); - } - if (passenger == null) { - continue; - } - - EntityLinkData.Type type = rider ? EntityLinkData.Type.RIDER : EntityLinkData.Type.PASSENGER; - SetEntityLinkPacket linkPacket = new SetEntityLinkPacket(); - linkPacket.setEntityLink(new EntityLinkData(entity.getGeyserId(), passenger.getGeyserId(), type, false)); - session.sendUpstreamPacket(linkPacket); - passengers.add(passengerId); - - // Head rotation on boats - if (entity.getEntityType() == EntityType.BOAT) { - passenger.getMetadata().put(EntityData.RIDER_ROTATION_LOCKED, (byte) 1); - passenger.getMetadata().put(EntityData.RIDER_MAX_ROTATION, 90f); - passenger.getMetadata().put(EntityData.RIDER_MIN_ROTATION, !passengers.isEmpty() ? -90f : 0f); - } else { - passenger.getMetadata().remove(EntityData.RIDER_ROTATION_LOCKED); - passenger.getMetadata().remove(EntityData.RIDER_MAX_ROTATION); - passenger.getMetadata().remove(EntityData.RIDER_MIN_ROTATION); - } - - passenger.updateBedrockMetadata(session); - rider = false; - } - - entity.setPassengers(passengers); - - for (long passengerId : entity.getPassengers()) { - Entity passenger = session.getEntityCache().getEntityByJavaId(passengerId); - if (passengerId == session.getPlayerEntity().getEntityId()) { - passenger = session.getPlayerEntity(); - } - if (passenger == null) { - continue; - } - if (Arrays.stream(packet.getPassengerIds()).noneMatch(id -> id == passengerId)) { - SetEntityLinkPacket linkPacket = new SetEntityLinkPacket(); - linkPacket.setEntityLink(new EntityLinkData(entity.getGeyserId(), passenger.getGeyserId(), EntityLinkData.Type.REMOVE, false)); - session.sendUpstreamPacket(linkPacket); - passengers.remove(passenger.getEntityId()); - - this.updateOffset(passenger, entity, session, false, false, (packet.getPassengerIds().length > 1)); - } else { - this.updateOffset(passenger, entity, session, (packet.getPassengerIds()[0] == passengerId), true, (packet.getPassengerIds().length > 1)); - } - - // Force an update to the passenger metadata - passenger.updateBedrockMetadata(session); - } - - switch (entity.getEntityType()) { - case HORSE: - case SKELETON_HORSE: - case DONKEY: - case MULE: - case RAVAGER: - entity.getMetadata().put(EntityData.RIDER_MAX_ROTATION, 181.0f); - entity.updateBedrockMetadata(session); - break; - } - } - - private float getMountedHeightOffset(Entity mount) { - float height = mount.getMetadata().getFloat(EntityData.BOUNDING_BOX_HEIGHT); - float mountedHeightOffset = height * 0.75f; - switch (mount.getEntityType()) { - case CHICKEN: - case SPIDER: - mountedHeightOffset = height * 0.5f; - break; - case DONKEY: - case MULE: - mountedHeightOffset -= 0.25f; - break; - case LLAMA: - mountedHeightOffset = height * 0.67f; - break; - case MINECART: - case MINECART_HOPPER: - case MINECART_TNT: - case MINECART_CHEST: - case MINECART_FURNACE: - case MINECART_SPAWNER: - case MINECART_COMMAND_BLOCK: - mountedHeightOffset = 0; - break; - case BOAT: - mountedHeightOffset = -0.1f; - break; - case HOGLIN: - case ZOGLIN: - boolean isBaby = mount.getMetadata().getFlags().getFlag(EntityFlag.BABY); - mountedHeightOffset = height - (isBaby ? 0.2f : 0.15f); - break; - case PIGLIN: - mountedHeightOffset = height * 0.92f; - break; - case RAVAGER: - mountedHeightOffset = 2.1f; - break; - case SKELETON_HORSE: - mountedHeightOffset -= 0.1875f; - break; - case STRIDER: - mountedHeightOffset = height - 0.19f; - break; - } - return mountedHeightOffset; - } - - private float getHeightOffset(Entity passenger) { - boolean isBaby; - switch (passenger.getEntityType()) { - case SKELETON: - case STRAY: - case WITHER_SKELETON: - return -0.6f; - case ARMOR_STAND: - if (((ArmorStandEntity) passenger).isMarker()) { - return 0.0f; - } else { - return 0.1f; - } - case ENDERMITE: - case SILVERFISH: - return 0.1f; - case PIGLIN: - case PIGLIN_BRUTE: - case ZOMBIFIED_PIGLIN: - isBaby = passenger.getMetadata().getFlags().getFlag(EntityFlag.BABY); - return isBaby ? -0.05f : -0.45f; - case ZOMBIE: - isBaby = passenger.getMetadata().getFlags().getFlag(EntityFlag.BABY); - return isBaby ? 0.0f : -0.45f; - case EVOKER: - case ILLUSIONER: - case PILLAGER: - case RAVAGER: - case VINDICATOR: - case WITCH: - return -0.45f; - case PLAYER: - return -0.35f; - } - if (passenger instanceof AnimalEntity) { - return 0.14f; - } - return 0f; - } - - private void updateOffset(Entity passenger, Entity mount, GeyserSession session, boolean rider, boolean riding, boolean moreThanOneEntity) { - passenger.getMetadata().getFlags().setFlag(EntityFlag.RIDING, riding); - if (riding) { - // Without the Y offset, Bedrock players will find themselves in the floor when mounting - float mountedHeightOffset = getMountedHeightOffset(mount); - float heightOffset = getHeightOffset(passenger); - - float xOffset = 0; - float yOffset = mountedHeightOffset + heightOffset; - float zOffset = 0; - switch (mount.getEntityType()) { - case BOAT: - // Without the X offset, more than one entity on a boat is stacked on top of each other - if (rider && moreThanOneEntity) { - xOffset = 0.2f; - } else if (moreThanOneEntity) { - xOffset = -0.6f; - } - break; - case CHICKEN: - zOffset = -0.1f; - break; - case LLAMA: - zOffset = -0.3f; - break; - } - /* - * Bedrock Differences - * Zoglin & Hoglin seem to be taller in Bedrock edition - * Horses are tinier - * Players, Minecarts, and Boats have different origins - */ - if (passenger.getEntityType() == EntityType.PLAYER && mount.getEntityType() != EntityType.PLAYER) { - yOffset += EntityType.PLAYER.getOffset(); - } - switch (mount.getEntityType()) { - case MINECART: - case MINECART_HOPPER: - case MINECART_TNT: - case MINECART_CHEST: - case MINECART_FURNACE: - case MINECART_SPAWNER: - case MINECART_COMMAND_BLOCK: - case BOAT: - yOffset -= mount.getEntityType().getHeight() * 0.5f; - } - Vector3f offset = Vector3f.from(xOffset, yOffset, zOffset); - passenger.getMetadata().put(EntityData.RIDER_SEAT_POSITION, offset); - } - passenger.updateBedrockMetadata(session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerAbilitiesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerAbilitiesTranslator.java deleted file mode 100644 index 0f5a12e5a..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerAbilitiesTranslator.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.entity.player; - -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerAbilitiesPacket; -import com.nukkitx.protocol.bedrock.data.AdventureSetting; -import com.nukkitx.protocol.bedrock.data.PlayerPermission; -import com.nukkitx.protocol.bedrock.data.command.CommandPermission; -import com.nukkitx.protocol.bedrock.packet.AdventureSettingsPacket; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.PlayerEntity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - -import java.util.Set; - -@Translator(packet = ServerPlayerAbilitiesPacket.class) -public class JavaPlayerAbilitiesTranslator extends PacketTranslator { - - @Override - public void translate(ServerPlayerAbilitiesPacket packet, GeyserSession session) { - PlayerEntity entity = session.getPlayerEntity(); - if (entity == null) - return; - - session.setCanFly(packet.isCanFly()); - session.setFlying(packet.isFlying()); - session.sendAdventureSettings(); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerActionAckTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerActionAckTranslator.java deleted file mode 100644 index 9ed11a23d..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerActionAckTranslator.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.entity.player; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerActionAckPacket; -import com.github.steveice10.opennbt.tag.builtin.*; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.LevelEventType; -import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; -import org.geysermc.connector.inventory.PlayerInventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.utils.BlockUtils; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.ChunkUtils; - -@Translator(packet = ServerPlayerActionAckPacket.class) -public class JavaPlayerActionAckTranslator extends PacketTranslator { - - @Override - public void translate(ServerPlayerActionAckPacket packet, GeyserSession session) { - LevelEventPacket levelEvent = new LevelEventPacket(); - switch (packet.getAction()) { - case FINISH_DIGGING: - double blockHardness = BlockTranslator.JAVA_RUNTIME_ID_TO_HARDNESS.get(session.getBreakingBlock()); - if (session.getGameMode() != GameMode.CREATIVE && blockHardness != 0) { - levelEvent.setType(LevelEventType.PARTICLE_DESTROY_BLOCK); - levelEvent.setPosition(Vector3f.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ())); - levelEvent.setData(BlockTranslator.getBedrockBlockId(session.getBreakingBlock())); - session.sendUpstreamPacket(levelEvent); - session.setBreakingBlock(0); - } - ChunkUtils.updateBlock(session, packet.getNewState(), packet.getPosition()); - break; - case START_DIGGING: - if (session.getGameMode() == GameMode.CREATIVE) { - break; - } - blockHardness = BlockTranslator.JAVA_RUNTIME_ID_TO_HARDNESS.get(packet.getNewState()); - levelEvent.setType(LevelEventType.BLOCK_START_BREAK); - levelEvent.setPosition(Vector3f.from( - packet.getPosition().getX(), - packet.getPosition().getY(), - packet.getPosition().getZ() - )); - PlayerInventory inventory = session.getInventory(); - ItemStack item = inventory.getItemInHand(); - ItemEntry itemEntry = null; - CompoundTag nbtData = new CompoundTag(""); - if (item != null) { - itemEntry = ItemRegistry.getItem(item); - nbtData = item.getNbt(); - } - double breakTime = Math.ceil(BlockUtils.getBreakTime(blockHardness, packet.getNewState(), itemEntry, nbtData, session) * 20); - levelEvent.setData((int) (65535 / breakTime)); - session.setBreakingBlock(packet.getNewState()); - session.sendUpstreamPacket(levelEvent); - break; - case CANCEL_DIGGING: - levelEvent.setType(LevelEventType.BLOCK_STOP_BREAK); - levelEvent.setPosition(Vector3f.from( - packet.getPosition().getX(), - packet.getPosition().getY(), - packet.getPosition().getZ() - )); - levelEvent.setData(0); - session.setBreakingBlock(0); - session.sendUpstreamPacket(levelEvent); - break; - } - } -} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerHealthTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerHealthTranslator.java deleted file mode 100644 index 7ba84ffd0..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerHealthTranslator.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.entity.player; - -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.attribute.AttributeType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerHealthPacket; -import com.nukkitx.protocol.bedrock.packet.SetHealthPacket; - -@Translator(packet = ServerPlayerHealthPacket.class) -public class JavaPlayerHealthTranslator extends PacketTranslator { - - @Override - public void translate(ServerPlayerHealthPacket packet, GeyserSession session) { - Entity entity = session.getPlayerEntity(); - if (entity == null) - return; - - int health = (int) Math.ceil(packet.getHealth()); - SetHealthPacket setHealthPacket = new SetHealthPacket(); - setHealthPacket.setHealth(health); - session.sendUpstreamPacket(setHealthPacket); - - float maxHealth = entity.getAttributes().containsKey(AttributeType.MAX_HEALTH) ? entity.getAttributes().get(AttributeType.MAX_HEALTH).getValue() : 20f; - // Max health must be divisible by two in bedrock - if ((maxHealth % 2) == 1) { - maxHealth += 1; - } - - entity.getAttributes().put(AttributeType.HEALTH, AttributeType.HEALTH.getAttribute(health, maxHealth)); - entity.getAttributes().put(AttributeType.HUNGER, AttributeType.HUNGER.getAttribute(packet.getFood())); - entity.getAttributes().put(AttributeType.SATURATION, AttributeType.SATURATION.getAttribute(packet.getSaturation())); - entity.updateBedrockAttributes(session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java deleted file mode 100644 index f0a6f32a8..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.entity.player; - -import com.github.steveice10.mc.protocol.data.game.entity.player.PositionElement; -import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerPositionRotationPacket; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; -import com.nukkitx.protocol.bedrock.packet.RespawnPacket; -import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; -import org.geysermc.connector.entity.PlayerEntity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.session.cache.TeleportCache; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.ChunkUtils; -import org.geysermc.connector.utils.LanguageUtils; - -@Translator(packet = ServerPlayerPositionRotationPacket.class) -public class JavaPlayerPositionRotationTranslator extends PacketTranslator { - - @Override - public void translate(ServerPlayerPositionRotationPacket packet, GeyserSession session) { - PlayerEntity entity = session.getPlayerEntity(); - if (entity == null) - return; - - if (!session.isLoggedIn()) - return; - - if (!session.isSpawned()) { - Vector3f pos = Vector3f.from(packet.getX(), packet.getY(), packet.getZ()); - entity.setPosition(pos); - entity.setRotation(Vector3f.from(packet.getYaw(), packet.getPitch(), packet.getYaw())); - - RespawnPacket respawnPacket = new RespawnPacket(); - respawnPacket.setRuntimeEntityId(0); // Bedrock server behavior - respawnPacket.setPosition(entity.getPosition()); - respawnPacket.setState(RespawnPacket.State.SERVER_READY); - session.sendUpstreamPacket(respawnPacket); - - SetEntityDataPacket entityDataPacket = new SetEntityDataPacket(); - entityDataPacket.setRuntimeEntityId(entity.getGeyserId()); - entityDataPacket.getMetadata().putAll(entity.getMetadata()); - session.sendUpstreamPacket(entityDataPacket); - - MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); - movePlayerPacket.setRuntimeEntityId(entity.getGeyserId()); - movePlayerPacket.setPosition(entity.getPosition()); - movePlayerPacket.setRotation(Vector3f.from(packet.getPitch(), packet.getYaw(), 0)); - movePlayerPacket.setMode(MovePlayerPacket.Mode.RESPAWN); - - session.sendUpstreamPacket(movePlayerPacket); - session.setSpawned(true); - - ClientTeleportConfirmPacket teleportConfirmPacket = new ClientTeleportConfirmPacket(packet.getTeleportId()); - session.sendDownstreamPacket(teleportConfirmPacket); - - ChunkUtils.updateChunkPosition(session, pos.toInt()); - - session.getConnector().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.entity.player.spawn", packet.getX(), packet.getY(), packet.getZ())); - return; - } - - session.setSpawned(true); - - // Ignore certain move correction packets for smoother movement - // These are never relative - if (packet.getRelative().isEmpty()) { - double xDis = Math.abs(entity.getPosition().getX() - packet.getX()); - double yDis = entity.getPosition().getY() - packet.getY(); - double zDis = Math.abs(entity.getPosition().getZ() - packet.getZ()); - if (!(xDis > 1.5 || (yDis < 1.45 || yDis > (session.isJumping() ? 4.3 : (session.isSprinting() ? 2.5 : 1.9))) || zDis > 1.5)) { - // Fake confirm the teleport but don't send it to the client - ClientTeleportConfirmPacket teleportConfirmPacket = new ClientTeleportConfirmPacket(packet.getTeleportId()); - session.sendDownstreamPacket(teleportConfirmPacket); - return; - } - } - - // If coordinates are relative, then add to the existing coordinate - double newX = packet.getX() + - (packet.getRelative().contains(PositionElement.X) ? entity.getPosition().getX() : 0); - double newY = packet.getY() + - (packet.getRelative().contains(PositionElement.Y) ? entity.getPosition().getY() - EntityType.PLAYER.getOffset() : 0); - double newZ = packet.getZ() + - (packet.getRelative().contains(PositionElement.Z) ? entity.getPosition().getZ() : 0); - - double newPitch = packet.getPitch() + - (packet.getRelative().contains(PositionElement.PITCH) ? entity.getBedrockRotation().getX() : 0); - double newYaw = packet.getYaw() + - (packet.getRelative().contains(PositionElement.YAW) ? entity.getBedrockRotation().getY() : 0); - - - session.setTeleportCache(new TeleportCache(newX, newY, newZ, packet.getTeleportId())); - entity.moveAbsolute(session, Vector3f.from(newX, newY, newZ), (float) newYaw, (float) newPitch, true, true); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerSetExperienceTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerSetExperienceTranslator.java deleted file mode 100644 index ace7a2b8e..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerSetExperienceTranslator.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.entity.player; - -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.attribute.AttributeType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerSetExperiencePacket; - -@Translator(packet = ServerPlayerSetExperiencePacket.class) -public class JavaPlayerSetExperienceTranslator extends PacketTranslator { - - @Override - public void translate(ServerPlayerSetExperiencePacket packet, GeyserSession session) { - Entity entity = session.getPlayerEntity(); - if (entity == null) - return; - - entity.getAttributes().put(AttributeType.EXPERIENCE, AttributeType.EXPERIENCE.getAttribute(packet.getExperience())); - entity.getAttributes().put(AttributeType.EXPERIENCE_LEVEL, AttributeType.EXPERIENCE_LEVEL.getAttribute(packet.getLevel())); - entity.updateBedrockAttributes(session); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java deleted file mode 100644 index d9c859d3e..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.entity.spawn; - -import com.github.steveice10.mc.protocol.data.game.entity.object.FallingBlockData; -import com.github.steveice10.mc.protocol.data.game.entity.object.HangingDirection; -import com.github.steveice10.mc.protocol.data.game.entity.object.ProjectileData; -import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnEntityPacket; -import com.nukkitx.math.vector.Vector3f; -import org.geysermc.connector.entity.*; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.EntityUtils; -import org.geysermc.connector.utils.LanguageUtils; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; - -@Translator(packet = ServerSpawnEntityPacket.class) -public class JavaSpawnEntityTranslator extends PacketTranslator { - - @Override - public void translate(ServerSpawnEntityPacket packet, GeyserSession session) { - - Vector3f position = Vector3f.from(packet.getX(), packet.getY(), packet.getZ()); - Vector3f motion = Vector3f.from(packet.getMotionX(), packet.getMotionY(), packet.getMotionZ()); - Vector3f rotation = Vector3f.from(packet.getYaw(), packet.getPitch(), 0); - - org.geysermc.connector.entity.type.EntityType type = EntityUtils.toBedrockEntity(packet.getType()); - if (type == null) { - session.getConnector().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.entity.type_null", packet.getType())); - return; - } - - Class entityClass = type.getEntityClass(); - try { - Entity entity; - if (packet.getType() == EntityType.FALLING_BLOCK) { - entity = new FallingBlockEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), - type, position, motion, rotation, ((FallingBlockData) packet.getData()).getId()); - } else if (packet.getType() == EntityType.ITEM_FRAME) { - // Item frames need the hanging direction - entity = new ItemFrameEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), - type, position, motion, rotation, (HangingDirection) packet.getData()); - } else if (packet.getType() == EntityType.FISHING_BOBBER) { - // Fishing bobbers need the owner for the line - entity = new FishingHookEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), - type, position, motion, rotation, (ProjectileData) packet.getData()); - } else if (packet.getType() == EntityType.BOAT) { - // Initial rotation is incorrect - entity = new BoatEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), - type, position, motion, Vector3f.from(packet.getYaw(), 0, packet.getYaw())); - } else { - Constructor entityConstructor = entityClass.getConstructor(long.class, long.class, org.geysermc.connector.entity.type.EntityType.class, - Vector3f.class, Vector3f.class, Vector3f.class); - - entity = entityConstructor.newInstance(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), - type, position, motion, rotation - ); - } - session.getEntityCache().spawnEntity(entity); - } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException ex) { - ex.printStackTrace(); - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnLivingEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnLivingEntityTranslator.java deleted file mode 100644 index 5e33c8a15..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnLivingEntityTranslator.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.entity.spawn; - -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnLivingEntityPacket; -import com.nukkitx.math.vector.Vector3f; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.EntityUtils; -import org.geysermc.connector.utils.LanguageUtils; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; - -@Translator(packet = ServerSpawnLivingEntityPacket.class) -public class JavaSpawnLivingEntityTranslator extends PacketTranslator { - - @Override - public void translate(ServerSpawnLivingEntityPacket packet, GeyserSession session) { - Vector3f position = Vector3f.from(packet.getX(), packet.getY(), packet.getZ()); - Vector3f motion = Vector3f.from(packet.getMotionX(), packet.getMotionY(), packet.getMotionZ()); - Vector3f rotation = Vector3f.from(packet.getYaw(), packet.getPitch(), packet.getHeadYaw()); - - EntityType type = EntityUtils.toBedrockEntity(packet.getType()); - if (type == null) { - session.getConnector().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.entity.type_null", packet.getType())); - return; - } - - Class entityClass = type.getEntityClass(); - try { - Constructor entityConstructor = entityClass.getConstructor(long.class, long.class, EntityType.class, - Vector3f.class, Vector3f.class, Vector3f.class); - - Entity entity = entityConstructor.newInstance(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), - type, position, motion, rotation - ); - session.getEntityCache().spawnEntity(entity); - } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException ex) { - ex.printStackTrace(); - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPaintingTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPaintingTranslator.java deleted file mode 100644 index 5fe3847f1..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPaintingTranslator.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.entity.spawn; - -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.entity.PaintingEntity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.PaintingType; - -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnPaintingPacket; -import com.nukkitx.math.vector.Vector3f; - -@Translator(packet = ServerSpawnPaintingPacket.class) -public class JavaSpawnPaintingTranslator extends PacketTranslator { - - @Override - public void translate(ServerSpawnPaintingPacket packet, GeyserSession session) { - Vector3f position = Vector3f.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ()); - - GeyserConnector.getInstance().getGeneralThreadPool().execute(() -> { // #slowdownbrother, just don't execute it directly - PaintingEntity entity = new PaintingEntity( - packet.getEntityId(), - session.getEntityCache().getNextEntityId().incrementAndGet(), - position - ) - .setPaintingName(PaintingType.getByPaintingType(packet.getPaintingType())) - .setDirection(packet.getDirection().ordinal()); - - session.getEntityCache().spawnEntity(entity); - }); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java deleted file mode 100644 index 1996f696f..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.scoreboard; - -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.session.cache.WorldCache; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.scoreboard.Objective; -import org.geysermc.connector.scoreboard.Scoreboard; -import org.geysermc.connector.scoreboard.ScoreboardUpdater; -import org.geysermc.connector.network.translators.chat.MessageTranslator; - -import com.github.steveice10.mc.protocol.data.game.scoreboard.ObjectiveAction; -import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerScoreboardObjectivePacket; - -@Translator(packet = ServerScoreboardObjectivePacket.class) -public class JavaScoreboardObjectiveTranslator extends PacketTranslator { - - @Override - public void translate(ServerScoreboardObjectivePacket packet, GeyserSession session) { - WorldCache worldCache = session.getWorldCache(); - Scoreboard scoreboard = worldCache.getScoreboard(); - Objective objective = scoreboard.getObjective(packet.getName()); - int pps = worldCache.increaseAndGetScoreboardPacketsPerSecond(); - - if (objective == null && packet.getAction() != ObjectiveAction.REMOVE) { - objective = scoreboard.registerNewObjective(packet.getName(), false); - } - - switch (packet.getAction()) { - case ADD: - case UPDATE: - objective.setDisplayName(MessageTranslator.convertMessage(packet.getDisplayName().toString())) - .setType(packet.getType().ordinal()); - break; - case REMOVE: - scoreboard.unregisterObjective(packet.getName()); - break; - } - - if (objective == null || !objective.isActive()) { - return; - } - - // ScoreboardUpdater will handle it for us if the packets per second - // (for score and team packets) is higher then the first threshold - if (pps < ScoreboardUpdater.FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) { - scoreboard.onUpdate(); - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java deleted file mode 100644 index d10b0caa9..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.scoreboard; - -import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerTeamPacket; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.GeyserLogger; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.scoreboard.Scoreboard; -import org.geysermc.connector.scoreboard.ScoreboardUpdater; -import org.geysermc.connector.scoreboard.Team; -import org.geysermc.connector.scoreboard.UpdateType; -import org.geysermc.connector.utils.LanguageUtils; -import org.geysermc.connector.network.translators.chat.MessageTranslator; - -import java.util.Arrays; -import java.util.Set; - -@Translator(packet = ServerTeamPacket.class) -public class JavaTeamTranslator extends PacketTranslator { - private static final GeyserLogger LOGGER = GeyserConnector.getInstance().getLogger(); - - @Override - public void translate(ServerTeamPacket packet, GeyserSession session) { - if (LOGGER.isDebug()) { - LOGGER.debug("Team packet " + packet.getTeamName() + " " + packet.getAction() + " " + Arrays.toString(packet.getPlayers())); - } - - int pps = session.getWorldCache().increaseAndGetScoreboardPacketsPerSecond(); - - Scoreboard scoreboard = session.getWorldCache().getScoreboard(); - Team team = scoreboard.getTeam(packet.getTeamName()); - switch (packet.getAction()) { - case CREATE: - scoreboard.registerNewTeam(packet.getTeamName(), toPlayerSet(packet.getPlayers())) - .setName(MessageTranslator.convertMessage(packet.getDisplayName().toString())) - .setColor(packet.getColor()) - .setNameTagVisibility(packet.getNameTagVisibility()) - .setPrefix(MessageTranslator.convertMessage(packet.getPrefix().toString(), session.getLocale())) - .setSuffix(MessageTranslator.convertMessage(packet.getSuffix().toString(), session.getLocale())); - break; - case UPDATE: - if (team == null) { - LOGGER.debug(LanguageUtils.getLocaleStringLog( - "geyser.network.translator.team.failed_not_registered", - packet.getAction(), packet.getTeamName() - )); - return; - } - - team.setName(MessageTranslator.convertMessage(packet.getDisplayName().toString())) - .setColor(packet.getColor()) - .setNameTagVisibility(packet.getNameTagVisibility()) - .setPrefix(MessageTranslator.convertMessage(packet.getPrefix().toString(), session.getLocale())) - .setSuffix(MessageTranslator.convertMessage(packet.getSuffix().toString(), session.getLocale())) - .setUpdateType(UpdateType.UPDATE); - break; - case ADD_PLAYER: - if (team == null) { - LOGGER.debug(LanguageUtils.getLocaleStringLog( - "geyser.network.translator.team.failed_not_registered", - packet.getAction(), packet.getTeamName() - )); - return; - } - team.addEntities(packet.getPlayers()); - break; - case REMOVE_PLAYER: - if (team == null) { - LOGGER.debug(LanguageUtils.getLocaleStringLog( - "geyser.network.translator.team.failed_not_registered", - packet.getAction(), packet.getTeamName() - )); - return; - } - team.removeEntities(packet.getPlayers()); - break; - case REMOVE: - scoreboard.removeTeam(packet.getTeamName()); - break; - } - - // ScoreboardUpdater will handle it for us if the packets per second - // (for score and team packets) is higher then the first threshold - if (pps < ScoreboardUpdater.FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) { - scoreboard.onUpdate(); - } - } - - private Set toPlayerSet(String[] players) { - return new ObjectOpenHashSet<>(players); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java deleted file mode 100644 index 35033ca52..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.scoreboard; - -import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardAction; -import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerUpdateScorePacket; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.GeyserLogger; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.session.cache.WorldCache; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.scoreboard.Objective; -import org.geysermc.connector.scoreboard.Scoreboard; -import org.geysermc.connector.scoreboard.ScoreboardUpdater; -import org.geysermc.connector.utils.LanguageUtils; - -@Translator(packet = ServerUpdateScorePacket.class) -public class JavaUpdateScoreTranslator extends PacketTranslator { - private final GeyserLogger logger; - - public JavaUpdateScoreTranslator() { - logger = GeyserConnector.getInstance().getLogger(); - } - - @Override - public void translate(ServerUpdateScorePacket packet, GeyserSession session) { - WorldCache worldCache = session.getWorldCache(); - Scoreboard scoreboard = worldCache.getScoreboard(); - int pps = worldCache.increaseAndGetScoreboardPacketsPerSecond(); - - Objective objective = scoreboard.getObjective(packet.getObjective()); - if (objective == null && packet.getAction() != ScoreboardAction.REMOVE) { - logger.info(LanguageUtils.getLocaleStringLog("geyser.network.translator.score.failed_objective", packet.getObjective())); - return; - } - - switch (packet.getAction()) { - case ADD_OR_UPDATE: - objective.setScore(packet.getEntry(), packet.getValue()); - break; - case REMOVE: - if (objective != null) { - objective.removeScore(packet.getEntry()); - } else { - for (Objective objective1 : scoreboard.getObjectives().values()) { - objective1.removeScore(packet.getEntry()); - } - } - break; - } - - // ScoreboardUpdater will handle it for us if the packets per second - // (for score and team packets) is higher then the first threshold - if (pps < ScoreboardUpdater.FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) { - scoreboard.onUpdate(); - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java deleted file mode 100644 index e67877536..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.window; - -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientConfirmTransactionPacket; -import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerConfirmTransactionPacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - -@Translator(packet = ServerConfirmTransactionPacket.class) -public class JavaConfirmTransactionTranslator extends PacketTranslator { - - @Override - public void translate(ServerConfirmTransactionPacket packet, GeyserSession session) { - if (!packet.isAccepted()) { - ClientConfirmTransactionPacket confirmPacket = new ClientConfirmTransactionPacket(packet.getWindowId(), packet.getActionId(), true); - session.sendDownstreamPacket(confirmPacket); - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java deleted file mode 100644 index 1fb088717..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.window; - -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCloseWindowPacket; -import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerOpenWindowPacket; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.inventory.InventoryTranslator; -import org.geysermc.connector.utils.InventoryUtils; -import org.geysermc.connector.utils.LocaleUtils; -import org.geysermc.connector.network.translators.chat.MessageTranslator; - -@Translator(packet = ServerOpenWindowPacket.class) -public class JavaOpenWindowTranslator extends PacketTranslator { - - @Override - public void translate(ServerOpenWindowPacket packet, GeyserSession session) { - if (packet.getWindowId() == 0) { - return; - } - InventoryTranslator newTranslator = InventoryTranslator.INVENTORY_TRANSLATORS.get(packet.getType()); - Inventory openInventory = session.getInventoryCache().getOpenInventory(); - if (newTranslator == null) { - if (openInventory != null) { - InventoryUtils.closeWindow(session, openInventory.getId()); - InventoryUtils.closeInventory(session, openInventory.getId()); - } - ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(packet.getWindowId()); - session.sendDownstreamPacket(closeWindowPacket); - return; - } - - String name = MessageTranslator.convertMessageLenient(packet.getName(), session.getLocale()); - - name = LocaleUtils.getLocaleString(name, session.getLocale()); - - Inventory newInventory = new Inventory(name, packet.getWindowId(), packet.getType(), newTranslator.size + 36); - session.getInventoryCache().cacheInventory(newInventory); - if (openInventory != null) { - InventoryTranslator openTranslator = InventoryTranslator.INVENTORY_TRANSLATORS.get(openInventory.getWindowType()); - if (!openTranslator.getClass().equals(newTranslator.getClass())) { - InventoryUtils.closeWindow(session, openInventory.getId()); - InventoryUtils.closeInventory(session, openInventory.getId()); - } - } - - InventoryUtils.openInventory(session, newInventory); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java deleted file mode 100644 index 19d7db217..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.window; - -import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerSetSlotPacket; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.inventory.InventoryTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.InventoryUtils; - -import java.util.Objects; - -@Translator(packet = ServerSetSlotPacket.class) -public class JavaSetSlotTranslator extends PacketTranslator { - - @Override - public void translate(ServerSetSlotPacket packet, GeyserSession session) { - if (packet.getWindowId() == 255 && packet.getSlot() == -1) { //cursor - if (session.getCraftSlot() != 0) - return; - - session.getInventory().setCursor(packet.getItem()); - InventoryUtils.updateCursor(session); - return; - } - - Inventory inventory = session.getInventoryCache().getInventories().get(packet.getWindowId()); - if (inventory == null || (packet.getWindowId() != 0 && inventory.getWindowType() == null)) - return; - - InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()); - if (translator != null) { - inventory.setItem(packet.getSlot(), packet.getItem()); - translator.updateSlot(session, inventory, packet.getSlot()); - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java deleted file mode 100644 index 2cc392f53..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.window; - -import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerWindowItemsPacket; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.inventory.InventoryTranslator; - -import java.util.Arrays; - -@Translator(packet = ServerWindowItemsPacket.class) -public class JavaWindowItemsTranslator extends PacketTranslator { - - @Override - public void translate(ServerWindowItemsPacket packet, GeyserSession session) { - Inventory inventory = session.getInventoryCache().getInventories().get(packet.getWindowId()); - if (inventory == null || (packet.getWindowId() != 0 && inventory.getWindowType() == null)) - return; - - if (packet.getItems().length < inventory.getSize()) { - inventory.setItems(Arrays.copyOf(packet.getItems(), inventory.getSize())); - } else { - inventory.setItems(packet.getItems()); - } - - InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()); - if (translator != null) { - translator.updateInventory(session, inventory); - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockBreakAnimTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockBreakAnimTranslator.java deleted file mode 100644 index 02e6514d4..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockBreakAnimTranslator.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.world; - -import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerBlockBreakAnimPacket; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.LevelEventType; -import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.utils.BlockUtils; - -@Translator(packet = ServerBlockBreakAnimPacket.class) -public class JavaBlockBreakAnimTranslator extends PacketTranslator { - - @Override - public void translate(ServerBlockBreakAnimPacket packet, GeyserSession session) { - int state = session.getConnector().getWorldManager().getBlockAt(session, packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ()); - int breakTime = (int) (65535 / Math.ceil(BlockUtils.getBreakTime(BlockTranslator.JAVA_RUNTIME_ID_TO_HARDNESS.get(state), state, ItemEntry.AIR, new CompoundTag(""), null) * 20)); - LevelEventPacket levelEventPacket = new LevelEventPacket(); - levelEventPacket.setPosition(Vector3f.from( - packet.getPosition().getX(), - packet.getPosition().getY(), - packet.getPosition().getZ() - )); - levelEventPacket.setType(LevelEventType.BLOCK_START_BREAK); - - switch (packet.getStage()) { - case STAGE_1: - levelEventPacket.setData(breakTime); - break; - case STAGE_2: - levelEventPacket.setData(breakTime * 2); - break; - case STAGE_3: - levelEventPacket.setData(breakTime * 3); - break; - case STAGE_4: - levelEventPacket.setData(breakTime * 4); - break; - case STAGE_5: - levelEventPacket.setData(breakTime * 5); - break; - case STAGE_6: - levelEventPacket.setData(breakTime * 6); - break; - case STAGE_7: - levelEventPacket.setData(breakTime * 7); - break; - case STAGE_8: - levelEventPacket.setData(breakTime * 8); - break; - case STAGE_9: - levelEventPacket.setData(breakTime * 9); - break; - case STAGE_10: - levelEventPacket.setData(breakTime * 10); - break; - case RESET: - levelEventPacket.setType(LevelEventType.BLOCK_STOP_BREAK); - levelEventPacket.setData(0); - break; - } - session.sendUpstreamPacket(levelEventPacket); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockValueTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockValueTranslator.java deleted file mode 100644 index 903cad0c4..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockValueTranslator.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.world; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; -import com.github.steveice10.mc.protocol.data.game.world.block.value.*; -import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerBlockValuePacket; -import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.nbt.NbtMapBuilder; -import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; -import com.nukkitx.protocol.bedrock.packet.BlockEventPacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.world.block.entity.NoteblockBlockEntityTranslator; - -import java.util.concurrent.TimeUnit; - - -@Translator(packet = ServerBlockValuePacket.class) -public class JavaBlockValueTranslator extends PacketTranslator { - - @Override - public void translate(ServerBlockValuePacket packet, GeyserSession session) { - BlockEventPacket blockEventPacket = new BlockEventPacket(); - blockEventPacket.setBlockPosition(Vector3i.from(packet.getPosition().getX(), - packet.getPosition().getY(), packet.getPosition().getZ())); - if (packet.getValue() instanceof ChestValue) { - ChestValue value = (ChestValue) packet.getValue() ; - blockEventPacket.setEventType(1); - blockEventPacket.setEventData(value.getViewers() > 0 ? 1 : 0); - session.sendUpstreamPacket(blockEventPacket); - } else if (packet.getValue() instanceof EndGatewayValue) { - blockEventPacket.setEventType(1); - session.sendUpstreamPacket(blockEventPacket); - } else if (packet.getValue() instanceof NoteBlockValue) { - NoteblockBlockEntityTranslator.translate(session, packet.getPosition()); - } else if (packet.getValue() instanceof PistonValue) { - PistonValueType type = (PistonValueType) packet.getType(); - - // Unlike everything else, pistons need a block entity packet to convey motion - // TODO: Doesn't register on chunk load; needs to be interacted with first - Vector3i position = Vector3i.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ()); - if (type == PistonValueType.PUSHING) { - extendPiston(session, position, 0.0f, 0.0f); - } else { - retractPiston(session, position, 1.0f, 1.0f); - } - } else if (packet.getValue() instanceof MobSpawnerValue) { - blockEventPacket.setEventType(1); - session.sendUpstreamPacket(blockEventPacket); - } else if (packet.getValue() instanceof EndGatewayValue) { - blockEventPacket.setEventType(1); - session.sendUpstreamPacket(blockEventPacket); - } else if (packet.getValue() instanceof GenericBlockValue && packet.getBlockId() == 677) { - // TODO: Remove hardcode? Remove hardcodes for all of these? (EndGatewayValue for example still hardcodes the block) - // Bells - needed to show ring from other players - GenericBlockValue bellValue = (GenericBlockValue) packet.getValue(); - Position position = packet.getPosition(); - - BlockEntityDataPacket blockEntityPacket = new BlockEntityDataPacket(); - blockEntityPacket.setBlockPosition(Vector3i.from(position.getX(), position.getY(), position.getZ())); - - NbtMapBuilder builder = NbtMap.builder(); - builder.putInt("x", position.getX()); - builder.putInt("y", position.getY()); - builder.putInt("z", position.getZ()); - builder.putString("id", "Bell"); - int bedrockRingDirection; - switch (bellValue.getValue()) { - case 3: // north - bedrockRingDirection = 0; - break; - case 4: // east - bedrockRingDirection = 1; - break; - case 5: // west - bedrockRingDirection = 3; - break; - default: // south (2) is identical - bedrockRingDirection = bellValue.getValue(); - } - builder.putInt("Direction", bedrockRingDirection); - builder.putByte("Ringing", (byte) 1); - builder.putInt("Ticks", 0); - - blockEntityPacket.setData(builder.build()); - session.sendUpstreamPacket(blockEntityPacket); - } - } - - /** - * Emulating a piston extending - * @param session GeyserSession - * @param position Block position - * @param progress How far the piston is - * @param lastProgress How far the piston last was - */ - private void extendPiston(GeyserSession session, Vector3i position, float progress, float lastProgress) { - BlockEntityDataPacket blockEntityDataPacket = new BlockEntityDataPacket(); - blockEntityDataPacket.setBlockPosition(position); - byte state = (byte) ((progress == 1.0f && lastProgress == 1.0f) ? 2 : 1); - blockEntityDataPacket.setData(buildPistonTag(position, progress, lastProgress, state)); - session.sendUpstreamPacket(blockEntityDataPacket); - if (lastProgress != 1.0f) { - session.getConnector().getGeneralThreadPool().schedule(() -> - extendPiston(session, position, (progress >= 1.0f) ? 1.0f : progress + 0.5f, progress), - 20, TimeUnit.MILLISECONDS); - } - } - - /** - * Emulate a piston retracting. - * @param session GeyserSession - * @param position Block position - * @param progress Current progress of piston - * @param lastProgress Last progress of piston - */ - private void retractPiston(GeyserSession session, Vector3i position, float progress, float lastProgress) { - BlockEntityDataPacket blockEntityDataPacket = new BlockEntityDataPacket(); - blockEntityDataPacket.setBlockPosition(position); - byte state = (byte) ((progress == 0.0f && lastProgress == 0.0f) ? 0 : 3); - blockEntityDataPacket.setData(buildPistonTag(position, progress, lastProgress, state)); - session.sendUpstreamPacket(blockEntityDataPacket); - if (lastProgress != 0.0f) { - session.getConnector().getGeneralThreadPool().schedule(() -> - retractPiston(session, position, (progress <= 0.0f) ? 0.0f : progress - 0.5f, progress), - 20, TimeUnit.MILLISECONDS); - } - } - - /** - * Build a piston tag - * @param position Piston position - * @param progress Current progress of piston - * @param lastProgress Last progress of piston - * @param state - * @return Bedrock CompoundTag of piston - */ - private NbtMap buildPistonTag(Vector3i position, float progress, float lastProgress, byte state) { - NbtMapBuilder builder = NbtMap.builder() - .putInt("x", position.getX()) - .putInt("y", position.getY()) - .putInt("z", position.getZ()) - .putFloat("Progress", progress) - .putFloat("LastProgress", lastProgress) - .putString("id", "PistonArm") - .putByte("NewState", state) - .putByte("State", state); - return builder.build(); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java deleted file mode 100644 index 741632bc9..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.world; - -import com.github.steveice10.mc.protocol.data.game.chunk.Column; -import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerChunkDataPacket; -import com.nukkitx.nbt.NBTOutputStream; -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.nbt.NbtUtils; -import com.nukkitx.network.VarInts; -import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.ByteBufOutputStream; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.BiomeTranslator; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.world.chunk.ChunkSection; -import org.geysermc.connector.utils.ChunkUtils; - -@Translator(packet = ServerChunkDataPacket.class) -public class JavaChunkDataTranslator extends PacketTranslator { - /** - * Determines if we should process non-full chunks - */ - private final boolean cacheChunks; - - public JavaChunkDataTranslator() { - cacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks(); - } - - @Override - public void translate(ServerChunkDataPacket packet, GeyserSession session) { - if (session.isSpawned()) { - ChunkUtils.updateChunkPosition(session, session.getPlayerEntity().getPosition().toInt()); - } - - if (packet.getColumn().getBiomeData() == null && !cacheChunks) { - // Non-full chunk without chunk caching - session.getConnector().getLogger().debug("Not sending non-full chunk because chunk caching is off."); - return; - } - - // Merge received column with cache on network thread - Column mergedColumn = session.getChunkCache().addToCache(packet.getColumn()); - if (mergedColumn == null) { // There were no changes?!? - return; - } - - boolean isNonFullChunk = packet.getColumn().getBiomeData() == null; - - GeyserConnector.getInstance().getGeneralThreadPool().execute(() -> { - try { - ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(session, mergedColumn, isNonFullChunk); - ChunkSection[] sections = chunkData.getSections(); - - // Find highest section - int sectionCount = sections.length - 1; - while (sectionCount >= 0 && sections[sectionCount] == null) { - sectionCount--; - } - sectionCount++; - - // Estimate chunk size - int size = 0; - for (int i = 0; i < sectionCount; i++) { - ChunkSection section = sections[i]; - size += (section != null ? section : ChunkUtils.EMPTY_SECTION).estimateNetworkSize(); - } - size += 256; // Biomes - size += 1; // Border blocks - size += 1; // Extra data length (always 0) - size += chunkData.getBlockEntities().length * 64; // Conservative estimate of 64 bytes per tile entity - - // Allocate output buffer - ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(size); - byte[] payload; - try { - for (int i = 0; i < sectionCount; i++) { - ChunkSection section = sections[i]; - (section != null ? section : ChunkUtils.EMPTY_SECTION).writeToNetwork(byteBuf); - } - - byteBuf.writeBytes(BiomeTranslator.toBedrockBiome(mergedColumn.getBiomeData())); // Biomes - 256 bytes - byteBuf.writeByte(0); // Border blocks - Edu edition only - VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now - - // Encode tile entities into buffer - NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(new ByteBufOutputStream(byteBuf)); - for (NbtMap blockEntity : chunkData.getBlockEntities()) { - nbtStream.writeTag(blockEntity); - } - - // Copy data into byte[], because the protocol lib really likes things that are s l o w - byteBuf.readBytes(payload = new byte[byteBuf.readableBytes()]); - } finally { - byteBuf.release(); // Release buffer to allow buffer pooling to be useful - } - - LevelChunkPacket levelChunkPacket = new LevelChunkPacket(); - levelChunkPacket.setSubChunksLength(sectionCount); - levelChunkPacket.setCachingEnabled(false); - levelChunkPacket.setChunkX(mergedColumn.getX()); - levelChunkPacket.setChunkZ(mergedColumn.getZ()); - levelChunkPacket.setData(payload); - session.sendUpstreamPacket(levelChunkPacket); - } catch (Exception ex) { - ex.printStackTrace(); - } - }); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java deleted file mode 100644 index 4620fc119..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.world; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.data.game.world.particle.*; -import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import com.nukkitx.protocol.bedrock.data.LevelEventType; -import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; -import com.nukkitx.protocol.bedrock.packet.SpawnParticleEffectPacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; - -import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerSpawnParticlePacket; -import com.nukkitx.math.vector.Vector3f; -import org.geysermc.connector.network.translators.effect.EffectRegistry; -import org.geysermc.connector.utils.DimensionUtils; - -@Translator(packet = ServerSpawnParticlePacket.class) -public class JavaSpawnParticleTranslator extends PacketTranslator { - - @Override - public void translate(ServerSpawnParticlePacket packet, GeyserSession session) { - LevelEventPacket particle = new LevelEventPacket(); - switch (packet.getParticle().getType()) { - case BLOCK: - particle.setType(LevelEventType.PARTICLE_DESTROY_BLOCK_NO_SOUND); - particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - particle.setData(BlockTranslator.getBedrockBlockId(((BlockParticleData) packet.getParticle().getData()).getBlockState())); - session.sendUpstreamPacket(particle); - break; - case FALLING_DUST: - //In fact, FallingDustParticle should have data like DustParticle, - //but in MCProtocol, its data is BlockState(1). - particle.setType(LevelEventType.PARTICLE_FALLING_DUST); - particle.setData(BlockTranslator.getBedrockBlockId(((FallingDustParticleData)packet.getParticle().getData()).getBlockState())); - particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - session.sendUpstreamPacket(particle); - break; - case ITEM: - ItemStack javaItem = ((ItemParticleData)packet.getParticle().getData()).getItemStack(); - ItemData bedrockItem = ItemTranslator.translateToBedrock(session, javaItem); - int id = bedrockItem.getId(); - short damage = bedrockItem.getDamage(); - particle.setType(LevelEventType.PARTICLE_ITEM_BREAK); - particle.setData(id << 16 | damage); - particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - session.sendUpstreamPacket(particle); - break; - case DUST: - DustParticleData data = (DustParticleData)packet.getParticle().getData(); - int r = (int) (data.getRed()*255); - int g = (int) (data.getGreen()*255); - int b = (int) (data.getBlue()*255); - particle.setType(LevelEventType.PARTICLE_REDSTONE); - particle.setData(((0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff)); - particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - session.sendUpstreamPacket(particle); - break; - default: - LevelEventType typeParticle = EffectRegistry.getParticleLevelEventType(packet.getParticle().getType()); - if (typeParticle != null) { - particle.setType(typeParticle); - particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - session.sendUpstreamPacket(particle); - } else { - String stringParticle = EffectRegistry.getParticleString(packet.getParticle().getType()); - if (stringParticle != null) { - SpawnParticleEffectPacket stringPacket = new SpawnParticleEffectPacket(); - stringPacket.setIdentifier(stringParticle); - stringPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension())); - stringPacket.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - session.sendUpstreamPacket(stringPacket); - } - } - break; - } - } - -} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java deleted file mode 100644 index 652e12947..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.world; - -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - -import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUnloadChunkPacket; - -@Translator(packet = ServerUnloadChunkPacket.class) -public class JavaUnloadChunkTranslator extends PacketTranslator { - - @Override - public void translate(ServerUnloadChunkPacket packet, GeyserSession session) { - session.getChunkCache().removeChunk(packet.getX(), packet.getZ()); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java deleted file mode 100644 index 8dc689185..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.java.world; - -import com.nukkitx.protocol.bedrock.data.GameRuleData; -import com.nukkitx.protocol.bedrock.packet.GameRulesChangedPacket; -import it.unimi.dsi.fastutil.longs.Long2LongMap; -import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - -import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdateTimePacket; -import com.nukkitx.protocol.bedrock.packet.SetTimePacket; - -@Translator(packet = ServerUpdateTimePacket.class) -public class JavaUpdateTimeTranslator extends PacketTranslator { - - // If negative, the last time is stored so we know it's not some plugin behavior doing weird things. - // Per-player for multi-world support - private static final Long2LongMap LAST_RECORDED_TIMES = new Long2LongOpenHashMap(); - - @Override - public void translate(ServerUpdateTimePacket packet, GeyserSession session) { - - // Bedrock sends a GameRulesChangedPacket if there is no daylight cycle - // Java just sends a negative long if there is no daylight cycle - long lastTime = LAST_RECORDED_TIMES.getOrDefault(session.getPlayerEntity().getEntityId(), 0); - long time = packet.getTime(); - - if (lastTime != time) { - // https://minecraft.gamepedia.com/Day-night_cycle#24-hour_Minecraft_day - SetTimePacket setTimePacket = new SetTimePacket(); - setTimePacket.setTime((int) Math.abs(time) % 24000); - session.sendUpstreamPacket(setTimePacket); - // TODO: Performance efficient to always do this? - LAST_RECORDED_TIMES.put(session.getPlayerEntity().getEntityId(), time); - } - if (lastTime < 0 && time >= 0) { - setDoDaylightCycleGamerule(session, true); - } else if (lastTime != time && time < 0) { - setDoDaylightCycleGamerule(session, false); - } - } - - private void setDoDaylightCycleGamerule(GeyserSession session, boolean doCycle) { - session.sendGameRule("dodaylightcycle", doCycle); - } - -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandlerRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandlerRegistry.java deleted file mode 100644 index 03b346e35..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandlerRegistry.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.sound; - -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.utils.FileUtils; -import org.reflections.Reflections; - -import java.util.HashMap; -import java.util.Map; - -/** - * Registry that holds {@link SoundInteractionHandler}s. - */ -public class SoundHandlerRegistry { - - static final Map> INTERACTION_HANDLERS = new HashMap<>(); - - static { - Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.sound") : new Reflections("org.geysermc.connector.network.translators.sound"); - for (Class clazz : ref.getTypesAnnotatedWith(SoundHandler.class)) { - try { - SoundInteractionHandler interactionHandler = (SoundInteractionHandler) clazz.newInstance(); - SoundHandler annotation = clazz.getAnnotation(SoundHandler.class); - INTERACTION_HANDLERS.put(annotation, interactionHandler); - } catch (InstantiationException | IllegalAccessException ex) { - ex.printStackTrace(); - } - } - } - - private SoundHandlerRegistry() { - } - - public static void init() { - // no-op - } - - /** - * Returns a map of the interaction handlers - * - * @return a map of the interaction handlers - */ - public static Map> getInteractionHandlers() { - return INTERACTION_HANDLERS; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java deleted file mode 100644 index bfd59cc7c..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.world.block; - -import com.fasterxml.jackson.databind.JsonNode; -import com.nukkitx.nbt.NbtMap; -import it.unimi.dsi.fastutil.ints.*; - -import java.util.HashMap; -import java.util.Map; - -/** - * Used for block entities if the Java block state contains Bedrock block information. - */ -public class BlockStateValues { - private static final Int2IntMap BANNER_COLORS = new Int2IntOpenHashMap(); - private static final Int2ByteMap BED_COLORS = new Int2ByteOpenHashMap(); - private static final Int2ByteMap COMMAND_BLOCK_VALUES = new Int2ByteOpenHashMap(); - private static final Int2ObjectMap DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>(); - private static final Int2ObjectMap FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>(); - private static final Map FLOWER_POT_BLOCKS = new HashMap<>(); - private static final Int2IntMap NOTEBLOCK_PITCHES = new Int2IntOpenHashMap(); - private static final Int2BooleanMap IS_STICKY_PISTON = new Int2BooleanOpenHashMap(); - private static final Int2BooleanMap PISTON_VALUES = new Int2BooleanOpenHashMap(); - private static final Int2ByteMap SKULL_VARIANTS = new Int2ByteOpenHashMap(); - private static final Int2ByteMap SKULL_ROTATIONS = new Int2ByteOpenHashMap(); - private static final Int2ByteMap SHULKERBOX_DIRECTIONS = new Int2ByteOpenHashMap(); - - /** - * Determines if the block state contains Bedrock block information - * - * @param entry The String to JsonNode map used in BlockTranslator - * @param javaBlockState the Java Block State of the block - */ - public static void storeBlockStateValues(Map.Entry entry, int javaBlockState) { - JsonNode bannerColor = entry.getValue().get("banner_color"); - if (bannerColor != null) { - BANNER_COLORS.put(javaBlockState, (byte) bannerColor.intValue()); - return; // There will never be a banner color and a skull variant - } - - JsonNode bedColor = entry.getValue().get("bed_color"); - if (bedColor != null) { - BED_COLORS.put(javaBlockState, (byte) bedColor.intValue()); - return; - } - - if (entry.getKey().contains("command_block")) { - COMMAND_BLOCK_VALUES.put(javaBlockState, entry.getKey().contains("conditional=true") ? (byte) 1 : (byte) 0); - return; - } - - if (entry.getValue().get("double_chest_position") != null) { - boolean isX = (entry.getValue().get("x") != null); - boolean isDirectionPositive = ((entry.getValue().get("x") != null && entry.getValue().get("x").asBoolean()) || - (entry.getValue().get("z") != null && entry.getValue().get("z").asBoolean())); - boolean isLeft = (entry.getValue().get("double_chest_position").asText().contains("left")); - DOUBLE_CHEST_VALUES.put(javaBlockState, new DoubleChestValue(isX, isDirectionPositive, isLeft)); - return; - } - - if (entry.getKey().contains("potted_") || entry.getKey().contains("flower_pot")) { - FLOWER_POT_VALUES.put(javaBlockState, entry.getKey().replace("potted_", "")); - return; - } - - JsonNode notePitch = entry.getValue().get("note_pitch"); - if (notePitch != null) { - NOTEBLOCK_PITCHES.put(javaBlockState, entry.getValue().get("note_pitch").intValue()); - return; - } - - if (entry.getKey().contains("piston")) { - // True if extended, false if not - PISTON_VALUES.put(javaBlockState, entry.getKey().contains("extended=true")); - IS_STICKY_PISTON.put(javaBlockState, entry.getKey().contains("sticky")); - return; - } - - JsonNode skullVariation = entry.getValue().get("variation"); - if (skullVariation != null) { - SKULL_VARIANTS.put(javaBlockState, (byte) skullVariation.intValue()); - } - - JsonNode skullRotation = entry.getValue().get("skull_rotation"); - if (skullRotation != null) { - SKULL_ROTATIONS.put(javaBlockState, (byte) skullRotation.intValue()); - } - - JsonNode shulkerDirection = entry.getValue().get("shulker_direction"); - if (shulkerDirection != null) { - BlockStateValues.SHULKERBOX_DIRECTIONS.put(javaBlockState, (byte) shulkerDirection.intValue()); - } - } - - /** - * Banner colors are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. - * This gives an integer color that Bedrock can use. - * - * @param state BlockState of the block - * @return Banner color integer or -1 if no color - */ - public static int getBannerColor(int state) { - return BANNER_COLORS.getOrDefault(state, -1); - } - - /** - * Bed colors are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. - * This gives a byte color that Bedrock can use - Bedrock needs a byte in the final tag. - * - * @param state BlockState of the block - * @return Bed color byte or -1 if no color - */ - public static byte getBedColor(int state) { - return BED_COLORS.getOrDefault(state, (byte) -1); - } - - /** - * The block state in Java and Bedrock both contain the conditional bit, however command block block entity tags - * in Bedrock need the conditional information. - * - * @return the list of all command blocks and if they are conditional (1 or 0) - */ - public static Int2ByteMap getCommandBlockValues() { - return COMMAND_BLOCK_VALUES; - } - - /** - * All double chest values are part of the block state in Java and part of the block entity tag in Bedrock. - * This gives the DoubleChestValue that can be calculated into the final tag. - * - * @return The map of all DoubleChestValues. - */ - public static Int2ObjectMap getDoubleChestValues() { - return DOUBLE_CHEST_VALUES; - } - - /** - * Get the Int2ObjectMap of flower pot block states to containing plant - * - * @return Int2ObjectMap of flower pot values - */ - public static Int2ObjectMap getFlowerPotValues() { - return FLOWER_POT_VALUES; - } - - /** - * Get the map of contained flower pot plants to Bedrock CompoundTag - * - * @return Map of flower pot blocks. - */ - public static Map getFlowerPotBlocks() { - return FLOWER_POT_BLOCKS; - } - - /** - * The note that noteblocks output when hit is part of the block state in Java but sent as a BlockEventPacket in Bedrock. - * This gives an integer pitch that Bedrock can use. - * - * @param state BlockState of the block - * @return note block note integer or -1 if not present - */ - public static int getNoteblockPitch(int state) { - return NOTEBLOCK_PITCHES.getOrDefault(state, -1); - } - - /** - * Get the Int2BooleanMap showing if a piston block state is extended or not. - * - * @return the Int2BooleanMap of piston extensions. - */ - public static Int2BooleanMap getPistonValues() { - return PISTON_VALUES; - } - - public static boolean isStickyPiston(int blockState) { - return IS_STICKY_PISTON.get(blockState); - } - - /** - * Skull variations are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. - * This gives a byte variant ID that Bedrock can use. - * - * @param state BlockState of the block - * @return Skull variant byte or -1 if no variant - */ - public static byte getSkullVariant(int state) { - return SKULL_VARIANTS.getOrDefault(state, (byte) -1); - } - - /** - * Skull rotations are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. - * This gives a byte rotation that Bedrock can use. - * - * @param state BlockState of the block - * @return Skull rotation value or -1 if no value - */ - public static byte getSkullRotation(int state) { - return SKULL_ROTATIONS.getOrDefault(state, (byte) -1); - } - - - /** - * Shulker box directions are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. - * This gives a byte direction that Bedrock can use. - * - * @param state BlockState of the block - * @return Shulker direction value or -1 if no value - */ - public static byte getShulkerBoxDirection(int state) { - return SHULKERBOX_DIRECTIONS.getOrDefault(state, (byte) -1); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java deleted file mode 100644 index eb09d63e3..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java +++ /dev/null @@ -1,366 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.world.block; - -import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.collect.BiMap; -import com.google.common.collect.HashBiMap; -import com.nukkitx.nbt.*; -import it.unimi.dsi.fastutil.ints.*; -import it.unimi.dsi.fastutil.objects.*; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.network.translators.world.block.entity.BlockEntity; -import org.geysermc.connector.utils.FileUtils; -import org.reflections.Reflections; - -import java.io.DataInputStream; -import java.io.InputStream; -import java.util.*; - -public class BlockTranslator { - /** - * The Java block runtime ID of air - */ - public static final int JAVA_AIR_ID = 0; - /** - * The Bedrock block runtime ID of air - */ - public static final int BEDROCK_AIR_ID; - public static final int BEDROCK_WATER_ID; - - private static final Int2IntMap JAVA_TO_BEDROCK_BLOCK_MAP = new Int2IntOpenHashMap(); - private static final Int2IntMap BEDROCK_TO_JAVA_BLOCK_MAP = new Int2IntOpenHashMap(); - /** - * Stores a list of differences in block identifiers. - * Items will not be added to this list if the key and value is the same. - */ - private static final Object2ObjectMap JAVA_TO_BEDROCK_IDENTIFIERS = new Object2ObjectOpenHashMap<>(); - private static final BiMap JAVA_ID_BLOCK_MAP = HashBiMap.create(); - private static final IntSet WATERLOGGED = new IntOpenHashSet(); - private static final Object2IntMap ITEM_FRAMES = new Object2IntOpenHashMap<>(); - - // Bedrock carpet ID, used in LlamaEntity.java for decoration - public static final int CARPET = 171; - - private static final Int2ObjectMap JAVA_ID_TO_BLOCK_ENTITY_MAP = new Int2ObjectOpenHashMap<>(); - - public static final Int2DoubleMap JAVA_RUNTIME_ID_TO_HARDNESS = new Int2DoubleOpenHashMap(); - public static final Int2BooleanMap JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND = new Int2BooleanOpenHashMap(); - public static final Int2ObjectMap JAVA_RUNTIME_ID_TO_TOOL_TYPE = new Int2ObjectOpenHashMap<>(); - - /** - * Java numeric ID to java unique identifier, used for block names in the statistics screen - */ - public static final Int2ObjectMap JAVA_ID_TO_JAVA_IDENTIFIER_MAP = new Int2ObjectOpenHashMap<>(); - - /** - * Runtime command block ID, used for fixing command block minecart appearances - */ - public static final int BEDROCK_RUNTIME_COMMAND_BLOCK_ID; - - // For block breaking animation math - public static final IntSet JAVA_RUNTIME_WOOL_IDS = new IntOpenHashSet(); - public static final int JAVA_RUNTIME_COBWEB_ID; - - public static final int JAVA_RUNTIME_FURNACE_ID; - public static final int JAVA_RUNTIME_FURNACE_LIT_ID; - - public static final int JAVA_RUNTIME_SPAWNER_ID; - - private static final int BLOCK_STATE_VERSION = 17825808; - - static { - /* Load block palette */ - InputStream stream = FileUtils.getResource("bedrock/blockpalette.nbt"); - - NbtList blocksTag; - try (NBTInputStream nbtInputStream = new NBTInputStream(new DataInputStream(stream))) { - NbtMap blockPalette = (NbtMap) nbtInputStream.readTag(); - blocksTag = (NbtList) blockPalette.getList("blocks", NbtType.COMPOUND); - } catch (Exception e) { - throw new AssertionError("Unable to get blocks from runtime block states", e); - } - - // New since 1.16.100 - find the block runtime ID by the order given to us in the block palette, - // as we no longer send a block palette - Object2IntMap blockStateOrderedMap = new Object2IntOpenHashMap<>(blocksTag.size()); - - for (int i = 0; i < blocksTag.size(); i++) { - NbtMap tag = blocksTag.get(i); - NbtMap blockTag = tag.getCompound("block"); - if (blockStateOrderedMap.containsKey(blockTag)) { - throw new AssertionError("Duplicate block states in Bedrock palette"); - } - blockStateOrderedMap.put(blockTag, i); - } - - stream = FileUtils.getResource("mappings/blocks.json"); - JsonNode blocks; - try { - blocks = GeyserConnector.JSON_MAPPER.readTree(stream); - } catch (Exception e) { - throw new AssertionError("Unable to load Java block mappings", e); - } - - Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") - : new Reflections("org.geysermc.connector.network.translators.world.block.entity"); - - int waterRuntimeId = -1; - int javaRuntimeId = -1; - int airRuntimeId = -1; - int cobwebRuntimeId = -1; - int commandBlockRuntimeId = -1; - int furnaceRuntimeId = -1; - int furnaceLitRuntimeId = -1; - int spawnerRuntimeId = -1; - int uniqueJavaId = -1; - Iterator> blocksIterator = blocks.fields(); - while (blocksIterator.hasNext()) { - javaRuntimeId++; - Map.Entry entry = blocksIterator.next(); - String javaId = entry.getKey(); - NbtMap blockTag = buildBedrockState(entry.getValue()); - int bedrockRuntimeId = blockStateOrderedMap.getOrDefault(blockTag, -1); - if (bedrockRuntimeId == -1) { - throw new RuntimeException("Unable to find " + javaId + " Bedrock runtime ID!"); - } - - // TODO fix this, (no block should have a null hardness) - JsonNode hardnessNode = entry.getValue().get("block_hardness"); - if (hardnessNode != null) { - JAVA_RUNTIME_ID_TO_HARDNESS.put(javaRuntimeId, hardnessNode.doubleValue()); - } - - try { - JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND.put(javaRuntimeId, entry.getValue().get("can_break_with_hand").booleanValue()); - } catch (Exception e) { - JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND.put(javaRuntimeId, false); - } - - JsonNode toolTypeNode = entry.getValue().get("tool_type"); - if (toolTypeNode != null) { - JAVA_RUNTIME_ID_TO_TOOL_TYPE.put(javaRuntimeId, toolTypeNode.textValue()); - } - - JAVA_ID_BLOCK_MAP.put(javaId, javaRuntimeId); - - // Used for adding all "special" Java block states to block state map - String identifier; - String bedrockIdentifier = entry.getValue().get("bedrock_identifier").asText(); - for (Class clazz : ref.getTypesAnnotatedWith(BlockEntity.class)) { - identifier = clazz.getAnnotation(BlockEntity.class).regex(); - // Endswith, or else the block bedrock gets picked up for bed - if (bedrockIdentifier.endsWith(identifier) && !identifier.equals("")) { - JAVA_ID_TO_BLOCK_ENTITY_MAP.put(javaRuntimeId, clazz.getAnnotation(BlockEntity.class).name()); - break; - } - } - - BlockStateValues.storeBlockStateValues(entry, javaRuntimeId); - - String cleanJavaIdentifier = entry.getKey().split("\\[")[0]; - - if (!JAVA_ID_TO_JAVA_IDENTIFIER_MAP.containsValue(cleanJavaIdentifier)) { - uniqueJavaId++; - JAVA_ID_TO_JAVA_IDENTIFIER_MAP.put(uniqueJavaId, cleanJavaIdentifier); - } - - if (!cleanJavaIdentifier.equals(bedrockIdentifier)) { - JAVA_TO_BEDROCK_IDENTIFIERS.put(cleanJavaIdentifier, bedrockIdentifier); - } - - // Get the tag needed for non-empty flower pots - if (entry.getValue().get("pottable") != null) { - BlockStateValues.getFlowerPotBlocks().put(cleanJavaIdentifier, buildBedrockState(entry.getValue())); - } - - if ("minecraft:water[level=0]".equals(javaId)) { - waterRuntimeId = bedrockRuntimeId; - } - boolean waterlogged = entry.getKey().contains("waterlogged=true") - || javaId.contains("minecraft:bubble_column") || javaId.contains("minecraft:kelp") || javaId.contains("seagrass"); - - if (waterlogged) { - BEDROCK_TO_JAVA_BLOCK_MAP.putIfAbsent(bedrockRuntimeId | 1 << 31, javaRuntimeId); - WATERLOGGED.add(javaRuntimeId); - } else { - BEDROCK_TO_JAVA_BLOCK_MAP.putIfAbsent(bedrockRuntimeId, javaRuntimeId); - } - - JAVA_TO_BEDROCK_BLOCK_MAP.put(javaRuntimeId, bedrockRuntimeId); - - if (bedrockIdentifier.equals("minecraft:air")) { - airRuntimeId = bedrockRuntimeId; - - } else if (javaId.contains("wool")) { - JAVA_RUNTIME_WOOL_IDS.add(javaRuntimeId); - - } else if (javaId.contains("cobweb")) { - cobwebRuntimeId = javaRuntimeId; - - } else if (javaId.equals("minecraft:command_block[conditional=false,facing=north]")) { - commandBlockRuntimeId = bedrockRuntimeId; - - } else if (javaId.startsWith("minecraft:furnace[facing=north")) { - if (javaId.contains("lit=true")) { - furnaceLitRuntimeId = javaRuntimeId; - } else { - furnaceRuntimeId = javaRuntimeId; - } - - } else if (javaId.startsWith("minecraft:spawner")) { - spawnerRuntimeId = javaRuntimeId; - } - } - - if (cobwebRuntimeId == -1) { - throw new AssertionError("Unable to find cobwebs in palette"); - } - JAVA_RUNTIME_COBWEB_ID = cobwebRuntimeId; - - if (commandBlockRuntimeId == -1) { - throw new AssertionError("Unable to find command block in palette"); - } - BEDROCK_RUNTIME_COMMAND_BLOCK_ID = commandBlockRuntimeId; - - if (furnaceRuntimeId == -1) { - throw new AssertionError("Unable to find furnace in palette"); - } - JAVA_RUNTIME_FURNACE_ID = furnaceRuntimeId; - - if (furnaceLitRuntimeId == -1) { - throw new AssertionError("Unable to find lit furnace in palette"); - } - JAVA_RUNTIME_FURNACE_LIT_ID = furnaceLitRuntimeId; - - if (spawnerRuntimeId == -1) { - throw new AssertionError("Unable to find spawner in palette"); - } - JAVA_RUNTIME_SPAWNER_ID = spawnerRuntimeId; - - if (waterRuntimeId == -1) { - throw new AssertionError("Unable to find water in palette"); - } - BEDROCK_WATER_ID = waterRuntimeId; - - if (airRuntimeId == -1) { - throw new AssertionError("Unable to find air in palette"); - } - BEDROCK_AIR_ID = airRuntimeId; - - // Loop around again to find all item frame runtime IDs - for (Object2IntMap.Entry entry : blockStateOrderedMap.object2IntEntrySet()) { - if (entry.getKey().getString("name").equals("minecraft:frame")) { - ITEM_FRAMES.put(entry.getKey(), entry.getIntValue()); - } - } - } - - private BlockTranslator() { - } - - public static void init() { - // no-op - } - - private static NbtMap buildBedrockState(JsonNode node) { - NbtMapBuilder tagBuilder = NbtMap.builder(); - tagBuilder.putString("name", node.get("bedrock_identifier").textValue()) - .putInt("version", BlockTranslator.BLOCK_STATE_VERSION); - - NbtMapBuilder statesBuilder = NbtMap.builder(); - - // check for states - if (node.has("bedrock_states")) { - Iterator> statesIterator = node.get("bedrock_states").fields(); - - while (statesIterator.hasNext()) { - Map.Entry stateEntry = statesIterator.next(); - JsonNode stateValue = stateEntry.getValue(); - switch (stateValue.getNodeType()) { - case BOOLEAN: - statesBuilder.putBoolean(stateEntry.getKey(), stateValue.booleanValue()); - continue; - case STRING: - statesBuilder.putString(stateEntry.getKey(), stateValue.textValue()); - continue; - case NUMBER: - statesBuilder.putInt(stateEntry.getKey(), stateValue.intValue()); - } - } - } - tagBuilder.put("states", statesBuilder.build()); - return tagBuilder.build(); - } - - public static int getBedrockBlockId(int state) { - return JAVA_TO_BEDROCK_BLOCK_MAP.get(state); - } - - public static int getJavaBlockState(int bedrockId) { - return BEDROCK_TO_JAVA_BLOCK_MAP.get(bedrockId); - } - - /** - * @param javaIdentifier the Java identifier of the block to search for - * @return the Bedrock identifier if different, or else the Java identifier - */ - public static String getBedrockBlockIdentifier(String javaIdentifier) { - return JAVA_TO_BEDROCK_IDENTIFIERS.getOrDefault(javaIdentifier, javaIdentifier); - } - - public static int getItemFrame(NbtMap tag) { - return ITEM_FRAMES.getOrDefault(tag, -1); - } - - public static boolean isItemFrame(int bedrockBlockRuntimeId) { - return ITEM_FRAMES.values().contains(bedrockBlockRuntimeId); - } - - public static int getBlockStateVersion() { - return BLOCK_STATE_VERSION; - } - - public static int getJavaBlockState(String javaId) { - return JAVA_ID_BLOCK_MAP.get(javaId); - } - - public static String getBlockEntityString(int javaId) { - return JAVA_ID_TO_BLOCK_ENTITY_MAP.get(javaId); - } - - public static boolean isWaterlogged(int state) { - return WATERLOGGED.contains(state); - } - - public static BiMap getJavaIdBlockMap() { - return JAVA_ID_BLOCK_MAP; - } - - public static int getJavaWaterloggedState(int bedrockId) { - return BEDROCK_TO_JAVA_BLOCK_MAP.get(1 << 31 | bedrockId); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java deleted file mode 100644 index 679636524..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.world.block.entity; - -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.github.steveice10.opennbt.tag.builtin.IntTag; -import com.github.steveice10.opennbt.tag.builtin.StringTag; -import com.github.steveice10.opennbt.tag.builtin.Tag; -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.nbt.NbtMapBuilder; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.utils.BlockEntityUtils; -import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.LanguageUtils; -import org.reflections.Reflections; - -import java.util.HashMap; -import java.util.Map; - -/** - * The class that all block entities (on both Java and Bedrock) should translate with - */ -public abstract class BlockEntityTranslator { - public static final Map BLOCK_ENTITY_TRANSLATORS = new HashMap<>(); - /** - * A list of all block entities that require the Java block state in order to fill out their block entity information. - * This list will be smaller with cache chunks on as we don't need to double-cache data - */ - public static final ObjectArrayList REQUIRES_BLOCK_STATE_LIST = new ObjectArrayList<>(); - - /** - * Contains a list of irregular block entity name translations that can't be fit into the regex - */ - public static final Map BLOCK_ENTITY_TRANSLATIONS = new HashMap() { - { - // Bedrock/Java differences - put("minecraft:enchanting_table", "EnchantTable"); - put("minecraft:jigsaw", "JigsawBlock"); - put("minecraft:piston_head", "PistonArm"); - put("minecraft:trapped_chest", "Chest"); - // There are some legacy IDs sent but as far as I can tell they are not needed for things to work properly - } - }; - - protected BlockEntityTranslator() { - } - - public static void init() { - // no-op - } - - static { - Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") : new Reflections("org.geysermc.connector.network.translators.world.block.entity"); - for (Class clazz : ref.getTypesAnnotatedWith(BlockEntity.class)) { - GeyserConnector.getInstance().getLogger().debug("Found annotated block entity: " + clazz.getCanonicalName()); - - try { - BLOCK_ENTITY_TRANSLATORS.put(clazz.getAnnotation(BlockEntity.class).name(), (BlockEntityTranslator) clazz.newInstance()); - } catch (InstantiationException | IllegalAccessException e) { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.block_entity.failed", clazz.getCanonicalName())); - } - } - boolean cacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks(); - for (Class clazz : ref.getSubTypesOf(RequiresBlockState.class)) { - GeyserConnector.getInstance().getLogger().debug("Found block entity that requires block state: " + clazz.getCanonicalName()); - - try { - RequiresBlockState requiresBlockState = (RequiresBlockState) clazz.newInstance(); - if (cacheChunks && !(requiresBlockState instanceof BedrockOnlyBlockEntity)) { - // Not needed to put this one in the map; cache chunks takes care of that for us - GeyserConnector.getInstance().getLogger().debug("Not adding because cache chunks is enabled."); - continue; - } - REQUIRES_BLOCK_STATE_LIST.add(requiresBlockState); - } catch (InstantiationException | IllegalAccessException e) { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.block_state.failed", clazz.getCanonicalName())); - } - } - } - - public abstract void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState); - - public NbtMap getBlockEntityTag(String id, CompoundTag tag, int blockState) { - int x = ((IntTag) tag.getValue().get("x")).getValue(); - int y = ((IntTag) tag.getValue().get("y")).getValue(); - int z = ((IntTag) tag.getValue().get("z")).getValue(); - - NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(id), x, y, z).toBuilder(); - translateTag(tagBuilder, tag, blockState); - return tagBuilder.build(); - } - - protected CompoundTag getConstantJavaTag(String javaId, int x, int y, int z) { - CompoundTag tag = new CompoundTag(""); - tag.put(new IntTag("x", x)); - tag.put(new IntTag("y", y)); - tag.put(new IntTag("z", z)); - tag.put(new StringTag("id", javaId)); - return tag; - } - - protected NbtMap getConstantBedrockTag(String bedrockId, int x, int y, int z) { - return NbtMap.builder() - .putInt("x", x) - .putInt("y", y) - .putInt("z", z) - .putString("id", bedrockId) - .build(); - } - - @SuppressWarnings("unchecked") - protected T getOrDefault(Tag tag, T defaultValue) { - return (tag != null && tag.getValue() != null) ? (T) tag.getValue() : defaultValue; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java deleted file mode 100644 index a9641d772..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.world.block.entity; - -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.nukkitx.nbt.NbtMapBuilder; -import org.geysermc.connector.network.translators.chat.MessageTranslator; -import org.geysermc.connector.utils.SignUtils; - -@BlockEntity(name = "Sign", regex = "sign") -public class SignBlockEntityTranslator extends BlockEntityTranslator { - /** - * Maps a color stored in a sign's Color tag to a Bedrock Edition formatting code. - *
- * The color names correspond to dye names, because of this we can't use {@link MessageTranslator#getColor(String)}. - * - * @param javaColor The dye color stored in the sign's Color tag. - * @return A Bedrock Edition formatting code for valid dye colors, otherwise an empty string. - */ - private String getBedrockSignColor(String javaColor) { - String base = "\u00a7"; - switch (javaColor) { - case "white": - base += 'f'; - break; - case "orange": - base += '6'; - break; - case "magenta": - case "purple": - base += '5'; - break; - case "light_blue": - base += 'b'; - break; - case "yellow": - base += 'e'; - break; - case "lime": - base += 'a'; - break; - case "pink": - base += 'd'; - break; - case "gray": - base += '8'; - break; - case "light_gray": - base += '7'; - break; - case "cyan": - base += '3'; - break; - case "blue": - base += '9'; - break; - case "brown": // Brown does not have a bedrock counterpart. - case "red": // In Java Edition light red (&c) can only be applied using commands. Red dye gives &4. - base += '4'; - break; - case "green": - base += '2'; - break; - case "black": - base += '0'; - break; - default: - return ""; - } - return base; - } - - @Override - public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { - StringBuilder signText = new StringBuilder(); - for (int i = 0; i < 4; i++) { - int currentLine = i + 1; - String signLine = getOrDefault(tag.getValue().get("Text" + currentLine), ""); - signLine = MessageTranslator.convertMessageLenient(signLine); - - // Trim any trailing formatting codes - if (signLine.length() > 2 && signLine.toCharArray()[signLine.length() - 2] == '\u00a7') { - signLine = signLine.substring(0, signLine.length() - 2); - } - - // Check the character width on the sign to ensure there is no overflow that is usually hidden - // to Java Edition clients but will appear to Bedrock clients - int signWidth = 0; - StringBuilder finalSignLine = new StringBuilder(); - for (char c : signLine.toCharArray()) { - signWidth += SignUtils.getCharacterWidth(c); - if (signWidth <= SignUtils.BEDROCK_CHARACTER_WIDTH_MAX) { - finalSignLine.append(c); - } else { - break; - } - } - - // Java Edition 1.14 added the ability to change the text color of the whole sign using dye - if (tag.contains("Color")) { - signText.append(getBedrockSignColor(tag.get("Color").getValue().toString())); - } - - signText.append(finalSignLine.toString()); - signText.append("\n"); - } - - builder.put("Text", signText.toString()); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java deleted file mode 100644 index 8eaa2e277..000000000 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.scoreboard; - -import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition; -import com.nukkitx.protocol.bedrock.data.ScoreInfo; -import com.nukkitx.protocol.bedrock.packet.RemoveObjectivePacket; -import com.nukkitx.protocol.bedrock.packet.SetDisplayObjectivePacket; -import com.nukkitx.protocol.bedrock.packet.SetScorePacket; -import lombok.Getter; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.GeyserLogger; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.utils.LanguageUtils; - -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicLong; - -import static org.geysermc.connector.scoreboard.UpdateType.*; - -@Getter -public final class Scoreboard { - private final GeyserSession session; - private final GeyserLogger logger; - private final AtomicLong nextId = new AtomicLong(0); - - private final Map objectives = new ConcurrentHashMap<>(); - private final Map teams = new HashMap<>(); - - private int lastAddScoreCount = 0; - private int lastRemoveScoreCount = 0; - - public Scoreboard(GeyserSession session) { - this.session = session; - this.logger = GeyserConnector.getInstance().getLogger(); - } - - public Objective registerNewObjective(String objectiveId, boolean active) { - Objective objective = objectives.get(objectiveId); - if (active || objective != null) { - return objective; - } - objective = new Objective(this, objectiveId); - objectives.put(objectiveId, objective); - return objective; - } - - public Objective displayObjective(String objectiveId, ScoreboardPosition displaySlot) { - Objective objective = objectives.get(objectiveId); - if (objective != null) { - if (!objective.isActive()) { - objective.setActive(displaySlot); - removeOldObjectives(objective); - return objective; - } - despawnObjective(objective); - } - - objective = new Objective(this, objectiveId, displaySlot, "unknown", 0); - objectives.put(objectiveId, objective); - removeOldObjectives(objective); - return objective; - } - - private void removeOldObjectives(Objective newObjective) { - for (Objective next : objectives.values()) { - if (next.getId() == newObjective.getId()) { - continue; - } - if (next.getDisplaySlot() == newObjective.getDisplaySlot()) { - next.setUpdateType(REMOVE); - } - } - } - - public Team registerNewTeam(String teamName, Set players) { - Team team = teams.get(teamName); - if (team != null) { - logger.info(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_overrides", teamName)); - return team; - } - - team = new Team(this, teamName).addEntities(players); - teams.put(teamName, team); - return team; - } - - public Objective getObjective(String objectiveName) { - return objectives.get(objectiveName); - } - - public Team getTeam(String teamName) { - return teams.get(teamName); - } - - public void unregisterObjective(String objectiveName) { - Objective objective = getObjective(objectiveName); - if (objective != null) { - objective.setUpdateType(REMOVE); - } - } - - public void removeTeam(String teamName) { - Team remove = teams.remove(teamName); - if (remove != null) { - remove.setUpdateType(REMOVE); - } - } - - public void onUpdate() { - List addScores = new ArrayList<>(getLastAddScoreCount()); - List removeScores = new ArrayList<>(getLastRemoveScoreCount()); - List removedObjectives = new ArrayList<>(); - - for (Objective objective : objectives.values()) { - if (!objective.isActive()) { - logger.debug("Ignoring non-active Scoreboard Objective '" + objective.getObjectiveName() + '\''); - continue; - } - - // hearts can't hold teams, so we treat them differently - if (objective.getType() == 1) { - for (Score score : objective.getScores().values()) { - boolean update = score.shouldUpdate(); - - if (update) { - score.update(objective.getObjectiveName()); - } - - if (score.getUpdateType() != REMOVE && update) { - addScores.add(score.getCachedInfo()); - } - if (score.getUpdateType() != ADD && update) { - removeScores.add(score.getCachedInfo()); - } - } - continue; - } - - boolean objectiveUpdate = objective.getUpdateType() == UPDATE; - boolean objectiveAdd = objective.getUpdateType() == ADD; - boolean objectiveRemove = objective.getUpdateType() == REMOVE; - - for (Score score : objective.getScores().values()) { - Team team = score.getTeam(); - - boolean add = objectiveAdd || objectiveUpdate; - boolean remove = false; - if (team != null) { - if (team.getUpdateType() == REMOVE || !team.hasEntity(score.getName())) { - score.setTeam(null); - add = true; - remove = true; - } - } - - add |= score.shouldUpdate(); - remove |= score.shouldUpdate(); - - if (score.getUpdateType() == REMOVE || objectiveRemove) { - add = false; - } - - if (score.getUpdateType() == ADD || objectiveRemove) { - remove = false; - } - - if (score.shouldUpdate()) { - score.update(objective.getObjectiveName()); - } - - if (add) { - addScores.add(score.getCachedInfo()); - } - - if (remove) { - removeScores.add(score.getCachedInfo()); - } - - // score is pending to be removed, so we can remove it from the objective - if (score.getUpdateType() == REMOVE) { - objective.removeScore0(score.getName()); - } - - score.setUpdateType(NOTHING); - } - - if (objectiveRemove) { - removedObjectives.add(objective); - } - - if (objectiveUpdate) { - RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); - removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); - session.sendUpstreamPacket(removeObjectivePacket); - } - - if ((objectiveAdd || objectiveUpdate) && !objectiveRemove) { - SetDisplayObjectivePacket displayObjectivePacket = new SetDisplayObjectivePacket(); - displayObjectivePacket.setObjectiveId(objective.getObjectiveName()); - displayObjectivePacket.setDisplayName(objective.getDisplayName()); - displayObjectivePacket.setCriteria("dummy"); - displayObjectivePacket.setDisplaySlot(objective.getDisplaySlotName()); - displayObjectivePacket.setSortOrder(1); // ?? - session.sendUpstreamPacket(displayObjectivePacket); - } - - objective.setUpdateType(NOTHING); - } - - Iterator teamIterator = teams.values().iterator(); - while (teamIterator.hasNext()) { - Team current = teamIterator.next(); - - switch (current.getUpdateType()) { - case ADD: - case UPDATE: - current.markUpdated(); - break; - case REMOVE: - teamIterator.remove(); - } - } - - if (!removeScores.isEmpty()) { - SetScorePacket setScorePacket = new SetScorePacket(); - setScorePacket.setAction(SetScorePacket.Action.REMOVE); - setScorePacket.setInfos(removeScores); - session.sendUpstreamPacket(setScorePacket); - } - - if (!addScores.isEmpty()) { - SetScorePacket setScorePacket = new SetScorePacket(); - setScorePacket.setAction(SetScorePacket.Action.SET); - setScorePacket.setInfos(addScores); - session.sendUpstreamPacket(setScorePacket); - } - - // prevents crashes in some cases - for (Objective objective : removedObjectives) { - despawnObjective(objective); - } - - lastAddScoreCount = addScores.size(); - lastRemoveScoreCount = removeScores.size(); - } - - public void despawnObjective(Objective objective) { - objectives.remove(objective.getObjectiveName()); - objective.removed(); - - RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); - removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); - session.sendUpstreamPacket(removeObjectivePacket); - } - - public Team getTeamFor(String entity) { - for (Team team : teams.values()) { - if (team.hasEntity(entity)) { - return team; - } - } - return null; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java b/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java deleted file mode 100644 index 3812fb141..000000000 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.scoreboard; - -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.configuration.GeyserConfiguration; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.session.cache.WorldCache; -import org.geysermc.connector.utils.LanguageUtils; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -public class ScoreboardUpdater extends Thread { - public static final int FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD; - public static final int SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD = 250; - - private static final int FIRST_MILLIS_BETWEEN_UPDATES = 250; // 4 updates per second - private static final int SECOND_MILLIS_BETWEEN_UPDATES = 1000 * 3; // 1 update per 3 seconds - - private static final boolean DEBUG_ENABLED; - - private final WorldCache worldCache; - private final GeyserSession session; - - private int millisBetweenUpdates = FIRST_MILLIS_BETWEEN_UPDATES; - private long lastUpdate = System.currentTimeMillis(); - private long lastLog = -1; - - private long lastPacketsPerSecondUpdate = System.currentTimeMillis(); - private final AtomicInteger packetsPerSecond = new AtomicInteger(0); - private final AtomicInteger pendingPacketsPerSecond = new AtomicInteger(0); - - public ScoreboardUpdater(WorldCache worldCache) { - super("Scoreboard Updater"); - this.worldCache = worldCache; - session = worldCache.getSession(); - } - - @Override - public void run() { - if (!session.isClosed()) { - long currentTime = System.currentTimeMillis(); - - // reset score-packets per second every second - if (currentTime - lastPacketsPerSecondUpdate > 1000) { - lastPacketsPerSecondUpdate = currentTime; - packetsPerSecond.set(pendingPacketsPerSecond.get()); - pendingPacketsPerSecond.set(0); - } - - if (currentTime - lastUpdate > millisBetweenUpdates) { - lastUpdate = currentTime; - - int pps = packetsPerSecond.get(); - if (pps >= FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) { - boolean reachedSecondThreshold = pps >= SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD; - if (reachedSecondThreshold) { - millisBetweenUpdates = SECOND_MILLIS_BETWEEN_UPDATES; - } else { - millisBetweenUpdates = FIRST_MILLIS_BETWEEN_UPDATES; - } - - worldCache.getScoreboard().onUpdate(); - - if (DEBUG_ENABLED && (currentTime - lastLog > 60000)) { // one minute - int threshold = reachedSecondThreshold ? - SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD : - FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD; - - GeyserConnector.getInstance().getLogger().info( - LanguageUtils.getLocaleStringLog("geyser.scoreboard.updater.threshold_reached.log", session.getName(), threshold, pps) + - LanguageUtils.getLocaleStringLog("geyser.scoreboard.updater.threshold_reached", (millisBetweenUpdates / 1000.0)) - ); - - lastLog = currentTime; - } - } - } - - session.getConnector().getGeneralThreadPool().schedule(this, 50, TimeUnit.MILLISECONDS); - } - } - - public int getPacketsPerSecond() { - return packetsPerSecond.get(); - } - - /** - * Increase the Scoreboard Packets Per Second and return the updated value - */ - public int incrementAndGetPacketsPerSecond() { - return pendingPacketsPerSecond.incrementAndGet(); - } - - static { - GeyserConfiguration config = GeyserConnector.getInstance().getConfig(); - FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD = Math.min(config.getScoreboardPacketThreshold(), SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD); - DEBUG_ENABLED = config.isDebugMode(); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java deleted file mode 100644 index 7e1d58b4c..000000000 --- a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.utils; - -import com.github.steveice10.mc.protocol.data.game.entity.Effect; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.nukkitx.math.vector.Vector3i; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.ToolItemEntry; - -import java.util.Optional; - -public class BlockUtils { - - private static boolean correctTool(String blockToolType, String itemToolType) { - return (blockToolType.equals("sword") && itemToolType.equals("sword")) || - (blockToolType.equals("shovel") && itemToolType.equals("shovel")) || - (blockToolType.equals("pickaxe") && itemToolType.equals("pickaxe")) || - (blockToolType.equals("axe") && itemToolType.equals("axe")) || - (blockToolType.equals("shears") && itemToolType.equals("shears")); - } - - private static double toolBreakTimeBonus(String toolType, String toolTier, boolean isWoolBlock) { - if (toolType.equals("shears")) return isWoolBlock ? 5.0 : 15.0; - if (toolType.equals("")) return 1.0; - switch (toolTier) { - case "wooden": - return 2.0; - case "stone": - return 4.0; - case "iron": - return 6.0; - case "diamond": - return 8.0; - case "golden": - return 12.0; - default: - return 1.0; - } - } - - //http://minecraft.gamepedia.com/Breaking - private static double calculateBreakTime(double blockHardness, String toolTier, boolean canHarvestWithHand, boolean correctTool, - String toolType, boolean isWoolBlock, boolean isCobweb, int toolEfficiencyLevel, int hasteLevel, int miningFatigueLevel, - boolean insideOfWaterWithoutAquaAffinity, boolean outOfWaterButNotOnGround, boolean insideWaterAndNotOnGround) { - double baseTime = ((correctTool || canHarvestWithHand) ? 1.5 : 5.0) * blockHardness; - double speed = 1.0 / baseTime; - - if (correctTool) { - speed *= toolBreakTimeBonus(toolType, toolTier, isWoolBlock); - speed += toolEfficiencyLevel == 0 ? 0 : toolEfficiencyLevel * toolEfficiencyLevel + 1; - } else if (toolType.equals("sword")) { - speed*= (isCobweb ? 15.0 : 1.5); - } - speed *= 1.0 + (0.2 * hasteLevel); - - switch (miningFatigueLevel) { - case 0: - break; - case 1: - speed -= (speed * 0.7); - break; - case 2: - speed -= (speed * 0.91); - break; - case 3: - speed -= (speed * 0.9973); - break; - default: - speed -= (speed * 0.99919); - break; - } - - if (insideOfWaterWithoutAquaAffinity) speed *= 0.2; - if (outOfWaterButNotOnGround) speed *= 0.2; - if (insideWaterAndNotOnGround) speed *= 0.2; - return 1.0 / speed; - } - - public static double getBreakTime(double blockHardness, int blockId, ItemEntry item, CompoundTag nbtData, GeyserSession session) { - boolean isWoolBlock = BlockTranslator.JAVA_RUNTIME_WOOL_IDS.contains(blockId); - boolean isCobweb = blockId == BlockTranslator.JAVA_RUNTIME_COBWEB_ID; - String blockToolType = BlockTranslator.JAVA_RUNTIME_ID_TO_TOOL_TYPE.getOrDefault(blockId, ""); - boolean canHarvestWithHand = BlockTranslator.JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND.get(blockId); - String toolType = ""; - String toolTier = ""; - boolean correctTool = false; - if (item instanceof ToolItemEntry) { - ToolItemEntry toolItem = (ToolItemEntry) item; - toolType = toolItem.getToolType(); - toolTier = toolItem.getToolTier(); - correctTool = correctTool(blockToolType, toolType); - } - int toolEfficiencyLevel = ItemUtils.getEnchantmentLevel(nbtData, "minecraft:efficiency"); - int hasteLevel = 0; - int miningFatigueLevel = 0; - - if (session == null) { - return calculateBreakTime(blockHardness, toolTier, canHarvestWithHand, correctTool, toolType, isWoolBlock, isCobweb, toolEfficiencyLevel, hasteLevel, miningFatigueLevel, false, false, false); - } - - hasteLevel = session.getPlayerEntity().getEffectCache().getEffectLevel(Effect.FASTER_DIG); - miningFatigueLevel = session.getPlayerEntity().getEffectCache().getEffectLevel(Effect.SLOWER_DIG); - - boolean isInWater = session.getConnector().getConfig().isCacheChunks() - && BlockTranslator.getBedrockBlockId(session.getConnector().getWorldManager().getBlockAt(session, session.getPlayerEntity().getPosition().toInt())) == BlockTranslator.BEDROCK_WATER_ID; - - boolean insideOfWaterWithoutAquaAffinity = isInWater && - ItemUtils.getEnchantmentLevel(Optional.ofNullable(session.getInventory().getItem(5)).map(ItemStack::getNbt).orElse(null), "minecraft:aqua_affinity") < 1; - - boolean outOfWaterButNotOnGround = (!isInWater) && (!session.getPlayerEntity().isOnGround()); - boolean insideWaterNotOnGround = isInWater && !session.getPlayerEntity().isOnGround(); - return calculateBreakTime(blockHardness, toolTier, canHarvestWithHand, correctTool, toolType, isWoolBlock, isCobweb, toolEfficiencyLevel, hasteLevel, miningFatigueLevel, insideOfWaterWithoutAquaAffinity, outOfWaterButNotOnGround, insideWaterNotOnGround); - } - - /** - * Given a position, return the position if a block were located on the specified block face. - * @param blockPos the block position - * @param face the face of the block - see {@link com.github.steveice10.mc.protocol.data.game.world.block.BlockFace} - * @return the block position with the block face accounted for - */ - public static Vector3i getBlockPosition(Vector3i blockPos, int face) { - switch (face) { - case 0: - return blockPos.sub(0, 1, 0); - case 1: - return blockPos.add(0, 1, 0); - case 2: - return blockPos.sub(0, 0, 1); - case 3: - return blockPos.add(0, 0, 1); - case 4: - return blockPos.sub(1, 0, 0); - case 5: - return blockPos.add(1, 0, 0); - } - return blockPos; - } - -} diff --git a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java deleted file mode 100644 index 005a4960e..000000000 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ /dev/null @@ -1,431 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.utils; - -import com.github.steveice10.mc.protocol.data.game.chunk.BitStorage; -import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; -import com.github.steveice10.mc.protocol.data.game.chunk.Column; -import com.github.steveice10.mc.protocol.data.game.chunk.palette.GlobalPalette; -import com.github.steveice10.mc.protocol.data.game.chunk.palette.Palette; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.github.steveice10.opennbt.tag.builtin.StringTag; -import com.github.steveice10.opennbt.tag.builtin.Tag; -import com.nukkitx.math.vector.Vector2i; -import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.nbt.NBTOutputStream; -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.nbt.NbtUtils; -import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; -import com.nukkitx.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket; -import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; -import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.ints.IntList; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import lombok.Data; -import lombok.experimental.UtilityClass; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.ItemFrameEntity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.network.translators.world.block.entity.BedrockOnlyBlockEntity; -import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator; -import org.geysermc.connector.network.translators.world.block.entity.RequiresBlockState; -import org.geysermc.connector.network.translators.world.chunk.BlockStorage; -import org.geysermc.connector.network.translators.world.chunk.ChunkSection; -import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArray; -import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArrayVersion; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.List; - -import static org.geysermc.connector.network.translators.world.block.BlockTranslator.JAVA_AIR_ID; -import static org.geysermc.connector.network.translators.world.block.BlockTranslator.BEDROCK_AIR_ID; -import static org.geysermc.connector.network.translators.world.block.BlockTranslator.BEDROCK_WATER_ID; - -@UtilityClass -public class ChunkUtils { - /** - * Temporarily stores positions of BlockState values that are needed for certain block entities actively. - * Not used if cache chunks is enabled - */ - public static final Object2IntMap CACHED_BLOCK_ENTITIES = new Object2IntOpenHashMap<>(); - - private static final NbtMap EMPTY_TAG = NbtMap.builder().build(); - public static final byte[] EMPTY_LEVEL_CHUNK_DATA; - - public static final BlockStorage EMPTY_STORAGE = new BlockStorage(); - public static final ChunkSection EMPTY_SECTION = new ChunkSection(new BlockStorage[]{ EMPTY_STORAGE }); - - static { - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - outputStream.write(new byte[258]); // Biomes + Border Size + Extra Data Size - - try (NBTOutputStream stream = NbtUtils.createNetworkWriter(outputStream)) { - stream.writeTag(EMPTY_TAG); - } - - EMPTY_LEVEL_CHUNK_DATA = outputStream.toByteArray(); - } catch (IOException e) { - throw new AssertionError("Unable to generate empty level chunk data"); - } - } - - private static int indexYZXtoXZY(int yzx) { - return (yzx >> 8) | (yzx & 0x0F0) | ((yzx & 0x00F) << 8); - } - - public static ChunkData translateToBedrock(GeyserSession session, Column column, boolean isNonFullChunk) { - Chunk[] javaSections = column.getChunks(); - ChunkSection[] sections = new ChunkSection[javaSections.length]; - - // Temporarily stores compound tags of Bedrock-only block entities - List bedrockOnlyBlockEntities = new ArrayList<>(); - - BitSet waterloggedPaletteIds = new BitSet(); - BitSet pistonOrFlowerPaletteIds = new BitSet(); - - boolean worldManagerHasMoreBlockDataThanCache = session.getConnector().getWorldManager().hasMoreBlockDataThanChunkCache(); - - // If the received packet was a full chunk update, null sections in the chunk are guaranteed to also be null in the world manager - boolean shouldCheckWorldManagerOnMissingSections = isNonFullChunk && worldManagerHasMoreBlockDataThanCache; - Chunk temporarySection = null; - - for (int sectionY = 0; sectionY < javaSections.length; sectionY++) { - Chunk javaSection = javaSections[sectionY]; - - // Section is null, the cache will not contain anything of use - if (javaSection == null) { - // The column parameter contains all data currently available from the cache. If the chunk is null and the world manager - // reports the ability to access more data than the cache, attempt to fetch from the world manager instead. - if (shouldCheckWorldManagerOnMissingSections) { - // Ensure that temporary chunk is set - if (temporarySection == null) { - temporarySection = new Chunk(); - } - - // Read block data in section - session.getConnector().getWorldManager().getBlocksInSection(session, column.getX(), sectionY, column.getZ(), temporarySection); - - if (temporarySection.isEmpty()) { - // The world manager only contains air for the given section - // We can leave temporarySection as-is to allow it to potentially be re-used for later sections - continue; - } else { - javaSection = temporarySection; - - // Section contents have been modified, we can't re-use it - temporarySection = null; - } - } else { - continue; - } - } - - // No need to encode an empty section... - if (javaSection.isEmpty()) { - continue; - } - - Palette javaPalette = javaSection.getPalette(); - BitStorage javaData = javaSection.getStorage(); - - if (javaPalette instanceof GlobalPalette) { - // As this is the global palette, simply iterate through the whole chunk section once - ChunkSection section = new ChunkSection(); - for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) { - int javaId = javaData.get(yzx); - int bedrockId = BlockTranslator.getBedrockBlockId(javaId); - int xzy = indexYZXtoXZY(yzx); - section.getBlockStorageArray()[0].setFullBlock(xzy, bedrockId); - - if (BlockTranslator.isWaterlogged(javaId)) { - section.getBlockStorageArray()[1].setFullBlock(xzy, BEDROCK_WATER_ID); - } - - // Check if block is piston or flower to see if we'll need to create additional block entities, as they're only block entities in Bedrock - if (BlockStateValues.getFlowerPotValues().containsKey(javaId) || BlockStateValues.getPistonValues().containsKey(javaId)) { - bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag( - Vector3i.from((column.getX() << 4) + (yzx & 0xF), (sectionY << 4) + ((yzx >> 8) & 0xF), (column.getZ() << 4) + ((yzx >> 4) & 0xF)), - javaId - )); - } - } - sections[sectionY] = section; - continue; - } - - IntList bedrockPalette = new IntArrayList(javaPalette.size()); - waterloggedPaletteIds.clear(); - pistonOrFlowerPaletteIds.clear(); - - // Iterate through palette and convert state IDs to Bedrock, doing some additional checks as we go - for (int i = 0; i < javaPalette.size(); i++) { - int javaId = javaPalette.idToState(i); - bedrockPalette.add(BlockTranslator.getBedrockBlockId(javaId)); - - if (BlockTranslator.isWaterlogged(javaId)) { - waterloggedPaletteIds.set(i); - } - - // Check if block is piston or flower to see if we'll need to create additional block entities, as they're only block entities in Bedrock - if (BlockStateValues.getFlowerPotValues().containsKey(javaId) || BlockStateValues.getPistonValues().containsKey(javaId)) { - pistonOrFlowerPaletteIds.set(i); - } - } - - // Add Bedrock-exclusive block entities - // We only if the palette contained any blocks that are Bedrock-exclusive block entities to avoid iterating through the whole block data - // for no reason, as most sections will not contain any pistons or flower pots - if (!pistonOrFlowerPaletteIds.isEmpty()) { - for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) { - int paletteId = javaData.get(yzx); - if (pistonOrFlowerPaletteIds.get(paletteId)) { - bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag( - Vector3i.from((column.getX() << 4) + (yzx & 0xF), (sectionY << 4) + ((yzx >> 8) & 0xF), (column.getZ() << 4) + ((yzx >> 4) & 0xF)), - javaPalette.idToState(paletteId) - )); - } - } - } - - BitArray bedrockData = BitArrayVersion.forBitsCeil(javaData.getBitsPerEntry()).createArray(BlockStorage.SIZE); - BlockStorage layer0 = new BlockStorage(bedrockData, bedrockPalette); - BlockStorage[] layers; - - // Convert data array from YZX to XZY coordinate order - if (waterloggedPaletteIds.isEmpty()) { - // No blocks are waterlogged, simply convert coordinate order - // This could probably be optimized further... - for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) { - bedrockData.set(indexYZXtoXZY(yzx), javaData.get(yzx)); - } - - layers = new BlockStorage[]{ layer0 }; - } else { - // The section contains waterlogged blocks, we need to convert coordinate order AND generate a V1 block storage for - // layer 1 with palette ID 1 indicating water - int[] layer1Data = new int[BlockStorage.SIZE >> 5]; - for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) { - int paletteId = javaData.get(yzx); - int xzy = indexYZXtoXZY(yzx); - bedrockData.set(xzy, paletteId); - - if (waterloggedPaletteIds.get(paletteId)) { - layer1Data[xzy >> 5] |= 1 << (xzy & 0x1F); - } - } - - // V1 palette - IntList layer1Palette = new IntArrayList(2); - layer1Palette.add(BEDROCK_AIR_ID); // Air - see BlockStorage's constructor for more information - layer1Palette.add(BEDROCK_WATER_ID); - - layers = new BlockStorage[]{ layer0, new BlockStorage(BitArrayVersion.V1.createArray(BlockStorage.SIZE, layer1Data), layer1Palette) }; - } - - sections[sectionY] = new ChunkSection(layers); - } - - CompoundTag[] blockEntities = column.getTileEntities(); - NbtMap[] bedrockBlockEntities = new NbtMap[blockEntities.length + bedrockOnlyBlockEntities.size()]; - int i = 0; - while (i < blockEntities.length) { - CompoundTag tag = blockEntities[i]; - String tagName; - if (tag.contains("id")) { - tagName = (String) tag.get("id").getValue(); - } else { - tagName = "Empty"; - // Sometimes legacy tags have their ID be a StringTag with empty value - for (Tag subTag : tag) { - if (subTag instanceof StringTag) { - StringTag stringTag = (StringTag) subTag; - if (stringTag.getValue().isEmpty()) { - tagName = stringTag.getName(); - break; - } - } - } - if (tagName.equals("Empty")) { - GeyserConnector.getInstance().getLogger().debug("Got tag with no id: " + tag.getValue()); - } - } - - String id = BlockEntityUtils.getBedrockBlockEntityId(tagName); - BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(id); - Position pos = new Position((int) tag.get("x").getValue(), (int) tag.get("y").getValue(), (int) tag.get("z").getValue()); - - // Get Java blockstate ID from block entity position - int blockState = 0; - Chunk section = column.getChunks()[pos.getY() >> 4]; - if (section != null) { - blockState = section.get(pos.getX() & 0xF, pos.getY() & 0xF, pos.getZ() & 0xF); - } - - bedrockBlockEntities[i] = blockEntityTranslator.getBlockEntityTag(tagName, tag, blockState); - i++; - } - - // Append Bedrock-exclusive block entities to output array - for (NbtMap tag : bedrockOnlyBlockEntities) { - bedrockBlockEntities[i] = tag; - i++; - } - - return new ChunkData(sections, bedrockBlockEntities); - } - - public static void updateChunkPosition(GeyserSession session, Vector3i position) { - Vector2i chunkPos = session.getLastChunkPosition(); - Vector2i newChunkPos = Vector2i.from(position.getX() >> 4, position.getZ() >> 4); - - if (chunkPos == null || !chunkPos.equals(newChunkPos)) { - NetworkChunkPublisherUpdatePacket chunkPublisherUpdatePacket = new NetworkChunkPublisherUpdatePacket(); - chunkPublisherUpdatePacket.setPosition(position); - chunkPublisherUpdatePacket.setRadius(session.getRenderDistance() << 4); - session.sendUpstreamPacket(chunkPublisherUpdatePacket); - - session.setLastChunkPosition(newChunkPos); - } - } - - /** - * Sends a block update to the Bedrock client. If chunk caching is enabled and the platform is not Spigot, this also - * adds that block to the cache. - * @param session the Bedrock session to send/register the block to - * @param blockState the Java block state of the block - * @param position the position of the block - */ - public static void updateBlock(GeyserSession session, int blockState, Position position) { - Vector3i pos = Vector3i.from(position.getX(), position.getY(), position.getZ()); - updateBlock(session, blockState, pos); - } - - /** - * Sends a block update to the Bedrock client. If chunk caching is enabled and the platform is not Spigot, this also - * adds that block to the cache. - * @param session the Bedrock session to send/register the block to - * @param blockState the Java block state of the block - * @param position the position of the block - */ - public static void updateBlock(GeyserSession session, int blockState, Vector3i position) { - // Checks for item frames so they aren't tripped up and removed - long frameEntityId = ItemFrameEntity.getItemFrameEntityId(session, position); - if (frameEntityId != -1) { - // TODO: Very occasionally the item frame doesn't sync up when destroyed - Entity entity = session.getEntityCache().getEntityByJavaId(frameEntityId); - if (blockState == JAVA_AIR_ID && entity != null) { // Item frame is still present and no block overrides that; refresh it - ((ItemFrameEntity) entity).updateBlock(session); - return; - } - - // Otherwise the item frame is gone - if (entity != null) { - session.getEntityCache().removeEntity(entity, false); - } else { - ItemFrameEntity.removePosition(session, position); - } - } - - int blockId = BlockTranslator.getBedrockBlockId(blockState); - - UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); - updateBlockPacket.setDataLayer(0); - updateBlockPacket.setBlockPosition(position); - updateBlockPacket.setRuntimeId(blockId); - updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); - updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); - session.sendUpstreamPacket(updateBlockPacket); - - UpdateBlockPacket waterPacket = new UpdateBlockPacket(); - waterPacket.setDataLayer(1); - waterPacket.setBlockPosition(position); - if (BlockTranslator.isWaterlogged(blockState)) { - waterPacket.setRuntimeId(BEDROCK_WATER_ID); - } else { - waterPacket.setRuntimeId(BEDROCK_AIR_ID); - } - session.sendUpstreamPacket(waterPacket); - - // Since Java stores bed colors/skull information as part of the namespaced ID and Bedrock stores it as a tag - // This is the only place I could find that interacts with the Java block state and block updates - // Iterates through all block entity translators and determines if the block state needs to be saved - for (RequiresBlockState requiresBlockState : BlockEntityTranslator.REQUIRES_BLOCK_STATE_LIST) { - if (requiresBlockState.isBlock(blockState)) { - // Flower pots are block entities only in Bedrock and are not updated anywhere else like note blocks - if (requiresBlockState instanceof BedrockOnlyBlockEntity) { - ((BedrockOnlyBlockEntity) requiresBlockState).updateBlock(session, blockState, position); - break; - } - if (!session.getConnector().getConfig().isCacheChunks()) { - // Blocks aren't saved to a chunk cache; resort to this smaller cache - CACHED_BLOCK_ENTITIES.put(new Position(position.getX(), position.getY(), position.getZ()), blockState); - } - break; //No block will be a part of two classes - } - } - session.getChunkCache().updateBlock(position.getX(), position.getY(), position.getZ(), blockState); - } - - public static void sendEmptyChunks(GeyserSession session, Vector3i position, int radius, boolean forceUpdate) { - int chunkX = position.getX() >> 4; - int chunkZ = position.getZ() >> 4; - for (int x = -radius; x <= radius; x++) { - for (int z = -radius; z <= radius; z++) { - LevelChunkPacket data = new LevelChunkPacket(); - data.setChunkX(chunkX + x); - data.setChunkZ(chunkZ + z); - data.setSubChunksLength(0); - data.setData(EMPTY_LEVEL_CHUNK_DATA); - data.setCachingEnabled(false); - session.sendUpstreamPacket(data); - - if (forceUpdate) { - Vector3i pos = Vector3i.from(chunkX + x << 4, 80, chunkZ + z << 4); - UpdateBlockPacket blockPacket = new UpdateBlockPacket(); - blockPacket.setBlockPosition(pos); - blockPacket.setDataLayer(0); - blockPacket.setRuntimeId(1); - session.sendUpstreamPacket(blockPacket); - } - } - } - } - - @Data - public static final class ChunkData { - private final ChunkSection[] sections; - - private final NbtMap[] blockEntities; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/utils/EntityUtils.java b/connector/src/main/java/org/geysermc/connector/utils/EntityUtils.java deleted file mode 100644 index 51102202d..000000000 --- a/connector/src/main/java/org/geysermc/connector/utils/EntityUtils.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.utils; - -import com.github.steveice10.mc.protocol.data.game.entity.Effect; -import org.geysermc.connector.entity.type.EntityType; - -public class EntityUtils { - - /** - * Convert Java edition effect IDs to Bedrock edition - * - * @param effect Effect to convert - * @return The numeric ID for the Bedrock edition effect - */ - public static int toBedrockEffectId(Effect effect) { - switch (effect) { - case GLOWING: - case LUCK: - case UNLUCK: - case DOLPHINS_GRACE: - // All Java-exclusive effects as of 1.16.2 - return 0; - case LEVITATION: - return 24; - case CONDUIT_POWER: - return 26; - case SLOW_FALLING: - return 27; - case BAD_OMEN: - return 28; - case HERO_OF_THE_VILLAGE: - return 29; - default: - return effect.ordinal() + 1; - } - } - - /** - * Converts a MobType to a Bedrock edition EntityType, returns null if the EntityType is not found - * - * @param type The MobType to convert - * @return Converted EntityType - */ - public static EntityType toBedrockEntity(com.github.steveice10.mc.protocol.data.game.entity.type.EntityType type) { - try { - return EntityType.valueOf(type.name()); - } catch (IllegalArgumentException ex) { - return null; - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java deleted file mode 100644 index 0b2b132ab..000000000 --- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.utils; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import org.geysermc.connector.GeyserConnector; -import org.reflections.Reflections; -import org.reflections.serializers.XmlSerializer; -import org.reflections.util.ConfigurationBuilder; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.nio.file.Files; -import java.security.MessageDigest; -import java.util.function.Function; - -public class FileUtils { - - /** - * Load the given YAML file into the given class - * - * @param src File to load - * @param valueType Class to load file into - * @param the type - * @return The data as the given class - * @throws IOException if the config could not be loaded - */ - public static T loadConfig(File src, Class valueType) throws IOException { - ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()); - return objectMapper.readValue(src, valueType); - } - - public static T loadYaml(InputStream src, Class valueType) throws IOException { - ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()).enable(JsonParser.Feature.IGNORE_UNDEFINED).disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - return objectMapper.readValue(src, valueType); - } - - public static T loadJson(InputStream src, Class valueType) throws IOException { - return GeyserConnector.JSON_MAPPER.readValue(src, valueType); - } - - /** - * Open the specified file or copy if from resources - * - * @param name File and resource name - * @param fallback Formatting callback - * @return File handle of the specified file - * @throws IOException if the file failed to copy from resource - */ - public static File fileOrCopiedFromResource(String name, Function fallback) throws IOException { - return fileOrCopiedFromResource(new File(name), name, fallback); - } - - /** - * Open the specified file or copy if from resources - * - * @param file File to open - * @param name Name of the resource get if needed - * @param format Formatting callback - * @return File handle of the specified file - * @throws IOException if the file failed to copy from resource - */ - public static File fileOrCopiedFromResource(File file, String name, Function format) throws IOException { - if (!file.exists()) { - file.createNewFile(); - FileOutputStream fos = new FileOutputStream(file); - InputStream input = GeyserConnector.class.getResourceAsStream("/" + name); // resources need leading "/" prefix - - byte[] bytes = new byte[input.available()]; - - input.read(bytes); - - for(char c : format.apply(new String(bytes)).toCharArray()) { - fos.write(c); - } - - fos.flush(); - input.close(); - fos.close(); - } - - return file; - } - - /** - * Writes the given data to the specified file on disk - * - * @param file File to write to - * @param data Data to write to the file - * @throws IOException if the file failed to write - */ - public static void writeFile(File file, char[] data) throws IOException { - if (!file.exists()) { - file.createNewFile(); - } - - FileOutputStream fos = new FileOutputStream(file); - - for (char c : data) { - fos.write(c); - } - - fos.flush(); - fos.close(); - } - - /** - * Writes the given data to the specified file on disk - * - * @param name File path to write to - * @param data Data to write to the file - * @throws IOException if the file failed to write - */ - public static void writeFile(String name, char[] data) throws IOException { - writeFile(new File(name), data); - } - - /** - * Get an InputStream for the given resource path, throws AssertionError if resource is not found - * - * @param resource Resource to get - * @return InputStream of the given resource - */ - public static InputStream getResource(String resource) { - InputStream stream = FileUtils.class.getClassLoader().getResourceAsStream(resource); - if (stream == null) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.resource", resource)); - } - return stream; - } - - /** - * Calculate the SHA256 hash of a file - * - * @param file File to calculate the hash for - * @return A byte[] representation of the hash - */ - public static byte[] calculateSHA256(File file) { - byte[] sha256; - - try { - sha256 = MessageDigest.getInstance("SHA-256").digest(Files.readAllBytes(file.toPath())); - } catch (Exception e) { - throw new RuntimeException("Could not calculate pack hash", e); - } - - return sha256; - } - - /** - * Calculate the SHA1 hash of a file - * - * @param file File to calculate the hash for - * @return A byte[] representation of the hash - */ - public static byte[] calculateSHA1(File file) { - byte[] sha1; - - try { - sha1 = MessageDigest.getInstance("SHA-1").digest(Files.readAllBytes(file.toPath())); - } catch (Exception e) { - throw new RuntimeException("Could not calculate pack hash", e); - } - - return sha1; - } - - /** - * Get the stored reflection data for a given path - * - * @param path The path to get the reflection data for - * @return The created Reflections object - */ - public static Reflections getReflections(String path) { - Reflections reflections = new Reflections(new ConfigurationBuilder()); - XmlSerializer serializer = new XmlSerializer(); - URL resource = FileUtils.class.getClassLoader().getResource("META-INF/reflections/" + path + "-reflections.xml"); - try (InputStream inputStream = resource.openConnection().getInputStream()) { - reflections.merge(serializer.read(inputStream)); - } catch (IOException e) { } - - return reflections; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/utils/FireworkColor.java b/connector/src/main/java/org/geysermc/connector/utils/FireworkColor.java deleted file mode 100644 index f97e57e8b..000000000 --- a/connector/src/main/java/org/geysermc/connector/utils/FireworkColor.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.utils; - -import lombok.Getter; - -public enum FireworkColor { - // Vanilla colors - BLACK((byte) 0, 1973019), - RED((byte) 1, 11743532), - GREEN((byte) 2, 3887386), - BROWN((byte) 3, 5320730), - BLUE((byte) 4, 2437522), - PURPLE((byte) 5, 8073150), - CYAN((byte) 6, 2651799), - LIGHT_GRAY((byte) 7, 11250603), - GRAY((byte) 8, 4408131), - PINK((byte) 9, 14188952), - LIME((byte) 10, 4312372), - YELLOW((byte) 11, 14602026), - LIGHT_BLUE((byte) 12, 6719955), - MAGENTA((byte) 13, 12801229), - ORANGE((byte) 14, 15435844), - WHITE((byte) 15, 15790320), - - // Bukkit colors - // https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Color.html - BUKKIT_WHITE((byte) 15, 0xFFFFFF), - BUKKIT_SILVER((byte) 7, 0xC0C0C0), - BUKKIT_GRAY((byte) 8, 0x808080), - BUKKIT_BLACK((byte) 0, 0x000000), - BUKKIT_RED((byte) 1, 0xFF0000), - BUKKIT_MAROON((byte) 1, 0x800000), // No perfect map but this is as close as it can be - BUKKIT_YELLOW((byte) 11, 0xFFFF00), - BUKKIT_OLIVE((byte) 2, 0x808000), // No perfect map but this is as close as it can be - BUKKIT_LIME((byte) 10, 0x00FF00), - BUKKIT_GREEN((byte) 2, 0x008000), - BUKKIT_AQUA((byte) 12, 0x00FFFF), - BUKKIT_TEAL((byte) 6, 0x008080), - BUKKIT_BLUE((byte) 4, 0x0000FF), - BUKKIT_NAVY((byte) 4, 0x000080), // No perfect map but this is as close as it can be - BUKKIT_FUCHSIA((byte) 9, 0xFF00FF), // No perfect map but this is as close as it can be - BUKKIT_PURPLE((byte) 5, 0x800080), - BUKKIT_ORANGE((byte) 14, 0xFFA500); - - private static final FireworkColor[] VALUES = values(); - - @Getter - private byte bedrockID; - @Getter - private int javaID; - - FireworkColor(byte bedrockID, int javaID) { - this.bedrockID = bedrockID; - this.javaID = javaID; - } - - public static FireworkColor fromJavaID(int id) { - for (FireworkColor color : VALUES) { - if (color.javaID == id) { - return color; - } - } - - return WHITE; - } - - public static FireworkColor fromBedrockID(int id) { - for (FireworkColor color : VALUES) { - if (color.bedrockID == id) { - return color; - } - } - - return WHITE; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java deleted file mode 100644 index c1224e6e2..000000000 --- a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.utils; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCreativeInventoryActionPacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientMoveItemToHotbarPacket; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.nbt.NbtMapBuilder; -import com.nukkitx.nbt.NbtType; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; -import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; -import com.nukkitx.protocol.bedrock.packet.PlayerHotbarPacket; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.common.ChatColor; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.DoubleChestInventoryTranslator; -import org.geysermc.connector.network.translators.inventory.InventoryTranslator; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.item.ItemTranslator; - -import java.util.Collections; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -public class InventoryUtils { - public static final ItemStack REFRESH_ITEM = new ItemStack(1, 127, new CompoundTag("")); //TODO: stop using this - - public static void openInventory(GeyserSession session, Inventory inventory) { - InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()); - if (translator != null) { - session.getInventoryCache().setOpenInventory(inventory); - translator.prepareInventory(session, inventory); - //Ensure at least half a second passes between closing and opening a new window - //The client will not open the new window if it is still closing the old one - long delay = 700 - (System.currentTimeMillis() - session.getLastWindowCloseTime()); - //TODO: find better way to handle double chest delay - if (translator instanceof DoubleChestInventoryTranslator) { - delay = Math.max(delay, 200); - } - if (delay > 0) { - GeyserConnector.getInstance().getGeneralThreadPool().schedule(() -> { - translator.openInventory(session, inventory); - translator.updateInventory(session, inventory); - }, delay, TimeUnit.MILLISECONDS); - } else { - translator.openInventory(session, inventory); - translator.updateInventory(session, inventory); - } - } - } - - public static void closeInventory(GeyserSession session, int windowId) { - if (windowId != 0) { - Inventory inventory = session.getInventoryCache().getInventories().get(windowId); - Inventory openInventory = session.getInventoryCache().getOpenInventory(); - session.getInventoryCache().uncacheInventory(windowId); - if (inventory != null && openInventory != null && inventory.getId() == openInventory.getId()) { - InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()); - translator.closeInventory(session, inventory); - session.getInventoryCache().setOpenInventory(null); - } else { - return; - } - } else { - Inventory inventory = session.getInventory(); - inventory.setOpen(false); - InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()); - translator.updateInventory(session, inventory); - } - - session.setCraftSlot(0); - session.getInventory().setCursor(null); - updateCursor(session); - } - - public static void closeWindow(GeyserSession session, int windowId) { - //TODO: Investigate client crash when force closing window and opening a new one - //Instead, the window will eventually close by removing the fake blocks - session.setLastWindowCloseTime(System.currentTimeMillis()); - - /* - //Spamming close window packets can bug the client - if (System.currentTimeMillis() - session.getLastWindowCloseTime() > 500) { - ContainerClosePacket closePacket = new ContainerClosePacket(); - closePacket.setId((byte) windowId); - session.sendUpstreamPacket(closePacket); - session.setLastWindowCloseTime(System.currentTimeMillis()); - } - */ - } - - public static void updateCursor(GeyserSession session) { - InventorySlotPacket cursorPacket = new InventorySlotPacket(); - cursorPacket.setContainerId(ContainerId.UI); - cursorPacket.setSlot(0); - cursorPacket.setItem(ItemTranslator.translateToBedrock(session, session.getInventory().getCursor())); - session.sendUpstreamPacket(cursorPacket); - } - - public static boolean canStack(ItemStack item1, ItemStack item2) { - if (item1 == null || item2 == null) - return false; - return item1.getId() == item2.getId() && Objects.equals(item1.getNbt(), item2.getNbt()); - } - - public static boolean canStack(ItemData item1, ItemData item2) { - if (item1 == null || item2 == null) - return false; - return item1.equals(item2, false, true, true); - } - - /** - * Returns a barrier block with custom name and lore to explain why - * part of the inventory is unusable. - * - * @param description the description - * @return the unusable space block - */ - public static ItemData createUnusableSpaceBlock(String description) { - NbtMapBuilder root = NbtMap.builder(); - NbtMapBuilder display = NbtMap.builder(); - - // Not ideal to use log here but we dont get a session - display.putString("Name", ChatColor.RESET + LanguageUtils.getLocaleStringLog("geyser.inventory.unusable_item.name")); - display.putList("Lore", NbtType.STRING, Collections.singletonList(ChatColor.RESET + ChatColor.DARK_PURPLE + description)); - - root.put("display", display.build()); - return ItemData.of(ItemRegistry.ITEM_ENTRIES.get(ItemRegistry.BARRIER_INDEX).getBedrockId(), (short) 0, 1, root.build()); - } - - /** - * Attempt to find the specified item name in the session's inventory. - * If it is found and in the hotbar, set the user's held item to that slot. - * If it is found in another part of the inventory, move it. - * If it is not found and the user is in creative mode, create the item, - * overriding the current item slot if no other hotbar slots are empty, or otherwise selecting the empty slot. - * - * This attempts to mimic Java Edition behavior as best as it can. - * @param session the Bedrock client's session - * @param itemName the Java identifier of the item to search/select - */ - public static void findOrCreatePickedBlock(GeyserSession session, String itemName) { - // Get the inventory to choose a slot to pick - Inventory inventory = session.getInventoryCache().getOpenInventory(); - if (inventory == null) { - inventory = session.getInventory(); - } - - // Check hotbar for item - for (int i = 36; i < 45; i++) { - if (inventory.getItem(i) == null) { - continue; - } - ItemEntry item = ItemRegistry.getItem(inventory.getItem(i)); - // If this isn't the item we're looking for - if (!item.getJavaIdentifier().equals(itemName)) { - continue; - } - - setHotbarItem(session, i); - // Don't check inventory if item was in hotbar - return; - } - - // Check inventory for item - for (int i = 9; i < 36; i++) { - if (inventory.getItem(i) == null) { - continue; - } - ItemEntry item = ItemRegistry.getItem(inventory.getItem(i)); - // If this isn't the item we're looking for - if (!item.getJavaIdentifier().equals(itemName)) { - continue; - } - - ClientMoveItemToHotbarPacket packetToSend = new ClientMoveItemToHotbarPacket(i); // https://wiki.vg/Protocol#Pick_Item - session.sendDownstreamPacket(packetToSend); - return; - } - - // If we still have not found the item, and we're in creative, ask for the item from the server. - if (session.getGameMode() == GameMode.CREATIVE) { - int slot = session.getInventory().getHeldItemSlot() + 36; - if (session.getInventory().getItemInHand() != null) { // Otherwise we should just use the current slot - for (int i = 36; i < 45; i++) { - if (inventory.getItem(i) == null) { - slot = i; - break; - } - } - } - - ClientCreativeInventoryActionPacket actionPacket = new ClientCreativeInventoryActionPacket(slot, - new ItemStack(ItemRegistry.getItemEntry(itemName).getJavaId())); - if ((slot - 36) != session.getInventory().getHeldItemSlot()) { - setHotbarItem(session, slot); - } - session.sendDownstreamPacket(actionPacket); - } - } - - /** - * Changes the held item slot to the specified slot - * @param session GeyserSession - * @param slot inventory slot to be selected - */ - private static void setHotbarItem(GeyserSession session, int slot) { - PlayerHotbarPacket hotbarPacket = new PlayerHotbarPacket(); - hotbarPacket.setContainerId(0); - // Java inventory slot to hotbar slot ID - hotbarPacket.setSelectedHotbarSlot(slot - 36); - hotbarPacket.setSelectHotbarSlot(true); - session.sendUpstreamPacket(hotbarPacket); - // No need to send a Java packet as Bedrock sends a confirmation packet back that we translate - } -} diff --git a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java deleted file mode 100644 index 87722e5b8..000000000 --- a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.utils; - -import org.geysermc.connector.GeyserConnector; - -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.text.MessageFormat; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.Properties; - -public class LanguageUtils { - - /** - * If we determine the locale that the user wishes to use, use that locale - */ - private static String CACHED_LOCALE; - - private static final Map LOCALE_MAPPINGS = new HashMap<>(); - - static { - // Load it as a backup in case something goes really wrong - if (!"en_US".equals(formatLocale(getDefaultLocale()))) { // getDefaultLocale() loads the locale automatically - loadGeyserLocale("en_US"); - } - } - - /** - * Loads a Geyser locale from resources, if the file doesn't exist it just logs a warning - * - * @param locale Locale to load - */ - public static void loadGeyserLocale(String locale) { - locale = formatLocale(locale); - // Don't load the locale if it's already loaded. - if (LOCALE_MAPPINGS.containsKey(locale)) return; - - InputStream localeStream = GeyserConnector.class.getClassLoader().getResourceAsStream("languages/texts/" + locale + ".properties"); - - // Load the locale - if (localeStream != null) { - Properties localeProp = new Properties(); - try { - localeProp.load(new InputStreamReader(localeStream, StandardCharsets.UTF_8)); - } catch (Exception e) { - throw new AssertionError(getLocaleStringLog("geyser.language.load_failed", locale), e); - } - - // Insert the locale into the mappings - LOCALE_MAPPINGS.put(locale, localeProp); - } else { - if (GeyserConnector.getInstance() != null && GeyserConnector.getInstance().getLogger() != null) { - GeyserConnector.getInstance().getLogger().warning("Missing locale: " + locale); - } - } - } - - /** - * Get a formatted language string with the default locale for Geyser - * - * @param key Language string to translate - * @param values Values to put into the string - * @return Translated string or the original message if it was not found in the given locale - */ - public static String getLocaleStringLog(String key, Object... values) { - return getPlayerLocaleString(key, getDefaultLocale(), values); - } - - /** - * Get a formatted language string with the given locale for Geyser - * - * @param key Language string to translate - * @param locale Locale to translate to - * @param values Values to put into the string - * @return Translated string or the original message if it was not found in the given locale - */ - public static String getPlayerLocaleString(String key, String locale, Object... values) { - locale = formatLocale(locale); - - Properties properties = LOCALE_MAPPINGS.get(locale); - String formatString = null; - - if (properties != null) { - formatString = properties.getProperty(key); - } - - // Try and get the key from the default locale - if (formatString == null) { - properties = LOCALE_MAPPINGS.get(formatLocale(getDefaultLocale())); - formatString = properties.getProperty(key); - } - - // Try and get the key from en_US (this should only ever happen in development) - if (formatString == null) { - properties = LOCALE_MAPPINGS.get("en_US"); - formatString = properties.getProperty(key); - } - - // Final fallback - if (formatString == null) { - formatString = key; - } - - return MessageFormat.format(formatString.replace("'", "''").replace("&", "\u00a7"), values); - } - - /** - * Cleans up and formats a locale string - * - * @param locale The locale to format - * @return The formatted locale - */ - public static String formatLocale(String locale) { - try { - String[] parts = locale.toLowerCase().split("_"); - return parts[0] + "_" + parts[1].toUpperCase(); - } catch (Exception e) { - return locale; - } - } - - /** - * Get the default locale that Geyser should use - * @return the current default locale - */ - public static String getDefaultLocale() { - if (CACHED_LOCALE != null) return CACHED_LOCALE; // We definitely know the locale the user is using - String locale; - boolean isValid = true; - if (GeyserConnector.getInstance() != null && - GeyserConnector.getInstance().getConfig() != null && - GeyserConnector.getInstance().getConfig().getDefaultLocale() != null) { // If the config option for getDefaultLocale does not equal null, use that - locale = formatLocale(GeyserConnector.getInstance().getConfig().getDefaultLocale()); - if (isValidLanguage(locale)) { - CACHED_LOCALE = locale; - return locale; - } else { - isValid = false; - } - } - locale = formatLocale(Locale.getDefault().getLanguage() + "_" + Locale.getDefault().getCountry()); - if (!isValidLanguage(locale)) { // Bedrock does not support this language - locale = "en_US"; - loadGeyserLocale(locale); - } - if (GeyserConnector.getInstance() != null && - GeyserConnector.getInstance().getConfig() != null && (GeyserConnector.getInstance().getConfig().getDefaultLocale() == null || !isValid)) { // Means we should use the system locale for sure - CACHED_LOCALE = locale; - } - return locale; - } - - /** - * Ensures that the given locale is supported by Bedrock - * @param locale the locale to validate - * @return true if the given locale is supported by Bedrock and by extension Geyser - */ - private static boolean isValidLanguage(String locale) { - boolean result = true; - if (FileUtils.class.getResource("/languages/texts/" + locale + ".properties") == null) { - result = false; - if (GeyserConnector.getInstance() != null && GeyserConnector.getInstance().getLogger() != null) { // Could be too early for these to be initialized - GeyserConnector.getInstance().getLogger().warning(locale + " is not a valid Bedrock language."); // We can't translate this since we just loaded an invalid language - } - } else { - if (!LOCALE_MAPPINGS.containsKey(locale)) { - loadGeyserLocale(locale); - } - } - return result; - } - - public static void init() { - // no-op - } -} diff --git a/connector/src/main/java/org/geysermc/connector/utils/LoadstoneTracker.java b/connector/src/main/java/org/geysermc/connector/utils/LoadstoneTracker.java deleted file mode 100644 index a0e3718b5..000000000 --- a/connector/src/main/java/org/geysermc/connector/utils/LoadstoneTracker.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.utils; - -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import lombok.AllArgsConstructor; -import lombok.EqualsAndHashCode; -import lombok.Getter; - -public class LoadstoneTracker { - - private static final Int2ObjectMap LOADSTONES = new Int2ObjectOpenHashMap<>(); - - /** - * Store the given coordinates and dimensions - * - * @param x The X position of the Loadstone - * @param y The Y position of the Loadstone - * @param z The Z position of the Loadstone - * @param dim The dimension containing of the Loadstone - * @return The id in the Map - */ - public static int store(int x, int y, int z, String dim) { - LoadstonePos pos = new LoadstonePos(x, y, z, dim); - - if (!LOADSTONES.containsValue(pos)) { - // Start at 1 as 0 seems to not work - LOADSTONES.put(LOADSTONES.size() + 1, pos); - } - - for (Int2ObjectMap.Entry loadstone : LOADSTONES.int2ObjectEntrySet()) { - if (loadstone.getValue().equals(pos)) { - return loadstone.getIntKey(); - } - } - - return 0; - } - - /** - * Get the loadstone data - * - * @param id The ID to get the data for - * @return The stored data - */ - public static LoadstonePos getPos(int id) { - return LOADSTONES.get(id); - } - - @Getter - @AllArgsConstructor - @EqualsAndHashCode - public static class LoadstonePos { - int x; - int y; - int z; - String dimension; - } -} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java deleted file mode 100644 index 4e9e4b003..000000000 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ /dev/null @@ -1,400 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.utils; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.JsonNode; -import com.github.steveice10.mc.protocol.MinecraftConstants; -import lombok.Getter; -import org.geysermc.connector.GeyserConnector; - -import java.io.*; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.zip.ZipFile; - -public class LocaleUtils { - - public static final Map> LOCALE_MAPPINGS = new HashMap<>(); - - private static final Map ASSET_MAP = new HashMap<>(); - - private static VersionDownload clientJarInfo; - - static { - // Create the locales folder - File localesFolder = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("locales").toFile(); - //noinspection ResultOfMethodCallIgnored - localesFolder.mkdir(); - - // Download the latest asset list and cache it - generateAssetCache(); - downloadAndLoadLocale(LanguageUtils.getDefaultLocale()); - } - - /** - * Fetch the latest versions asset cache from Mojang so we can grab the locale files later - */ - private static void generateAssetCache() { - try { - // Get the version manifest from Mojang - VersionManifest versionManifest = GeyserConnector.JSON_MAPPER.readValue(WebUtils.getBody("https://launchermeta.mojang.com/mc/game/version_manifest.json"), VersionManifest.class); - - // Get the url for the latest version of the games manifest - String latestInfoURL = ""; - for (Version version : versionManifest.getVersions()) { - if (version.getId().equals(MinecraftConstants.GAME_VERSION)) { - latestInfoURL = version.getUrl(); - break; - } - } - - // Make sure we definitely got a version - if (latestInfoURL.isEmpty()) { - throw new Exception(LanguageUtils.getLocaleStringLog("geyser.locale.fail.latest_version")); - } - - // Get the individual version manifest - VersionInfo versionInfo = GeyserConnector.JSON_MAPPER.readValue(WebUtils.getBody(latestInfoURL), VersionInfo.class); - - // Get the client jar for use when downloading the en_us locale - GeyserConnector.getInstance().getLogger().debug(GeyserConnector.JSON_MAPPER.writeValueAsString(versionInfo.getDownloads())); - clientJarInfo = versionInfo.getDownloads().get("client"); - GeyserConnector.getInstance().getLogger().debug(GeyserConnector.JSON_MAPPER.writeValueAsString(clientJarInfo)); - - // Get the assets list - JsonNode assets = GeyserConnector.JSON_MAPPER.readTree(WebUtils.getBody(versionInfo.getAssetIndex().getUrl())).get("objects"); - - // Put each asset into an array for use later - Iterator> assetIterator = assets.fields(); - while (assetIterator.hasNext()) { - Map.Entry entry = assetIterator.next(); - Asset asset = GeyserConnector.JSON_MAPPER.treeToValue(entry.getValue(), Asset.class); - ASSET_MAP.put(entry.getKey(), asset); - } - } catch (Exception e) { - GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.locale.fail.asset_cache", (!e.getMessage().isEmpty() ? e.getMessage() : e.getStackTrace()))); - } - } - - /** - * Downloads a locale from Mojang if its not already loaded - * - * @param locale Locale to download and load - */ - public static void downloadAndLoadLocale(String locale) { - locale = locale.toLowerCase(); - - // Check the locale isn't already loaded - if (!ASSET_MAP.containsKey("minecraft/lang/" + locale + ".json") && !locale.equals("en_us")) { - GeyserConnector.getInstance().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.locale.fail.invalid", locale)); - return; - } - - GeyserConnector.getInstance().getLogger().debug("Downloading and loading locale: " + locale); - - downloadLocale(locale); - loadLocale(locale); - } - - /** - * Downloads the specified locale if its not already downloaded - * - * @param locale Locale to download - */ - private static void downloadLocale(String locale) { - File localeFile = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("locales/" + locale + ".json").toFile(); - - // Check if we have already downloaded the locale file - if (localeFile.exists()) { - String curHash = ""; - String targetHash = ""; - - if (locale.equals("en_us")) { - try { - Path hashFile = localeFile.getParentFile().toPath().resolve("en_us.hash"); - if (hashFile.toFile().exists()) { - curHash = String.join("", Files.readAllLines(hashFile)); - } - } catch (IOException ignored) { } - targetHash = clientJarInfo.getSha1(); - } else { - curHash = byteArrayToHexString(FileUtils.calculateSHA1(localeFile)); - targetHash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash(); - } - - if (!curHash.equals(targetHash)) { - GeyserConnector.getInstance().getLogger().debug("Locale out of date; re-downloading: " + locale); - } else { - GeyserConnector.getInstance().getLogger().debug("Locale already downloaded and up-to date: " + locale); - return; - } - } - - // Create the en_us locale - if (locale.equals("en_us")) { - downloadEN_US(localeFile); - - return; - } - - // Get the hash and download the locale - String hash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash(); - WebUtils.downloadFile("https://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, localeFile.toString()); - } - - /** - * Loads a locale already downloaded, if the file doesn't exist it just logs a warning - * - * @param locale Locale to load - */ - private static void loadLocale(String locale) { - File localeFile = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("locales/" + locale + ".json").toFile(); - - // Load the locale - if (localeFile.exists()) { - // Read the localefile - InputStream localeStream; - try { - localeStream = new FileInputStream(localeFile); - } catch (FileNotFoundException e) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.locale.fail.file", locale, e.getMessage())); - } - - // Parse the file as json - JsonNode localeObj; - try { - localeObj = GeyserConnector.JSON_MAPPER.readTree(localeStream); - } catch (Exception e) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.locale.fail.json", locale), e); - } - - // Parse all the locale fields - Iterator> localeIterator = localeObj.fields(); - Map langMap = new HashMap<>(); - while (localeIterator.hasNext()) { - Map.Entry entry = localeIterator.next(); - langMap.put(entry.getKey(), entry.getValue().asText()); - } - - // Insert the locale into the mappings - LOCALE_MAPPINGS.put(locale.toLowerCase(), langMap); - } else { - GeyserConnector.getInstance().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.locale.fail.missing", locale)); - } - } - - /** - * Download then en_us locale by downloading the server jar and extracting it from there. - * - * @param localeFile File to save the locale to - */ - private static void downloadEN_US(File localeFile) { - try { - // Let the user know we are downloading the JAR - GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.locale.download.en_us")); - GeyserConnector.getInstance().getLogger().debug("Download URL: " + clientJarInfo.getUrl()); - - // Download the smallest JAR (client or server) - Path tmpFilePath = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("tmp_locale.jar"); - WebUtils.downloadFile(clientJarInfo.getUrl(), tmpFilePath.toString()); - - // Load in the JAR as a zip and extract the file - ZipFile localeJar = new ZipFile(tmpFilePath.toString()); - InputStream fileStream = localeJar.getInputStream(localeJar.getEntry("assets/minecraft/lang/en_us.json")); - FileOutputStream outStream = new FileOutputStream(localeFile); - - // Write the file to the locale dir - byte[] buf = new byte[fileStream.available()]; - int length; - while ((length = fileStream.read(buf)) != -1) { - outStream.write(buf, 0, length); - } - - // Flush all changes to disk and cleanup - outStream.flush(); - outStream.close(); - - fileStream.close(); - localeJar.close(); - - // Store the latest jar hash - FileUtils.writeFile(localeFile.getParentFile().toPath().resolve("en_us.hash").toString(), clientJarInfo.getSha1().toCharArray()); - - // Delete the nolonger needed client/server jar - Files.delete(tmpFilePath); - } catch (Exception e) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.locale.fail.en_us"), e); - } - } - - /** - * Translate the given language string into the given locale, or falls back to the default locale - * - * @param messageText Language string to translate - * @param locale Locale to translate to - * @return Translated string or the original message if it was not found in the given locale - */ - public static String getLocaleString(String messageText, String locale) { - Map localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(locale.toLowerCase()); - if (localeStrings == null) { - localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(LanguageUtils.getDefaultLocale()); - if (localeStrings == null) { - // Don't cause a NPE if the locale is STILL missing - GeyserConnector.getInstance().getLogger().debug("MISSING DEFAULT LOCALE: " + LanguageUtils.getDefaultLocale()); - return messageText; - } - } - - return localeStrings.getOrDefault(messageText, messageText); - } - - /** - * Convert a byte array into a hex string - * - * @param b Byte array to convert - * @return The hex representation of the given byte array - */ - private static String byteArrayToHexString(byte[] b) { - StringBuilder result = new StringBuilder(); - for (byte value : b) { - result.append(Integer.toString((value & 0xff) + 0x100, 16).substring(1)); - } - return result.toString(); - } - - public static void init() { - // no-op - } -} - -@JsonIgnoreProperties(ignoreUnknown = true) -@Getter -class VersionManifest { - @JsonProperty("latest") - private LatestVersion latestVersion; - - @JsonProperty("versions") - private List versions; -} - -@JsonIgnoreProperties(ignoreUnknown = true) -@Getter -class LatestVersion { - @JsonProperty("release") - private String release; - - @JsonProperty("snapshot") - private String snapshot; -} - -@JsonIgnoreProperties(ignoreUnknown = true) -@Getter -class Version { - @JsonProperty("id") - private String id; - - @JsonProperty("type") - private String type; - - @JsonProperty("url") - private String url; - - @JsonProperty("time") - private String time; - - @JsonProperty("releaseTime") - private String releaseTime; -} - -@JsonIgnoreProperties(ignoreUnknown = true) -@Getter -class VersionInfo { - @JsonProperty("id") - private String id; - - @JsonProperty("type") - private String type; - - @JsonProperty("time") - private String time; - - @JsonProperty("releaseTime") - private String releaseTime; - - @JsonProperty("assetIndex") - private AssetIndex assetIndex; - - @JsonProperty("downloads") - private Map downloads; -} - -@JsonIgnoreProperties(ignoreUnknown = true) -@Getter -class VersionDownload { - @JsonProperty("sha1") - private String sha1; - - @JsonProperty("size") - private int size; - - @JsonProperty("url") - private String url; -} - -@JsonIgnoreProperties(ignoreUnknown = true) -@Getter -class AssetIndex { - @JsonProperty("id") - private String id; - - @JsonProperty("sha1") - private String sha1; - - @JsonProperty("size") - private int size; - - @JsonProperty("totalSize") - private int totalSize; - - @JsonProperty("url") - private String url; -} - -@JsonIgnoreProperties(ignoreUnknown = true) -@Getter -class Asset { - @JsonProperty("hash") - private String hash; - - @JsonProperty("size") - private int size; -} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java deleted file mode 100644 index fe63bc917..000000000 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.utils; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.JsonNodeType; -import com.nimbusds.jose.JWSObject; -import com.nukkitx.network.util.Preconditions; -import com.nukkitx.protocol.bedrock.packet.LoginPacket; -import com.nukkitx.protocol.bedrock.packet.ServerToClientHandshakePacket; -import com.nukkitx.protocol.bedrock.util.EncryptionUtils; -import org.geysermc.common.window.CustomFormBuilder; -import org.geysermc.common.window.CustomFormWindow; -import org.geysermc.common.window.FormWindow; -import org.geysermc.common.window.SimpleFormWindow; -import org.geysermc.common.window.button.FormButton; -import org.geysermc.common.window.component.InputComponent; -import org.geysermc.common.window.component.LabelComponent; -import org.geysermc.common.window.response.CustomFormResponse; -import org.geysermc.common.window.response.SimpleFormResponse; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.session.auth.AuthData; -import org.geysermc.connector.network.session.auth.BedrockClientData; -import org.geysermc.connector.network.session.cache.WindowCache; - -import javax.crypto.SecretKey; -import java.io.IOException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.PublicKey; -import java.security.interfaces.ECPublicKey; -import java.security.spec.ECGenParameterSpec; -import java.util.UUID; - -public class LoginEncryptionUtils { - private static final ObjectMapper JSON_MAPPER = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - - private static boolean validateChainData(JsonNode data) throws Exception { - ECPublicKey lastKey = null; - boolean validChain = false; - for (JsonNode node : data) { - JWSObject jwt = JWSObject.parse(node.asText()); - - if (!validChain) { - validChain = EncryptionUtils.verifyJwt(jwt, EncryptionUtils.getMojangPublicKey()); - } - - if (lastKey != null) { - if (!EncryptionUtils.verifyJwt(jwt, lastKey)) return false; - } - - JsonNode payloadNode = JSON_MAPPER.readTree(jwt.getPayload().toString()); - JsonNode ipkNode = payloadNode.get("identityPublicKey"); - Preconditions.checkState(ipkNode != null && ipkNode.getNodeType() == JsonNodeType.STRING, "identityPublicKey node is missing in chain"); - lastKey = EncryptionUtils.generateKey(ipkNode.asText()); - } - return validChain; - } - - public static void encryptPlayerConnection(GeyserConnector connector, GeyserSession session, LoginPacket loginPacket) { - JsonNode certData; - try { - certData = JSON_MAPPER.readTree(loginPacket.getChainData().toByteArray()); - } catch (IOException ex) { - throw new RuntimeException("Certificate JSON can not be read."); - } - - JsonNode certChainData = certData.get("chain"); - if (certChainData.getNodeType() != JsonNodeType.ARRAY) { - throw new RuntimeException("Certificate data is not valid"); - } - - encryptConnectionWithCert(connector, session, loginPacket.getSkinData().toString(), certChainData); - } - - private static void encryptConnectionWithCert(GeyserConnector connector, GeyserSession session, String clientData, JsonNode certChainData) { - try { - boolean validChain = validateChainData(certChainData); - - connector.getLogger().debug(String.format("Is player data valid? %s", validChain)); - - if (!validChain && !session.getConnector().getConfig().isEnableProxyConnections()) { - session.disconnect(LanguageUtils.getLocaleStringLog("geyser.network.remote.invalid_xbox_account")); - return; - } - JWSObject jwt = JWSObject.parse(certChainData.get(certChainData.size() - 1).asText()); - JsonNode payload = JSON_MAPPER.readTree(jwt.getPayload().toBytes()); - - if (payload.get("extraData").getNodeType() != JsonNodeType.OBJECT) { - throw new RuntimeException("AuthData was not found!"); - } - - JsonNode extraData = payload.get("extraData"); - session.setAuthenticationData(new AuthData( - extraData.get("displayName").asText(), - UUID.fromString(extraData.get("identity").asText()), - extraData.get("XUID").asText() - )); - - if (payload.get("identityPublicKey").getNodeType() != JsonNodeType.STRING) { - throw new RuntimeException("Identity Public Key was not found!"); - } - - ECPublicKey identityPublicKey = EncryptionUtils.generateKey(payload.get("identityPublicKey").textValue()); - JWSObject clientJwt = JWSObject.parse(clientData); - EncryptionUtils.verifyJwt(clientJwt, identityPublicKey); - - session.setClientData(JSON_MAPPER.convertValue(JSON_MAPPER.readTree(clientJwt.getPayload().toBytes()), BedrockClientData.class)); - - if (EncryptionUtils.canUseEncryption()) { - LoginEncryptionUtils.startEncryptionHandshake(session, identityPublicKey); - } - } catch (Exception ex) { - session.disconnect("disconnectionScreen.internalError.cantConnect"); - throw new RuntimeException("Unable to complete login", ex); - } - } - - private static void startEncryptionHandshake(GeyserSession session, PublicKey key) throws Exception { - KeyPairGenerator generator = KeyPairGenerator.getInstance("EC"); - generator.initialize(new ECGenParameterSpec("secp384r1")); - KeyPair serverKeyPair = generator.generateKeyPair(); - - byte[] token = EncryptionUtils.generateRandomToken(); - SecretKey encryptionKey = EncryptionUtils.getSecretKey(serverKeyPair.getPrivate(), key, token); - session.getUpstream().getSession().enableEncryption(encryptionKey); - - ServerToClientHandshakePacket packet = new ServerToClientHandshakePacket(); - packet.setJwt(EncryptionUtils.createHandshakeJwt(serverKeyPair, token).serialize()); - session.sendUpstreamPacketImmediately(packet); - } - - private static int AUTH_FORM_ID = 1336; - private static int AUTH_DETAILS_FORM_ID = 1337; - - public static void showLoginWindow(GeyserSession session) { - String userLanguage = session.getLocale(); - SimpleFormWindow window = new SimpleFormWindow(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.title", userLanguage), LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.desc", userLanguage)); - window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login", userLanguage))); - window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_disconnect", userLanguage))); - - session.sendForm(window, AUTH_FORM_ID); - } - - public static void showLoginDetailsWindow(GeyserSession session) { - String userLanguage = session.getLocale(); - CustomFormWindow window = new CustomFormBuilder(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.title", userLanguage)) - .addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.desc", userLanguage))) - .addComponent(new InputComponent(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.email", userLanguage), "account@geysermc.org", "")) - .addComponent(new InputComponent(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.pass", userLanguage), "123456", "")) - .build(); - - session.sendForm(window, AUTH_DETAILS_FORM_ID); - } - - public static boolean authenticateFromForm(GeyserSession session, GeyserConnector connector, int formId, String formData) { - WindowCache windowCache = session.getWindowCache(); - if (!windowCache.getWindows().containsKey(formId)) - return false; - - if(formId == AUTH_FORM_ID || formId == AUTH_DETAILS_FORM_ID) { - FormWindow window = windowCache.getWindows().remove(formId); - window.setResponse(formData.trim()); - - if (!session.isLoggedIn()) { - if (formId == AUTH_DETAILS_FORM_ID && window instanceof CustomFormWindow) { - CustomFormWindow customFormWindow = (CustomFormWindow) window; - - CustomFormResponse response = (CustomFormResponse) customFormWindow.getResponse(); - if (response != null) { - String email = response.getInputResponses().get(1); - String password = response.getInputResponses().get(2); - - session.authenticate(email, password); - } else { - showLoginDetailsWindow(session); - } - - // Clear windows so authentication data isn't accidentally cached - windowCache.getWindows().clear(); - } else if (formId == AUTH_FORM_ID && window instanceof SimpleFormWindow) { - SimpleFormResponse response = (SimpleFormResponse) window.getResponse(); - if (response != null) { - if (response.getClickedButtonId() == 0) { - showLoginDetailsWindow(session); - } else if(response.getClickedButtonId() == 1) { - session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale())); - } - } else { - showLoginWindow(session); - } - } - } - } - return true; - } - -} diff --git a/connector/src/main/java/org/geysermc/connector/utils/MapColor.java b/connector/src/main/java/org/geysermc/connector/utils/MapColor.java deleted file mode 100644 index f70414cab..000000000 --- a/connector/src/main/java/org/geysermc/connector/utils/MapColor.java +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.utils; - -public enum MapColor { - COLOR_0(-1, -1, -1), - COLOR_1(-1, -1, -1), - COLOR_2(-1, -1, -1), - COLOR_3(-1, -1, -1), - COLOR_4(90, 126, 40), - COLOR_5(110, 154, 48), - COLOR_6(127, 178, 56), - COLOR_7(67, 94, 30), - COLOR_8(174, 164, 115), - COLOR_9(213, 201, 141), - COLOR_10(247, 233, 163), - COLOR_11(131, 123, 86), - COLOR_12(140, 140, 140), - COLOR_13(172, 172, 172), - COLOR_14(199, 199, 199), - COLOR_15(105, 105, 105), - COLOR_16(180, 0, 0), - COLOR_17(220, 0, 0), - COLOR_18(255, 0, 0), - COLOR_19(135, 0, 0), - COLOR_20(113, 113, 180), - COLOR_21(138, 138, 220), - COLOR_22(160, 160, 255), - COLOR_23(85, 85, 135), - COLOR_24(118, 118, 118), - COLOR_25(144, 144, 144), - COLOR_26(167, 167, 167), - COLOR_27(88, 88, 88), - COLOR_28(0, 88, 0), - COLOR_29(0, 107, 0), - COLOR_30(0, 124, 0), - COLOR_31(0, 66, 0), - COLOR_32(180, 180, 180), - COLOR_33(220, 220, 220), - COLOR_34(255, 255, 255), - COLOR_35(135, 135, 135), - COLOR_36(116, 119, 130), - COLOR_37(141, 145, 159), - COLOR_38(164, 168, 184), - COLOR_39(87, 89, 97), - COLOR_40(107, 77, 54), - COLOR_41(130, 94, 66), - COLOR_42(151, 109, 77), - COLOR_43(80, 58, 41), - COLOR_44(79, 79, 79), - COLOR_45(97, 97, 97), - COLOR_46(112, 112, 112), - COLOR_47(59, 59, 59), - COLOR_48(45, 45, 180), - COLOR_49(55, 55, 220), - COLOR_50(64, 64, 255), - COLOR_51(34, 34, 135), - COLOR_52(101, 84, 51), - COLOR_53(123, 103, 62), - COLOR_54(143, 119, 72), - COLOR_55(76, 63, 38), - COLOR_56(180, 178, 173), - COLOR_57(220, 217, 211), - COLOR_58(255, 252, 245), - COLOR_59(135, 133, 130), - COLOR_60(152, 90, 36), - COLOR_61(186, 110, 44), - COLOR_62(216, 127, 51), - COLOR_63(114, 67, 27), - COLOR_64(126, 54, 152), - COLOR_65(154, 66, 186), - COLOR_66(178, 76, 216), - COLOR_67(94, 40, 114), - COLOR_68(72, 108, 152), - COLOR_69(88, 132, 186), - COLOR_70(102, 153, 216), - COLOR_71(54, 81, 114), - COLOR_72(162, 162, 36), - COLOR_73(198, 198, 44), - COLOR_74(229, 229, 51), - COLOR_75(121, 121, 27), - COLOR_76(90, 144, 18), - COLOR_77(110, 176, 22), - COLOR_78(127, 204, 25), - COLOR_79(67, 108, 13), - COLOR_80(171, 90, 116), - COLOR_81(209, 110, 142), - COLOR_82(242, 127, 165), - COLOR_83(128, 67, 87), - COLOR_84(54, 54, 54), - COLOR_85(66, 66, 66), - COLOR_86(76, 76, 76), - COLOR_87(40, 40, 40), - COLOR_88(108, 108, 108), - COLOR_89(132, 132, 132), - COLOR_90(153, 153, 153), - COLOR_91(81, 81, 81), - COLOR_92(54, 90, 108), - COLOR_93(66, 110, 132), - COLOR_94(76, 127, 153), - COLOR_95(40, 67, 81), - COLOR_96(90, 44, 126), - COLOR_97(110, 54, 154), - COLOR_98(127, 63, 178), - COLOR_99(67, 33, 94), - COLOR_100(36, 54, 126), - COLOR_101(44, 66, 154), - COLOR_102(51, 76, 178), - COLOR_103(27, 40, 94), - COLOR_104(72, 54, 36), - COLOR_105(88, 66, 44), - COLOR_106(102, 76, 51), - COLOR_107(54, 40, 27), - COLOR_108(72, 90, 36), - COLOR_109(88, 110, 44), - COLOR_110(102, 127, 51), - COLOR_111(54, 67, 27), - COLOR_112(108, 36, 36), - COLOR_113(132, 44, 44), - COLOR_114(153, 51, 51), - COLOR_115(81, 27, 27), - COLOR_116(18, 18, 18), - COLOR_117(22, 22, 22), - COLOR_118(25, 25, 25), - COLOR_119(13, 13, 13), - COLOR_120(176, 168, 54), - COLOR_121(216, 205, 66), - COLOR_122(250, 238, 77), - COLOR_123(132, 126, 41), - COLOR_124(65, 155, 150), - COLOR_125(79, 189, 184), - COLOR_126(92, 219, 213), - COLOR_127(49, 116, 113), - COLOR_128(52, 90, 180), - COLOR_129(64, 110, 220), - COLOR_130(74, 128, 255), - COLOR_131(39, 68, 135), - COLOR_132(0, 153, 41), - COLOR_133(0, 187, 50), - COLOR_134(0, 217, 58), - COLOR_135(0, 115, 31), - COLOR_136(91, 61, 35), - COLOR_137(111, 74, 42), - COLOR_138(129, 86, 49), - COLOR_139(68, 46, 26), - COLOR_140(79, 1, 0), - COLOR_141(97, 2, 0), - COLOR_142(112, 2, 0), - COLOR_143(59, 1, 0), - COLOR_144(148, 125, 114), - COLOR_145(180, 153, 139), - COLOR_146(209, 177, 161), - COLOR_147(111, 94, 85), - COLOR_148(112, 58, 25), - COLOR_149(137, 71, 31), - COLOR_150(159, 82, 36), - COLOR_151(84, 43, 19), - COLOR_152(105, 61, 76), - COLOR_153(129, 75, 93), - COLOR_154(149, 87, 108), - COLOR_155(79, 46, 57), - COLOR_156(79, 76, 97), - COLOR_157(97, 93, 119), - COLOR_158(112, 108, 138), - COLOR_159(59, 57, 73), - COLOR_160(131, 94, 25), - COLOR_161(160, 115, 31), - COLOR_162(186, 133, 36), - COLOR_163(98, 70, 19), - COLOR_164(73, 83, 37), - COLOR_165(89, 101, 46), - COLOR_166(103, 117, 53), - COLOR_167(55, 62, 28), - COLOR_168(113, 54, 55), - COLOR_169(138, 66, 67), - COLOR_170(160, 77, 78), - COLOR_171(85, 41, 41), - COLOR_172(40, 29, 25), - COLOR_173(49, 35, 30), - COLOR_174(57, 41, 35), - COLOR_175(30, 22, 19), - COLOR_176(95, 76, 69), - COLOR_177(116, 92, 85), - COLOR_178(135, 107, 98), - COLOR_179(71, 57, 52), - COLOR_180(61, 65, 65), - COLOR_181(75, 79, 79), - COLOR_182(87, 92, 92), - COLOR_183(46, 49, 49), - COLOR_184(86, 52, 62), - COLOR_185(105, 63, 76), - COLOR_186(122, 73, 88), - COLOR_187(65, 39, 47), - COLOR_188(54, 44, 65), - COLOR_189(66, 53, 79), - COLOR_190(76, 62, 92), - COLOR_191(40, 33, 49), - COLOR_192(54, 35, 25), - COLOR_193(66, 43, 30), - COLOR_194(76, 50, 35), - COLOR_195(40, 26, 19), - COLOR_196(54, 58, 30), - COLOR_197(66, 71, 36), - COLOR_198(76, 82, 42), - COLOR_199(40, 43, 22), - COLOR_200(100, 42, 32), - COLOR_201(123, 52, 40), - COLOR_202(142, 60, 46), - COLOR_203(75, 32, 24), - COLOR_204(26, 16, 11), - COLOR_205(32, 19, 14), - COLOR_206(37, 22, 16), - COLOR_207(20, 12, 8), - COLOR_208(133, 34, 35), - COLOR_209(163, 41, 42), - COLOR_210(189, 48, 49), - COLOR_211(100, 25, 26), - COLOR_212(104, 44, 68), - COLOR_213(128, 54, 84), - COLOR_214(148, 63, 97), - COLOR_215(78, 33, 51), - COLOR_216(65, 18, 20), - COLOR_217(79, 22, 25), - COLOR_218(92, 25, 29), - COLOR_219(49, 13, 15), - COLOR_220(16, 89, 95), - COLOR_221(19, 109, 116), - COLOR_222(22, 126, 134), - COLOR_223(12, 67, 71), - COLOR_224(41, 100, 99), - COLOR_225(50, 123, 121), - COLOR_226(58, 142, 140), - COLOR_227(31, 75, 74), - COLOR_228(61, 31, 44), - COLOR_229(74, 38, 53), - COLOR_230(86, 44, 62), - COLOR_231(46, 23, 33), - COLOR_232(14, 127, 94), - COLOR_233(17, 155, 115), - COLOR_234(20, 180, 133), - COLOR_235(11, 95, 70); - - private static final MapColor[] VALUES = values(); - - private final int red; - private final int green; - private final int blue; - - MapColor(int red, int green, int blue) { - this.red = red; - this.green = green; - this.blue = blue; - } - - public static MapColor fromId(int id) { - return id >= 0 && id < VALUES.length ? VALUES[id] : COLOR_0; - } - - public int toABGR() { - int alpha = 255; - if (red == -1 && green == -1 && blue == -1) - alpha = 0; // transparent - - return ((alpha & 0xFF) << 24) | - ((blue & 0xFF) << 16) | - ((green & 0xFF) << 8) | - ((red & 0xFF) << 0); - } -} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java deleted file mode 100644 index eaf71058c..000000000 --- a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.utils; - -import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; -import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; -import org.geysermc.common.window.CustomFormBuilder; -import org.geysermc.common.window.CustomFormWindow; -import org.geysermc.common.window.button.FormImage; -import org.geysermc.common.window.component.DropdownComponent; -import org.geysermc.common.window.component.InputComponent; -import org.geysermc.common.window.component.LabelComponent; -import org.geysermc.common.window.component.ToggleComponent; -import org.geysermc.common.window.response.CustomFormResponse; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.network.session.GeyserSession; - -import java.util.ArrayList; - -public class SettingsUtils { - - // Used in UpstreamPacketHandler.java - public static final int SETTINGS_FORM_ID = 1338; - - /** - * Build a settings form for the given session and store it for later - * - * @param session The session to build the form for - */ - public static void buildForm(GeyserSession session) { - // Cache the language for cleaner access - String language = session.getLocale(); - - CustomFormBuilder builder = new CustomFormBuilder(LanguageUtils.getPlayerLocaleString("geyser.settings.title.main", language)); - builder.setIcon(new FormImage(FormImage.FormImageType.PATH, "textures/ui/settings_glyph_color_2x.png")); - - builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.client", language))); - builder.addComponent(new ToggleComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.option.coordinates", language), session.getWorldCache().isShowCoordinates())); - - - if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) { - builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.server", language))); - - DropdownComponent gamemodeDropdown = new DropdownComponent(); - gamemodeDropdown.setText("%createWorldScreen.gameMode.personal"); - gamemodeDropdown.setOptions(new ArrayList<>()); - for (GameMode gamemode : GameMode.values()) { - gamemodeDropdown.addOption(LocaleUtils.getLocaleString("selectWorld.gameMode." + gamemode.name().toLowerCase(), language), session.getGameMode() == gamemode); - } - builder.addComponent(gamemodeDropdown); - - DropdownComponent difficultyDropdown = new DropdownComponent(); - difficultyDropdown.setText("%options.difficulty"); - difficultyDropdown.setOptions(new ArrayList<>()); - for (Difficulty difficulty : Difficulty.values()) { - difficultyDropdown.addOption("%options.difficulty." + difficulty.name().toLowerCase(), session.getWorldCache().getDifficulty() == difficulty); - } - builder.addComponent(difficultyDropdown); - } - - if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules")) { - builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.game_rules", language))); - for (GameRule gamerule : GameRule.values()) { - if (gamerule.equals(GameRule.UNKNOWN)) { - continue; - } - - // Add the relevant form item based on the gamerule type - if (Boolean.class.equals(gamerule.getType())) { - builder.addComponent(new ToggleComponent(LocaleUtils.getLocaleString("gamerule." + gamerule.getJavaID(), language), GeyserConnector.getInstance().getWorldManager().getGameRuleBool(session, gamerule))); - } else if (Integer.class.equals(gamerule.getType())) { - builder.addComponent(new InputComponent(LocaleUtils.getLocaleString("gamerule." + gamerule.getJavaID(), language), "", String.valueOf(GeyserConnector.getInstance().getWorldManager().getGameRuleInt(session, gamerule)))); - } - } - } - - session.setSettingsForm(builder.build()); - } - - /** - * Handle the settings form response - * - * @param session The session that sent the response - * @param response The response string to parse - * @return True if the form was parsed correctly, false if not - */ - public static boolean handleSettingsForm(GeyserSession session, String response) { - CustomFormWindow settingsForm = session.getSettingsForm(); - settingsForm.setResponse(response); - - CustomFormResponse settingsResponse = (CustomFormResponse) settingsForm.getResponse(); - int offset = 0; - - offset++; // Client settings title - - session.getWorldCache().setShowCoordinates(settingsResponse.getToggleResponses().get(offset)); - offset++; - - if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) { - offset++; // Server settings title - - GameMode gameMode = GameMode.values()[settingsResponse.getDropdownResponses().get(offset).getElementID()]; - if (gameMode != null && gameMode != session.getGameMode()) { - session.getConnector().getWorldManager().setPlayerGameMode(session, gameMode); - } - offset++; - - Difficulty difficulty = Difficulty.values()[settingsResponse.getDropdownResponses().get(offset).getElementID()]; - if (difficulty != null && difficulty != session.getWorldCache().getDifficulty()) { - session.getConnector().getWorldManager().setDifficulty(session, difficulty); - } - offset++; - } - - if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules")) { - offset++; // Game rule title - - for (GameRule gamerule : GameRule.values()) { - if (gamerule.equals(GameRule.UNKNOWN)) { - continue; - } - - if (Boolean.class.equals(gamerule.getType())) { - Boolean value = settingsResponse.getToggleResponses().get(offset).booleanValue(); - if (value != session.getConnector().getWorldManager().getGameRuleBool(session, gamerule)) { - session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value); - } - } else if (Integer.class.equals(gamerule.getType())) { - int value = Integer.parseInt(settingsResponse.getInputResponses().get(offset)); - if (value != session.getConnector().getWorldManager().getGameRuleInt(session, gamerule)) { - session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value); - } - } - offset++; - } - } - - return true; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java deleted file mode 100644 index d65dbc81c..000000000 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.utils; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.steveice10.mc.auth.data.GameProfile; -import com.nukkitx.protocol.bedrock.data.skin.ImageData; -import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin; -import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.common.AuthType; -import org.geysermc.connector.entity.PlayerEntity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.session.auth.BedrockClientData; - -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.Collections; -import java.util.UUID; -import java.util.function.Consumer; - -public class SkinUtils { - - public static PlayerListPacket.Entry buildCachedEntry(GeyserSession session, GameProfile profile, long geyserId) { - GameProfileData data = GameProfileData.from(profile); - SkinProvider.Cape cape = SkinProvider.getCachedCape(data.getCapeUrl()); - - SkinProvider.SkinGeometry geometry = SkinProvider.SkinGeometry.getLegacy(data.isAlex()); - - SkinProvider.Skin skin = SkinProvider.getCachedSkin(data.getSkinUrl()); - if (skin == null) { - skin = SkinProvider.EMPTY_SKIN; - } - - return buildEntryManually( - session, - profile.getId(), - profile.getName(), - geyserId, - skin.getTextureUrl(), - skin.getSkinData(), - cape.getCapeId(), - cape.getCapeData(), - geometry.getGeometryName(), - geometry.getGeometryData() - ); - } - - public static PlayerListPacket.Entry buildEntryManually(GeyserSession session, UUID uuid, String username, long geyserId, - String skinId, byte[] skinData, - String capeId, byte[] capeData, - String geometryName, String geometryData) { - SerializedSkin serializedSkin = SerializedSkin.of( - skinId, geometryName, ImageData.of(skinData), Collections.emptyList(), - ImageData.of(capeData), geometryData, "", true, false, !capeId.equals(SkinProvider.EMPTY_CAPE.getCapeId()), capeId, skinId - ); - - // This attempts to find the xuid of the player so profile images show up for xbox accounts - String xuid = ""; - for (GeyserSession player : GeyserConnector.getInstance().getPlayers()) { - if (player.getPlayerEntity().getUuid().equals(uuid)) { - xuid = player.getAuthData().getXboxUUID(); - break; - } - } - - PlayerListPacket.Entry entry; - - // If we are building a PlayerListEntry for our own session we use our AuthData UUID instead of the Java UUID - // as bedrock expects to get back its own provided uuid - if (session.getPlayerEntity().getUuid().equals(uuid)) { - entry = new PlayerListPacket.Entry(session.getAuthData().getUUID()); - } else { - entry = new PlayerListPacket.Entry(uuid); - } - - entry.setName(username); - entry.setEntityId(geyserId); - entry.setSkin(serializedSkin); - entry.setXuid(xuid); - entry.setPlatformChatId(""); - entry.setTeacher(false); - entry.setTrustedSkin(true); - return entry; - } - - public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSession session, - Consumer skinAndCapeConsumer) { - GameProfileData data = GameProfileData.from(entity.getProfile()); - - SkinProvider.requestSkinAndCape(entity.getUuid(), data.getSkinUrl(), data.getCapeUrl()) - .whenCompleteAsync((skinAndCape, throwable) -> { - try { - SkinProvider.Skin skin = skinAndCape.getSkin(); - SkinProvider.Cape cape = skinAndCape.getCape(); - - if (cape.isFailed()) { - cape = SkinProvider.getOrDefault(SkinProvider.requestBedrockCape( - entity.getUuid(), false - ), SkinProvider.EMPTY_CAPE, 3); - } - - if (cape.isFailed() && SkinProvider.ALLOW_THIRD_PARTY_CAPES) { - cape = SkinProvider.getOrDefault(SkinProvider.requestUnofficialCape( - cape, entity.getUuid(), - entity.getUsername(), false - ), SkinProvider.EMPTY_CAPE, SkinProvider.CapeProvider.VALUES.length * 3); - } - - SkinProvider.SkinGeometry geometry = SkinProvider.SkinGeometry.getLegacy(data.isAlex()); - geometry = SkinProvider.getOrDefault(SkinProvider.requestBedrockGeometry( - geometry, entity.getUuid(), false - ), geometry, 3); - - // Not a bedrock player check for ears - if (geometry.isFailed() && SkinProvider.ALLOW_THIRD_PARTY_EARS) { - boolean isEars; - - // Its deadmau5, gotta support his skin :) - if (entity.getUuid().toString().equals("1e18d5ff-643d-45c8-b509-43b8461d8614")) { - isEars = true; - } else { - // Get the ears texture for the player - skin = SkinProvider.getOrDefault(SkinProvider.requestUnofficialEars( - skin, entity.getUuid(), entity.getUsername(), false - ), skin, 3); - - isEars = skin.isEars(); - } - - // Does the skin have an ears texture - if (isEars) { - // Get the new geometry - geometry = SkinProvider.SkinGeometry.getEars(data.isAlex()); - - // Store the skin and geometry for the ears - SkinProvider.storeEarSkin(entity.getUuid(), skin); - SkinProvider.storeEarGeometry(entity.getUuid(), data.isAlex()); - } - } - - entity.setLastSkinUpdate(skin.getRequestedOn()); - - if (session.getUpstream().isInitialized()) { - PlayerListPacket.Entry updatedEntry = buildEntryManually( - session, - entity.getUuid(), - entity.getUsername(), - entity.getGeyserId(), - skin.getTextureUrl(), - skin.getSkinData(), - cape.getCapeId(), - cape.getCapeData(), - geometry.getGeometryName(), - geometry.getGeometryData() - ); - - - PlayerListPacket playerAddPacket = new PlayerListPacket(); - playerAddPacket.setAction(PlayerListPacket.Action.ADD); - playerAddPacket.getEntries().add(updatedEntry); - session.sendUpstreamPacket(playerAddPacket); - - if (!entity.isPlayerList()) { - PlayerListPacket playerRemovePacket = new PlayerListPacket(); - playerRemovePacket.setAction(PlayerListPacket.Action.REMOVE); - playerRemovePacket.getEntries().add(updatedEntry); - session.sendUpstreamPacket(playerRemovePacket); - - } - } - } catch (Exception e) { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e); - } - - if (skinAndCapeConsumer != null) { - skinAndCapeConsumer.accept(skinAndCape); - } - }); - } - - public static void handleBedrockSkin(PlayerEntity playerEntity, BedrockClientData clientData) { - GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.skin.bedrock.register", playerEntity.getUsername(), playerEntity.getUuid())); - - try { - byte[] skinBytes = Base64.getDecoder().decode(clientData.getSkinData().getBytes(StandardCharsets.UTF_8)); - byte[] capeBytes = clientData.getCapeData(); - - byte[] geometryNameBytes = Base64.getDecoder().decode(clientData.getGeometryName().getBytes(StandardCharsets.UTF_8)); - byte[] geometryBytes = Base64.getDecoder().decode(clientData.getGeometryData().getBytes(StandardCharsets.UTF_8)); - - if (skinBytes.length <= (128 * 128 * 4) && !clientData.isPersonaSkin()) { - SkinProvider.storeBedrockSkin(playerEntity.getUuid(), clientData.getSkinId(), skinBytes); - SkinProvider.storeBedrockGeometry(playerEntity.getUuid(), geometryNameBytes, geometryBytes); - } else { - GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.skin.bedrock.fail", playerEntity.getUsername())); - GeyserConnector.getInstance().getLogger().debug("The size of '" + playerEntity.getUsername() + "' skin is: " + clientData.getSkinImageWidth() + "x" + clientData.getSkinImageHeight()); - } - - if (!clientData.getCapeId().equals("")) { - SkinProvider.storeBedrockCape(playerEntity.getUuid(), capeBytes); - } - } catch (Exception e) { - throw new AssertionError("Failed to cache skin for bedrock user (" + playerEntity.getUsername() + "): ", e); - } - } - - @AllArgsConstructor - @Getter - public static class GameProfileData { - private final String skinUrl; - private final String capeUrl; - private final boolean alex; - - /** - * Generate the GameProfileData from the given GameProfile - * - * @param profile GameProfile to build the GameProfileData from - * @return The built GameProfileData - */ - public static GameProfileData from(GameProfile profile) { - // Fallback to the offline mode of working it out - boolean isAlex = (Math.abs(profile.getId().hashCode() % 2) == 1); - - try { - GameProfile.Property skinProperty = profile.getProperty("textures"); - - // TODO: Remove try/catch here - JsonNode skinObject = new ObjectMapper().readTree(new String(Base64.getDecoder().decode(skinProperty.getValue()), StandardCharsets.UTF_8)); - JsonNode textures = skinObject.get("textures"); - - JsonNode skinTexture = textures.get("SKIN"); - String skinUrl = skinTexture.get("url").asText().replace("http://", "https://"); - - isAlex = skinTexture.has("metadata"); - - String capeUrl = null; - if (textures.has("CAPE")) { - JsonNode capeTexture = textures.get("CAPE"); - capeUrl = capeTexture.get("url").asText().replace("http://", "https://"); - } - - return new GameProfileData(skinUrl, capeUrl, isAlex); - } catch (Exception exception) { - if (GeyserConnector.getInstance().getAuthType() != AuthType.OFFLINE) { - GeyserConnector.getInstance().getLogger().debug("Got invalid texture data for " + profile.getName() + " " + exception.getMessage()); - } - // return default skin with default cape when texture data is invalid - String skinUrl = isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl(); - if ("steve".equals(skinUrl) || "alex".equals(skinUrl)) { - for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) { - if (session.getPlayerEntity().getUuid().equals(profile.getId())) { - skinUrl = session.getClientData().getSkinId(); - break; - } - } - } - return new GameProfileData(skinUrl, SkinProvider.EMPTY_CAPE.getTextureUrl(), isAlex); - } - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java deleted file mode 100644 index 3c42182d7..000000000 --- a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.utils; - -import com.github.steveice10.mc.protocol.data.MagicValues; -import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; -import com.github.steveice10.mc.protocol.data.game.statistic.*; -import org.geysermc.common.window.SimpleFormWindow; -import org.geysermc.common.window.button.FormButton; -import org.geysermc.common.window.response.SimpleFormResponse; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; - -import java.util.Map; - -public class StatisticsUtils { - - // Used in UpstreamPacketHandler.java - public static final int STATISTICS_MENU_FORM_ID = 1339; - public static final int STATISTICS_LIST_FORM_ID = 1340; - - /** - * Build a form for the given session with all statistic categories - * - * @param session The session to build the form for - */ - public static SimpleFormWindow buildMenuForm(GeyserSession session) { - // Cache the language for cleaner access - String language = session.getClientData().getLanguageCode(); - - SimpleFormWindow window = new SimpleFormWindow(LocaleUtils.getLocaleString("gui.stats", language), ""); - - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.generalButton", language))); - - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.mined", language))); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.broken", language))); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.crafted", language))); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.used", language))); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.picked_up", language))); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.dropped", language))); - - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed", language))); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed_by", language))); - - return window; - } - - /** - * Handle the menu form response - * - * @param session The session that sent the response - * @param response The response string to parse - * @return True if the form was parsed correctly, false if not - */ - public static boolean handleMenuForm(GeyserSession session, String response) { - SimpleFormWindow menuForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(STATISTICS_MENU_FORM_ID); - menuForm.setResponse(response); - SimpleFormResponse formResponse = (SimpleFormResponse) menuForm.getResponse(); - - // Cache the language for cleaner access - String language = session.getClientData().getLanguageCode(); - - if (formResponse != null && formResponse.getClickedButton() != null) { - String title; - StringBuilder content = new StringBuilder(); - - switch (formResponse.getClickedButtonId()) { - case 0: - title = LocaleUtils.getLocaleString("stat.generalButton", language); - - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof GenericStatistic) { - content.append(LocaleUtils.getLocaleString("stat.minecraft." + ((GenericStatistic) entry.getKey()).name().toLowerCase(), language) + ": " + entry.getValue() + "\n"); - } - } - break; - case 1: - title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.mined", language); - - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof BreakBlockStatistic) { - String block = BlockTranslator.JAVA_ID_TO_JAVA_IDENTIFIER_MAP.get(((BreakBlockStatistic) entry.getKey()).getId()); - block = block.replace("minecraft:", "block.minecraft."); - block = LocaleUtils.getLocaleString(block, language); - content.append(block + ": " + entry.getValue() + "\n"); - } - } - break; - case 2: - title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.broken", language); - - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof BreakItemStatistic) { - String item = ItemRegistry.ITEM_ENTRIES.get(((BreakItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); - } - } - break; - case 3: - title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.crafted", language); - - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof CraftItemStatistic) { - String item = ItemRegistry.ITEM_ENTRIES.get(((CraftItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); - } - } - break; - case 4: - title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.used", language); - - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof UseItemStatistic) { - String item = ItemRegistry.ITEM_ENTRIES.get(((UseItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); - } - } - break; - case 5: - title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.picked_up", language); - - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof PickupItemStatistic) { - String item = ItemRegistry.ITEM_ENTRIES.get(((PickupItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); - } - } - break; - case 6: - title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.dropped", language); - - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof DropItemStatistic) { - String item = ItemRegistry.ITEM_ENTRIES.get(((DropItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); - } - } - break; - case 7: - title = LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed", language); - - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof KillEntityStatistic) { - String mob = LocaleUtils.getLocaleString("entity.minecraft." + MagicValues.key(EntityType.class, ((KillEntityStatistic) entry.getKey()).getId()).name().toLowerCase(), language); - content.append(mob + ": " + entry.getValue() + "\n"); - } - } - break; - case 8: - title = LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed_by", language); - - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof KilledByEntityStatistic) { - String mob = LocaleUtils.getLocaleString("entity.minecraft." + MagicValues.key(EntityType.class, ((KilledByEntityStatistic) entry.getKey()).getId()).name().toLowerCase(), language); - content.append(mob + ": " + entry.getValue() + "\n"); - } - } - break; - default: - return false; - } - - if (content.length() == 0) { - content = new StringBuilder(LanguageUtils.getPlayerLocaleString("geyser.statistics.none", language)); - } - - SimpleFormWindow window = new SimpleFormWindow(title, content.toString()); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("gui.back", language))); - session.sendForm(window, STATISTICS_LIST_FORM_ID); - } - - return true; - } - - /** - * Handle the list form response - * - * @param session The session that sent the response - * @param response The response string to parse - * @return True if the form was parsed correctly, false if not - */ - public static boolean handleListForm(GeyserSession session, String response) { - SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(STATISTICS_LIST_FORM_ID); - listForm.setResponse(response); - - if (!listForm.isClosed()) { - session.sendForm(buildMenuForm(session), STATISTICS_MENU_FORM_ID); - } - - return true; - } - - /** - * Finds the item translation key from the Java locale. - * - * @param item the namespaced item to search for. - * @param language the language to search in - * @return the full name of the item - */ - private static String getItemTranslateKey(String item, String language) { - item = item.replace("minecraft:", "item.minecraft."); - String translatedItem = LocaleUtils.getLocaleString(item, language); - if (translatedItem.equals(item)) { - // Didn't translate; must be a block - translatedItem = LocaleUtils.getLocaleString(item.replace("item.", "block."), language); - } - return translatedItem; - } -} diff --git a/connector/src/main/resources/bedrock/biome_definitions.dat b/connector/src/main/resources/bedrock/biome_definitions.dat deleted file mode 100644 index 6d72cc924..000000000 Binary files a/connector/src/main/resources/bedrock/biome_definitions.dat and /dev/null differ diff --git a/connector/src/main/resources/bedrock/blockpalette.nbt b/connector/src/main/resources/bedrock/blockpalette.nbt deleted file mode 100644 index b92a06260..000000000 Binary files a/connector/src/main/resources/bedrock/blockpalette.nbt and /dev/null differ diff --git a/connector/src/main/resources/bedrock/creative_items.json b/connector/src/main/resources/bedrock/creative_items.json deleted file mode 100644 index 90839b0dc..000000000 --- a/connector/src/main/resources/bedrock/creative_items.json +++ /dev/null @@ -1,4427 +0,0 @@ -{ - "items" : [ - { - "id" : 5 - }, - { - "id" : 5, - "damage" : 1 - }, - { - "id" : 5, - "damage" : 2 - }, - { - "id" : 5, - "damage" : 3 - }, - { - "id" : 5, - "damage" : 4 - }, - { - "id" : 5, - "damage" : 5 - }, - { - "id" : -242 - }, - { - "id" : -243 - }, - { - "id" : 139 - }, - { - "id" : 139, - "damage" : 1 - }, - { - "id" : 139, - "damage" : 2 - }, - { - "id" : 139, - "damage" : 3 - }, - { - "id" : 139, - "damage" : 4 - }, - { - "id" : 139, - "damage" : 5 - }, - { - "id" : 139, - "damage" : 12 - }, - { - "id" : 139, - "damage" : 7 - }, - { - "id" : 139, - "damage" : 8 - }, - { - "id" : 139, - "damage" : 6 - }, - { - "id" : 139, - "damage" : 9 - }, - { - "id" : 139, - "damage" : 13 - }, - { - "id" : 139, - "damage" : 10 - }, - { - "id" : 139, - "damage" : 11 - }, - { - "id" : -277 - }, - { - "id" : -297 - }, - { - "id" : -278 - }, - { - "id" : 85 - }, - { - "id" : 85, - "damage" : 1 - }, - { - "id" : 85, - "damage" : 2 - }, - { - "id" : 85, - "damage" : 3 - }, - { - "id" : 85, - "damage" : 4 - }, - { - "id" : 85, - "damage" : 5 - }, - { - "id" : 113 - }, - { - "id" : -256 - }, - { - "id" : -257 - }, - { - "id" : 107 - }, - { - "id" : 183 - }, - { - "id" : 184 - }, - { - "id" : 185 - }, - { - "id" : 187 - }, - { - "id" : 186 - }, - { - "id" : -258 - }, - { - "id" : -259 - }, - { - "id" : -180 - }, - { - "id" : 67 - }, - { - "id" : -179 - }, - { - "id" : 53 - }, - { - "id" : 134 - }, - { - "id" : 135 - }, - { - "id" : 136 - }, - { - "id" : 163 - }, - { - "id" : 164 - }, - { - "id" : 109 - }, - { - "id" : -175 - }, - { - "id" : 128 - }, - { - "id" : -177 - }, - { - "id" : 180 - }, - { - "id" : -176 - }, - { - "id" : -169 - }, - { - "id" : -172 - }, - { - "id" : -170 - }, - { - "id" : -173 - }, - { - "id" : -171 - }, - { - "id" : -174 - }, - { - "id" : 108 - }, - { - "id" : 114 - }, - { - "id" : -184 - }, - { - "id" : -178 - }, - { - "id" : 156 - }, - { - "id" : -185 - }, - { - "id" : 203 - }, - { - "id" : -2 - }, - { - "id" : -3 - }, - { - "id" : -4 - }, - { - "id" : -254 - }, - { - "id" : -255 - }, - { - "id" : -276 - }, - { - "id" : -292 - }, - { - "id" : -275 - }, - { - "id" : 359 - }, - { - "id" : 543 - }, - { - "id" : 544 - }, - { - "id" : 545 - }, - { - "id" : 546 - }, - { - "id" : 547 - }, - { - "id" : 370 - }, - { - "id" : 604 - }, - { - "id" : 605 - }, - { - "id" : 96 - }, - { - "id" : -149 - }, - { - "id" : -146 - }, - { - "id" : -148 - }, - { - "id" : -145 - }, - { - "id" : -147 - }, - { - "id" : 167 - }, - { - "id" : -246 - }, - { - "id" : -247 - }, - { - "id" : 101 - }, - { - "id" : 20 - }, - { - "id" : 241 - }, - { - "id" : 241, - "damage" : 8 - }, - { - "id" : 241, - "damage" : 7 - }, - { - "id" : 241, - "damage" : 15 - }, - { - "id" : 241, - "damage" : 12 - }, - { - "id" : 241, - "damage" : 14 - }, - { - "id" : 241, - "damage" : 1 - }, - { - "id" : 241, - "damage" : 4 - }, - { - "id" : 241, - "damage" : 5 - }, - { - "id" : 241, - "damage" : 13 - }, - { - "id" : 241, - "damage" : 9 - }, - { - "id" : 241, - "damage" : 3 - }, - { - "id" : 241, - "damage" : 11 - }, - { - "id" : 241, - "damage" : 10 - }, - { - "id" : 241, - "damage" : 2 - }, - { - "id" : 241, - "damage" : 6 - }, - { - "id" : 102 - }, - { - "id" : 160 - }, - { - "id" : 160, - "damage" : 8 - }, - { - "id" : 160, - "damage" : 7 - }, - { - "id" : 160, - "damage" : 15 - }, - { - "id" : 160, - "damage" : 12 - }, - { - "id" : 160, - "damage" : 14 - }, - { - "id" : 160, - "damage" : 1 - }, - { - "id" : 160, - "damage" : 4 - }, - { - "id" : 160, - "damage" : 5 - }, - { - "id" : 160, - "damage" : 13 - }, - { - "id" : 160, - "damage" : 9 - }, - { - "id" : 160, - "damage" : 3 - }, - { - "id" : 160, - "damage" : 11 - }, - { - "id" : 160, - "damage" : 10 - }, - { - "id" : 160, - "damage" : 2 - }, - { - "id" : 160, - "damage" : 6 - }, - { - "id" : 65 - }, - { - "id" : -165 - }, - { - "id" : 44 - }, - { - "id" : -166, - "damage" : 2 - }, - { - "id" : 44, - "damage" : 3 - }, - { - "id" : 182, - "damage" : 5 - }, - { - "id" : 158 - }, - { - "id" : 158, - "damage" : 1 - }, - { - "id" : 158, - "damage" : 2 - }, - { - "id" : 158, - "damage" : 3 - }, - { - "id" : 158, - "damage" : 4 - }, - { - "id" : 158, - "damage" : 5 - }, - { - "id" : 44, - "damage" : 5 - }, - { - "id" : -166 - }, - { - "id" : 44, - "damage" : 1 - }, - { - "id" : -166, - "damage" : 3 - }, - { - "id" : 182, - "damage" : 6 - }, - { - "id" : 182 - }, - { - "id" : -166, - "damage" : 4 - }, - { - "id" : -162, - "damage" : 1 - }, - { - "id" : -162, - "damage" : 6 - }, - { - "id" : -162, - "damage" : 7 - }, - { - "id" : -162, - "damage" : 4 - }, - { - "id" : -162, - "damage" : 5 - }, - { - "id" : -162, - "damage" : 3 - }, - { - "id" : -162, - "damage" : 2 - }, - { - "id" : 44, - "damage" : 4 - }, - { - "id" : 44, - "damage" : 7 - }, - { - "id" : 182, - "damage" : 7 - }, - { - "id" : -162 - }, - { - "id" : 44, - "damage" : 6 - }, - { - "id" : -166, - "damage" : 1 - }, - { - "id" : 182, - "damage" : 1 - }, - { - "id" : 182, - "damage" : 2 - }, - { - "id" : 182, - "damage" : 3 - }, - { - "id" : 182, - "damage" : 4 - }, - { - "id" : -264 - }, - { - "id" : -265 - }, - { - "id" : -282 - }, - { - "id" : -293 - }, - { - "id" : -284 - }, - { - "id" : 45 - }, - { - "id" : -302 - }, - { - "id" : -303 - }, - { - "id" : -304 - }, - { - "id" : 98 - }, - { - "id" : 98, - "damage" : 1 - }, - { - "id" : 98, - "damage" : 2 - }, - { - "id" : 98, - "damage" : 3 - }, - { - "id" : 206 - }, - { - "id" : 168, - "damage" : 2 - }, - { - "id" : -274 - }, - { - "id" : -280 - }, - { - "id" : -281 - }, - { - "id" : -279 - }, - { - "id" : 4 - }, - { - "id" : 48 - }, - { - "id" : -183 - }, - { - "id" : 24 - }, - { - "id" : 24, - "damage" : 1 - }, - { - "id" : 24, - "damage" : 2 - }, - { - "id" : 24, - "damage" : 3 - }, - { - "id" : 179 - }, - { - "id" : 179, - "damage" : 1 - }, - { - "id" : 179, - "damage" : 2 - }, - { - "id" : 179, - "damage" : 3 - }, - { - "id" : 173 - }, - { - "id" : -139 - }, - { - "id" : 41 - }, - { - "id" : 42 - }, - { - "id" : 133 - }, - { - "id" : 57 - }, - { - "id" : 22 - }, - { - "id" : 155 - }, - { - "id" : 155, - "damage" : 2 - }, - { - "id" : 155, - "damage" : 1 - }, - { - "id" : 155, - "damage" : 3 - }, - { - "id" : 168 - }, - { - "id" : 168, - "damage" : 1 - }, - { - "id" : 165 - }, - { - "id" : -220 - }, - { - "id" : -221 - }, - { - "id" : 170 - }, - { - "id" : 216 - }, - { - "id" : 112 - }, - { - "id" : 215 - }, - { - "id" : -270 - }, - { - "id" : -222 - }, - { - "id" : 35 - }, - { - "id" : 35, - "damage" : 8 - }, - { - "id" : 35, - "damage" : 7 - }, - { - "id" : 35, - "damage" : 15 - }, - { - "id" : 35, - "damage" : 12 - }, - { - "id" : 35, - "damage" : 14 - }, - { - "id" : 35, - "damage" : 1 - }, - { - "id" : 35, - "damage" : 4 - }, - { - "id" : 35, - "damage" : 5 - }, - { - "id" : 35, - "damage" : 13 - }, - { - "id" : 35, - "damage" : 9 - }, - { - "id" : 35, - "damage" : 3 - }, - { - "id" : 35, - "damage" : 11 - }, - { - "id" : 35, - "damage" : 10 - }, - { - "id" : 35, - "damage" : 2 - }, - { - "id" : 35, - "damage" : 6 - }, - { - "id" : 171 - }, - { - "id" : 171, - "damage" : 8 - }, - { - "id" : 171, - "damage" : 7 - }, - { - "id" : 171, - "damage" : 15 - }, - { - "id" : 171, - "damage" : 12 - }, - { - "id" : 171, - "damage" : 14 - }, - { - "id" : 171, - "damage" : 1 - }, - { - "id" : 171, - "damage" : 4 - }, - { - "id" : 171, - "damage" : 5 - }, - { - "id" : 171, - "damage" : 13 - }, - { - "id" : 171, - "damage" : 9 - }, - { - "id" : 171, - "damage" : 3 - }, - { - "id" : 171, - "damage" : 11 - }, - { - "id" : 171, - "damage" : 10 - }, - { - "id" : 171, - "damage" : 2 - }, - { - "id" : 171, - "damage" : 6 - }, - { - "id" : 237 - }, - { - "id" : 237, - "damage" : 8 - }, - { - "id" : 237, - "damage" : 7 - }, - { - "id" : 237, - "damage" : 15 - }, - { - "id" : 237, - "damage" : 12 - }, - { - "id" : 237, - "damage" : 14 - }, - { - "id" : 237, - "damage" : 1 - }, - { - "id" : 237, - "damage" : 4 - }, - { - "id" : 237, - "damage" : 5 - }, - { - "id" : 237, - "damage" : 13 - }, - { - "id" : 237, - "damage" : 9 - }, - { - "id" : 237, - "damage" : 3 - }, - { - "id" : 237, - "damage" : 11 - }, - { - "id" : 237, - "damage" : 10 - }, - { - "id" : 237, - "damage" : 2 - }, - { - "id" : 237, - "damage" : 6 - }, - { - "id" : 236 - }, - { - "id" : 236, - "damage" : 8 - }, - { - "id" : 236, - "damage" : 7 - }, - { - "id" : 236, - "damage" : 15 - }, - { - "id" : 236, - "damage" : 12 - }, - { - "id" : 236, - "damage" : 14 - }, - { - "id" : 236, - "damage" : 1 - }, - { - "id" : 236, - "damage" : 4 - }, - { - "id" : 236, - "damage" : 5 - }, - { - "id" : 236, - "damage" : 13 - }, - { - "id" : 236, - "damage" : 9 - }, - { - "id" : 236, - "damage" : 3 - }, - { - "id" : 236, - "damage" : 11 - }, - { - "id" : 236, - "damage" : 10 - }, - { - "id" : 236, - "damage" : 2 - }, - { - "id" : 236, - "damage" : 6 - }, - { - "id" : 82 - }, - { - "id" : 172 - }, - { - "id" : 159 - }, - { - "id" : 159, - "damage" : 8 - }, - { - "id" : 159, - "damage" : 7 - }, - { - "id" : 159, - "damage" : 15 - }, - { - "id" : 159, - "damage" : 12 - }, - { - "id" : 159, - "damage" : 14 - }, - { - "id" : 159, - "damage" : 1 - }, - { - "id" : 159, - "damage" : 4 - }, - { - "id" : 159, - "damage" : 5 - }, - { - "id" : 159, - "damage" : 13 - }, - { - "id" : 159, - "damage" : 9 - }, - { - "id" : 159, - "damage" : 3 - }, - { - "id" : 159, - "damage" : 11 - }, - { - "id" : 159, - "damage" : 10 - }, - { - "id" : 159, - "damage" : 2 - }, - { - "id" : 159, - "damage" : 6 - }, - { - "id" : 220 - }, - { - "id" : 228 - }, - { - "id" : 227 - }, - { - "id" : 235 - }, - { - "id" : 232 - }, - { - "id" : 234 - }, - { - "id" : 221 - }, - { - "id" : 224 - }, - { - "id" : 225 - }, - { - "id" : 233 - }, - { - "id" : 229 - }, - { - "id" : 223 - }, - { - "id" : 231 - }, - { - "id" : 219 - }, - { - "id" : 222 - }, - { - "id" : 226 - }, - { - "id" : 201 - }, - { - "id" : 201, - "damage" : 2 - }, - { - "id" : 214 - }, - { - "id" : -227 - }, - { - "id" : -230 - }, - { - "id" : -232 - }, - { - "id" : -233 - }, - { - "id" : -234 - }, - { - "id" : -235 - }, - { - "id" : -236 - }, - { - "id" : 3 - }, - { - "id" : 3, - "damage" : 1 - }, - { - "id" : 60 - }, - { - "id" : 2 - }, - { - "id" : 198 - }, - { - "id" : 243 - }, - { - "id" : 110 - }, - { - "id" : 1 - }, - { - "id" : 15 - }, - { - "id" : 14 - }, - { - "id" : 56 - }, - { - "id" : 21 - }, - { - "id" : 73 - }, - { - "id" : 16 - }, - { - "id" : 129 - }, - { - "id" : 153 - }, - { - "id" : -288 - }, - { - "id" : -271 - }, - { - "id" : 13 - }, - { - "id" : 1, - "damage" : 1 - }, - { - "id" : 1, - "damage" : 3 - }, - { - "id" : 1, - "damage" : 5 - }, - { - "id" : -273 - }, - { - "id" : 1, - "damage" : 2 - }, - { - "id" : 1, - "damage" : 4 - }, - { - "id" : 1, - "damage" : 6 - }, - { - "id" : -291 - }, - { - "id" : 12 - }, - { - "id" : 12, - "damage" : 1 - }, - { - "id" : 81 - }, - { - "id" : 17 - }, - { - "id" : -10 - }, - { - "id" : 17, - "damage" : 1 - }, - { - "id" : -5 - }, - { - "id" : 17, - "damage" : 2 - }, - { - "id" : -6 - }, - { - "id" : 17, - "damage" : 3 - }, - { - "id" : -7 - }, - { - "id" : 162 - }, - { - "id" : -8 - }, - { - "id" : 162, - "damage" : 1 - }, - { - "id" : -9 - }, - { - "id" : -225 - }, - { - "id" : -240 - }, - { - "id" : -226 - }, - { - "id" : -241 - }, - { - "id" : -212 - }, - { - "id" : -212, - "damage" : 8 - }, - { - "id" : -212, - "damage" : 1 - }, - { - "id" : -212, - "damage" : 9 - }, - { - "id" : -212, - "damage" : 2 - }, - { - "id" : -212, - "damage" : 10 - }, - { - "id" : -212, - "damage" : 3 - }, - { - "id" : -212, - "damage" : 11 - }, - { - "id" : -212, - "damage" : 4 - }, - { - "id" : -212, - "damage" : 12 - }, - { - "id" : -212, - "damage" : 5 - }, - { - "id" : -212, - "damage" : 13 - }, - { - "id" : -299 - }, - { - "id" : -300 - }, - { - "id" : -298 - }, - { - "id" : -301 - }, - { - "id" : 18 - }, - { - "id" : 18, - "damage" : 1 - }, - { - "id" : 18, - "damage" : 2 - }, - { - "id" : 18, - "damage" : 3 - }, - { - "id" : 161 - }, - { - "id" : 161, - "damage" : 1 - }, - { - "id" : 6 - }, - { - "id" : 6, - "damage" : 1 - }, - { - "id" : 6, - "damage" : 2 - }, - { - "id" : 6, - "damage" : 3 - }, - { - "id" : 6, - "damage" : 4 - }, - { - "id" : 6, - "damage" : 5 - }, - { - "id" : -218 - }, - { - "id" : 291 - }, - { - "id" : 292 - }, - { - "id" : 293 - }, - { - "id" : 295 - }, - { - "id" : 334 - }, - { - "id" : 285 - }, - { - "id" : 280 - }, - { - "id" : 282 - }, - { - "id" : 279 - }, - { - "id" : 283 - }, - { - "id" : 257 - }, - { - "id" : 258 - }, - { - "id" : 259 - }, - { - "id" : 103 - }, - { - "id" : 272 - }, - { - "id" : 432 - }, - { - "id" : 287 - }, - { - "id" : 86 - }, - { - "id" : -155 - }, - { - "id" : 91 - }, - { - "id" : 580 - }, - { - "id" : 31, - "damage" : 2 - }, - { - "id" : 175, - "damage" : 3 - }, - { - "id" : 31, - "damage" : 1 - }, - { - "id" : 175, - "damage" : 2 - }, - { - "id" : 609 - }, - { - "id" : -131, - "damage" : 3 - }, - { - "id" : -131, - "damage" : 1 - }, - { - "id" : -131, - "damage" : 2 - }, - { - "id" : -131 - }, - { - "id" : -131, - "damage" : 4 - }, - { - "id" : -131, - "damage" : 11 - }, - { - "id" : -131, - "damage" : 9 - }, - { - "id" : -131, - "damage" : 10 - }, - { - "id" : -131, - "damage" : 8 - }, - { - "id" : -131, - "damage" : 12 - }, - { - "id" : -133, - "damage" : 3 - }, - { - "id" : -133, - "damage" : 1 - }, - { - "id" : -133, - "damage" : 2 - }, - { - "id" : -133 - }, - { - "id" : -133, - "damage" : 4 - }, - { - "id" : -134, - "damage" : 3 - }, - { - "id" : -134, - "damage" : 1 - }, - { - "id" : -134, - "damage" : 2 - }, - { - "id" : -134 - }, - { - "id" : -134, - "damage" : 4 - }, - { - "id" : 380 - }, - { - "id" : -130 - }, - { - "id" : -223 - }, - { - "id" : -224 - }, - { - "id" : 37 - }, - { - "id" : 38 - }, - { - "id" : 38, - "damage" : 1 - }, - { - "id" : 38, - "damage" : 2 - }, - { - "id" : 38, - "damage" : 3 - }, - { - "id" : 38, - "damage" : 4 - }, - { - "id" : 38, - "damage" : 5 - }, - { - "id" : 38, - "damage" : 6 - }, - { - "id" : 38, - "damage" : 7 - }, - { - "id" : 38, - "damage" : 8 - }, - { - "id" : 38, - "damage" : 9 - }, - { - "id" : 38, - "damage" : 10 - }, - { - "id" : 175 - }, - { - "id" : 175, - "damage" : 1 - }, - { - "id" : 175, - "damage" : 4 - }, - { - "id" : 175, - "damage" : 5 - }, - { - "id" : -216 - }, - { - "id" : 408 - }, - { - "id" : 400 - }, - { - "id" : 401 - }, - { - "id" : 393 - }, - { - "id" : 396 - }, - { - "id" : 394 - }, - { - "id" : 407 - }, - { - "id" : 404 - }, - { - "id" : 403 - }, - { - "id" : 395 - }, - { - "id" : 399 - }, - { - "id" : 405 - }, - { - "id" : 397 - }, - { - "id" : 398 - }, - { - "id" : 406 - }, - { - "id" : 402 - }, - { - "id" : 411 - }, - { - "id" : 410 - }, - { - "id" : 412 - }, - { - "id" : 409 - }, - { - "id" : 106 - }, - { - "id" : -231 - }, - { - "id" : -287 - }, - { - "id" : 111 - }, - { - "id" : 32 - }, - { - "id" : -163 - }, - { - "id" : 80 - }, - { - "id" : 79 - }, - { - "id" : 174 - }, - { - "id" : -11 - }, - { - "id" : 78 - }, - { - "id" : 275 - }, - { - "id" : 262 - }, - { - "id" : 273 - }, - { - "id" : 540 - }, - { - "id" : 288 - }, - { - "id" : 264 - }, - { - "id" : 265 - }, - { - "id" : 266 - }, - { - "id" : 267 - }, - { - "id" : 39 - }, - { - "id" : 40 - }, - { - "id" : -228 - }, - { - "id" : -229 - }, - { - "id" : 99, - "damage" : 14 - }, - { - "id" : 100, - "damage" : 14 - }, - { - "id" : 99, - "damage" : 15 - }, - { - "id" : 99 - }, - { - "id" : 388 - }, - { - "id" : 383 - }, - { - "id" : 414 - }, - { - "id" : 277 - }, - { - "id" : 413 - }, - { - "id" : 30 - }, - { - "id" : 278 - }, - { - "id" : 52 - }, - { - "id" : 97 - }, - { - "id" : 97, - "damage" : 1 - }, - { - "id" : 97, - "damage" : 2 - }, - { - "id" : 97, - "damage" : 3 - }, - { - "id" : 97, - "damage" : 4 - }, - { - "id" : 97, - "damage" : 5 - }, - { - "id" : 122 - }, - { - "id" : -159 - }, - { - "id" : 433 - }, - { - "id" : 492 - }, - { - "id" : 434 - }, - { - "id" : 435 - }, - { - "id" : 436 - }, - { - "id" : 437 - }, - { - "id" : 470 - }, - { - "id" : 449 - }, - { - "id" : 486 - }, - { - "id" : 438 - }, - { - "id" : 451 - }, - { - "id" : 476 - }, - { - "id" : 457 - }, - { - "id" : 471 - }, - { - "id" : 456 - }, - { - "id" : 463 - }, - { - "id" : 464 - }, - { - "id" : 465 - }, - { - "id" : 466 - }, - { - "id" : 477 - }, - { - "id" : 478 - }, - { - "id" : 479 - }, - { - "id" : 480 - }, - { - "id" : 482 - }, - { - "id" : 483 - }, - { - "id" : 487 - }, - { - "id" : 488 - }, - { - "id" : 439 - }, - { - "id" : 440 - }, - { - "id" : 441 - }, - { - "id" : 442 - }, - { - "id" : 462 - }, - { - "id" : 460 - }, - { - "id" : 443 - }, - { - "id" : 444 - }, - { - "id" : 445 - }, - { - "id" : 446 - }, - { - "id" : 461 - }, - { - "id" : 481 - }, - { - "id" : 448 - }, - { - "id" : 455 - }, - { - "id" : 450 - }, - { - "id" : 459 - }, - { - "id" : 469 - }, - { - "id" : 458 - }, - { - "id" : 453 - }, - { - "id" : 493 - }, - { - "id" : 494 - }, - { - "id" : 495 - }, - { - "id" : 496 - }, - { - "id" : 497 - }, - { - "id" : 452 - }, - { - "id" : 454 - }, - { - "id" : 467 - }, - { - "id" : 472 - }, - { - "id" : 473 - }, - { - "id" : 474 - }, - { - "id" : 447 - }, - { - "id" : 490 - }, - { - "id" : 475 - }, - { - "id" : 484 - }, - { - "id" : 489 - }, - { - "id" : 491 - }, - { - "id" : 49 - }, - { - "id" : -289 - }, - { - "id" : 7 - }, - { - "id" : 88 - }, - { - "id" : 87 - }, - { - "id" : 213 - }, - { - "id" : 294 - }, - { - "id" : 121 - }, - { - "id" : 200 - }, - { - "id" : 240 - }, - { - "id" : 548 - }, - { - "id" : 549 - }, - { - "id" : 19 - }, - { - "id" : 19, - "damage" : 1 - }, - { - "id" : -132 - }, - { - "id" : -132, - "damage" : 1 - }, - { - "id" : -132, - "damage" : 2 - }, - { - "id" : -132, - "damage" : 3 - }, - { - "id" : -132, - "damage" : 4 - }, - { - "id" : -132, - "damage" : 8 - }, - { - "id" : -132, - "damage" : 9 - }, - { - "id" : -132, - "damage" : 10 - }, - { - "id" : -132, - "damage" : 11 - }, - { - "id" : -132, - "damage" : 12 - }, - { - "id" : 335 - }, - { - "id" : 339 - }, - { - "id" : 343 - }, - { - "id" : 351 - }, - { - "id" : 347 - }, - { - "id" : 597 - }, - { - "id" : 336 - }, - { - "id" : 340 - }, - { - "id" : 344 - }, - { - "id" : 352 - }, - { - "id" : 348 - }, - { - "id" : 598 - }, - { - "id" : 337 - }, - { - "id" : 341 - }, - { - "id" : 345 - }, - { - "id" : 353 - }, - { - "id" : 349 - }, - { - "id" : 599 - }, - { - "id" : 338 - }, - { - "id" : 342 - }, - { - "id" : 346 - }, - { - "id" : 354 - }, - { - "id" : 350 - }, - { - "id" : 600 - }, - { - "id" : 308 - }, - { - "id" : 312 - }, - { - "id" : 307 - }, - { - "id" : 322 - }, - { - "id" : 316 - }, - { - "id" : 592 - }, - { - "id" : 311 - }, - { - "id" : 315 - }, - { - "id" : 298 - }, - { - "id" : 325 - }, - { - "id" : 319 - }, - { - "id" : 595 - }, - { - "id" : 310 - }, - { - "id" : 314 - }, - { - "id" : 297 - }, - { - "id" : 324 - }, - { - "id" : 318 - }, - { - "id" : 594 - }, - { - "id" : 309 - }, - { - "id" : 313 - }, - { - "id" : 296 - }, - { - "id" : 323 - }, - { - "id" : 317 - }, - { - "id" : 593 - }, - { - "id" : 329 - }, - { - "id" : 330 - }, - { - "id" : 331 - }, - { - "id" : 333 - }, - { - "id" : 332 - }, - { - "id" : 596 - }, - { - "id" : 300 - }, - { - "id" : 565 - }, - { - "id" : 301 - }, - { - "id" : 301, - "damage" : 6 - }, - { - "id" : 301, - "damage" : 7 - }, - { - "id" : 301, - "damage" : 8 - }, - { - "id" : 301, - "damage" : 9 - }, - { - "id" : 301, - "damage" : 10 - }, - { - "id" : 301, - "damage" : 11 - }, - { - "id" : 301, - "damage" : 12 - }, - { - "id" : 301, - "damage" : 13 - }, - { - "id" : 301, - "damage" : 14 - }, - { - "id" : 301, - "damage" : 15 - }, - { - "id" : 301, - "damage" : 16 - }, - { - "id" : 301, - "damage" : 17 - }, - { - "id" : 301, - "damage" : 18 - }, - { - "id" : 301, - "damage" : 19 - }, - { - "id" : 301, - "damage" : 20 - }, - { - "id" : 301, - "damage" : 21 - }, - { - "id" : 301, - "damage" : 22 - }, - { - "id" : 301, - "damage" : 23 - }, - { - "id" : 301, - "damage" : 24 - }, - { - "id" : 301, - "damage" : 25 - }, - { - "id" : 301, - "damage" : 26 - }, - { - "id" : 301, - "damage" : 27 - }, - { - "id" : 301, - "damage" : 28 - }, - { - "id" : 301, - "damage" : 29 - }, - { - "id" : 301, - "damage" : 30 - }, - { - "id" : 301, - "damage" : 31 - }, - { - "id" : 301, - "damage" : 32 - }, - { - "id" : 301, - "damage" : 33 - }, - { - "id" : 301, - "damage" : 34 - }, - { - "id" : 301, - "damage" : 35 - }, - { - "id" : 301, - "damage" : 36 - }, - { - "id" : 301, - "damage" : 37 - }, - { - "id" : 301, - "damage" : 38 - }, - { - "id" : 301, - "damage" : 39 - }, - { - "id" : 301, - "damage" : 40 - }, - { - "id" : 301, - "damage" : 41 - }, - { - "id" : 301, - "damage" : 42 - }, - { - "id" : 301, - "damage" : 43 - }, - { - "id" : 355 - }, - { - "id" : 276 - }, - { - "id" : 263 - }, - { - "id" : 274 - }, - { - "id" : 541 - }, - { - "id" : 289 - }, - { - "id" : 268 - }, - { - "id" : 269 - }, - { - "id" : 261 - }, - { - "id" : 260 - }, - { - "id" : 286 - }, - { - "id" : 290 - }, - { - "id" : 281 - }, - { - "id" : 271 - }, - { - "id" : 284 - }, - { - "id" : 415 - }, - { - "id" : 270 - }, - { - "id" : 390 - }, - { - "id" : 507 - }, - { - "id" : 606 - }, - { - "id" : 372 - }, - { - "id" : 419 - }, - { - "id" : 299 - }, - { - "id" : 537 - }, - { - "id" : 391 - }, - { - "id" : 389 - }, - { - "id" : 505 - }, - { - "id" : 505, - "damage" : 2 - }, - { - "id" : 369 - }, - { - "id" : 520 - }, - { - "id" : 521 - }, - { - "id" : 522 - }, - { - "id" : 523 - }, - { - "id" : 536 - }, - { - "id" : 563 - }, - { - "id" : 554 - }, - { - "id" : 558 - }, - { - "id" : 425 - }, - { - "id" : 498 - }, - { - "id" : 424 - }, - { - "id" : 424, - "damage" : 1 - }, - { - "id" : 424, - "damage" : 2 - }, - { - "id" : 424, - "damage" : 3 - }, - { - "id" : 424, - "damage" : 4 - }, - { - "id" : 424, - "damage" : 5 - }, - { - "id" : 424, - "damage" : 6 - }, - { - "id" : 424, - "damage" : 7 - }, - { - "id" : 424, - "damage" : 8 - }, - { - "id" : 424, - "damage" : 9 - }, - { - "id" : 424, - "damage" : 10 - }, - { - "id" : 424, - "damage" : 11 - }, - { - "id" : 424, - "damage" : 12 - }, - { - "id" : 424, - "damage" : 13 - }, - { - "id" : 424, - "damage" : 14 - }, - { - "id" : 424, - "damage" : 15 - }, - { - "id" : 424, - "damage" : 16 - }, - { - "id" : 424, - "damage" : 17 - }, - { - "id" : 424, - "damage" : 18 - }, - { - "id" : 424, - "damage" : 19 - }, - { - "id" : 424, - "damage" : 20 - }, - { - "id" : 424, - "damage" : 21 - }, - { - "id" : 424, - "damage" : 22 - }, - { - "id" : 424, - "damage" : 23 - }, - { - "id" : 424, - "damage" : 24 - }, - { - "id" : 424, - "damage" : 25 - }, - { - "id" : 424, - "damage" : 26 - }, - { - "id" : 424, - "damage" : 27 - }, - { - "id" : 424, - "damage" : 28 - }, - { - "id" : 424, - "damage" : 29 - }, - { - "id" : 424, - "damage" : 30 - }, - { - "id" : 424, - "damage" : 31 - }, - { - "id" : 424, - "damage" : 32 - }, - { - "id" : 424, - "damage" : 33 - }, - { - "id" : 424, - "damage" : 34 - }, - { - "id" : 424, - "damage" : 35 - }, - { - "id" : 424, - "damage" : 36 - }, - { - "id" : 424, - "damage" : 37 - }, - { - "id" : 424, - "damage" : 38 - }, - { - "id" : 424, - "damage" : 39 - }, - { - "id" : 424, - "damage" : 40 - }, - { - "id" : 424, - "damage" : 41 - }, - { - "id" : 424, - "damage" : 42 - }, - { - "id" : 551 - }, - { - "id" : 551, - "damage" : 1 - }, - { - "id" : 551, - "damage" : 2 - }, - { - "id" : 551, - "damage" : 3 - }, - { - "id" : 551, - "damage" : 4 - }, - { - "id" : 551, - "damage" : 5 - }, - { - "id" : 551, - "damage" : 6 - }, - { - "id" : 551, - "damage" : 7 - }, - { - "id" : 551, - "damage" : 8 - }, - { - "id" : 551, - "damage" : 9 - }, - { - "id" : 551, - "damage" : 10 - }, - { - "id" : 551, - "damage" : 11 - }, - { - "id" : 551, - "damage" : 12 - }, - { - "id" : 551, - "damage" : 13 - }, - { - "id" : 551, - "damage" : 14 - }, - { - "id" : 551, - "damage" : 15 - }, - { - "id" : 551, - "damage" : 16 - }, - { - "id" : 551, - "damage" : 17 - }, - { - "id" : 551, - "damage" : 18 - }, - { - "id" : 551, - "damage" : 19 - }, - { - "id" : 551, - "damage" : 20 - }, - { - "id" : 551, - "damage" : 21 - }, - { - "id" : 551, - "damage" : 22 - }, - { - "id" : 551, - "damage" : 23 - }, - { - "id" : 551, - "damage" : 24 - }, - { - "id" : 551, - "damage" : 25 - }, - { - "id" : 551, - "damage" : 26 - }, - { - "id" : 551, - "damage" : 27 - }, - { - "id" : 551, - "damage" : 28 - }, - { - "id" : 551, - "damage" : 29 - }, - { - "id" : 551, - "damage" : 30 - }, - { - "id" : 551, - "damage" : 31 - }, - { - "id" : 551, - "damage" : 32 - }, - { - "id" : 551, - "damage" : 33 - }, - { - "id" : 551, - "damage" : 34 - }, - { - "id" : 551, - "damage" : 35 - }, - { - "id" : 551, - "damage" : 36 - }, - { - "id" : 551, - "damage" : 37 - }, - { - "id" : 551, - "damage" : 38 - }, - { - "id" : 551, - "damage" : 39 - }, - { - "id" : 551, - "damage" : 40 - }, - { - "id" : 551, - "damage" : 41 - }, - { - "id" : 551, - "damage" : 42 - }, - { - "id" : 552 - }, - { - "id" : 552, - "damage" : 1 - }, - { - "id" : 552, - "damage" : 2 - }, - { - "id" : 552, - "damage" : 3 - }, - { - "id" : 552, - "damage" : 4 - }, - { - "id" : 552, - "damage" : 5 - }, - { - "id" : 552, - "damage" : 6 - }, - { - "id" : 552, - "damage" : 7 - }, - { - "id" : 552, - "damage" : 8 - }, - { - "id" : 552, - "damage" : 9 - }, - { - "id" : 552, - "damage" : 10 - }, - { - "id" : 552, - "damage" : 11 - }, - { - "id" : 552, - "damage" : 12 - }, - { - "id" : 552, - "damage" : 13 - }, - { - "id" : 552, - "damage" : 14 - }, - { - "id" : 552, - "damage" : 15 - }, - { - "id" : 552, - "damage" : 16 - }, - { - "id" : 552, - "damage" : 17 - }, - { - "id" : 552, - "damage" : 18 - }, - { - "id" : 552, - "damage" : 19 - }, - { - "id" : 552, - "damage" : 20 - }, - { - "id" : 552, - "damage" : 21 - }, - { - "id" : 552, - "damage" : 22 - }, - { - "id" : 552, - "damage" : 23 - }, - { - "id" : 552, - "damage" : 24 - }, - { - "id" : 552, - "damage" : 25 - }, - { - "id" : 552, - "damage" : 26 - }, - { - "id" : 552, - "damage" : 27 - }, - { - "id" : 552, - "damage" : 28 - }, - { - "id" : 552, - "damage" : 29 - }, - { - "id" : 552, - "damage" : 30 - }, - { - "id" : 552, - "damage" : 31 - }, - { - "id" : 552, - "damage" : 32 - }, - { - "id" : 552, - "damage" : 33 - }, - { - "id" : 552, - "damage" : 34 - }, - { - "id" : 552, - "damage" : 35 - }, - { - "id" : 552, - "damage" : 36 - }, - { - "id" : 552, - "damage" : 37 - }, - { - "id" : 552, - "damage" : 38 - }, - { - "id" : 552, - "damage" : 39 - }, - { - "id" : 552, - "damage" : 40 - }, - { - "id" : 552, - "damage" : 41 - }, - { - "id" : 552, - "damage" : 42 - }, - { - "id" : 320 - }, - { - "id" : 416 - }, - { - "id" : 416, - "damage" : 8 - }, - { - "id" : 416, - "damage" : 7 - }, - { - "id" : 416, - "damage" : 15 - }, - { - "id" : 416, - "damage" : 12 - }, - { - "id" : 416, - "damage" : 14 - }, - { - "id" : 416, - "damage" : 1 - }, - { - "id" : 416, - "damage" : 4 - }, - { - "id" : 416, - "damage" : 5 - }, - { - "id" : 416, - "damage" : 13 - }, - { - "id" : 416, - "damage" : 9 - }, - { - "id" : 416, - "damage" : 3 - }, - { - "id" : 416, - "damage" : 11 - }, - { - "id" : 416, - "damage" : 10 - }, - { - "id" : 416, - "damage" : 2 - }, - { - "id" : 416, - "damage" : 6 - }, - { - "id" : 50 - }, - { - "id" : -268 - }, - { - "id" : -156 - }, - { - "id" : -208 - }, - { - "id" : -269 - }, - { - "id" : 58 - }, - { - "id" : -200 - }, - { - "id" : -201 - }, - { - "id" : -202 - }, - { - "id" : -219 - }, - { - "id" : 578 - }, - { - "id" : 610 - }, - { - "id" : 61 - }, - { - "id" : -196 - }, - { - "id" : -198 - }, - { - "id" : -272 - }, - { - "id" : 429 - }, - { - "id" : 145 - }, - { - "id" : 145, - "damage" : 4 - }, - { - "id" : 145, - "damage" : 8 - }, - { - "id" : -195 - }, - { - "id" : 116 - }, - { - "id" : 47 - }, - { - "id" : -194 - }, - { - "id" : 430 - }, - { - "id" : -213 - }, - { - "id" : 54 - }, - { - "id" : 146 - }, - { - "id" : 130 - }, - { - "id" : -203 - }, - { - "id" : 205 - }, - { - "id" : 218 - }, - { - "id" : 218, - "damage" : 8 - }, - { - "id" : 218, - "damage" : 7 - }, - { - "id" : 218, - "damage" : 15 - }, - { - "id" : 218, - "damage" : 12 - }, - { - "id" : 218, - "damage" : 14 - }, - { - "id" : 218, - "damage" : 1 - }, - { - "id" : 218, - "damage" : 4 - }, - { - "id" : 218, - "damage" : 5 - }, - { - "id" : 218, - "damage" : 13 - }, - { - "id" : 218, - "damage" : 9 - }, - { - "id" : 218, - "damage" : 3 - }, - { - "id" : 218, - "damage" : 11 - }, - { - "id" : 218, - "damage" : 10 - }, - { - "id" : 218, - "damage" : 2 - }, - { - "id" : 218, - "damage" : 6 - }, - { - "id" : 542 - }, - { - "id" : 25 - }, - { - "id" : 84 - }, - { - "id" : 524 - }, - { - "id" : 525 - }, - { - "id" : 526 - }, - { - "id" : 527 - }, - { - "id" : 528 - }, - { - "id" : 529 - }, - { - "id" : 530 - }, - { - "id" : 531 - }, - { - "id" : 532 - }, - { - "id" : 533 - }, - { - "id" : 534 - }, - { - "id" : 535 - }, - { - "id" : 608 - }, - { - "id" : 392 - }, - { - "id" : 89 - }, - { - "id" : 123 - }, - { - "id" : 169 - }, - { - "id" : 358 - }, - { - "id" : 566 - }, - { - "id" : 567 - }, - { - "id" : 568 - }, - { - "id" : 569 - }, - { - "id" : 570 - }, - { - "id" : 602 - }, - { - "id" : 603 - }, - { - "id" : 357 - }, - { - "id" : 503 - }, - { - "id" : 581 - }, - { - "id" : 504 - }, - { - "id" : 321 - }, - { - "id" : 360 - }, - { - "id" : 361 - }, - { - "id" : 362 - }, - { - "id" : 363 - }, - { - "id" : 364 - }, - { - "id" : 365 - }, - { - "id" : 366 - }, - { - "id" : 367 - }, - { - "id" : 506, - "damage" : 3 - }, - { - "id" : 506, - "damage" : 2 - }, - { - "id" : 506, - "damage" : 4 - }, - { - "id" : 506, - "damage" : 5 - }, - { - "id" : 506 - }, - { - "id" : 506, - "damage" : 1 - }, - { - "id" : 138 - }, - { - "id" : -206 - }, - { - "id" : -157 - }, - { - "id" : -197 - }, - { - "id" : 120 - }, - { - "id" : 302 - }, - { - "id" : 303 - }, - { - "id" : 304 - }, - { - "id" : 559 - }, - { - "id" : 305 - }, - { - "id" : 601 - }, - { - "id" : 591 - }, - { - "id" : 423 - }, - { - "id" : 306 - }, - { - "id" : 502 - }, - { - "id" : 514 - }, - { - "id" : 382 - }, - { - "id" : 381 - }, - { - "id" : 513 - }, - { - "id" : 555 - }, - { - "id" : 539 - }, - { - "id" : 560 - }, - { - "id" : 561 - }, - { - "id" : 562 - }, - { - "id" : 564 - }, - { - "id" : 326 - }, - { - "id" : 327 - }, - { - "id" : 356 - }, - { - "id" : 328 - }, - { - "id" : 379 - }, - { - "id" : 519 - }, - { - "id" : 518 - }, - { - "id" : 499 - }, - { - "id" : 421 - }, - { - "id" : 427 - }, - { - "id" : 428 - }, - { - "id" : 426 - }, - { - "id" : 550 - }, - { - "id" : 556 - }, - { - "id" : 422 - }, - { - "id" : 386 - }, - { - "id" : 420 - }, - { - "id" : 431 - }, - { - "id" : 508 - }, - { - "id" : 208 - }, - { - "id" : 615 - }, - { - "id" : 384 - }, - { - "id" : 385 - }, - { - "id" : 500 - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAMAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAQAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAMAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAQAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAMAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAQAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAMAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAQAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAMAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAQAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAMAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAMAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAMAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQIAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAMAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAQAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAUAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAMAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAQAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAUAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAMAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAQAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAUAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAMAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAMAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAQAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAUAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQQAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAMAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAMAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAMAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAQAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAUAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQVAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQWAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAMAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAMAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQaAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQbAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQcAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAMAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAQAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAUAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAMAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAMAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQgAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQhAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAMAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAQAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAMAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAEAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAIAAAA=" - }, - { - "id" : 511, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAMAAAA=" - }, - { - "id" : 373 - }, - { - "id" : 376 - }, - { - "id" : 374 - }, - { - "id" : 375 - }, - { - "id" : 377 - }, - { - "id" : 378 - }, - { - "id" : 66 - }, - { - "id" : 27 - }, - { - "id" : 28 - }, - { - "id" : 126 - }, - { - "id" : 368 - }, - { - "id" : 387 - }, - { - "id" : 516 - }, - { - "id" : 515 - }, - { - "id" : 371 - }, - { - "id" : 152 - }, - { - "id" : 76 - }, - { - "id" : 69 - }, - { - "id" : 143 - }, - { - "id" : -144 - }, - { - "id" : -141 - }, - { - "id" : -143 - }, - { - "id" : -140 - }, - { - "id" : -142 - }, - { - "id" : 77 - }, - { - "id" : -260 - }, - { - "id" : -261 - }, - { - "id" : -296 - }, - { - "id" : 131 - }, - { - "id" : 72 - }, - { - "id" : -154 - }, - { - "id" : -151 - }, - { - "id" : -153 - }, - { - "id" : -150 - }, - { - "id" : -152 - }, - { - "id" : -262 - }, - { - "id" : -263 - }, - { - "id" : 70 - }, - { - "id" : 147 - }, - { - "id" : 148 - }, - { - "id" : -295 - }, - { - "id" : 251 - }, - { - "id" : 151 - }, - { - "id" : 417 - }, - { - "id" : 512 - }, - { - "id" : 517 - }, - { - "id" : 125, - "damage" : 3 - }, - { - "id" : 23, - "damage" : 3 - }, - { - "id" : 33, - "damage" : 1 - }, - { - "id" : 29, - "damage" : 1 - }, - { - "id" : 46 - }, - { - "id" : 538 - }, - { - "id" : -204 - }, - { - "id" : 557 - }, - { - "id" : 557, - "damage" : 8 - }, - { - "id" : 557, - "damage" : 7 - }, - { - "id" : 557, - "damage" : 15 - }, - { - "id" : 557, - "damage" : 12 - }, - { - "id" : 557, - "damage" : 14 - }, - { - "id" : 557, - "damage" : 1 - }, - { - "id" : 557, - "damage" : 4 - }, - { - "id" : 557, - "damage" : 5 - }, - { - "id" : 557, - "damage" : 13 - }, - { - "id" : 557, - "damage" : 9 - }, - { - "id" : 557, - "damage" : 3 - }, - { - "id" : 557, - "damage" : 11 - }, - { - "id" : 557, - "damage" : 10 - }, - { - "id" : 557, - "damage" : 2 - }, - { - "id" : 557, - "damage" : 6 - }, - { - "id" : 557, - "damage" : 15, - "nbt_b64" : "CgAAAwQAVHlwZQEAAAAA" - }, - { - "id" : 572 - }, - { - "id" : 573 - }, - { - "id" : 571 - }, - { - "id" : 574 - }, - { - "id" : 575 - }, - { - "id" : 576 - }, - { - "id" : 577 - }, - { - "id" : 509, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwAAAAAAAQYARmxpZ2h0AQAA" - }, - { - "id" : 509, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAABwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : 509, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAIBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : 509, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAHBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : 509, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAPBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : 509, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAMBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : 509, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAOBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : 509, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAABBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : 509, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAEBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : 509, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAFBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : 509, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAANBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : 509, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAJBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : 509, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAADBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : 509, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAALBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : 509, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAKBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : 509, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAACBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : 509, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAGBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" - }, - { - "id" : 510, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yIR0d/wA=" - }, - { - "id" : 510, - "damage" : 8, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yUk9H/wA=" - }, - { - "id" : 510, - "damage" : 7, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yl52d/wA=" - }, - { - "id" : 510, - "damage" : 15, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y8PDw/wA=" - }, - { - "id" : 510, - "damage" : 12, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y2rM6/wA=" - }, - { - "id" : 510, - "damage" : 14, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yHYD5/wA=" - }, - { - "id" : 510, - "damage" : 1, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yJi6w/wA=" - }, - { - "id" : 510, - "damage" : 4, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqkQ8/wA=" - }, - { - "id" : 510, - "damage" : 5, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yuDKJ/wA=" - }, - { - "id" : 510, - "damage" : 13, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yvU7H/wA=" - }, - { - "id" : 510, - "damage" : 9, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqovz/wA=" - }, - { - "id" : 510, - "damage" : 3, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yMlSD/wA=" - }, - { - "id" : 510, - "damage" : 11, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yPdj+/wA=" - }, - { - "id" : 510, - "damage" : 10, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yH8eA/wA=" - }, - { - "id" : 510, - "damage" : 2, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yFnxe/wA=" - }, - { - "id" : 510, - "damage" : 6, - "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9ynJwW/wA=" - }, - { - "id" : 607 - }, - { - "id" : -239 - }, - { - "id" : 590 - } - ] -} \ No newline at end of file diff --git a/connector/src/main/resources/bedrock/entity_identifiers.dat b/connector/src/main/resources/bedrock/entity_identifiers.dat deleted file mode 100644 index c9477ba85..000000000 Binary files a/connector/src/main/resources/bedrock/entity_identifiers.dat and /dev/null differ diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages deleted file mode 160000 index bf4b0b710..000000000 --- a/connector/src/main/resources/languages +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bf4b0b7103193154dd0b06e0459dc375c753069a diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings deleted file mode 160000 index 618a9b981..000000000 --- a/connector/src/main/resources/mappings +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 618a9b981398647125b1b63494cb49ad93433243 diff --git a/connector/pom.xml b/core/pom.xml similarity index 52% rename from connector/pom.xml rename to core/pom.xml index 95794ff25..de264f908 100644 --- a/connector/pom.xml +++ b/core/pom.xml @@ -6,99 +6,59 @@ org.geysermc geyser-parent - 1.2.0-SNAPSHOT + 2.0.0-SNAPSHOT - connector + core + + + 4.9.3 + 8.5.2 + 2.12.4 + 4.1.66.Final + + + org.geysermc + ap + 2.0.0-SNAPSHOT + provided + + + org.geysermc + geyser-api + 2.0.0-SNAPSHOT + compile + org.geysermc common - 1.2.0-SNAPSHOT + 2.0.0-SNAPSHOT + compile + + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + compile + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + compile + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} compile com.fasterxml.jackson.dataformat jackson-dataformat-yaml - 2.9.8 - compile - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - 2.9.8 - compile - - - com.github.CloudburstMC.Protocol - bedrock-v419 - ce59d39118 - compile - - - net.sf.trove4j - trove - - - - - com.nukkitx.fastutil - fastutil-int-int-maps - 8.3.1 - compile - - - com.nukkitx.fastutil - fastutil-int-float-maps - 8.3.1 - compile - - - com.nukkitx.fastutil - fastutil-long-long-maps - 8.3.1 - compile - - - com.nukkitx.fastutil - fastutil-object-long-maps - 8.3.1 - compile - - - com.nukkitx.fastutil - fastutil-int-byte-maps - 8.3.1 - compile - - - com.nukkitx.fastutil - fastutil-int-double-maps - 8.3.1 - compile - - - com.nukkitx.fastutil - fastutil-int-boolean-maps - 8.3.1 - compile - - - com.nukkitx.fastutil - fastutil-object-int-maps - 8.3.1 - compile - - - com.nukkitx.fastutil - fastutil-object-byte-maps - 8.3.1 - compile - - - com.nukkitx.fastutil - fastutil-object-object-maps - 8.3.1 + ${jackson.version} compile @@ -107,52 +67,185 @@ 29.0-jre compile + + + com.nukkitx + nbt + + 2.1.0 + compile + + + com.nukkitx.fastutil + fastutil-int-int-maps + ${fastutil.version} + compile + + + com.nukkitx.fastutil + fastutil-long-long-maps + ${fastutil.version} + compile + + + com.nukkitx.fastutil + fastutil-int-byte-maps + ${fastutil.version} + compile + + + com.nukkitx.fastutil + fastutil-int-boolean-maps + ${fastutil.version} + compile + + + com.nukkitx.fastutil + fastutil-object-int-maps + ${fastutil.version} + compile + + + com.nukkitx.fastutil + fastutil-object-object-maps + ${fastutil.version} + compile + + + + org.java-websocket + Java-WebSocket + 1.5.1 + compile + + + com.github.CloudburstMC.Protocol + bedrock-v475 + c22aa595 + compile + + + com.nukkitx.network + raknet + + + com.nukkitx + nbt + + + + + com.nukkitx.network + raknet + 1.6.28-20211202.034102-5 + compile + + + io.netty + * + + + + + com.github.RednedEpic + MCAuthLib + 6c99331 + compile + + + com.github.GeyserMC + MCProtocolLib + c247b1f + compile + + + com.github.steveice10 + packetlib + + + com.github.steveice10 + mcauthlib + + + com.github.steveice10 - mcprotocollib - 86e1901be5 + packetlib + 2.1-SNAPSHOT compile io.netty netty-all + + + io.netty.incubator + netty-incubator-transport-native-io_uring + io.netty netty-resolver-dns - 4.1.43.Final + ${netty.version} compile - org.reflections - reflections - 0.9.11 + io.netty + netty-resolver-dns-native-macos + ${netty.version} + compile + osx-x86_64 - org.dom4j - dom4j - 2.1.3 + io.netty + netty-codec-haproxy + ${netty.version} + compile + - com.github.kyoripowered.adventure - adventure-api - 7acd956 + io.netty + netty-handler + ${netty.version} compile - com.github.kyoripowered.adventure - adventure-text-serializer-gson - 7acd956 + io.netty + netty-transport-native-epoll + ${netty.version} compile + linux-x86_64 - com.github.kyoripowered.adventure + io.netty + netty-transport-native-epoll + ${netty.version} + compile + linux-aarch_64 + + + io.netty + netty-transport-native-kqueue + ${netty.version} + compile + osx-x86_64 + + + + net.kyori adventure-text-serializer-legacy - 7acd956 + ${adventure.version} compile + + net.kyori + adventure-text-serializer-plain + ${adventure.version} + compile + + junit junit @@ -163,6 +256,16 @@ + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + **/services/javax.annotation.processing.Processor + + + pl.project13.maven git-commit-id-plugin @@ -211,12 +314,17 @@ - ${project.basedir}/src/main/java/org/geysermc/connector/GeyserConnector.java + ${project.basedir}/src/main/java/org/geysermc/geyser/GeyserImpl.java - VERSION = ".*" - VERSION = "${project.version} (git-${git.branch}-${git.commit.id.abbrev})" + String VERSION = ".*" + String VERSION = "${project.version} (" + GIT_VERSION + ")" + + + String GIT_VERSION = ".*" + + String GIT_VERSION = "git-${git.branch}-${git.commit.id.abbrev}" @@ -230,64 +338,22 @@ - ${project.basedir}/src/main/java/org/geysermc/connector/GeyserConnector.java + ${project.basedir}/src/main/java/org/geysermc/geyser/GeyserImpl.java - VERSION = ".*" - VERSION = "DEV" + String VERSION = ".*" + String VERSION = "DEV" + + + String GIT_VERSION = ".*" + String GIT_VERSION = "DEV" - - org.codehaus.gmavenplus - gmavenplus-plugin - 1.9.1 - - - process-classes - - execute - - - - - - - - - - - org.reflections - reflections - 0.9.11 - - - org.dom4j - dom4j - 2.1.3 - - - org.codehaus.groovy - groovy-all - 3.0.5 - runtime - pom - - - org.apache.maven.plugins maven-surefire-plugin diff --git a/core/src/main/java/org/geysermc/connector/GeyserConnector.java b/core/src/main/java/org/geysermc/connector/GeyserConnector.java new file mode 100644 index 000000000..0ecc40c7e --- /dev/null +++ b/core/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector; + +import com.nukkitx.protocol.bedrock.BedrockServer; +import org.geysermc.common.PlatformType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.api.Geyser; + +import java.util.UUID; + +/** + * Deprecated, please use {@link Geyser} or {@link GeyserImpl}. + * + * @deprecated legacy code + */ +@Deprecated +public class GeyserConnector { + public static final String NAME = GeyserImpl.NAME; + public static final String GIT_VERSION = GeyserImpl.GIT_VERSION; // A fallback for running in IDEs + public static final String VERSION = GeyserImpl.VERSION; // A fallback for running in IDEs + + public static final String OAUTH_CLIENT_ID = GeyserImpl.OAUTH_CLIENT_ID; + + private static final GeyserConnector INSTANCE = new GeyserConnector(); + + public static GeyserConnector getInstance() { + return INSTANCE; + } + + public BedrockServer getBedrockServer() { + return GeyserImpl.getInstance().getBedrockServer(); + } + + public boolean isShuttingDown() { + return GeyserImpl.getInstance().isShuttingDown(); + } + + public PlatformType getPlatformType() { + return GeyserImpl.getInstance().getPlatformType(); + } + + public void shutdown() { + GeyserImpl.getInstance().shutdown(); + } + + public void reload() { + GeyserImpl.getInstance().reload(); + } + + public GeyserSession getPlayerByXuid(String xuid) { + org.geysermc.geyser.session.GeyserSession session = GeyserImpl.getInstance().connectionByXuid(xuid); + if (session != null) { + return new GeyserSession(session); + } else { + return null; + } + } + + public GeyserSession getPlayerByUuid(UUID uuid) { + org.geysermc.geyser.session.GeyserSession session = GeyserImpl.getInstance().connectionByUuid(uuid); + if (session != null) { + return new GeyserSession(session); + } else { + return null; + } + } + + public boolean isProductionEnvironment() { + return GeyserImpl.getInstance().productionEnvironment(); + } +} diff --git a/core/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/core/src/main/java/org/geysermc/connector/network/session/GeyserSession.java new file mode 100644 index 000000000..05f9de722 --- /dev/null +++ b/core/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.session; + +import com.github.steveice10.packetlib.packet.Packet; +import com.nukkitx.protocol.bedrock.BedrockPacket; +import org.geysermc.connector.network.session.auth.AuthData; + +/** + * Deprecated, legacy code. Serves as a wrapper around + * the class used now. + * + * @deprecated legacy code + */ +@Deprecated +public class GeyserSession { + private final org.geysermc.geyser.session.GeyserSession handle; + + public GeyserSession(org.geysermc.geyser.session.GeyserSession handle) { + this.handle = handle; + } + + public AuthData getAuthData() { + return new AuthData(this.handle.getAuthData()); + } + + public boolean isMicrosoftAccount() { + return this.handle.isMicrosoftAccount(); + } + + public boolean isClosed() { + return this.handle.isClosed(); + } + + public String getRemoteAddress() { + return this.handle.getRemoteAddress(); + } + + public int getRemotePort() { + return this.handle.getRemotePort(); + } + + public int getRenderDistance() { + return this.handle.getRenderDistance(); + } + + public boolean isSentSpawnPacket() { + return this.handle.isSentSpawnPacket(); + } + + public boolean isLoggedIn() { + return this.handle.isLoggedIn(); + } + + public boolean isLoggingIn() { + return this.handle.isLoggingIn(); + } + + public boolean isSpawned() { + return this.handle.isSpawned(); + } + + public boolean isInteracting() { + return this.handle.isInteracting(); + } + + public boolean isCanFly() { + return this.handle.isCanFly(); + } + + public boolean isFlying() { + return this.handle.isFlying(); + } + + public void connect() { + this.handle.connect(); + } + + public void login() { + this.handle.login(); + } + + public void authenticate(String username) { + this.handle.authenticate(username); + } + + public void authenticate(String username, String password) { + this.handle.authenticate(username, password); + } + + public void authenticateWithMicrosoftCode() { + this.handle.authenticateWithMicrosoftCode(); + } + + public void disconnect(String reason) { + this.handle.disconnect(reason); + } + + public void close() { + this.handle.close(); + } + + public void executeInEventLoop(Runnable runnable) { + this.handle.executeInEventLoop(runnable); + } + + public String getName() { + return this.handle.name(); + } + + public boolean isConsole() { + return this.handle.isConsole(); + } + + public String getLocale() { + return this.handle.getLocale(); + } + + public void sendUpstreamPacket(BedrockPacket packet) { + this.handle.sendUpstreamPacket(packet); + } + + public void sendUpstreamPacketImmediately(BedrockPacket packet) { + this.handle.sendUpstreamPacketImmediately(packet); + } + + public void sendDownstreamPacket(Packet packet) { + this.handle.sendDownstreamPacket(packet); + } + + public boolean hasPermission(String permission) { + return this.handle.hasPermission(permission); + } + + public void sendAdventureSettings() { + this.handle.sendAdventureSettings(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java b/core/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java similarity index 68% rename from connector/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java rename to core/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java index a1245d906..3c9a5ee72 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java +++ b/core/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,16 +25,30 @@ package org.geysermc.connector.network.session.auth; -import lombok.AllArgsConstructor; -import lombok.Getter; - import java.util.UUID; -@Getter -@AllArgsConstructor +/** + * Deprecated, legacy code. Serves as a wrapper around + * the class used now. + * + * @deprecated legacy code + */ public class AuthData { + private final org.geysermc.geyser.session.auth.AuthData handle; - private String name; - private UUID UUID; - private String xboxUUID; + public AuthData(org.geysermc.geyser.session.auth.AuthData handle) { + this.handle = handle; + } + + public String getName() { + return this.handle.name(); + } + + public UUID getUUID() { + return this.handle.uuid(); + } + + public String getXboxUUID() { + return this.handle.xuid(); + } } diff --git a/core/src/main/java/org/geysermc/geyser/Constants.java b/core/src/main/java/org/geysermc/geyser/Constants.java new file mode 100644 index 000000000..23029d195 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/Constants.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser; + +import java.net.URI; +import java.net.URISyntaxException; + +public final class Constants { + public static final URI GLOBAL_API_WS_URI; + public static final String NTP_SERVER = "time.cloudflare.com"; + + public static final String NEWS_OVERVIEW_URL = "https://api.geysermc.org/v2/news/"; + public static final String NEWS_PROJECT_NAME = "geyser"; + + public static final String FLOODGATE_DOWNLOAD_LOCATION = "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/"; + + static { + URI wsUri = null; + try { + wsUri = new URI("wss://api.geysermc.org/ws"); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + GLOBAL_API_WS_URI = wsUri; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/FloodgateKeyLoader.java b/core/src/main/java/org/geysermc/geyser/FloodgateKeyLoader.java new file mode 100644 index 000000000..df7a049c6 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/FloodgateKeyLoader.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser; + +import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; +import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.text.GeyserLocale; + +import java.nio.file.Files; +import java.nio.file.Path; + +public class FloodgateKeyLoader { + public static Path getKeyPath(GeyserJacksonConfiguration config, Path floodgateDataFolder, Path geyserDataFolder, GeyserLogger logger) { + if (config.getRemote().getAuthType() != AuthType.FLOODGATE) { + return geyserDataFolder.resolve(config.getFloodgateKeyFile()); + } + + // Always prioritize Floodgate's key, if it is installed. + // This mostly prevents people from trying to copy the key and corrupting it in the process + if (floodgateDataFolder != null) { + Path autoKey = floodgateDataFolder.resolve("key.pem"); + if (Files.exists(autoKey)) { + logger.info(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.auto_loaded")); + return autoKey; + } else { + logger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.missing_key")); + } + } + + Path floodgateKey; + if (config.getFloodgateKeyFile().equals("public-key.pem")) { + logger.info("Floodgate 2.0 doesn't use a public/private key system anymore. We'll search for key.pem instead"); + floodgateKey = geyserDataFolder.resolve("key.pem"); + } else { + floodgateKey = geyserDataFolder.resolve(config.getFloodgateKeyFile()); + } + + if (!Files.exists(floodgateKey)) { + logger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed")); + } + + return floodgateKey; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/bootstrap/GeyserBootstrap.java b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java similarity index 53% rename from connector/src/main/java/org/geysermc/connector/bootstrap/GeyserBootstrap.java rename to core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java index b6a766a3b..8797d972a 100644 --- a/connector/src/main/java/org/geysermc/connector/bootstrap/GeyserBootstrap.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,17 +23,21 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.bootstrap; +package org.geysermc.geyser; -import org.geysermc.connector.dump.BootstrapDumpInfo; -import org.geysermc.connector.ping.IGeyserPingPassthrough; -import org.geysermc.connector.configuration.GeyserConfiguration; -import org.geysermc.connector.GeyserLogger; -import org.geysermc.connector.command.CommandManager; -import org.geysermc.connector.network.translators.world.GeyserWorldManager; -import org.geysermc.connector.network.translators.world.WorldManager; +import org.geysermc.geyser.command.CommandManager; +import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.dump.BootstrapDumpInfo; +import org.geysermc.geyser.level.GeyserWorldManager; +import org.geysermc.geyser.level.WorldManager; +import org.geysermc.geyser.ping.IGeyserPingPassthrough; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.InputStream; +import java.net.SocketAddress; import java.nio.file.Path; +import java.nio.file.Paths; public interface GeyserBootstrap { @@ -99,4 +103,52 @@ public interface GeyserBootstrap { * @return The info about the bootstrap */ BootstrapDumpInfo getDumpInfo(); + + /** + * Returns the Minecraft version currently being used on the server. This should be only be implemented on platforms + * that have direct server access - platforms such as proxies always have to be on their latest version to support + * the newest Minecraft version, but older servers can use ViaVersion to enable newer versions to join. + *
+ * If used, this should not be null before {@link GeyserImpl} initialization. + * + * @return the Minecraft version being used on the server, or null if not applicable + */ + @Nullable + default String getMinecraftServerVersion() { + return null; + } + + @Nullable + default SocketAddress getSocketAddress() { + return null; + } + + default Path getLogsPath() { + return Paths.get("logs/latest.log"); + } + + /** + * Get an InputStream for the given resource path. + * Overridden on platforms that have different class loader properties. + * + * @param resource Resource to get + * @return InputStream of the given resource, or null if not found + */ + default @Nullable InputStream getResourceOrNull(String resource) { + return GeyserBootstrap.class.getClassLoader().getResourceAsStream(resource); + } + + /** + * Get an InputStream for the given resource path, throws AssertionError if resource is not found. + * + * @param resource Resource to get + * @return InputStream of the given resource + */ + default @Nonnull InputStream getResource(String resource) { + InputStream stream = getResourceOrNull(resource); + if (stream == null) { + throw new AssertionError("Unable to find resource: " + resource); + } + return stream; + } } diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java new file mode 100644 index 000000000..bac5e0735 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -0,0 +1,494 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.steveice10.packetlib.tcp.TcpSession; +import com.nukkitx.network.raknet.RakNetConstants; +import com.nukkitx.network.util.EventLoops; +import com.nukkitx.protocol.bedrock.BedrockServer; +import io.netty.channel.epoll.Epoll; +import io.netty.channel.kqueue.KQueue; +import io.netty.util.NettyRuntime; +import io.netty.util.concurrent.DefaultThreadFactory; +import io.netty.util.internal.SystemPropertyUtil; +import lombok.Getter; +import lombok.Setter; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.api.Geyser; +import org.geysermc.common.PlatformType; +import org.geysermc.floodgate.crypto.AesCipher; +import org.geysermc.floodgate.crypto.AesKeyProducer; +import org.geysermc.floodgate.crypto.Base64Topping; +import org.geysermc.floodgate.crypto.FloodgateCipher; +import org.geysermc.floodgate.news.NewsItemAction; +import org.geysermc.geyser.api.GeyserApi; +import org.geysermc.geyser.command.CommandManager; +import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.level.WorldManager; +import org.geysermc.geyser.network.ConnectorServerEventHandler; +import org.geysermc.geyser.pack.ResourcePack; +import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.scoreboard.ScoreboardUpdater; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.SessionManager; +import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.skin.FloodgateSkinUploader; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.text.MinecraftLocale; +import org.geysermc.geyser.translator.inventory.item.ItemTranslator; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.geyser.util.*; + +import javax.naming.directory.Attribute; +import javax.naming.directory.InitialDirContext; +import java.io.InputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.security.Key; +import java.text.DecimalFormat; +import java.util.*; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Getter +public class GeyserImpl implements GeyserApi { + public static final ObjectMapper JSON_MAPPER = new ObjectMapper() + .enable(JsonParser.Feature.IGNORE_UNDEFINED) + .enable(JsonParser.Feature.ALLOW_COMMENTS) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES) + .enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES); + + public static final String NAME = "Geyser"; + public static final String GIT_VERSION = "DEV"; // A fallback for running in IDEs + public static final String VERSION = "DEV"; // A fallback for running in IDEs + + /** + * Oauth client ID for Microsoft authentication + */ + public static final String OAUTH_CLIENT_ID = "204cefd1-4818-4de1-b98d-513fae875d88"; + + private static final String IP_REGEX = "\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b"; + + private final SessionManager sessionManager = new SessionManager(); + + /** + * This is used in GeyserConnect to stop the bedrock server binding to a port + */ + @Setter + private static boolean shouldStartListener = true; + + private FloodgateCipher cipher; + private FloodgateSkinUploader skinUploader; + private final NewsHandler newsHandler; + + private volatile boolean shuttingDown = false; + + private final ScheduledExecutorService scheduledThread; + + private final BedrockServer bedrockServer; + private final PlatformType platformType; + private final GeyserBootstrap bootstrap; + + private final Metrics metrics; + + private static GeyserImpl instance; + + private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) { + long startupTime = System.currentTimeMillis(); + + this.bootstrap = bootstrap; + + instance = this; + + Geyser.set(this); + + GeyserLogger logger = bootstrap.getGeyserLogger(); + GeyserConfiguration config = bootstrap.getGeyserConfig(); + + this.platformType = platformType; + + GeyserLocale.finalizeDefaultLocale(this); + + logger.info("******************************************"); + logger.info(""); + logger.info(GeyserLocale.getLocaleStringLog("geyser.core.load", NAME, VERSION)); + logger.info(""); + logger.info("******************************************"); + + this.scheduledThread = Executors.newSingleThreadScheduledExecutor(new DefaultThreadFactory("Geyser Scheduled Thread")); + + logger.setDebug(config.isDebugMode()); + + /* Initialize translators and registries */ + BlockRegistries.init(); + Registries.init(); + + EntityDefinitions.init(); + ItemTranslator.init(); + MessageTranslator.init(); + MinecraftLocale.init(); + ScoreboardUpdater.init(); + + ResourcePack.loadPacks(); + + if (platformType != PlatformType.STANDALONE && config.getRemote().getAddress().equals("auto")) { + // Set the remote address to localhost since that is where we are always connecting + try { + config.getRemote().setAddress(InetAddress.getLocalHost().getHostAddress()); + } catch (UnknownHostException ex) { + logger.debug("Unknown host when trying to find localhost."); + if (config.isDebugMode()) { + ex.printStackTrace(); + } + config.getRemote().setAddress(InetAddress.getLoopbackAddress().getHostAddress()); + } + } + String remoteAddress = config.getRemote().getAddress(); + // Filters whether it is not an IP address or localhost, because otherwise it is not possible to find out an SRV entry. + if (!remoteAddress.matches(IP_REGEX) && !remoteAddress.equalsIgnoreCase("localhost")) { + int remotePort; + try { + // Searches for a server address and a port from a SRV record of the specified host name + InitialDirContext ctx = new InitialDirContext(); + Attribute attr = ctx.getAttributes("dns:///_minecraft._tcp." + remoteAddress, new String[]{"SRV"}).get("SRV"); + // size > 0 = SRV entry found + if (attr != null && attr.size() > 0) { + String[] record = ((String) attr.get(0)).split(" "); + // Overwrites the existing address and port with that from the SRV record. + config.getRemote().setAddress(remoteAddress = record[3]); + config.getRemote().setPort(remotePort = Integer.parseInt(record[2])); + logger.debug("Found SRV record \"" + remoteAddress + ":" + remotePort + "\""); + } + } catch (Exception | NoClassDefFoundError ex) { // Check for a NoClassDefFoundError to prevent Android crashes + logger.debug("Exception while trying to find an SRV record for the remote host."); + if (config.isDebugMode()) + ex.printStackTrace(); // Otherwise we can get a stack trace for any domain that doesn't have an SRV record + } + } + + // Ensure that PacketLib does not create an event loop for handling packets; we'll do that ourselves + TcpSession.USE_EVENT_LOOP_FOR_PACKETS = false; + + if (config.getRemote().getAuthType() == AuthType.FLOODGATE) { + try { + Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyPath()); + cipher = new AesCipher(new Base64Topping()); + cipher.init(key); + logger.info(GeyserLocale.getLocaleStringLog("geyser.auth.floodgate.loaded_key")); + skinUploader = new FloodgateSkinUploader(this).start(); + } catch (Exception exception) { + logger.severe(GeyserLocale.getLocaleStringLog("geyser.auth.floodgate.bad_key"), exception); + } + } + + String branch = "unknown"; + int buildNumber = -1; + if (this.productionEnvironment()) { + try (InputStream stream = bootstrap.getResource("git.properties")) { + Properties gitProperties = new Properties(); + gitProperties.load(stream); + branch = gitProperties.getProperty("git.branch"); + String build = gitProperties.getProperty("git.build.number"); + if (build != null) { + buildNumber = Integer.parseInt(build); + } + } catch (Throwable e) { + logger.error("Failed to read git.properties", e); + } + } else { + logger.debug("Not getting git properties for the news handler as we are in a development environment."); + } + newsHandler = new NewsHandler(branch, buildNumber); + + CooldownUtils.setDefaultShowCooldown(config.getShowCooldown()); + DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether + + // https://github.com/GeyserMC/Geyser/issues/957 + RakNetConstants.MAXIMUM_MTU_SIZE = (short) config.getMtu(); + logger.debug("Setting MTU to " + config.getMtu()); + + Integer bedrockThreadCount = Integer.getInteger("Geyser.BedrockNetworkThreads"); + if (bedrockThreadCount == null) { + // Copy the code from Netty's default thread count fallback + bedrockThreadCount = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2)); + } + + boolean enableProxyProtocol = config.getBedrock().isEnableProxyProtocol(); + bedrockServer = new BedrockServer( + new InetSocketAddress(config.getBedrock().getAddress(), config.getBedrock().getPort()), + bedrockThreadCount, + EventLoops.commonGroup(), + enableProxyProtocol + ); + + if (config.isDebugMode()) { + logger.debug("EventLoop type: " + EventLoops.getChannelType()); + if (EventLoops.getChannelType() == EventLoops.ChannelType.NIO) { + if (System.getProperties().contains("disableNativeEventLoop")) { + logger.debug("EventLoop type is NIO because native event loops are disabled."); + } else { + logger.debug("Reason for no Epoll: " + Epoll.unavailabilityCause().toString()); + logger.debug("Reason for no KQueue: " + KQueue.unavailabilityCause().toString()); + } + } + } + + bedrockServer.setHandler(new ConnectorServerEventHandler(this)); + + if (shouldStartListener) { + bedrockServer.bind().whenComplete((avoid, throwable) -> { + if (throwable == null) { + logger.info(GeyserLocale.getLocaleStringLog("geyser.core.start", config.getBedrock().getAddress(), String.valueOf(config.getBedrock().getPort()))); + } else { + logger.severe(GeyserLocale.getLocaleStringLog("geyser.core.fail", config.getBedrock().getAddress(), String.valueOf(config.getBedrock().getPort()))); + throwable.printStackTrace(); + } + }).join(); + } + + if (config.getMetrics().isEnabled()) { + metrics = new Metrics(this, "GeyserMC", config.getMetrics().getUniqueId(), false, java.util.logging.Logger.getLogger("")); + metrics.addCustomChart(new Metrics.SingleLineChart("players", sessionManager::size)); + // Prevent unwanted words best we can + metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> config.getRemote().getAuthType().toString().toLowerCase())); + metrics.addCustomChart(new Metrics.SimplePie("platform", platformType::getPlatformName)); + metrics.addCustomChart(new Metrics.SimplePie("defaultLocale", GeyserLocale::getDefaultLocale)); + metrics.addCustomChart(new Metrics.SimplePie("version", () -> GeyserImpl.VERSION)); + metrics.addCustomChart(new Metrics.AdvancedPie("playerPlatform", () -> { + Map valueMap = new HashMap<>(); + for (GeyserSession session : sessionManager.getAllSessions()) { + if (session == null) continue; + if (session.getClientData() == null) continue; + String os = session.getClientData().getDeviceOs().toString(); + if (!valueMap.containsKey(os)) { + valueMap.put(os, 1); + } else { + valueMap.put(os, valueMap.get(os) + 1); + } + } + return valueMap; + })); + metrics.addCustomChart(new Metrics.AdvancedPie("playerVersion", () -> { + Map valueMap = new HashMap<>(); + for (GeyserSession session : sessionManager.getAllSessions()) { + if (session == null) continue; + if (session.getClientData() == null) continue; + String version = session.getClientData().getGameVersion(); + if (!valueMap.containsKey(version)) { + valueMap.put(version, 1); + } else { + valueMap.put(version, valueMap.get(version) + 1); + } + } + return valueMap; + })); + + String minecraftVersion = bootstrap.getMinecraftServerVersion(); + if (minecraftVersion != null) { + Map> versionMap = new HashMap<>(); + Map platformMap = new HashMap<>(); + platformMap.put(platformType.getPlatformName(), 1); + versionMap.put(minecraftVersion, platformMap); + + metrics.addCustomChart(new Metrics.DrilldownPie("minecraftServerVersion", () -> { + // By the end, we should return, for example: + // 1.16.5 => (Spigot, 1) + return versionMap; + })); + } + + // The following code can be attributed to the PaperMC project + // https://github.com/PaperMC/Paper/blob/master/Spigot-Server-Patches/0005-Paper-Metrics.patch#L614 + metrics.addCustomChart(new Metrics.DrilldownPie("javaVersion", () -> { + Map> map = new HashMap<>(); + String javaVersion = System.getProperty("java.version"); + Map entry = new HashMap<>(); + entry.put(javaVersion, 1); + + // http://openjdk.java.net/jeps/223 + // Java decided to change their versioning scheme and in doing so modified the + // java.version system property to return $major[.$minor][.$security][-ea], as opposed to + // 1.$major.0_$identifier we can handle pre-9 by checking if the "major" is equal to "1", + // otherwise, 9+ + String majorVersion = javaVersion.split("\\.")[0]; + String release; + + int indexOf = javaVersion.lastIndexOf('.'); + + if (majorVersion.equals("1")) { + release = "Java " + javaVersion.substring(0, indexOf); + } else { + // of course, it really wouldn't be all that simple if they didn't add a quirk, now + // would it valid strings for the major may potentially include values such as -ea to + // denote a pre release + Matcher versionMatcher = Pattern.compile("\\d+").matcher(majorVersion); + if (versionMatcher.find()) { + majorVersion = versionMatcher.group(0); + } + release = "Java " + majorVersion; + } + map.put(release, entry); + return map; + })); + } else { + metrics = null; + } + + boolean isGui = false; + // This will check if we are in standalone and get the 'useGui' variable from there + if (platformType == PlatformType.STANDALONE) { + try { + Class cls = Class.forName("org.geysermc.geyser.platform.standalone.GeyserStandaloneBootstrap"); + isGui = (boolean) cls.getMethod("isUseGui").invoke(cls.cast(bootstrap)); + } catch (Exception e) { + logger.debug("Failed detecting if standalone is using a GUI; if this is a GeyserConnect instance this can be safely ignored."); + } + } + + double completeTime = (System.currentTimeMillis() - startupTime) / 1000D; + String message = GeyserLocale.getLocaleStringLog("geyser.core.finish.done", new DecimalFormat("#.###").format(completeTime)) + " "; + if (isGui) { + message += GeyserLocale.getLocaleStringLog("geyser.core.finish.gui"); + } else { + message += GeyserLocale.getLocaleStringLog("geyser.core.finish.console"); + } + logger.info(message); + + if (platformType == PlatformType.STANDALONE) { + logger.warning(GeyserLocale.getLocaleStringLog("geyser.core.movement_warn")); + } else if (config.getRemote().getAuthType() == AuthType.FLOODGATE) { + VersionCheckUtils.checkForOutdatedFloodgate(logger); + } + + newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED); + } + + @Override + public @Nullable GeyserSession connectionByName(@NonNull String name) { + for (GeyserSession session : sessionManager.getAllSessions()) { + if (session.name().equals(name) || session.getProtocol().getProfile().getName().equals(name)) { + return session; + } + } + + return null; + } + + @Override + public @NonNull List onlineConnections() { + return this.sessionManager.getAllSessions(); + } + + @Override + public @Nullable GeyserSession connectionByUuid(@NonNull UUID uuid) { + return this.sessionManager.getSessions().get(uuid); + } + + @Override + public @Nullable GeyserSession connectionByXuid(@NonNull String xuid) { + for (GeyserSession session : sessionManager.getAllSessions()) { + if (session.xuid().equals(xuid)) { + return session; + } + } + + return null; + } + + @Override + public void shutdown() { + bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown")); + shuttingDown = true; + + if (sessionManager.size() >= 1) { + bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.kick.log", sessionManager.size())); + sessionManager.disconnectAll("geyser.core.shutdown.kick.message"); + bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.kick.done")); + } + + scheduledThread.shutdown(); + bedrockServer.close(); + if (skinUploader != null) { + skinUploader.close(); + } + newsHandler.shutdown(); + this.getCommandManager().getCommands().clear(); + + bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done")); + } + + @Override + public void reload() { + shutdown(); + bootstrap.onEnable(); + } + + /** + * Returns false if this Geyser instance is running in an IDE. This only needs to be used in cases where files + * expected to be in a jarfile are not present. + * + * @return true if the version number is not 'DEV'. + */ + @Override + public boolean productionEnvironment() { + //noinspection ConstantConditions - changes in production + return !"DEV".equals(GeyserImpl.VERSION); + } + + public static GeyserImpl start(PlatformType platformType, GeyserBootstrap bootstrap) { + return new GeyserImpl(platformType, bootstrap); + } + + public GeyserLogger getLogger() { + return bootstrap.getGeyserLogger(); + } + + public GeyserConfiguration getConfig() { + return bootstrap.getGeyserConfig(); + } + + public CommandManager getCommandManager() { + return bootstrap.getGeyserCommandManager(); + } + + public WorldManager getWorldManager() { + return bootstrap.getWorldManager(); + } + + public static GeyserImpl getInstance() { + return instance; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/GeyserLogger.java b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java similarity index 96% rename from connector/src/main/java/org/geysermc/connector/GeyserLogger.java rename to core/src/main/java/org/geysermc/geyser/GeyserLogger.java index 138285eb1..266132f63 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserLogger.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector; +package org.geysermc.geyser; public interface GeyserLogger { diff --git a/connector/src/main/java/org/geysermc/connector/common/main/IGeyserMain.java b/core/src/main/java/org/geysermc/geyser/GeyserMain.java similarity index 85% rename from connector/src/main/java/org/geysermc/connector/common/main/IGeyserMain.java rename to core/src/main/java/org/geysermc/geyser/GeyserMain.java index 66d110797..9ddbbbe86 100644 --- a/connector/src/main/java/org/geysermc/connector/common/main/IGeyserMain.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserMain.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.common.main; +package org.geysermc.geyser; import javax.swing.*; import java.io.InputStream; @@ -31,7 +31,7 @@ import java.lang.reflect.Method; import java.util.Locale; import java.util.Scanner; -public class IGeyserMain { +public class GeyserMain { /** * Displays the run help message in the console and a message box if running with a gui @@ -52,12 +52,12 @@ public class IGeyserMain { * @return The formatted message */ private String createMessage() { - String message = ""; + StringBuilder message = new StringBuilder(); - InputStream helpStream = IGeyserMain.class.getClassLoader().getResourceAsStream("languages/run-help/" + Locale.getDefault().toString() + ".txt"); + InputStream helpStream = GeyserMain.class.getClassLoader().getResourceAsStream("languages/run-help/" + Locale.getDefault().toString() + ".txt"); if (helpStream == null) { - helpStream = IGeyserMain.class.getClassLoader().getResourceAsStream("languages/run-help/en_US.txt"); + helpStream = GeyserMain.class.getClassLoader().getResourceAsStream("languages/run-help/en_US.txt"); } Scanner help = new Scanner(helpStream).useDelimiter("\\Z"); @@ -68,10 +68,10 @@ public class IGeyserMain { line = line.replace("${plugin_type}", this.getPluginType()); line = line.replace("${plugin_folder}", this.getPluginFolder()); - message += line + "\n"; + message.append(line).append("\n"); } - return message; + return message.toString(); } /** @@ -84,7 +84,8 @@ public class IGeyserMain { Class graphicsEnvironment = Class.forName("java.awt.GraphicsEnvironment"); Method isHeadless = graphicsEnvironment.getDeclaredMethod("isHeadless"); return (Boolean)isHeadless.invoke(null); - } catch (Exception ex) { } + } catch (Exception ignored) { + } return true; } diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandExecutor.java b/core/src/main/java/org/geysermc/geyser/command/CommandExecutor.java new file mode 100644 index 000000000..de66c2fe3 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/CommandExecutor.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.command; + +import lombok.AllArgsConstructor; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.session.GeyserSession; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Represents helper functions for listening to {@code /geyser} commands. + */ +@AllArgsConstructor +public class CommandExecutor { + + protected final GeyserImpl geyser; + + public GeyserCommand getCommand(String label) { + return geyser.getCommandManager().getCommands().get(label); + } + + @Nullable + public GeyserSession getGeyserSession(CommandSender sender) { + if (sender.isConsole()) { + return null; + } + + for (GeyserSession session : geyser.getSessionManager().getSessions().values()) { + if (sender.name().equals(session.getPlayerEntity().getUsername())) { + return session; + } + } + return null; + } + + /** + * Determine which subcommands to suggest in the tab complete for the main /geyser command by a given command sender. + * + * @param sender The command sender to receive the tab complete suggestions. + * If the command sender is a bedrock player, an empty list will be returned as bedrock players do not get command argument suggestions. + * If the command sender is not a bedrock player, bedrock commands will not be shown. + * If the command sender does not have the permission for a given command, the command will not be shown. + * @return A list of command names to include in the tab complete + */ + public List tabComplete(CommandSender sender) { + if (getGeyserSession(sender) != null) { + // Bedrock doesn't get tab completions or argument suggestions + return Collections.emptyList(); + } + + List availableCommands = new ArrayList<>(); + Map commands = geyser.getCommandManager().getCommands(); + + // Only show commands they have permission to use + for (Map.Entry entry : commands.entrySet()) { + GeyserCommand geyserCommand = entry.getValue(); + if (sender.hasPermission(geyserCommand.getPermission())) { + + if (geyserCommand.isBedrockOnly()) { + // Don't show commands the JE player can't run + continue; + } + + availableCommands.add(entry.getKey()); + } + } + + return availableCommands; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandManager.java b/core/src/main/java/org/geysermc/geyser/command/CommandManager.java new file mode 100644 index 000000000..5e4fe3dd5 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/CommandManager.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.command; + +import lombok.Getter; + +import org.geysermc.common.PlatformType; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.defaults.*; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.GeyserLocale; + +import java.util.*; + +public abstract class CommandManager { + + @Getter + private final Map commands = new HashMap<>(); + + private final GeyserImpl geyser; + + public CommandManager(GeyserImpl geyser) { + this.geyser = geyser; + + registerCommand(new HelpCommand(geyser, "help", "geyser.commands.help.desc", "geyser.command.help")); + registerCommand(new ListCommand(geyser, "list", "geyser.commands.list.desc", "geyser.command.list")); + registerCommand(new ReloadCommand(geyser, "reload", "geyser.commands.reload.desc", "geyser.command.reload")); + registerCommand(new OffhandCommand(geyser, "offhand", "geyser.commands.offhand.desc", "geyser.command.offhand")); + registerCommand(new DumpCommand(geyser, "dump", "geyser.commands.dump.desc", "geyser.command.dump")); + registerCommand(new VersionCommand(geyser, "version", "geyser.commands.version.desc", "geyser.command.version")); + registerCommand(new SettingsCommand(geyser, "settings", "geyser.commands.settings.desc", "geyser.command.settings")); + registerCommand(new StatisticsCommand(geyser, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics")); + registerCommand(new AdvancementsCommand("advancements", "geyser.commands.advancements.desc", "geyser.command.advancements")); + registerCommand(new AdvancedTooltipsCommand("tooltips", "geyser.commands.advancedtooltips.desc", "geyser.command.tooltips")); + if (GeyserImpl.getInstance().getPlatformType() == PlatformType.STANDALONE) { + registerCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop")); + } + } + + public void registerCommand(GeyserCommand command) { + commands.put(command.getName(), command); + geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.commands.registered", command.getName())); + + if (command.getAliases().isEmpty()) + return; + + for (String alias : command.getAliases()) + commands.put(alias, command); + } + + public void runCommand(CommandSender sender, String command) { + if (!command.startsWith("geyser ")) + return; + + command = command.trim().replace("geyser ", ""); + String label; + String[] args; + + if (!command.contains(" ")) { + label = command.toLowerCase(); + args = new String[0]; + } else { + label = command.substring(0, command.indexOf(" ")).toLowerCase(); + String argLine = command.substring(command.indexOf(" ") + 1); + args = argLine.contains(" ") ? argLine.split(" ") : new String[] { argLine }; + } + + GeyserCommand cmd = commands.get(label); + if (cmd == null) { + geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.invalid")); + return; + } + + if (sender instanceof GeyserSession) { + cmd.execute((GeyserSession) sender, sender, args); + } else { + if (!cmd.isBedrockOnly()) { + cmd.execute(null, sender, args); + } else { + geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.bedrock_only")); + } + } + } + + /** + * @return a list of all subcommands under {@code /geyser}. + */ + public List getCommandNames() { + return Arrays.asList(geyser.getCommandManager().getCommands().keySet().toArray(new String[0])); + } + + /** + * Returns the description of the given command + * + * @param command Command to get the description for + * @return Command description + */ + public abstract String getDescription(String command); +} diff --git a/connector/src/main/java/org/geysermc/connector/command/CommandSender.java b/core/src/main/java/org/geysermc/geyser/command/CommandSender.java similarity index 77% rename from connector/src/main/java/org/geysermc/connector/command/CommandSender.java rename to core/src/main/java/org/geysermc/geyser/command/CommandSender.java index 9c398a2a2..962883e3d 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandSender.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandSender.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,9 +23,9 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.command; +package org.geysermc.geyser.command; -import org.geysermc.connector.utils.LanguageUtils; +import org.geysermc.geyser.text.GeyserLocale; /** * Implemented on top of any class that can send a command. @@ -33,7 +33,7 @@ import org.geysermc.connector.utils.LanguageUtils; */ public interface CommandSender { - String getName(); + String name(); default void sendMessage(String[] messages) { for (String message : messages) { @@ -49,11 +49,19 @@ public interface CommandSender { boolean isConsole(); /** - * Returns the locale of the command sender. Defaults to the default locale at {@link LanguageUtils#getDefaultLocale()}. + * Returns the locale of the command sender. Defaults to the default locale at {@link GeyserLocale#getDefaultLocale()}. * * @return the locale of the command sender. */ default String getLocale() { - return LanguageUtils.getDefaultLocale(); + return GeyserLocale.getDefaultLocale(); } + + /** + * Checks if the CommandSender has a permission + * + * @param permission The permission node to check + * @return true if the CommandSender has the requested permission, false if not + */ + boolean hasPermission(String permission); } diff --git a/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java similarity index 81% rename from connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java rename to core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java index adbebd0cd..6971ad44d 100644 --- a/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,12 +23,14 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.command; +package org.geysermc.geyser.command; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; +import org.geysermc.geyser.session.GeyserSession; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -47,7 +49,7 @@ public abstract class GeyserCommand { @Setter private List aliases = new ArrayList<>(); - public abstract void execute(CommandSender sender, String[] args); + public abstract void execute(@Nullable GeyserSession session, CommandSender sender, String[] args); /** * If false, hides the command from being shown on the Geyser Standalone GUI. @@ -75,4 +77,13 @@ public abstract class GeyserCommand { public boolean hasSubCommands() { return !getSubCommands().isEmpty(); } + + /** + * Used to send a deny message to Java players if this command can only be used by Bedrock players. + * + * @return true if this command can only be used by Bedrock players. + */ + public boolean isBedrockOnly() { + return false; + } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java new file mode 100644 index 000000000..edd4691e3 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.command.defaults; + +import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.MinecraftLocale; + +public class AdvancedTooltipsCommand extends GeyserCommand { + public AdvancedTooltipsCommand(String name, String description, String permission) { + super(name, description, permission); + } + + @Override + public void execute(GeyserSession session, CommandSender sender, String[] args) { + if (session != null) { + String onOrOff = session.isAdvancedTooltips() ? "off" : "on"; + session.setAdvancedTooltips(!session.isAdvancedTooltips()); + session.sendMessage("§l§e" + MinecraftLocale.getLocaleString("debug.prefix", session.getLocale()) + " §r" + MinecraftLocale.getLocaleString("debug.advanced_tooltips." + onOrOff, session.getLocale())); + session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory()); + } + } + + @Override + public boolean isExecutableOnConsole() { + return false; + } + + @Override + public boolean isBedrockOnly() { + return true; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java new file mode 100644 index 000000000..41bb8e6cd --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.command.defaults; + +import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.session.GeyserSession; + +public class AdvancementsCommand extends GeyserCommand { + public AdvancementsCommand(String name, String description, String permission) { + super(name, description, permission); + } + + @Override + public void execute(GeyserSession session, CommandSender sender, String[] args) { + if (session != null) { + session.getAdvancementsCache().buildAndShowMenuForm(); + } + } + + @Override + public boolean isExecutableOnConsole() { + return false; + } + + @Override + public boolean isBedrockOnly() { + return true; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java new file mode 100644 index 000000000..1b69f48fb --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.command.defaults; + +import com.fasterxml.jackson.core.util.DefaultIndenter; +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.geysermc.common.PlatformType; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.text.ChatColor; +import org.geysermc.geyser.text.AsteriskSerializer; +import org.geysermc.geyser.dump.DumpInfo; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.util.WebUtils; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +public class DumpCommand extends GeyserCommand { + + private final GeyserImpl geyser; + private static final ObjectMapper MAPPER = new ObjectMapper(); + private static final String DUMP_URL = "https://dump.geysermc.org/"; + + public DumpCommand(GeyserImpl geyser, String name, String description, String permission) { + super(name, description, permission); + + this.geyser = geyser; + } + + @Override + public void execute(GeyserSession session, CommandSender sender, String[] args) { + // Only allow the console to create dumps on Geyser Standalone + if (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE) { + sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.getLocale())); + return; + } + + boolean showSensitive = false; + boolean offlineDump = false; + boolean addLog = false; + if (args.length >= 1) { + for (String arg : args) { + switch (arg) { + case "full" -> showSensitive = true; + case "offline" -> offlineDump = true; + case "logs" -> addLog = true; + } + } + } + + AsteriskSerializer.showSensitive = showSensitive; + + sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collecting", sender.getLocale())); + String dumpData; + try { + if (offlineDump) { + DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter(); + // Make arrays easier to read + prettyPrinter.indentArraysWith(new DefaultIndenter(" ", "\n")); + dumpData = MAPPER.writer(prettyPrinter).writeValueAsString(new DumpInfo(addLog)); + } else { + dumpData = MAPPER.writeValueAsString(new DumpInfo(addLog)); + } + } catch (IOException e) { + sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collect_error", sender.getLocale())); + geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.collect_error_short"), e); + return; + } + + String uploadedDumpUrl = ""; + + if (offlineDump) { + sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.writing", sender.getLocale())); + + try { + FileOutputStream outputStream = new FileOutputStream(GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("dump.json").toFile()); + outputStream.write(dumpData.getBytes()); + outputStream.close(); + } catch (IOException e) { + sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.write_error", sender.getLocale())); + geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.write_error_short"), e); + return; + } + + uploadedDumpUrl = "dump.json"; + } else { + sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.uploading", sender.getLocale())); + + String response; + JsonNode responseNode; + try { + response = WebUtils.post(DUMP_URL + "documents", dumpData); + responseNode = MAPPER.readTree(response); + } catch (IOException e) { + sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error", sender.getLocale())); + geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.upload_error_short"), e); + return; + } + + if (!responseNode.has("key")) { + sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error_short", sender.getLocale()) + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response)); + return; + } + + uploadedDumpUrl = DUMP_URL + responseNode.get("key").asText(); + } + + sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.message", sender.getLocale()) + " " + ChatColor.DARK_AQUA + uploadedDumpUrl); + if (!sender.isConsole()) { + geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.commands.dump.created", sender.name(), uploadedDumpUrl)); + } + } + + @Override + public List getSubCommands() { + return Arrays.asList("offline", "full", "logs"); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java new file mode 100644 index 000000000..633912300 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.command.defaults; + +import org.geysermc.common.PlatformType; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.text.ChatColor; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.GeyserLocale; + +import java.util.Collections; +import java.util.Map; + +public class HelpCommand extends GeyserCommand { + private final GeyserImpl geyser; + + public HelpCommand(GeyserImpl geyser, String name, String description, String permission) { + super(name, description, permission); + this.geyser = geyser; + + this.setAliases(Collections.singletonList("?")); + } + + /** + * Sends the help menu to a command sender. Will not show certain commands depending on the command sender and session. + * + * @param session The Geyser session of the command sender, if it is a bedrock player. If null, bedrock-only commands will be hidden. + * @param sender The CommandSender to send the help message to. + * @param args Not used. + */ + @Override + public void execute(GeyserSession session, CommandSender sender, String[] args) { + int page = 1; + int maxPage = 1; + String header = GeyserLocale.getPlayerLocaleString("geyser.commands.help.header", sender.getLocale(), page, maxPage); + sender.sendMessage(header); + + Map cmds = geyser.getCommandManager().getCommands(); + for (Map.Entry entry : cmds.entrySet()) { + GeyserCommand cmd = entry.getValue(); + + // Standalone hack-in since it doesn't have a concept of permissions + if (geyser.getPlatformType() == PlatformType.STANDALONE || sender.hasPermission(cmd.getPermission())) { + // Only list commands the player can actually run + if (cmd.isBedrockOnly() && session == null) { + continue; + } + + sender.sendMessage(ChatColor.YELLOW + "/geyser " + entry.getKey() + ChatColor.WHITE + ": " + + GeyserLocale.getPlayerLocaleString(cmd.getDescription(), sender.getLocale())); + } + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/ListCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java similarity index 59% rename from connector/src/main/java/org/geysermc/connector/command/defaults/ListCommand.java rename to core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java index 255c6a9b6..3ff648570 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/ListCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,32 +23,31 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.command.defaults; +package org.geysermc.geyser.command.defaults; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.CommandSender; -import org.geysermc.connector.command.GeyserCommand; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.utils.LanguageUtils; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.GeyserLocale; import java.util.stream.Collectors; public class ListCommand extends GeyserCommand { - private final GeyserConnector connector; + private final GeyserImpl geyser; - public ListCommand(GeyserConnector connector, String name, String description, String permission) { + public ListCommand(GeyserImpl geyser, String name, String description, String permission) { super(name, description, permission); - this.connector = connector; + this.geyser = geyser; } @Override - public void execute(CommandSender sender, String[] args) { - String message = ""; - message = LanguageUtils.getPlayerLocaleString("geyser.commands.list.message", sender.getLocale(), - connector.getPlayers().size(), - connector.getPlayers().stream().map(GeyserSession::getName).collect(Collectors.joining(" "))); + public void execute(GeyserSession session, CommandSender sender, String[] args) { + String message = GeyserLocale.getPlayerLocaleString("geyser.commands.list.message", sender.getLocale(), + geyser.getSessionManager().size(), + geyser.getSessionManager().getAllSessions().stream().map(GeyserSession::name).collect(Collectors.joining(" "))); sender.sendMessage(message); } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java new file mode 100644 index 000000000..934cd8c87 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.command.defaults; + +import com.github.steveice10.mc.protocol.data.game.entity.object.Direction; +import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.BlockUtils; + +public class OffhandCommand extends GeyserCommand { + + public OffhandCommand(GeyserImpl geyser, String name, String description, String permission) { + super(name, description, permission); + } + + @Override + public void execute(GeyserSession session, CommandSender sender, String[] args) { + if (session == null) { + return; + } + + ServerboundPlayerActionPacket releaseItemPacket = new ServerboundPlayerActionPacket(PlayerAction.SWAP_HANDS, BlockUtils.POSITION_ZERO, + Direction.DOWN); + session.sendDownstreamPacket(releaseItemPacket); + } + + @Override + public boolean isExecutableOnConsole() { + return false; + } + + @Override + public boolean isBedrockOnly() { + return true; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java similarity index 57% rename from connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java rename to core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java index 64d0017ec..1a72e1734 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,37 +23,35 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.command.defaults; +package org.geysermc.geyser.command.defaults; import org.geysermc.common.PlatformType; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.CommandSender; -import org.geysermc.connector.command.GeyserCommand; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.utils.LanguageUtils; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.GeyserLocale; public class ReloadCommand extends GeyserCommand { - private GeyserConnector connector; + private final GeyserImpl geyser; - public ReloadCommand(GeyserConnector connector, String name, String description, String permission) { + public ReloadCommand(GeyserImpl geyser, String name, String description, String permission) { super(name, description, permission); - this.connector = connector; + this.geyser = geyser; } @Override - public void execute(CommandSender sender, String[] args) { - if (!sender.isConsole() && connector.getPlatformType() == PlatformType.STANDALONE) { + public void execute(GeyserSession session, CommandSender sender, String[] args) { + if (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE) { return; } - String message = LanguageUtils.getPlayerLocaleString("geyser.commands.reload.message", sender.getLocale()); + String message = GeyserLocale.getPlayerLocaleString("geyser.commands.reload.message", sender.getLocale()); sender.sendMessage(message); - for (GeyserSession session : connector.getPlayers()) { - session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.commands.reload.kick", session.getLocale())); - } - connector.reload(); + geyser.getSessionManager().disconnectAll("geyser.commands.reload.kick"); + geyser.reload(); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java new file mode 100644 index 000000000..349f7288b --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.command.defaults; + +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.SettingsUtils; + +public class SettingsCommand extends GeyserCommand { + public SettingsCommand(GeyserImpl geyser, String name, String description, String permission) { + super(name, description, permission); + } + + @Override + public void execute(GeyserSession session, CommandSender sender, String[] args) { + if (session != null) { + session.sendForm(SettingsUtils.buildForm(session)); + } + } + + @Override + public boolean isExecutableOnConsole() { + return false; + } + + @Override + public boolean isBedrockOnly() { + return true; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java new file mode 100644 index 000000000..c570770b7 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.command.defaults; + +import com.github.steveice10.mc.protocol.data.game.ClientCommand; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundClientCommandPacket; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.session.GeyserSession; + +public class StatisticsCommand extends GeyserCommand { + + public StatisticsCommand(GeyserImpl geyser, String name, String description, String permission) { + super(name, description, permission); + } + + @Override + public void execute(GeyserSession session, CommandSender sender, String[] args) { + if (session == null) return; + + session.setWaitingForStatistics(true); + ServerboundClientCommandPacket ServerboundClientCommandPacket = new ServerboundClientCommandPacket(ClientCommand.STATS); + session.sendDownstreamPacket(ServerboundClientCommandPacket); + } + + @Override + public boolean isExecutableOnConsole() { + return false; + } + + @Override + public boolean isBedrockOnly() { + return true; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java similarity index 62% rename from connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java rename to core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java index 5be6253eb..6b74f9c8b 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,32 +23,35 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.command.defaults; +package org.geysermc.geyser.command.defaults; import org.geysermc.common.PlatformType; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.CommandSender; -import org.geysermc.connector.command.GeyserCommand; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.GeyserLocale; import java.util.Collections; public class StopCommand extends GeyserCommand { - private GeyserConnector connector; + private final GeyserImpl geyser; - public StopCommand(GeyserConnector connector, String name, String description, String permission) { + public StopCommand(GeyserImpl geyser, String name, String description, String permission) { super(name, description, permission); - this.connector = connector; + this.geyser = geyser; this.setAliases(Collections.singletonList("shutdown")); } @Override - public void execute(CommandSender sender, String[] args) { - if (!sender.isConsole() && connector.getPlatformType() == PlatformType.STANDALONE) { + public void execute(GeyserSession session, CommandSender sender, String[] args) { + if (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE) { + sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.getLocale())); return; } - connector.getBootstrap().onDisable(); + geyser.getBootstrap().onDisable(); } -} +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java new file mode 100644 index 000000000..4dd93ee74 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.command.defaults; + +import com.nukkitx.protocol.bedrock.BedrockPacketCodec; +import org.geysermc.common.PlatformType; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.ChatColor; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.util.WebUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Properties; + +public class VersionCommand extends GeyserCommand { + + private final GeyserImpl geyser; + + public VersionCommand(GeyserImpl geyser, String name, String description, String permission) { + super(name, description, permission); + + this.geyser = geyser; + } + + @Override + public void execute(GeyserSession session, CommandSender sender, String[] args) { + String bedrockVersions; + List supportedCodecs = MinecraftProtocol.SUPPORTED_BEDROCK_CODECS; + if (supportedCodecs.size() > 1) { + bedrockVersions = supportedCodecs.get(0).getMinecraftVersion() + " - " + supportedCodecs.get(supportedCodecs.size() - 1).getMinecraftVersion(); + } else { + bedrockVersions = MinecraftProtocol.SUPPORTED_BEDROCK_CODECS.get(0).getMinecraftVersion(); + } + String javaVersions; + List supportedJavaVersions = MinecraftProtocol.getJavaVersions(); + if (supportedJavaVersions.size() > 1) { + javaVersions = supportedJavaVersions.get(0) + " - " + supportedJavaVersions.get(supportedJavaVersions.size() - 1); + } else { + javaVersions = supportedJavaVersions.get(0); + } + + sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.version", sender.getLocale(), + GeyserImpl.NAME, GeyserImpl.VERSION, javaVersions, bedrockVersions)); + + // Disable update checking in dev mode and for players in Geyser Standalone + if (GeyserImpl.getInstance().productionEnvironment() && !(!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE)) { + sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.checking", sender.getLocale())); + try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource("git.properties")) { + Properties gitProp = new Properties(); + gitProp.load(stream); + + String buildXML = WebUtils.getBody("https://ci.opencollab.dev/job/GeyserMC/job/Geyser/job/" + + URLEncoder.encode(gitProp.getProperty("git.branch"), StandardCharsets.UTF_8.toString()) + "/lastSuccessfulBuild/api/xml?xpath=//buildNumber"); + if (buildXML.startsWith("")) { + int latestBuildNum = Integer.parseInt(buildXML.replaceAll("<(\\\\)?(/)?buildNumber>", "").trim()); + int buildNum = Integer.parseInt(gitProp.getProperty("git.build.number")); + if (latestBuildNum == buildNum) { + sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.no_updates", sender.getLocale())); + } else { + sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.outdated", + sender.getLocale(), (latestBuildNum - buildNum), "https://ci.geysermc.org/")); + } + } else { + throw new AssertionError("buildNumber missing"); + } + } catch (IOException | AssertionError | NumberFormatException e) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.version.failed"), e); + sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.version.failed", sender.getLocale())); + } + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/configuration/EmoteOffhandWorkaroundOption.java b/core/src/main/java/org/geysermc/geyser/configuration/EmoteOffhandWorkaroundOption.java new file mode 100644 index 000000000..87421ad92 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/configuration/EmoteOffhandWorkaroundOption.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.configuration; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; + +public enum EmoteOffhandWorkaroundOption { + NO_EMOTES, + EMOTES_AND_OFFHAND, + DISABLED; + + public static class Deserializer extends JsonDeserializer { + @Override + public EmoteOffhandWorkaroundOption deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + String value = p.getValueAsString(); + return switch (value) { + case "no-emotes" -> NO_EMOTES; + case "emotes-and-offhand" -> EMOTES_AND_OFFHAND; + default -> DISABLED; + }; + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java similarity index 68% rename from connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java rename to core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java index 153a91746..57cb5fb09 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,18 +23,21 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.configuration; +package org.geysermc.geyser.configuration; import com.fasterxml.jackson.annotation.JsonIgnore; -import org.geysermc.connector.GeyserLogger; -import org.geysermc.connector.utils.LanguageUtils; +import org.geysermc.geyser.GeyserLogger; +import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.network.CIDRMatcher; +import org.geysermc.geyser.text.GeyserLocale; import java.nio.file.Path; +import java.util.List; import java.util.Map; public interface GeyserConfiguration { - // Modify this when you update the config + // Modify this when you introduce breaking changes into the config int CURRENT_CONFIG_VERSION = 4; IBedrockConfiguration getBedrock(); @@ -59,25 +62,29 @@ public interface GeyserConfiguration { int getPingPassthroughInterval(); + boolean isForwardPlayerPing(); + int getMaxPlayers(); boolean isDebugMode(); - int getGeneralThreadPool(); - boolean isAllowThirdPartyCapes(); boolean isAllowThirdPartyEars(); - boolean isShowCooldown(); + String getShowCooldown(); + + boolean isShowCoordinates(); + + EmoteOffhandWorkaroundOption getEmoteOffhandWorkaround(); String getDefaultLocale(); Path getFloodgateKeyPath(); - boolean isAboveBedrockNetherBuilding(); + boolean isAddNonBedrockItems(); - boolean isCacheChunks(); + boolean isAboveBedrockNetherBuilding(); boolean isForceResourcePacks(); @@ -85,6 +92,8 @@ public interface GeyserConfiguration { int getCacheImages(); + boolean isAllowCustomSkulls(); + IMetricsInfo getMetrics(); interface IBedrockConfiguration { @@ -100,6 +109,17 @@ public interface GeyserConfiguration { String getMotd2(); String getServerName(); + + int getCompressionLevel(); + + boolean isEnableProxyProtocol(); + + List getProxyProtocolWhitelistedIPs(); + + /** + * @return Unmodifiable list of {@link CIDRMatcher}s from {@link #getProxyProtocolWhitelistedIPs()} + */ + List getWhitelistedIPsMatchers(); } interface IRemoteConfiguration { @@ -107,18 +127,30 @@ public interface GeyserConfiguration { String getAddress(); int getPort(); - + void setAddress(String address); void setPort(int port); - String getAuthType(); + AuthType getAuthType(); + + boolean isPasswordAuthentication(); + + boolean isUseProxyProtocol(); + + boolean isForwardHost(); } interface IUserAuthenticationInfo { String getEmail(); String getPassword(); + + /** + * Will be removed after Microsoft accounts are fully migrated + */ + @Deprecated + boolean isMicrosoftAccount(); } interface IMetricsInfo { @@ -135,13 +167,15 @@ public interface GeyserConfiguration { int getMtu(); + boolean isUseDirectConnection(); + int getConfigVersion(); static void checkGeyserConfiguration(GeyserConfiguration geyserConfig, GeyserLogger geyserLogger) { if (geyserConfig.getConfigVersion() < CURRENT_CONFIG_VERSION) { - geyserLogger.warning(LanguageUtils.getLocaleStringLog("geyser.bootstrap.config.outdated")); + geyserLogger.warning(GeyserLocale.getLocaleStringLog("geyser.bootstrap.config.outdated")); } else if (geyserConfig.getConfigVersion() > CURRENT_CONFIG_VERSION) { - geyserLogger.warning(LanguageUtils.getLocaleStringLog("geyser.bootstrap.config.too_new")); + geyserLogger.warning(GeyserLocale.getLocaleStringLog("geyser.bootstrap.config.too_new")); } } } diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java similarity index 52% rename from connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java rename to core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java index d19cfe49f..e3c937aae 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,21 +23,34 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.configuration; +package org.geysermc.geyser.configuration; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.Getter; import lombok.Setter; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.common.serializer.AsteriskSerializer; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.text.AsteriskSerializer; +import org.geysermc.geyser.network.CIDRMatcher; +import org.geysermc.geyser.text.GeyserLocale; +import java.io.IOException; import java.nio.file.Path; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.stream.Collectors; @Getter @JsonIgnoreProperties(ignoreUnknown = true) +@SuppressWarnings("FieldMayBeFinal") // Jackson requires that the fields are not final public abstract class GeyserJacksonConfiguration implements GeyserConfiguration { /** @@ -50,7 +63,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration private RemoteConfiguration remote = new RemoteConfiguration(); @JsonProperty("floodgate-key-file") - private String floodgateKeyFile = "public-key.pem"; + private String floodgateKeyFile = "key.pem"; public abstract Path getFloodgateKeyPath(); @@ -74,20 +87,27 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("ping-passthrough-interval") private int pingPassthroughInterval = 3; + @JsonProperty("forward-player-ping") + private boolean forwardPlayerPing = false; + @JsonProperty("max-players") private int maxPlayers = 100; @JsonProperty("debug-mode") private boolean debugMode = false; - @JsonProperty("general-thread-pool") - private int generalThreadPool = 32; - @JsonProperty("allow-third-party-capes") private boolean allowThirdPartyCapes = true; @JsonProperty("show-cooldown") - private boolean showCooldown = true; + private String showCooldown = "title"; + + @JsonProperty("show-coordinates") + private boolean showCoordinates = true; + + @JsonDeserialize(using = EmoteOffhandWorkaroundOption.Deserializer.class) + @JsonProperty("emote-offhand-workaround") + private EmoteOffhandWorkaroundOption emoteOffhandWorkaround = EmoteOffhandWorkaroundOption.DISABLED; @JsonProperty("allow-third-party-ears") private boolean allowThirdPartyEars = false; @@ -95,12 +115,15 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("default-locale") private String defaultLocale = null; // is null by default so system language takes priority - @JsonProperty("cache-chunks") - private boolean cacheChunks = false; - @JsonProperty("cache-images") private int cacheImages = 0; + @JsonProperty("allow-custom-skulls") + private boolean allowCustomSkulls = true; + + @JsonProperty("add-non-bedrock-items") + private boolean addNonBedrockItems = true; + @JsonProperty("above-bedrock-nether-building") private boolean aboveBedrockNetherBuilding = false; @@ -113,8 +136,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration private MetricsInfo metrics = new MetricsInfo(); @Getter + @JsonIgnoreProperties(ignoreUnknown = true) public static class BedrockConfiguration implements IBedrockConfiguration { - @AsteriskSerializer.Asterisk(sensitive = true) + @AsteriskSerializer.Asterisk(isIp = true) private String address = "0.0.0.0"; @Setter @@ -127,33 +151,80 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration private String motd2 = "Geyser"; @JsonProperty("server-name") - private String serverName = GeyserConnector.NAME; + private String serverName = GeyserImpl.NAME; + + @JsonProperty("compression-level") + private int compressionLevel = 6; + + public int getCompressionLevel() { + return Math.max(-1, Math.min(compressionLevel, 9)); + } + + @JsonProperty("enable-proxy-protocol") + private boolean enableProxyProtocol = false; + + @JsonProperty("proxy-protocol-whitelisted-ips") + private List proxyProtocolWhitelistedIPs = Collections.emptyList(); + + @JsonIgnore + private List whitelistedIPsMatchers = null; + + @Override + public List getWhitelistedIPsMatchers() { + // Effective Java, Third Edition; Item 83: Use lazy initialization judiciously + List matchers = this.whitelistedIPsMatchers; + if (matchers == null) { + synchronized (this) { + this.whitelistedIPsMatchers = matchers = proxyProtocolWhitelistedIPs.stream() + .map(CIDRMatcher::new) + .collect(Collectors.toList()); + } + } + return Collections.unmodifiableList(matchers); + } } @Getter + @JsonIgnoreProperties(ignoreUnknown = true) public static class RemoteConfiguration implements IRemoteConfiguration { @Setter - @AsteriskSerializer.Asterisk(sensitive = true) + @AsteriskSerializer.Asterisk(isIp = true) private String address = "auto"; + @JsonDeserialize(using = PortDeserializer.class) @Setter private int port = 25565; @Setter + @JsonDeserialize(using = AuthType.Deserializer.class) @JsonProperty("auth-type") - private String authType = "online"; + private AuthType authType = AuthType.ONLINE; + + @JsonProperty("allow-password-authentication") + private boolean passwordAuthentication = true; + + @JsonProperty("use-proxy-protocol") + private boolean useProxyProtocol = false; + + @JsonProperty("forward-hostname") + private boolean forwardHost = false; } @Getter + @JsonIgnoreProperties(ignoreUnknown = true) // DO NOT REMOVE THIS! Otherwise, after we remove microsoft-account configs will not load public static class UserAuthenticationInfo implements IUserAuthenticationInfo { @AsteriskSerializer.Asterisk() private String email; @AsteriskSerializer.Asterisk() private String password; + + @JsonProperty("microsoft-account") + private boolean microsoftAccount = false; } @Getter + @JsonIgnoreProperties(ignoreUnknown = true) public static class MetricsInfo implements IMetricsInfo { private boolean enabled = true; @@ -170,6 +241,25 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("mtu") private int mtu = 1400; + @JsonProperty("use-direct-connection") + private boolean useDirectConnection = true; + @JsonProperty("config-version") private int configVersion = 0; + + /** + * Ensure that the port deserializes in the config as a number no matter what. + */ + protected static class PortDeserializer extends JsonDeserializer { + @Override + public Integer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + String value = p.getValueAsString(); + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + System.err.println(GeyserLocale.getLocaleStringLog("geyser.bootstrap.config.invalid_port")); + return 25565; + } + } + } } diff --git a/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java b/core/src/main/java/org/geysermc/geyser/dump/BootstrapDumpInfo.java similarity index 83% rename from connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java rename to core/src/main/java/org/geysermc/geyser/dump/BootstrapDumpInfo.java index 585565533..6b421f912 100644 --- a/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java +++ b/core/src/main/java/org/geysermc/geyser/dump/BootstrapDumpInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,28 +23,26 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.dump; +package org.geysermc.geyser.dump; import lombok.AllArgsConstructor; import lombok.Getter; import org.geysermc.common.PlatformType; -import org.geysermc.connector.GeyserConnector; +import org.geysermc.geyser.GeyserImpl; import java.util.List; @Getter public class BootstrapDumpInfo { - - private PlatformType platform; + private final PlatformType platform; public BootstrapDumpInfo() { - this.platform = GeyserConnector.getInstance().getPlatformType(); + this.platform = GeyserImpl.getInstance().getPlatformType(); } @Getter @AllArgsConstructor - public class PluginInfo { - + public static class PluginInfo { public boolean enabled; public String name; public String version; @@ -54,8 +52,7 @@ public class BootstrapDumpInfo { @Getter @AllArgsConstructor - public class ListenerInfo { - + public static class ListenerInfo { public String ip; public int port; } diff --git a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java new file mode 100644 index 000000000..7d4807050 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.dump; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.hash.Hashing; +import com.google.common.io.ByteSource; +import com.google.common.io.Files; +import com.nukkitx.protocol.bedrock.BedrockPacketCodec; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.text.AsteriskSerializer; +import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.FileUtils; +import org.geysermc.geyser.util.WebUtils; +import org.geysermc.floodgate.util.DeviceOs; +import org.geysermc.floodgate.util.FloodgateInfoHolder; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.management.ManagementFactory; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Collectors; + +@Getter +public class DumpInfo { + @JsonIgnore + private static final long MEGABYTE = 1024L * 1024L; + + private final DumpInfo.VersionInfo versionInfo; + private final int cpuCount; + private Properties gitInfo; + private final GeyserConfiguration config; + private final Floodgate floodgate; + private final Object2IntMap userPlatforms; + private final HashInfo hashInfo; + private final RamInfo ramInfo; + private LogsInfo logsInfo; + private final BootstrapDumpInfo bootstrapInfo; + private final FlagsInfo flagsInfo; + + public DumpInfo(boolean addLog) { + this.versionInfo = new VersionInfo(); + + this.cpuCount = Runtime.getRuntime().availableProcessors(); + + try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource("git.properties")) { + this.gitInfo = new Properties(); + this.gitInfo.load(stream); + } catch (IOException ignored) { + } + + this.config = GeyserImpl.getInstance().getConfig(); + this.floodgate = new Floodgate(); + + String md5Hash = "unknown"; + String sha256Hash = "unknown"; + try { + // https://stackoverflow.com/questions/320542/how-to-get-the-path-of-a-running-jar-file + // https://stackoverflow.com/questions/304268/getting-a-files-md5-checksum-in-java + File file = new File(DumpInfo.class.getProtectionDomain().getCodeSource().getLocation().toURI()); + ByteSource byteSource = Files.asByteSource(file); + // Jenkins uses MD5 for its hash + //noinspection UnstableApiUsage + md5Hash = byteSource.hash(Hashing.md5()).toString(); + //noinspection UnstableApiUsage + sha256Hash = byteSource.hash(Hashing.sha256()).toString(); + } catch (Exception e) { + if (GeyserImpl.getInstance().getConfig().isDebugMode()) { + e.printStackTrace(); + } + } + this.hashInfo = new HashInfo(md5Hash, sha256Hash); + + this.ramInfo = new DumpInfo.RamInfo(); + + if (addLog) { + this.logsInfo = new LogsInfo(); + } + + this.userPlatforms = new Object2IntOpenHashMap<>(); + for (GeyserSession session : GeyserImpl.getInstance().getSessionManager().getAllSessions()) { + DeviceOs device = session.getClientData().getDeviceOs(); + userPlatforms.put(device, userPlatforms.getOrDefault(device, 0) + 1); + } + + this.bootstrapInfo = GeyserImpl.getInstance().getBootstrap().getDumpInfo(); + + this.flagsInfo = new FlagsInfo(); + } + + @Getter + public static class VersionInfo { + private final String name; + private final String version; + private final String javaName; + private final String javaVendor; + private final String javaVersion; + private final String architecture; + private final String operatingSystem; + private final String operatingSystemVersion; + + private final NetworkInfo network; + private final MCInfo mcInfo; + + VersionInfo() { + this.name = GeyserImpl.NAME; + this.version = GeyserImpl.VERSION; + this.javaName = System.getProperty("java.vm.name"); + this.javaVendor = System.getProperty("java.vendor"); + this.javaVersion = ManagementFactory.getRuntimeMXBean().getVmVersion(); // Gives a little more to the version we can use over the system property + // Usually gives Java architecture but still may be helpful. + this.architecture = System.getProperty("os.arch"); + this.operatingSystem = System.getProperty("os.name"); + this.operatingSystemVersion = System.getProperty("os.version"); + + this.network = new NetworkInfo(); + this.mcInfo = new MCInfo(); + } + } + + @Getter + public static class NetworkInfo { + private final boolean dockerCheck; + private String internalIP; + + NetworkInfo() { + if (AsteriskSerializer.showSensitive) { + try { + // This is the most reliable for getting the main local IP + Socket socket = new Socket(); + socket.connect(new InetSocketAddress("geysermc.org", 80)); + this.internalIP = socket.getLocalAddress().getHostAddress(); + } catch (IOException e1) { + try { + // Fallback to the normal way of getting the local IP + this.internalIP = InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException ignored) { + } + } + } else { + // Sometimes the internal IP is the external IP... + this.internalIP = "***"; + } + + this.dockerCheck = checkDockerBasic(); + } + + // By default, Geyser now sets the IP to the local IP in all cases on plugin versions so we don't notify the user of anything + // However we still have this check for the potential future bug + private boolean checkDockerBasic() { + try { + String OS = System.getProperty("os.name").toLowerCase(); + if (OS.indexOf("nix") >= 0 || OS.indexOf("nux") >= 0 || OS.indexOf("aix") > 0) { + String output = new String(java.nio.file.Files.readAllBytes(Paths.get("/proc/1/cgroup"))); + + if (output.contains("docker")) { + return true; + } + } + } catch (Exception ignored) { } // Ignore any errors, inc ip failed to fetch, process could not run or access denied + + return false; + } + } + + @Getter + public static class MCInfo { + private final List bedrockVersions; + private final List bedrockProtocols; + private final int defaultBedrockProtocol; + private final List javaVersions; + private final int javaProtocol; + + MCInfo() { + this.bedrockVersions = MinecraftProtocol.SUPPORTED_BEDROCK_CODECS.stream().map(BedrockPacketCodec::getMinecraftVersion).toList(); + this.bedrockProtocols = MinecraftProtocol.SUPPORTED_BEDROCK_CODECS.stream().map(BedrockPacketCodec::getProtocolVersion).toList(); + this.defaultBedrockProtocol = MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion(); + this.javaVersions = MinecraftProtocol.getJavaVersions(); + this.javaProtocol = MinecraftProtocol.getJavaProtocolVersion(); + } + } + + @Getter + public static class Floodgate { + private final Properties gitInfo; + private final Object config; + + Floodgate() { + this.gitInfo = FloodgateInfoHolder.getGitProperties(); + this.config = FloodgateInfoHolder.getConfig(); + } + } + + @Getter + public static class LogsInfo { + private String link; + + public LogsInfo() { + try { + Map fields = new HashMap<>(); + fields.put("content", FileUtils.readAllLines(GeyserImpl.getInstance().getBootstrap().getLogsPath()).collect(Collectors.joining("\n"))); + + JsonNode logData = GeyserImpl.JSON_MAPPER.readTree(WebUtils.postForm("https://api.mclo.gs/1/log", fields)); + + this.link = logData.get("url").textValue(); + } catch (IOException ignored) { } + } + } + + @AllArgsConstructor + @Getter + public static class HashInfo { + private final String md5Hash; + private final String sha256Hash; + } + + @Getter + public static class RamInfo { + private final long free; + private final long total; + private final long max; + + RamInfo() { + this.free = Runtime.getRuntime().freeMemory() / MEGABYTE; + this.total = Runtime.getRuntime().totalMemory() / MEGABYTE; + this.max = Runtime.getRuntime().maxMemory() / MEGABYTE; + } + } + + /** + * E.G. `-Xmx1024M` - all runtime JVM flags on this machine + */ + @Getter + public static class FlagsInfo { + private final List flags; + + FlagsInfo() { + this.flags = ManagementFactory.getRuntimeMXBean().getInputArguments(); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinition.java b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinition.java new file mode 100644 index 000000000..f66a0c56b --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinition.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType; +import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.entity.factory.BaseEntityFactory; +import org.geysermc.geyser.entity.factory.EntityFactory; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.translator.entity.EntityMetadataTranslator; + +import java.util.List; +import java.util.Locale; +import java.util.function.BiConsumer; + +/** + * Represents data for an entity. This includes properties such as height and width, as well as the list of entity + * metadata translators needed to translate the properties sent from the server. The translators are structured in such + * a way that inserting a new one (for example in version updates) is convenient. + * + * @param the entity type this definition represents + */ +public record EntityDefinition(EntityFactory factory, EntityType entityType, String identifier, + float width, float height, float offset, List> translators) { + + public static Builder inherited(BaseEntityFactory factory, EntityDefinition parent) { + return inherited((EntityFactory) factory, parent); + } + + public static Builder inherited(EntityFactory factory, EntityDefinition parent) { + return new Builder<>(factory, parent.entityType, parent.identifier, parent.width, parent.height, parent.offset, new ObjectArrayList<>(parent.translators)); + } + + public static Builder builder(EntityFactory factory) { + return new Builder<>(factory); + } + + + public void translateMetadata(T entity, EntityMetadata> metadata) { + EntityMetadataTranslator>> translator = (EntityMetadataTranslator>>) this.translators.get(metadata.getId()); + if (translator == null) { + // This can safely happen; it means we don't translate this entity metadata + return; + } + + if (translator.acceptedType() != metadata.getType()) { + GeyserImpl.getInstance().getLogger().warning("Metadata ID " + metadata.getId() + " was received with type " + metadata.getType() + " but we expected " + translator.acceptedType() + " for " + entity.getDefinition().entityType()); + if (GeyserImpl.getInstance().getConfig().isDebugMode()) { + GeyserImpl.getInstance().getLogger().debug(metadata.toString()); + } + return; + } + + translator.translate(entity, metadata); + } + + @Setter + @Accessors(fluent = true, chain = true) + public static class Builder { + private final EntityFactory factory; + private EntityType type; + private String identifier; + private float width; + private float height; + private float offset = 0.00001f; + private final List> translators; + + private Builder(EntityFactory factory) { + this.factory = factory; + translators = new ObjectArrayList<>(); + } + + public Builder(EntityFactory factory, EntityType type, String identifier, float width, float height, float offset, List> translators) { + this.factory = factory; + this.type = type; + this.identifier = identifier; + this.width = width; + this.height = height; + this.offset = offset; + this.translators = translators; + } + + /** + * Sets the height and width as one value + */ + public Builder heightAndWidth(float value) { + height = value; + width = value; + return this; + } + + public Builder offset(float offset) { + this.offset = offset + 0.00001f; + return this; + } + + /** + * Resets the identifier as well + */ + public Builder type(EntityType type) { + this.type = type; + identifier = null; + return this; + } + + public >> Builder addTranslator(MetadataType type, BiConsumer translateFunction) { + translators.add(new EntityMetadataTranslator<>(type, translateFunction)); + return this; + } + + public Builder addTranslator(EntityMetadataTranslator translator) { + translators.add(translator); + return this; + } + + public EntityDefinition build() { + return build(true); + } + + /** + * @param register whether to register this entity in the Registries for entity types. Generally this should be + * set to false if we're not expecting this entity to spawn from the network. + */ + public EntityDefinition build(boolean register) { + if (identifier == null && type != null) { + identifier = "minecraft:" + type.name().toLowerCase(Locale.ROOT); + } + EntityDefinition definition = new EntityDefinition<>(factory, type, identifier, width, height, offset, translators); + if (register && definition.entityType() != null) { + Registries.ENTITY_DEFINITIONS.get().putIfAbsent(definition.entityType(), definition); + Registries.JAVA_ENTITY_IDENTIFIERS.get().putIfAbsent("minecraft:" + type.name().toLowerCase(Locale.ROOT), definition); + } + return definition; + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java new file mode 100644 index 000000000..e1f2169ef --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java @@ -0,0 +1,910 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.entity.factory.BaseEntityFactory; +import org.geysermc.geyser.entity.factory.ExperienceOrbEntityFactory; +import org.geysermc.geyser.entity.factory.PaintingEntityFactory; +import org.geysermc.geyser.entity.type.*; +import org.geysermc.geyser.entity.type.living.*; +import org.geysermc.geyser.entity.type.living.animal.*; +import org.geysermc.geyser.entity.type.living.animal.horse.*; +import org.geysermc.geyser.entity.type.living.animal.tameable.CatEntity; +import org.geysermc.geyser.entity.type.living.animal.tameable.ParrotEntity; +import org.geysermc.geyser.entity.type.living.animal.tameable.TameableEntity; +import org.geysermc.geyser.entity.type.living.animal.tameable.WolfEntity; +import org.geysermc.geyser.entity.type.living.merchant.AbstractMerchantEntity; +import org.geysermc.geyser.entity.type.living.merchant.VillagerEntity; +import org.geysermc.geyser.entity.type.living.monster.*; +import org.geysermc.geyser.entity.type.living.monster.raid.PillagerEntity; +import org.geysermc.geyser.entity.type.living.monster.raid.RaidParticipantEntity; +import org.geysermc.geyser.entity.type.living.monster.raid.SpellcasterIllagerEntity; +import org.geysermc.geyser.entity.type.living.monster.raid.VindicatorEntity; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.geyser.registry.Registries; + +public final class EntityDefinitions { + public static final EntityDefinition AREA_EFFECT_CLOUD; + public static final EntityDefinition ARMOR_STAND; + public static final EntityDefinition ARROW; + public static final EntityDefinition AXOLOTL; + public static final EntityDefinition BAT; + public static final EntityDefinition BEE; + public static final EntityDefinition BLAZE; + public static final EntityDefinition BOAT; + public static final EntityDefinition CAT; + public static final EntityDefinition CAVE_SPIDER; + public static final EntityDefinition CHEST_MINECART; + public static final EntityDefinition CHICKEN; + public static final EntityDefinition COD; + public static final EntityDefinition COMMAND_BLOCK_MINECART; + public static final EntityDefinition COW; + public static final EntityDefinition CREEPER; + public static final EntityDefinition DOLPHIN; + public static final EntityDefinition DONKEY; + public static final EntityDefinition DRAGON_FIREBALL; + public static final EntityDefinition DROWNED; + public static final EntityDefinition EGG; + public static final EntityDefinition ELDER_GUARDIAN; + public static final EntityDefinition ENDERMAN; + public static final EntityDefinition ENDERMITE; + public static final EntityDefinition ENDER_DRAGON; + public static final EntityDefinition ENDER_PEARL; + public static final EntityDefinition END_CRYSTAL; + public static final EntityDefinition EVOKER; + public static final EntityDefinition EVOKER_FANGS; + public static final EntityDefinition EXPERIENCE_BOTTLE; + public static final EntityDefinition EXPERIENCE_ORB; + public static final EntityDefinition EYE_OF_ENDER; + public static final EntityDefinition FALLING_BLOCK; + public static final EntityDefinition FIREBALL; + public static final EntityDefinition FIREWORK_ROCKET; + public static final EntityDefinition FISHING_BOBBER; + public static final EntityDefinition FOX; + public static final EntityDefinition FURNACE_MINECART; // Not present on Bedrock + public static final EntityDefinition GHAST; + public static final EntityDefinition GIANT; + public static final EntityDefinition GLOW_ITEM_FRAME; + public static final EntityDefinition GLOW_SQUID; + public static final EntityDefinition GOAT; + public static final EntityDefinition GUARDIAN; + public static final EntityDefinition HOGLIN; + public static final EntityDefinition HOPPER_MINECART; + public static final EntityDefinition HORSE; + public static final EntityDefinition HUSK; + public static final EntityDefinition ILLUSIONER; // Not present on Bedrock + public static final EntityDefinition IRON_GOLEM; + public static final EntityDefinition ITEM; + public static final EntityDefinition ITEM_FRAME; + public static final EntityDefinition LEASH_KNOT; + public static final EntityDefinition LIGHTNING_BOLT; + public static final EntityDefinition LLAMA; + public static final EntityDefinition LLAMA_SPIT; + public static final EntityDefinition MAGMA_CUBE; + public static final EntityDefinition MINECART; + public static final EntityDefinition MOOSHROOM; + public static final EntityDefinition MULE; + public static final EntityDefinition OCELOT; + public static final EntityDefinition PAINTING; + public static final EntityDefinition PANDA; + public static final EntityDefinition PARROT; + public static final EntityDefinition PHANTOM; + public static final EntityDefinition PIG; + public static final EntityDefinition PIGLIN; + public static final EntityDefinition PIGLIN_BRUTE; + public static final EntityDefinition PILLAGER; + public static final EntityDefinition PLAYER; + public static final EntityDefinition POLAR_BEAR; + public static final EntityDefinition POTION; + public static final EntityDefinition PUFFERFISH; + public static final EntityDefinition RABBIT; + public static final EntityDefinition RAVAGER; + public static final EntityDefinition SALMON; + public static final EntityDefinition SHEEP; + public static final EntityDefinition SHULKER; + public static final EntityDefinition SHULKER_BULLET; + public static final EntityDefinition SILVERFISH; + public static final EntityDefinition SKELETON; + public static final EntityDefinition SKELETON_HORSE; + public static final EntityDefinition SLIME; + public static final EntityDefinition SMALL_FIREBALL; + public static final EntityDefinition SNOWBALL; + public static final EntityDefinition SNOW_GOLEM; + public static final EntityDefinition SPAWNER_MINECART; // Not present on Bedrock + public static final EntityDefinition SPECTRAL_ARROW; + public static final EntityDefinition SPIDER; + public static final EntityDefinition SQUID; + public static final EntityDefinition STRAY; + public static final EntityDefinition STRIDER; + public static final EntityDefinition TNT; + public static final EntityDefinition TNT_MINECART; + public static final EntityDefinition TRADER_LLAMA; + public static final EntityDefinition TRIDENT; + public static final EntityDefinition TROPICAL_FISH; + public static final EntityDefinition TURTLE; + public static final EntityDefinition VEX; + public static final EntityDefinition VILLAGER; + public static final EntityDefinition VINDICATOR; + public static final EntityDefinition WANDERING_TRADER; + public static final EntityDefinition WITCH; + public static final EntityDefinition WITHER; + public static final EntityDefinition WITHER_SKELETON; + public static final EntityDefinition WITHER_SKULL; + public static final EntityDefinition WOLF; + public static final EntityDefinition ZOGLIN; + public static final EntityDefinition ZOMBIE; + public static final EntityDefinition ZOMBIE_HORSE; + public static final EntityDefinition ZOMBIE_VILLAGER; + public static final EntityDefinition ZOMBIFIED_PIGLIN; + + /** + * Is not sent over the network + */ + public static final EntityDefinition ENDER_DRAGON_PART; + /** + * Special Bedrock type + */ + public static final EntityDefinition WITHER_SKULL_DANGEROUS; + + static { + EntityDefinition entityBase = EntityDefinition.builder((BaseEntityFactory) Entity::new) + .addTranslator(MetadataType.BYTE, Entity::setFlags) + .addTranslator(MetadataType.INT, Entity::setAir) // Air/bubbles + .addTranslator(MetadataType.OPTIONAL_CHAT, Entity::setDisplayName) + .addTranslator(MetadataType.BOOLEAN, Entity::setDisplayNameVisible) + .addTranslator(MetadataType.BOOLEAN, (entity, entityMetadata) -> entity.setFlag(EntityFlag.SILENT, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue())) + .addTranslator(MetadataType.BOOLEAN, Entity::setGravity) + .addTranslator(MetadataType.POSE, Entity::setPose) + .addTranslator(MetadataType.INT, Entity::setFreezing) + .build(); + + // Extends entity + { + AREA_EFFECT_CLOUD = EntityDefinition.inherited(AreaEffectCloudEntity::new, entityBase) + .type(EntityType.AREA_EFFECT_CLOUD) + .height(0.5f).width(1.0f) + .addTranslator(MetadataType.FLOAT, AreaEffectCloudEntity::setRadius) + .addTranslator(MetadataType.INT, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityData.EFFECT_COLOR, entityMetadata.getValue())) + .addTranslator(null) // Waiting + .addTranslator(MetadataType.PARTICLE, AreaEffectCloudEntity::setParticle) + .build(); + BOAT = EntityDefinition.inherited(BoatEntity::new, entityBase) + .type(EntityType.BOAT) + .height(0.6f).width(1.6f) + .offset(0.35f) + .addTranslator(MetadataType.INT, (boatEntity, entityMetadata) -> boatEntity.getDirtyMetadata().put(EntityData.HURT_TIME, entityMetadata.getValue())) // Time since last hit + .addTranslator(MetadataType.INT, (boatEntity, entityMetadata) -> boatEntity.getDirtyMetadata().put(EntityData.HURT_DIRECTION, entityMetadata.getValue())) // Rocking direction + .addTranslator(MetadataType.FLOAT, (boatEntity, entityMetadata) -> + // 'Health' in Bedrock, damage taken in Java - it makes motion in Bedrock + boatEntity.getDirtyMetadata().put(EntityData.HEALTH, 40 - ((int) ((FloatEntityMetadata) entityMetadata).getPrimitiveValue()))) + .addTranslator(MetadataType.INT, BoatEntity::setVariant) + .addTranslator(MetadataType.BOOLEAN, BoatEntity::setPaddlingLeft) + .addTranslator(MetadataType.BOOLEAN, BoatEntity::setPaddlingRight) + .addTranslator(MetadataType.INT, (boatEntity, entityMetadata) -> boatEntity.getDirtyMetadata().put(EntityData.BOAT_BUBBLE_TIME, entityMetadata.getValue())) // May not actually do anything + .build(); + DRAGON_FIREBALL = EntityDefinition.inherited(FireballEntity::new, entityBase) + .type(EntityType.DRAGON_FIREBALL) + .heightAndWidth(1.0f) + .build(); + END_CRYSTAL = EntityDefinition.inherited(EnderCrystalEntity::new, entityBase) + .type(EntityType.END_CRYSTAL) + .heightAndWidth(2.0f) + .identifier("minecraft:ender_crystal") + .addTranslator(MetadataType.OPTIONAL_POSITION, EnderCrystalEntity::setBlockTarget) + .addTranslator(MetadataType.BOOLEAN, + (enderCrystalEntity, entityMetadata) -> enderCrystalEntity.setFlag(EntityFlag.SHOW_BOTTOM, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue())) // There is a base located on the ender crystal + .build(); + EXPERIENCE_ORB = EntityDefinition.inherited((ExperienceOrbEntityFactory) ExpOrbEntity::new, entityBase) + .type(EntityType.EXPERIENCE_ORB) + .identifier("minecraft:xp_orb") + .build(); + EVOKER_FANGS = EntityDefinition.inherited(entityBase.factory(), entityBase) + .type(EntityType.EVOKER_FANGS) + .height(0.8f).width(0.5f) + .identifier("minecraft:evocation_fang") + .build(); + EYE_OF_ENDER = EntityDefinition.inherited(Entity::new, entityBase) + .type(EntityType.EYE_OF_ENDER) + .heightAndWidth(0.25f) + .identifier("minecraft:eye_of_ender_signal") + .addTranslator(null) // Item + .build(); + FALLING_BLOCK = EntityDefinition.inherited(null, entityBase) + .type(EntityType.FALLING_BLOCK) + .heightAndWidth(0.98f) + .addTranslator(null) // "start block position" + .build(); + FIREWORK_ROCKET = EntityDefinition.inherited(FireworkEntity::new, entityBase) + .type(EntityType.FIREWORK_ROCKET) + .heightAndWidth(0.25f) + .identifier("minecraft:fireworks_rocket") + .addTranslator(MetadataType.ITEM, FireworkEntity::setFireworkItem) + .addTranslator(MetadataType.OPTIONAL_VARINT, FireworkEntity::setPlayerGliding) + .addTranslator(null) // Shot at angle + .build(); + FISHING_BOBBER = EntityDefinition.inherited(null, entityBase) + .type(EntityType.FISHING_BOBBER) + .identifier("minecraft:fishing_hook") + .addTranslator(MetadataType.INT, FishingHookEntity::setHookedEntity) + .addTranslator(null) // Biting TODO check + .build(); + ITEM = EntityDefinition.inherited(ItemEntity::new, entityBase) + .type(EntityType.ITEM) + .heightAndWidth(0.25f) + .offset(0.125f) + .addTranslator(MetadataType.ITEM, ItemEntity::setItem) + .build(); + LEASH_KNOT = EntityDefinition.inherited(LeashKnotEntity::new, entityBase) + .type(EntityType.LEASH_KNOT) + .height(0.5f).width(0.375f) + .build(); + LIGHTNING_BOLT = EntityDefinition.inherited(LightningEntity::new, entityBase) + .type(EntityType.LIGHTNING_BOLT) + .build(); + LLAMA_SPIT = EntityDefinition.inherited(ThrowableEntity::new, entityBase) + .type(EntityType.LLAMA_SPIT) + .heightAndWidth(0.25f) + .build(); + PAINTING = EntityDefinition.inherited((PaintingEntityFactory) PaintingEntity::new, entityBase) + .type(EntityType.PAINTING) + .build(); + SHULKER_BULLET = EntityDefinition.inherited(ThrowableEntity::new, entityBase) + .type(EntityType.SHULKER_BULLET) + .heightAndWidth(0.3125f) + .build(); + TNT = EntityDefinition.inherited(TNTEntity::new, entityBase) + .type(EntityType.TNT) + .heightAndWidth(0.98f) + .addTranslator(MetadataType.INT, TNTEntity::setFuseLength) + .build(); + + EntityDefinition fireballBase = EntityDefinition.inherited(FireballEntity::new, entityBase) + .addTranslator(null) // Item + .build(); + FIREBALL = EntityDefinition.inherited(FireballEntity::new, fireballBase) + .type(EntityType.FIREBALL) + .heightAndWidth(1.0f) + .build(); + SMALL_FIREBALL = EntityDefinition.inherited(FireballEntity::new, fireballBase) + .type(EntityType.SMALL_FIREBALL) + .heightAndWidth(0.3125f) + .build(); + + EntityDefinition throwableItemBase = EntityDefinition.inherited(ThrowableItemEntity::new, entityBase) + .addTranslator(MetadataType.ITEM, ThrowableItemEntity::setItem) + .build(); + EGG = EntityDefinition.inherited(ThrowableItemEntity::new, throwableItemBase) + .type(EntityType.EGG) + .heightAndWidth(0.25f) + .build(); + ENDER_PEARL = EntityDefinition.inherited(ThrowableItemEntity::new, throwableItemBase) + .type(EntityType.ENDER_PEARL) + .heightAndWidth(0.25f) + .build(); + EXPERIENCE_BOTTLE = EntityDefinition.inherited(ThrowableItemEntity::new, throwableItemBase) + .type(EntityType.EXPERIENCE_BOTTLE) + .heightAndWidth(0.25f) + .identifier("minecraft:xp_bottle") + .build(); + POTION = EntityDefinition.inherited(ThrownPotionEntity::new, throwableItemBase) + .type(EntityType.POTION) + .heightAndWidth(0.25f) + .identifier("minecraft:splash_potion") + .build(); + SNOWBALL = EntityDefinition.inherited(ThrowableItemEntity::new, throwableItemBase) + .type(EntityType.SNOWBALL) + .heightAndWidth(0.25f) + .build(); + + EntityDefinition abstractArrowBase = EntityDefinition.inherited(AbstractArrowEntity::new, entityBase) + .addTranslator(MetadataType.BYTE, AbstractArrowEntity::setArrowFlags) + .addTranslator(null) // "Piercing level" + .build(); + ARROW = EntityDefinition.inherited(TippedArrowEntity::new, abstractArrowBase) + .type(EntityType.ARROW) + .heightAndWidth(0.25f) + .addTranslator(MetadataType.INT, TippedArrowEntity::setPotionEffectColor) + .build(); + SPECTRAL_ARROW = EntityDefinition.inherited(abstractArrowBase.factory(), abstractArrowBase) + .type(EntityType.SPECTRAL_ARROW) + .heightAndWidth(0.25f) + .identifier("minecraft:arrow") + .build(); + TRIDENT = EntityDefinition.inherited(TridentEntity::new, abstractArrowBase) // TODO remove class + .type(EntityType.TRIDENT) + .identifier("minecraft:thrown_trident") + .addTranslator(null) // Loyalty + .addTranslator(MetadataType.BOOLEAN, (tridentEntity, entityMetadata) -> tridentEntity.setFlag(EntityFlag.ENCHANTED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue())) + .build(); + + // Item frames are handled differently as they are blocks, not items, in Bedrock + ITEM_FRAME = EntityDefinition.inherited(null, entityBase) + .type(EntityType.ITEM_FRAME) + .addTranslator(MetadataType.ITEM, ItemFrameEntity::setItemInFrame) + .addTranslator(MetadataType.INT, ItemFrameEntity::setItemRotation) + .build(); + GLOW_ITEM_FRAME = EntityDefinition.inherited(ITEM_FRAME.factory(), ITEM_FRAME) + .type(EntityType.GLOW_ITEM_FRAME) + .build(); + + MINECART = EntityDefinition.inherited(MinecartEntity::new, entityBase) + .type(EntityType.MINECART) + .height(0.7f).width(0.98f) + .offset(0.35f) + .addTranslator(MetadataType.INT, (minecartEntity, entityMetadata) -> minecartEntity.getDirtyMetadata().put(EntityData.HEALTH, entityMetadata.getValue())) + .addTranslator(MetadataType.INT, (minecartEntity, entityMetadata) -> minecartEntity.getDirtyMetadata().put(EntityData.HURT_DIRECTION, entityMetadata.getValue())) // Direction in which the minecart is shaking + .addTranslator(MetadataType.FLOAT, (minecartEntity, entityMetadata) -> + // Power in Java, time in Bedrock + minecartEntity.getDirtyMetadata().put(EntityData.HURT_TIME, Math.min((int) ((FloatEntityMetadata) entityMetadata).getPrimitiveValue(), 15))) + .addTranslator(MetadataType.INT, MinecartEntity::setCustomBlock) + .addTranslator(MetadataType.INT, MinecartEntity::setCustomBlockOffset) + .addTranslator(MetadataType.BOOLEAN, MinecartEntity::setShowCustomBlock) + .build(); + CHEST_MINECART = EntityDefinition.inherited(MINECART.factory(), MINECART) + .type(EntityType.CHEST_MINECART) + .build(); + COMMAND_BLOCK_MINECART = EntityDefinition.inherited(CommandBlockMinecartEntity::new, MINECART) + .type(EntityType.COMMAND_BLOCK_MINECART) + .addTranslator(MetadataType.STRING, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityData.COMMAND_BLOCK_COMMAND, entityMetadata.getValue())) + .addTranslator(MetadataType.CHAT, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityData.COMMAND_BLOCK_LAST_OUTPUT, MessageTranslator.convertMessage(entityMetadata.getValue()))) + .build(); + FURNACE_MINECART = EntityDefinition.inherited(FurnaceMinecartEntity::new, MINECART) + .type(EntityType.FURNACE_MINECART) + .identifier("minecraft:minecart") + .addTranslator(MetadataType.BOOLEAN, FurnaceMinecartEntity::setHasFuel) + .build(); + HOPPER_MINECART = EntityDefinition.inherited(MINECART.factory(), MINECART) + .type(EntityType.HOPPER_MINECART) + .build(); + SPAWNER_MINECART = EntityDefinition.inherited(SpawnerMinecartEntity::new, MINECART) + .type(EntityType.SPAWNER_MINECART) + .identifier("minecraft:minecart") + .build(); + TNT_MINECART = EntityDefinition.inherited(MINECART.factory(), MINECART) + .type(EntityType.TNT_MINECART) + .build(); + + WITHER_SKULL = EntityDefinition.inherited(WitherSkullEntity::new, entityBase) + .type(EntityType.WITHER_SKULL) + .heightAndWidth(0.3125f) + .addTranslator(MetadataType.BOOLEAN, WitherSkullEntity::setDangerous) + .build(); + WITHER_SKULL_DANGEROUS = EntityDefinition.inherited(WITHER_SKULL.factory(), WITHER_SKULL) + .build(false); + } + + EntityDefinition livingEntityBase = EntityDefinition.inherited(LivingEntity::new, entityBase) + .addTranslator(MetadataType.BYTE, LivingEntity::setLivingEntityFlags) + .addTranslator(MetadataType.FLOAT, LivingEntity::setHealth) + .addTranslator(MetadataType.INT, + (livingEntity, entityMetadata) -> livingEntity.getDirtyMetadata().put(EntityData.EFFECT_COLOR, entityMetadata.getValue())) + .addTranslator(MetadataType.BOOLEAN, + (livingEntity, entityMetadata) -> livingEntity.getDirtyMetadata().put(EntityData.EFFECT_AMBIENT, (byte) (((BooleanEntityMetadata) entityMetadata).getPrimitiveValue() ? 1 : 0))) + .addTranslator(null) // Arrow count + .addTranslator(null) // Stinger count + .addTranslator(MetadataType.OPTIONAL_POSITION, LivingEntity::setBedPosition) + .build(); + + ARMOR_STAND = EntityDefinition.inherited(ArmorStandEntity::new, livingEntityBase) + .type(EntityType.ARMOR_STAND) + .height(1.975f).width(0.5f) + .addTranslator(MetadataType.BYTE, ArmorStandEntity::setArmorStandFlags) + .addTranslator(MetadataType.ROTATION, ArmorStandEntity::setHeadRotation) + .addTranslator(MetadataType.ROTATION, ArmorStandEntity::setBodyRotation) + .addTranslator(MetadataType.ROTATION, ArmorStandEntity::setLeftArmRotation) + .addTranslator(MetadataType.ROTATION, ArmorStandEntity::setRightArmRotation) + .addTranslator(MetadataType.ROTATION, ArmorStandEntity::setLeftLegRotation) + .addTranslator(MetadataType.ROTATION, ArmorStandEntity::setRightLegRotation) + .build(); + PLAYER = EntityDefinition.inherited(null, livingEntityBase) + .type(EntityType.PLAYER) + .height(1.8f).width(0.6f) + .offset(1.62f) + .addTranslator(MetadataType.FLOAT, PlayerEntity::setAbsorptionHearts) + .addTranslator(null) // Player score + .addTranslator(MetadataType.BYTE, PlayerEntity::setSkinVisibility) + .addTranslator(null) // Player main hand + .addTranslator(MetadataType.NBT_TAG, PlayerEntity::setLeftParrot) + .addTranslator(MetadataType.NBT_TAG, PlayerEntity::setRightParrot) + .build(); + + EntityDefinition mobEntityBase = EntityDefinition.inherited(MobEntity::new, livingEntityBase) + .addTranslator(MetadataType.BYTE, MobEntity::setMobFlags) + .build(); + + // Extends mob + { + BAT = EntityDefinition.inherited(BatEntity::new, mobEntityBase) + .type(EntityType.BAT) + .height(0.9f).width(0.5f) + .addTranslator(MetadataType.BYTE, BatEntity::setBatFlags) + .build(); + BLAZE = EntityDefinition.inherited(BlazeEntity::new, mobEntityBase) + .type(EntityType.BLAZE) + .height(1.8f).width(0.6f) + .addTranslator(MetadataType.BYTE, BlazeEntity::setBlazeFlags) + .build(); + CREEPER = EntityDefinition.inherited(CreeperEntity::new, mobEntityBase) + .type(EntityType.CREEPER) + .height(1.7f).width(0.6f) + .offset(1.62f) + .addTranslator(MetadataType.INT, CreeperEntity::setSwelling) + .addTranslator(MetadataType.BOOLEAN, (entity, entityMetadata) -> entity.setFlag(EntityFlag.POWERED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue())) + .addTranslator(MetadataType.BOOLEAN, CreeperEntity::setIgnited) + .build(); + DOLPHIN = EntityDefinition.inherited(WaterEntity::new, mobEntityBase) + .type(EntityType.DOLPHIN) + .height(0.6f).width(0.9f) + //TODO check + .addTranslator(null) // treasure position + .addTranslator(null) // "got fish" + .addTranslator(null) // "moistness level" + .build(); + ENDERMAN = EntityDefinition.inherited(EndermanEntity::new, mobEntityBase) + .type(EntityType.ENDERMAN) + .height(2.9f).width(0.6f) + .addTranslator(MetadataType.BLOCK_STATE, EndermanEntity::setCarriedBlock) + .addTranslator(MetadataType.BOOLEAN, EndermanEntity::setScreaming) + .addTranslator(MetadataType.BOOLEAN, EndermanEntity::setAngry) + .build(); + ENDERMITE = EntityDefinition.inherited(MonsterEntity::new, mobEntityBase) + .type(EntityType.ENDERMITE) + .height(0.3f).width(0.4f) + .build(); + ENDER_DRAGON = EntityDefinition.inherited(EnderDragonEntity::new, mobEntityBase) + .type(EntityType.ENDER_DRAGON) + .addTranslator(MetadataType.INT, EnderDragonEntity::setPhase) + .build(); + GHAST = EntityDefinition.inherited(GhastEntity::new, mobEntityBase) + .type(EntityType.GHAST) + .heightAndWidth(4.0f) + .addTranslator(MetadataType.BOOLEAN, GhastEntity::setGhastAttacking) + .build(); + GIANT = EntityDefinition.inherited(GiantEntity::new, mobEntityBase) + .type(EntityType.GIANT) + .height(1.8f).width(1.6f) + .offset(1.62f) + .identifier("minecraft:zombie") + .build(); + IRON_GOLEM = EntityDefinition.inherited(IronGolemEntity::new, mobEntityBase) + .type(EntityType.IRON_GOLEM) + .height(2.7f).width(1.4f) + .addTranslator(null) // "is player created", which doesn't seem to do anything clientside + .build(); + PHANTOM = EntityDefinition.inherited(PhantomEntity::new, mobEntityBase) + .type(EntityType.PHANTOM) + .height(0.5f).width(0.9f) + .offset(0.6f) + .addTranslator(MetadataType.INT, PhantomEntity::setPhantomScale) + .build(); + SILVERFISH = EntityDefinition.inherited(MonsterEntity::new, mobEntityBase) + .type(EntityType.SILVERFISH) + .height(0.3f).width(0.4f) + .build(); + SHULKER = EntityDefinition.inherited(ShulkerEntity::new, mobEntityBase) + .type(EntityType.SHULKER) + .heightAndWidth(1f) + .addTranslator(MetadataType.DIRECTION, ShulkerEntity::setAttachedFace) + .addTranslator(MetadataType.BYTE, ShulkerEntity::setShulkerHeight) + .addTranslator(MetadataType.BYTE, ShulkerEntity::setShulkerColor) + .build(); + SKELETON = EntityDefinition.inherited(SkeletonEntity::new, mobEntityBase) + .type(EntityType.SKELETON) + .height(1.8f).width(0.6f) + .offset(1.62f) + .addTranslator(MetadataType.BOOLEAN, SkeletonEntity::setConvertingToStray) + .build(); + SNOW_GOLEM = EntityDefinition.inherited(SnowGolemEntity::new, mobEntityBase) + .type(EntityType.SNOW_GOLEM) + .height(1.9f).width(0.7f) + .addTranslator(MetadataType.BYTE, SnowGolemEntity::setSnowGolemFlags) + .build(); + SPIDER = EntityDefinition.inherited(SpiderEntity::new, mobEntityBase) + .type(EntityType.SPIDER) + .height(0.9f).width(1.4f) + .offset(1f) + .addTranslator(MetadataType.BYTE, SpiderEntity::setSpiderFlags) + .build(); + CAVE_SPIDER = EntityDefinition.inherited(SpiderEntity::new, SPIDER) + .type(EntityType.CAVE_SPIDER) + .height(0.5f).width(0.7f) + .build(); + SQUID = EntityDefinition.inherited(SquidEntity::new, mobEntityBase) + .type(EntityType.SQUID) + .heightAndWidth(0.8f) + .build(); + STRAY = EntityDefinition.inherited(AbstractSkeletonEntity::new, mobEntityBase) + .type(EntityType.STRAY) + .height(1.8f).width(0.6f) + .offset(1.62f) + .build(); + VEX = EntityDefinition.inherited(VexEntity::new, mobEntityBase) + .type(EntityType.VEX) + .height(0.8f).width(0.4f) + .addTranslator(MetadataType.BYTE, VexEntity::setVexFlags) + .build(); + WITHER = EntityDefinition.inherited(WitherEntity::new, mobEntityBase) + .type(EntityType.WITHER) + .height(3.5f).width(0.9f) + .addTranslator(MetadataType.INT, WitherEntity::setTarget1) + .addTranslator(MetadataType.INT, WitherEntity::setTarget2) + .addTranslator(MetadataType.INT, WitherEntity::setTarget3) + .addTranslator(MetadataType.INT, WitherEntity::setInvulnerableTicks) + .build(); + WITHER_SKELETON = EntityDefinition.inherited(AbstractSkeletonEntity::new, mobEntityBase) + .type(EntityType.WITHER_SKELETON) + .height(2.4f).width(0.7f) + .build(); + ZOGLIN = EntityDefinition.inherited(ZoglinEntity::new, mobEntityBase) + .type(EntityType.ZOGLIN) + .height(1.4f).width(1.3965f) + .addTranslator(MetadataType.BOOLEAN, ZoglinEntity::setBaby) + .build(); + ZOMBIE = EntityDefinition.inherited(ZombieEntity::new, mobEntityBase) + .type(EntityType.ZOMBIE) + .height(1.8f).width(0.6f) + .offset(1.62f) + .addTranslator(MetadataType.BOOLEAN, ZombieEntity::setZombieBaby) + .addTranslator(null) // "set special type", doesn't do anything + .addTranslator(MetadataType.BOOLEAN, ZombieEntity::setConvertingToDrowned) + .build(); + ZOMBIE_VILLAGER = EntityDefinition.inherited(ZombieVillagerEntity::new, ZOMBIE) + .type(EntityType.ZOMBIE_VILLAGER) + .height(1.8f).width(0.6f) + .offset(1.62f) + .identifier("minecraft:zombie_villager_v2") + .addTranslator(MetadataType.BOOLEAN, ZombieVillagerEntity::setTransforming) + .addTranslator(MetadataType.VILLAGER_DATA, ZombieVillagerEntity::setZombieVillagerData) + .build(); + ZOMBIFIED_PIGLIN = EntityDefinition.inherited(ZombifiedPiglinEntity::new, ZOMBIE) //TODO test how zombie entity metadata is handled? + .type(EntityType.ZOMBIFIED_PIGLIN) + .height(1.95f).width(0.6f) + .offset(1.62f) + .identifier("minecraft:zombie_pigman") + .build(); + + DROWNED = EntityDefinition.inherited(ZOMBIE.factory(), ZOMBIE) + .type(EntityType.DROWNED) + .height(1.95f).width(0.6f) + .build(); + HUSK = EntityDefinition.inherited(ZOMBIE.factory(), ZOMBIE) + .type(EntityType.HUSK) + .build(); + + GUARDIAN = EntityDefinition.inherited(GuardianEntity::new, mobEntityBase) + .type(EntityType.GUARDIAN) + .heightAndWidth(0.85f) + .addTranslator(null) // Moving //TODO + .addTranslator(MetadataType.INT, GuardianEntity::setGuardianTarget) + .build(); + ELDER_GUARDIAN = EntityDefinition.inherited(ElderGuardianEntity::new, GUARDIAN) + .type(EntityType.ELDER_GUARDIAN) + .heightAndWidth(1.9975f) + .build(); + + SLIME = EntityDefinition.inherited(SlimeEntity::new, mobEntityBase) + .type(EntityType.SLIME) + .heightAndWidth(0.51f) + .addTranslator(MetadataType.INT, SlimeEntity::setScale) + .build(); + MAGMA_CUBE = EntityDefinition.inherited(MagmaCubeEntity::new, SLIME) + .type(EntityType.MAGMA_CUBE) + .build(); + + EntityDefinition abstractFishEntityBase = EntityDefinition.inherited(AbstractFishEntity::new, mobEntityBase) + .addTranslator(null) // From bucket + .build(); + COD = EntityDefinition.inherited(abstractFishEntityBase.factory(), abstractFishEntityBase) + .type(EntityType.COD) + .height(0.25f).width(0.5f) + .build(); + PUFFERFISH = EntityDefinition.inherited(PufferFishEntity::new, abstractFishEntityBase) + .type(EntityType.PUFFERFISH) + .heightAndWidth(0.7f) + .addTranslator(MetadataType.INT, PufferFishEntity::setPufferfishSize) + .build(); + SALMON = EntityDefinition.inherited(abstractFishEntityBase.factory(), abstractFishEntityBase) + .type(EntityType.SALMON) + .height(0.5f).width(0.7f) + .build(); + TROPICAL_FISH = EntityDefinition.inherited(TropicalFishEntity::new, abstractFishEntityBase) + .type(EntityType.TROPICAL_FISH) + .heightAndWidth(0.6f) + .identifier("minecraft:tropicalfish") + .addTranslator(MetadataType.INT, TropicalFishEntity::setFishVariant) + .build(); + + EntityDefinition abstractPiglinEntityBase = EntityDefinition.inherited(BasePiglinEntity::new, mobEntityBase) + .addTranslator(MetadataType.BOOLEAN, BasePiglinEntity::setImmuneToZombification) + .build(); + PIGLIN = EntityDefinition.inherited(PiglinEntity::new, abstractPiglinEntityBase) + .type(EntityType.PIGLIN) + .height(1.95f).width(0.6f) + .addTranslator(MetadataType.BOOLEAN, PiglinEntity::setBaby) + .addTranslator(MetadataType.BOOLEAN, PiglinEntity::setChargingCrossbow) + .addTranslator(MetadataType.BOOLEAN, PiglinEntity::setDancing) + .build(); + PIGLIN_BRUTE = EntityDefinition.inherited(abstractPiglinEntityBase.factory(), abstractPiglinEntityBase) + .type(EntityType.PIGLIN_BRUTE) + .height(1.95f).width(0.6f) + .build(); + + GLOW_SQUID = EntityDefinition.inherited(GlowSquidEntity::new, SQUID) + .type(EntityType.GLOW_SQUID) + .addTranslator(null) // Set dark ticks remaining, possible TODO + .build(); + + EntityDefinition raidParticipantEntityBase = EntityDefinition.inherited(RaidParticipantEntity::new, mobEntityBase) + .addTranslator(null) // Celebrating //TODO + .build(); + EntityDefinition spellcasterEntityBase = EntityDefinition.inherited(SpellcasterIllagerEntity::new, raidParticipantEntityBase) + .addTranslator(MetadataType.BYTE, SpellcasterIllagerEntity::setSpellType) + .build(); + EVOKER = EntityDefinition.inherited(spellcasterEntityBase.factory(), spellcasterEntityBase) + .type(EntityType.EVOKER) + .height(1.95f).width(0.6f) + .identifier("minecraft:evocation_illager") + .build(); + ILLUSIONER = EntityDefinition.inherited(spellcasterEntityBase.factory(), spellcasterEntityBase) + .type(EntityType.ILLUSIONER) + .height(1.95f).width(0.6f) + .identifier("minecraft:evocation_illager") + .build(); + PILLAGER = EntityDefinition.inherited(PillagerEntity::new, raidParticipantEntityBase) + .type(EntityType.PILLAGER) + .height(1.8f).width(0.6f) + .offset(1.62f) + .addTranslator(null) // Charging; doesn't have an equivalent on Bedrock //TODO check + .build(); + RAVAGER = EntityDefinition.inherited(raidParticipantEntityBase.factory(), raidParticipantEntityBase) + .type(EntityType.RAVAGER) + .height(1.9f).width(1.2f) + .build(); + VINDICATOR = EntityDefinition.inherited(VindicatorEntity::new, raidParticipantEntityBase) + .type(EntityType.VINDICATOR) + .height(1.8f).width(0.6f) + .offset(1.62f) + .build(); + WITCH = EntityDefinition.inherited(raidParticipantEntityBase.factory(), raidParticipantEntityBase) + .type(EntityType.WITCH) + .height(1.8f).width(0.6f) + .offset(1.62f) + .addTranslator(null) // Using item + .build(); + } + + EntityDefinition ageableEntityBase = EntityDefinition.inherited(AgeableEntity::new, mobEntityBase) + .addTranslator(MetadataType.BOOLEAN, AgeableEntity::setBaby) + .build(); + + // Extends ageable + { + AXOLOTL = EntityDefinition.inherited(AxolotlEntity::new, ageableEntityBase) + .type(EntityType.AXOLOTL) + .height(0.42f).width(0.7f) + .addTranslator(MetadataType.INT, AxolotlEntity::setVariant) + .addTranslator(MetadataType.BOOLEAN, AxolotlEntity::setPlayingDead) + .addTranslator(null) // From bucket + .build(); + BEE = EntityDefinition.inherited(BeeEntity::new, ageableEntityBase) + .type(EntityType.BEE) + .heightAndWidth(0.6f) + .addTranslator(MetadataType.BYTE, BeeEntity::setBeeFlags) + .addTranslator(MetadataType.INT, BeeEntity::setAngerTime) + .build(); + CHICKEN = EntityDefinition.inherited(ChickenEntity::new, ageableEntityBase) + .type(EntityType.CHICKEN) + .height(0.7f).width(0.4f) + .build(); + COW = EntityDefinition.inherited(AnimalEntity::new, ageableEntityBase) + .type(EntityType.COW) + .height(1.4f).width(0.9f) + .build(); + FOX = EntityDefinition.inherited(FoxEntity::new, ageableEntityBase) + .type(EntityType.FOX) + .height(0.5f).width(1.25f) + .addTranslator(MetadataType.INT, FoxEntity::setFoxVariant) + .addTranslator(MetadataType.BYTE, FoxEntity::setFoxFlags) + .addTranslator(null) // Trusted player 1 + .addTranslator(null) // Trusted player 2 + .build(); + HOGLIN = EntityDefinition.inherited(HoglinEntity::new, ageableEntityBase) + .type(EntityType.HOGLIN) + .height(1.4f).width(1.3965f) + .addTranslator(MetadataType.BOOLEAN, HoglinEntity::setImmuneToZombification) + .build(); + GOAT = EntityDefinition.inherited(GoatEntity::new, ageableEntityBase) + .type(EntityType.GOAT) + .height(1.3f).width(0.9f) + .addTranslator(MetadataType.BOOLEAN, GoatEntity::setScreamer) + .build(); + MOOSHROOM = EntityDefinition.inherited(MooshroomEntity::new, ageableEntityBase) // TODO remove class + .type(EntityType.MOOSHROOM) + .height(1.4f).width(0.9f) + .addTranslator(MetadataType.STRING, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityData.VARIANT, entityMetadata.getValue().equals("brown") ? 1 : 0)) + .build(); + OCELOT = EntityDefinition.inherited(OcelotEntity::new, ageableEntityBase) + .type(EntityType.OCELOT) + .height(0.35f).width(0.3f) + .addTranslator(MetadataType.BOOLEAN, (ocelotEntity, entityMetadata) -> ocelotEntity.setFlag(EntityFlag.TRUSTING, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue())) + .build(); + PANDA = EntityDefinition.inherited(PandaEntity::new, ageableEntityBase) + .type(EntityType.PANDA) + .height(1.25f).width(1.125f) + .addTranslator(null) // Unhappy counter + .addTranslator(null) // Sneeze counter + .addTranslator(MetadataType.INT, PandaEntity::setEatingCounter) + .addTranslator(MetadataType.BYTE, PandaEntity::setMainGene) + .addTranslator(MetadataType.BYTE, PandaEntity::setHiddenGene) + .addTranslator(MetadataType.BYTE, PandaEntity::setPandaFlags) + .build(); + PIG = EntityDefinition.inherited(PigEntity::new, ageableEntityBase) + .type(EntityType.PIG) + .heightAndWidth(0.9f) + .addTranslator(MetadataType.BOOLEAN, (pigEntity, entityMetadata) -> pigEntity.setFlag(EntityFlag.SADDLED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue())) + .addTranslator(null) // Boost time + .build(); + POLAR_BEAR = EntityDefinition.inherited(PolarBearEntity::new, ageableEntityBase) + .type(EntityType.POLAR_BEAR) + .height(1.4f).width(1.3f) + .addTranslator(MetadataType.BOOLEAN, (entity, entityMetadata) -> entity.setFlag(EntityFlag.STANDING, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue())) + .build(); + RABBIT = EntityDefinition.inherited(RabbitEntity::new, ageableEntityBase) + .type(EntityType.RABBIT) + .height(0.5f).width(0.4f) + .addTranslator(MetadataType.INT, RabbitEntity::setRabbitVariant) + .build(); + SHEEP = EntityDefinition.inherited(SheepEntity::new, ageableEntityBase) + .type(EntityType.SHEEP) + .heightAndWidth(0.9f) + .addTranslator(MetadataType.BYTE, SheepEntity::setSheepFlags) + .build(); + STRIDER = EntityDefinition.inherited(StriderEntity::new, ageableEntityBase) + .type(EntityType.STRIDER) + .height(1.7f).width(0.9f) + .addTranslator(null) // Boost time + .addTranslator(MetadataType.BOOLEAN, StriderEntity::setCold) + .addTranslator(MetadataType.BOOLEAN, StriderEntity::setSaddled) + .build(); + TURTLE = EntityDefinition.inherited(TurtleEntity::new, ageableEntityBase) + .type(EntityType.TURTLE) + .height(0.4f).width(1.2f) + .addTranslator(null) // Home position + .addTranslator(MetadataType.BOOLEAN, TurtleEntity::setPregnant) + .addTranslator(MetadataType.BOOLEAN, TurtleEntity::setLayingEgg) + .addTranslator(null) // Travel position + .addTranslator(null) // Going home + .addTranslator(null) // Travelling + .build(); + + EntityDefinition abstractVillagerEntityBase = EntityDefinition.inherited(AbstractMerchantEntity::new, ageableEntityBase) + .addTranslator(null) // Unhappy ticks + .build(); + VILLAGER = EntityDefinition.inherited(VillagerEntity::new, abstractVillagerEntityBase) + .type(EntityType.VILLAGER) + .height(1.8f).width(0.6f) + .offset(1.62f) + .identifier("minecraft:villager_v2") + .addTranslator(MetadataType.VILLAGER_DATA, VillagerEntity::setVillagerData) + .build(); + WANDERING_TRADER = EntityDefinition.inherited(abstractVillagerEntityBase.factory(), abstractVillagerEntityBase) + .type(EntityType.WANDERING_TRADER) + .height(1.8f).width(0.6f) + .offset(1.62f) + .build(); + } + + // Horses + { + EntityDefinition abstractHorseEntityBase = EntityDefinition.inherited(AbstractHorseEntity::new, ageableEntityBase) + .addTranslator(MetadataType.BYTE, AbstractHorseEntity::setHorseFlags) + .addTranslator(null) // UUID of owner + .build(); + HORSE = EntityDefinition.inherited(HorseEntity::new, abstractHorseEntityBase) + .type(EntityType.HORSE) + .height(1.6f).width(1.3965f) + .addTranslator(MetadataType.INT, HorseEntity::setHorseVariant) + .build(); + SKELETON_HORSE = EntityDefinition.inherited(abstractHorseEntityBase.factory(), abstractHorseEntityBase) + .type(EntityType.SKELETON_HORSE) + .height(1.6f).width(1.3965f) + .build(); + ZOMBIE_HORSE = EntityDefinition.inherited(abstractHorseEntityBase.factory(), abstractHorseEntityBase) + .type(EntityType.ZOMBIE_HORSE) + .height(1.6f).width(1.3965f) + .build(); + EntityDefinition chestedHorseEntityBase = EntityDefinition.inherited(ChestedHorseEntity::new, abstractHorseEntityBase) + .addTranslator(MetadataType.BOOLEAN, (horseEntity, entityMetadata) -> horseEntity.setFlag(EntityFlag.CHESTED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue())) + .build(); + DONKEY = EntityDefinition.inherited(chestedHorseEntityBase.factory(), chestedHorseEntityBase) + .type(EntityType.DONKEY) + .height(1.6f).width(1.3965f) + .build(); + MULE = EntityDefinition.inherited(chestedHorseEntityBase.factory(), chestedHorseEntityBase) + .type(EntityType.MULE) + .height(1.6f).width(1.3965f) + .build(); + LLAMA = EntityDefinition.inherited(LlamaEntity::new, chestedHorseEntityBase) + .type(EntityType.LLAMA) + .height(1.87f).width(0.9f) + .addTranslator(MetadataType.INT, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityData.STRENGTH, entityMetadata.getValue())) + .addTranslator(MetadataType.INT, LlamaEntity::setCarpetedColor) + .addTranslator(MetadataType.INT, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityData.VARIANT, entityMetadata.getValue())) + .build(); + TRADER_LLAMA = EntityDefinition.inherited(TraderLlamaEntity::new, LLAMA) + .type(EntityType.TRADER_LLAMA) + .identifier("minecraft:llama") + .build(); + } + + EntityDefinition tameableEntityBase = EntityDefinition.inherited(TameableEntity::new, ageableEntityBase) + .addTranslator(MetadataType.BYTE, TameableEntity::setTameableFlags) + .addTranslator(MetadataType.OPTIONAL_UUID, TameableEntity::setOwner) + .build(); + CAT = EntityDefinition.inherited(CatEntity::new, tameableEntityBase) + .type(EntityType.CAT) + .height(0.35f).width(0.3f) + .addTranslator(MetadataType.INT, CatEntity::setCatVariant) + .addTranslator(MetadataType.BOOLEAN, CatEntity::setResting) + .addTranslator(null) // "resting state one" //TODO + .addTranslator(MetadataType.INT, CatEntity::setCollarColor) + .build(); + PARROT = EntityDefinition.inherited(ParrotEntity::new, tameableEntityBase) + .type(EntityType.PARROT) + .height(0.9f).width(0.5f) + .addTranslator(MetadataType.INT, (parrotEntity, entityMetadata) -> parrotEntity.getDirtyMetadata().put(EntityData.VARIANT, entityMetadata.getValue())) // Parrot color + .build(); + WOLF = EntityDefinition.inherited(WolfEntity::new, tameableEntityBase) + .type(EntityType.WOLF) + .height(0.85f).width(0.6f) + // "Begging" on wiki.vg, "Interested" in Nukkit - the tilt of the head + .addTranslator(MetadataType.BOOLEAN, (wolfEntity, entityMetadata) -> wolfEntity.setFlag(EntityFlag.INTERESTED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue())) + .addTranslator(MetadataType.INT, WolfEntity::setCollarColor) + .addTranslator(MetadataType.INT, WolfEntity::setWolfAngerTime) + .build(); + + // As of 1.18 these don't track entity data at all + ENDER_DRAGON_PART = EntityDefinition.builder(null) + .identifier("minecraft:armor_stand") // Emulated + .build(false); // Never sent over the network + + Registries.JAVA_ENTITY_IDENTIFIERS.get().put("minecraft:marker", null); // We don't need an entity definition for this as it is never sent over the network + } + + public static void init() { + // no-op + } + + private EntityDefinitions() { + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/GeyserDirtyMetadata.java b/core/src/main/java/org/geysermc/geyser/entity/GeyserDirtyMetadata.java new file mode 100644 index 000000000..f50548f15 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/GeyserDirtyMetadata.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity; + +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityDataMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; + +import java.util.Map; + +/** + * A write-only wrapper for temporarily storing entity metadata that will be sent to Bedrock. + */ +public class GeyserDirtyMetadata { + private final Map metadata = new Object2ObjectLinkedOpenHashMap<>(); + + public void put(EntityData entityData, Object value) { + metadata.put(entityData, value); + } + + /** + * Applies the contents of the dirty metadata into the input and clears the contents of our map. + */ + public void apply(EntityDataMap map) { + map.putAll(metadata); + metadata.clear(); + } + + public boolean hasEntries() { + return !metadata.isEmpty(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/InteractiveTagManager.java b/core/src/main/java/org/geysermc/geyser/entity/InteractiveTagManager.java new file mode 100644 index 000000000..f8e1f43aa --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/InteractiveTagManager.java @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity; + +import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import lombok.Getter; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.type.living.MobEntity; +import org.geysermc.geyser.entity.type.living.animal.AnimalEntity; +import org.geysermc.geyser.entity.type.living.animal.horse.HorseEntity; +import org.geysermc.geyser.entity.type.living.animal.tameable.CatEntity; +import org.geysermc.geyser.entity.type.living.animal.tameable.WolfEntity; +import org.geysermc.geyser.entity.type.living.merchant.VillagerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.type.ItemMapping; + +import java.util.EnumSet; +import java.util.Set; + +public class InteractiveTagManager { + /** + * All entity types that can be leashed on Java Edition + */ + private static final Set LEASHABLE_MOB_TYPES = EnumSet.of(EntityType.AXOLOTL, EntityType.BEE, EntityType.CAT, EntityType.CHICKEN, + EntityType.COW, EntityType.DOLPHIN, EntityType.DONKEY, EntityType.FOX, EntityType.GOAT, EntityType.GLOW_SQUID, EntityType.HOGLIN, + EntityType.HORSE, EntityType.SKELETON_HORSE, EntityType.ZOMBIE_HORSE, EntityType.IRON_GOLEM, EntityType.LLAMA, + EntityType.TRADER_LLAMA, EntityType.MOOSHROOM, EntityType.MULE, EntityType.OCELOT, EntityType.PARROT, EntityType.PIG, + EntityType.POLAR_BEAR, EntityType.RABBIT, EntityType.SHEEP, EntityType.SNOW_GOLEM, EntityType.SQUID, EntityType.STRIDER, + EntityType.WOLF, EntityType.ZOGLIN); + + private static final Set SADDLEABLE_WHEN_TAMED_MOB_TYPES = EnumSet.of(EntityType.DONKEY, EntityType.HORSE, + EntityType.ZOMBIE_HORSE, EntityType.MULE); + + /** + * Update the suggestion that the client currently has on their screen for this entity (for example, "Feed" or "Ride") + * + * @param session the Bedrock client session + * @param interactEntity the entity that the client is currently facing. + */ + public static void updateTag(GeyserSession session, Entity interactEntity) { + ItemMapping mapping = session.getPlayerInventory().getItemInHand().getMapping(session); + String javaIdentifierStripped = mapping.getJavaIdentifier().replace("minecraft:", ""); + EntityType entityType = interactEntity.getDefinition().entityType(); + if (entityType == null) { + // Likely a technical entity; we don't need to worry about this + return; + } + + InteractiveTag interactiveTag = InteractiveTag.NONE; + + if (interactEntity instanceof MobEntity mobEntity && mobEntity.getLeashHolderBedrockId() == session.getPlayerEntity().getGeyserId()) { + // Unleash the entity + interactiveTag = InteractiveTag.REMOVE_LEASH; + } else if (javaIdentifierStripped.equals("saddle") && !interactEntity.getFlag(EntityFlag.SADDLED) && + ((SADDLEABLE_WHEN_TAMED_MOB_TYPES.contains(entityType) && interactEntity.getFlag(EntityFlag.TAMED) && !session.isSneaking()) || + entityType == EntityType.PIG || entityType == EntityType.STRIDER)) { + // Entity can be saddled and the conditions meet (entity can be saddled and, if needed, is tamed) + interactiveTag = InteractiveTag.SADDLE; + } else if (javaIdentifierStripped.equals("name_tag") && session.getPlayerInventory().getItemInHand().getNbt() != null && + session.getPlayerInventory().getItemInHand().getNbt().contains("display")) { + // Holding a named name tag + interactiveTag = InteractiveTag.NAME; + } else if (interactEntity instanceof MobEntity mobEntity &&javaIdentifierStripped.equals("lead") + && LEASHABLE_MOB_TYPES.contains(entityType) && mobEntity.getLeashHolderBedrockId() == -1L) { + // Holding a leash and the mob is leashable for sure + // (Plugins can change this behavior so that's something to look into in the far far future) + interactiveTag = InteractiveTag.LEASH; + } else if (interactEntity instanceof AnimalEntity && ((AnimalEntity) interactEntity).canEat(javaIdentifierStripped, mapping)) { + // This animal can be fed + interactiveTag = InteractiveTag.FEED; + } else { + switch (entityType) { + case BOAT: + if (interactEntity.getPassengers().size() < 2) { + interactiveTag = InteractiveTag.BOARD_BOAT; + } + break; + case CAT: + if (interactEntity.getFlag(EntityFlag.TAMED) && + ((CatEntity) interactEntity).getOwnerBedrockId() == session.getPlayerEntity().getGeyserId()) { + // Tamed and owned by player - can sit/stand + interactiveTag = interactEntity.getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; + break; + } + break; + case MOOSHROOM: + // Shear the mooshroom + if (javaIdentifierStripped.equals("shears")) { + interactiveTag = InteractiveTag.MOOSHROOM_SHEAR; + break; + } + // Bowls are acceptable here + else if (javaIdentifierStripped.equals("bowl")) { + interactiveTag = InteractiveTag.MOOSHROOM_MILK_STEW; + break; + } + // Fall down to COW as this works on mooshrooms + case COW: + if (javaIdentifierStripped.equals("bucket")) { + // Milk the cow + interactiveTag = InteractiveTag.MILK; + } + break; + case CREEPER: + if (javaIdentifierStripped.equals("flint_and_steel")) { + // Today I learned that you can ignite a creeper with flint and steel! Huh. + interactiveTag = InteractiveTag.IGNITE_CREEPER; + } + break; + case DONKEY: + case LLAMA: + case MULE: + if (interactEntity.getFlag(EntityFlag.TAMED) && !interactEntity.getFlag(EntityFlag.CHESTED) + && javaIdentifierStripped.equals("chest")) { + // Can attach a chest + interactiveTag = InteractiveTag.ATTACH_CHEST; + break; + } + // Intentional fall-through + case HORSE: + case SKELETON_HORSE: + case TRADER_LLAMA: + case ZOMBIE_HORSE: + boolean tamed = interactEntity.getFlag(EntityFlag.TAMED); + if (session.isSneaking() && tamed && (interactEntity instanceof HorseEntity || interactEntity.getFlag(EntityFlag.CHESTED))) { + interactiveTag = InteractiveTag.OPEN_CONTAINER; + break; + } + if (!interactEntity.getFlag(EntityFlag.BABY)) { + // Can't ride a baby + if (tamed) { + interactiveTag = InteractiveTag.RIDE_HORSE; + } else if (mapping.getJavaId() == 0) { + // Can't hide an untamed entity without having your hand empty + interactiveTag = InteractiveTag.MOUNT; + } + } + break; + case MINECART: + if (interactEntity.getPassengers().isEmpty()) { + interactiveTag = InteractiveTag.RIDE_MINECART; + } + break; + case CHEST_MINECART: + case COMMAND_BLOCK_MINECART: + case HOPPER_MINECART: + interactiveTag = InteractiveTag.OPEN_CONTAINER; + break; + case PIG: + if (interactEntity.getFlag(EntityFlag.SADDLED)) { + interactiveTag = InteractiveTag.MOUNT; + } + break; + case PIGLIN: + if (!interactEntity.getFlag(EntityFlag.BABY) && javaIdentifierStripped.equals("gold_ingot")) { + interactiveTag = InteractiveTag.BARTER; + } + break; + case SHEEP: + if (!interactEntity.getFlag(EntityFlag.SHEARED)) { + if (javaIdentifierStripped.equals("shears")) { + // Shear the sheep + interactiveTag = InteractiveTag.SHEAR; + } else if (javaIdentifierStripped.contains("_dye")) { + // Dye the sheep + interactiveTag = InteractiveTag.DYE; + } + } + break; + case STRIDER: + if (interactEntity.getFlag(EntityFlag.SADDLED)) { + interactiveTag = InteractiveTag.RIDE_STRIDER; + } + break; + case VILLAGER: + VillagerEntity villager = (VillagerEntity) interactEntity; + if (villager.isCanTradeWith() && !villager.isBaby()) { // Not a nitwit, has a profession and is not a baby + interactiveTag = InteractiveTag.TRADE; + } + break; + case WANDERING_TRADER: + interactiveTag = InteractiveTag.TRADE; // Since you can always trade with a wandering villager, presumably. + break; + case WOLF: + if (javaIdentifierStripped.equals("bone") && !interactEntity.getFlag(EntityFlag.TAMED)) { + // Bone and untamed - can tame + interactiveTag = InteractiveTag.TAME; + } else if (interactEntity.getFlag(EntityFlag.TAMED) && + ((WolfEntity) interactEntity).getOwnerBedrockId() == session.getPlayerEntity().getGeyserId()) { + // Tamed and owned by player - can sit/stand + interactiveTag = interactEntity.getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; + } + break; + case ZOMBIE_VILLAGER: + // We can't guarantee the existence of the weakness effect so we just always show it. + if (javaIdentifierStripped.equals("golden_apple")) { + interactiveTag = InteractiveTag.CURE; + } + break; + default: + break; + } + } + session.getPlayerEntity().getDirtyMetadata().put(EntityData.INTERACTIVE_TAG, interactiveTag.getValue()); + session.getPlayerEntity().updateBedrockMetadata(); + } + + /** + * All interactive tags in enum form. For potential API usage. + */ + public enum InteractiveTag { + NONE((Void) null), + IGNITE_CREEPER("creeper"), + EDIT, + LEAVE_BOAT("exit.boat"), + FEED, + FISH("fishing"), + MILK, + MOOSHROOM_SHEAR("mooshear"), + MOOSHROOM_MILK_STEW("moostew"), + BOARD_BOAT("ride.boat"), + RIDE_MINECART("ride.minecart"), + RIDE_HORSE("ride.horse"), + RIDE_STRIDER("ride.strider"), + SHEAR, + SIT, + STAND, + TALK, + TAME, + DYE, + CURE, + OPEN_CONTAINER("opencontainer"), + CREATE_MAP("createMap"), + TAKE_PICTURE("takepicture"), + SADDLE, + MOUNT, + BOOST, + WRITE, + LEASH, + REMOVE_LEASH("unleash"), + NAME, + ATTACH_CHEST("attachchest"), + TRADE, + POSE_ARMOR_STAND("armorstand.pose"), + EQUIP_ARMOR_STAND("armorstand.equip"), + READ, + WAKE_VILLAGER("wakevillager"), + BARTER; + + /** + * The full string that should be passed on to the client. + */ + @Getter + private final String value; + + InteractiveTag(Void isNone) { + this.value = ""; + } + + InteractiveTag(String value) { + this.value = "action.interact." + value; + } + + InteractiveTag() { + this.value = "action.interact." + name().toLowerCase(); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/attribute/AttributeType.java b/core/src/main/java/org/geysermc/geyser/entity/attribute/GeyserAttributeType.java similarity index 76% rename from connector/src/main/java/org/geysermc/connector/entity/attribute/AttributeType.java rename to core/src/main/java/org/geysermc/geyser/entity/attribute/GeyserAttributeType.java index 1d692e2a7..86f2a1923 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/attribute/AttributeType.java +++ b/core/src/main/java/org/geysermc/geyser/entity/attribute/GeyserAttributeType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,14 +23,15 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.attribute; +package org.geysermc.geyser.entity.attribute; +import com.nukkitx.protocol.bedrock.data.AttributeData; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor -public enum AttributeType { +public enum GeyserAttributeType { // Universal Attributes FOLLOW_RANGE("minecraft:generic.follow_range", "minecraft:follow_range", 0f, 2048f, 32f), @@ -39,13 +40,13 @@ public enum AttributeType { FLYING_SPEED("minecraft:generic.flying_speed", "minecraft:movement", 0.0f, 1024.0f, 0.4000000059604645f), ATTACK_DAMAGE("minecraft:generic.attack_damage", "minecraft:attack_damage", 0f, 2048f, 1f), HORSE_JUMP_STRENGTH("minecraft:horse.jump_strength", "minecraft:horse.jump_strength", 0.0f, 2.0f, 0.7f), + LUCK("minecraft:generic.luck", "minecraft:luck", -1024f, 1024f, 0f), // Java Attributes ARMOR("minecraft:generic.armor", null, 0f, 30f, 0f), ARMOR_TOUGHNESS("minecraft:generic.armor_toughness", null, 0F, 20f, 0f), ATTACK_KNOCKBACK("minecraft:generic.attack_knockback", null, 1.5f, Float.MAX_VALUE, 0f), ATTACK_SPEED("minecraft:generic.attack_speed", null, 0f, 1024f, 4f), - LUCK("minecraft:generic.luck", null, -1024f, 1024f, 0f), MAX_HEALTH("minecraft:generic.max_health", null, 0f, 1024f, 20f), // Bedrock Attributes @@ -57,26 +58,22 @@ public enum AttributeType { HUNGER(null, "minecraft:player.hunger", 0f, 20f, 20f), SATURATION(null, "minecraft:player.saturation", 0f, 20f, 20f); - private String javaIdentifier; - private String bedrockIdentifier; + private final String javaIdentifier; + private final String bedrockIdentifier; - private float minimum; - private float maximum; - private float defaultValue; + private final float minimum; + private final float maximum; + private final float defaultValue; - public Attribute getAttribute(float value) { + public AttributeData getAttribute(float value) { return getAttribute(value, maximum); } - public Attribute getAttribute(float value, float maximum) { - return new Attribute(this, minimum, maximum, value, defaultValue); - } - - public boolean isJavaAttribute() { - return javaIdentifier != null; - } - - public boolean isBedrockAttribute() { - return bedrockIdentifier != null; + public AttributeData getAttribute(float value, float maximum) { + if (bedrockIdentifier == null) { + return null; + } + // Minimum, maximum, and default values are hardcoded on Java Edition, whereas controlled by the server on Bedrock + return new AttributeData(bedrockIdentifier, minimum, maximum, value, defaultValue); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/FlyingEntity.java b/core/src/main/java/org/geysermc/geyser/entity/factory/BaseEntityFactory.java similarity index 68% rename from connector/src/main/java/org/geysermc/connector/entity/living/FlyingEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/factory/BaseEntityFactory.java index ac36bed21..dff1617f9 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/FlyingEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/factory/BaseEntityFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,14 +23,17 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living; +package org.geysermc.geyser.entity.factory; import com.nukkitx.math.vector.Vector3f; -import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; -public class FlyingEntity extends InsentientEntity { +import java.util.UUID; - public FlyingEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } +@FunctionalInterface +public interface BaseEntityFactory extends EntityFactory { + + T create(GeyserSession session, long javaId, long bedrockId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw); } diff --git a/common/src/main/java/org/geysermc/common/window/response/FormResponseData.java b/core/src/main/java/org/geysermc/geyser/entity/factory/EntityFactory.java similarity index 80% rename from common/src/main/java/org/geysermc/common/window/response/FormResponseData.java rename to core/src/main/java/org/geysermc/geyser/entity/factory/EntityFactory.java index fd40be0fb..774166d44 100644 --- a/common/src/main/java/org/geysermc/common/window/response/FormResponseData.java +++ b/core/src/main/java/org/geysermc/geyser/entity/factory/EntityFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,15 +23,9 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.response; +package org.geysermc.geyser.entity.factory; -import lombok.AllArgsConstructor; -import lombok.Getter; +import org.geysermc.geyser.entity.type.Entity; -@AllArgsConstructor -@Getter -public class FormResponseData { - - private int elementID; - private String elementContent; +public interface EntityFactory { } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/SquidEntity.java b/core/src/main/java/org/geysermc/geyser/entity/factory/ExperienceOrbEntityFactory.java similarity index 74% rename from connector/src/main/java/org/geysermc/connector/entity/living/SquidEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/factory/ExperienceOrbEntityFactory.java index 81c0eeef1..40a93c08b 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/SquidEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/factory/ExperienceOrbEntityFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,14 +23,14 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living; +package org.geysermc.geyser.entity.factory; import com.nukkitx.math.vector.Vector3f; -import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.geyser.entity.type.ExpOrbEntity; +import org.geysermc.geyser.session.GeyserSession; -public class SquidEntity extends WaterEntity { +@FunctionalInterface +public interface ExperienceOrbEntityFactory extends EntityFactory { - public SquidEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } + ExpOrbEntity create(GeyserSession session, int amount, long entityId, long geyserId, Vector3f position); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/LeashKnotEntity.java b/core/src/main/java/org/geysermc/geyser/entity/factory/PaintingEntityFactory.java similarity index 70% rename from connector/src/main/java/org/geysermc/connector/entity/LeashKnotEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/factory/PaintingEntityFactory.java index 1b6f208bb..2990b0ac5 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/LeashKnotEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/factory/PaintingEntityFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,16 +23,16 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity; +package org.geysermc.geyser.entity.factory; import com.nukkitx.math.vector.Vector3f; -import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.geyser.entity.type.PaintingEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.PaintingType; -public class LeashKnotEntity extends Entity { +import java.util.UUID; - public LeashKnotEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - // Position is incorrect by default - super(entityId, geyserId, entityType, position.add(0.5f, 0.25f, 0.5f), motion, rotation); - } +public interface PaintingEntityFactory extends EntityFactory { + PaintingEntity create(GeyserSession session, long entityId, long geyserId, UUID uuid, Vector3f position, PaintingType paintingName, int direction); } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/AbstractArrowEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/AbstractArrowEntity.java new file mode 100644 index 000000000..b80db2570 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/AbstractArrowEntity.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +public class AbstractArrowEntity extends Entity { + + public AbstractArrowEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + + // Set the correct texture if using the resource pack + setFlag(EntityFlag.BRIBED, definition.entityType() == EntityType.SPECTRAL_ARROW); + + setMotion(motion); + } + + public void setArrowFlags(ByteEntityMetadata entityMetadata) { + byte data = entityMetadata.getPrimitiveValue(); + + setFlag(EntityFlag.CRITICAL, (data & 0x01) == 0x01); + } + + // Ignore the rotation sent by the Java server since the + // Java client calculates the rotation from the motion + @Override + public void setYaw(float yaw) { + } + + @Override + public void setPitch(float pitch) { + } + + @Override + public void setHeadYaw(float headYaw) { + } + + @Override + public void setMotion(Vector3f motion) { + super.setMotion(motion); + + double horizontalSpeed = Math.sqrt(motion.getX() * motion.getX() + motion.getZ() * motion.getZ()); + this.yaw = (float) Math.toDegrees(Math.atan2(motion.getX(), motion.getZ())); + this.pitch = (float) Math.toDegrees(Math.atan2(motion.getY(), horizontalSpeed)); + this.headYaw = yaw; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/AreaEffectCloudEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/AreaEffectCloudEntity.java new file mode 100644 index 000000000..6063c81f9 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/AreaEffectCloudEntity.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.level.particle.Particle; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.Registries; + +import java.util.UUID; + +public class AreaEffectCloudEntity extends Entity { + + public AreaEffectCloudEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + @Override + protected void initializeMetadata() { + super.initializeMetadata(); + // Without this the cloud doesn't appear, + dirtyMetadata.put(EntityData.AREA_EFFECT_CLOUD_DURATION, 600); + + // This disabled client side shrink of the cloud + dirtyMetadata.put(EntityData.AREA_EFFECT_CLOUD_RADIUS, 0.0f); + dirtyMetadata.put(EntityData.AREA_EFFECT_CLOUD_CHANGE_RATE, -0.005f); + dirtyMetadata.put(EntityData.AREA_EFFECT_CLOUD_CHANGE_ON_PICKUP, -0.5f); + + setFlag(EntityFlag.FIRE_IMMUNE, true); + } + + public void setRadius(FloatEntityMetadata entityMetadata) { + float value = entityMetadata.getPrimitiveValue(); + dirtyMetadata.put(EntityData.AREA_EFFECT_CLOUD_RADIUS, value); + dirtyMetadata.put(EntityData.BOUNDING_BOX_WIDTH, 2.0f * value); + } + + public void setParticle(EntityMetadata entityMetadata) { + Particle particle = entityMetadata.getValue(); + int particleId = Registries.PARTICLES.map(particle.getType(), mapping -> mapping.getParticleId(this.session)).orElse(-1); + if (particleId != -1) { + dirtyMetadata.put(EntityData.AREA_EFFECT_CLOUD_PARTICLE_ID, particleId); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java new file mode 100644 index 000000000..ac1b3fcbd --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.packet.AnimatePacket; +import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; +import lombok.Getter; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +public class BoatEntity extends Entity { + + /** + * Required when IS_BUOYANT is sent in order for boats to work in the water.
+ * + * Taken from BDS 1.16.200, with the modification of simulate_waves since Java doesn't bob the boat up and down + * like Bedrock. + */ + private static final String BUOYANCY_DATA = "{\"apply_gravity\":true,\"base_buoyancy\":1.0,\"big_wave_probability\":0.02999999932944775," + + "\"big_wave_speed\":10.0,\"drag_down_on_buoyancy_removed\":0.0,\"liquid_blocks\":[\"minecraft:water\"," + + "\"minecraft:flowing_water\"],\"simulate_waves\":false}"; + + private boolean isPaddlingLeft; + private float paddleTimeLeft; + private boolean isPaddlingRight; + private float paddleTimeRight; + + /** + * Saved for using the "pick" functionality on a boat. + */ + @Getter + private int variant; + + // Looks too fast and too choppy with 0.1f, which is how I believe the Microsoftian client handles it + private final float ROWING_SPEED = 0.05f; + + public BoatEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + // Initial rotation is incorrect + super(session, entityId, geyserId, uuid, definition, position.add(0d, definition.offset(), 0d), motion, yaw + 90, 0, yaw + 90); + + // Required to be able to move on land 1.16.200+ or apply gravity not in the water 1.16.100+ + dirtyMetadata.put(EntityData.IS_BUOYANT, (byte) 1); + dirtyMetadata.put(EntityData.BUOYANCY_DATA, BUOYANCY_DATA); + } + + @Override + public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { + // We don't include the rotation (y) as it causes the boat to appear sideways + setPosition(position.add(0d, this.definition.offset(), 0d)); + this.yaw = yaw + 90; + this.headYaw = yaw + 90; + setOnGround(isOnGround); + + MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket(); + moveEntityPacket.setRuntimeEntityId(geyserId); + // Minimal glitching when ClientboundMoveVehiclePacket is sent + moveEntityPacket.setPosition(session.getRidingVehicleEntity() == this ? position.up(EntityDefinitions.PLAYER.offset() - this.definition.offset()) : this.position); + moveEntityPacket.setRotation(getBedrockRotation()); + moveEntityPacket.setOnGround(isOnGround); + moveEntityPacket.setTeleported(teleported); + + session.sendUpstreamPacket(moveEntityPacket); + } + + /** + * Move the boat without making the adjustments needed to translate from Java + */ + public void moveAbsoluteWithoutAdjustments(Vector3f position, float yaw, boolean isOnGround, boolean teleported) { + super.moveAbsolute(position, yaw, 0, yaw, isOnGround, teleported); + } + + @Override + public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) { + super.moveRelative(relX, relY, relZ, yaw, 0, yaw, isOnGround); + } + + @Override + public void updatePositionAndRotation(double moveX, double moveY, double moveZ, float yaw, float pitch, boolean isOnGround) { + moveRelative(moveX, moveY, moveZ, yaw + 90, pitch, isOnGround); + } + + @Override + public void updateRotation(float yaw, float pitch, boolean isOnGround) { + moveRelative(0, 0, 0, yaw + 90, 0, 0, isOnGround); + } + + public void setVariant(IntEntityMetadata entityMetadata) { + variant = entityMetadata.getPrimitiveValue(); + dirtyMetadata.put(EntityData.VARIANT, variant); + } + + public void setPaddlingLeft(BooleanEntityMetadata entityMetadata) { + isPaddlingLeft = entityMetadata.getPrimitiveValue(); + if (isPaddlingLeft) { + // Java sends simply "true" and "false" (is_paddling_left), Bedrock keeps sending packets as you're rowing + // This is an asynchronous method that emulates Bedrock rowing until "false" is sent. + paddleTimeLeft = 0f; + if (!this.passengers.isEmpty()) { + // Get the entity by the first stored passenger and convey motion in this manner + Entity entity = session.getEntityCache().getEntityByJavaId(this.passengers.iterator().nextLong()); + if (entity != null) { + updateLeftPaddle(session, entity); + } + } + } else { + // Indicate that the row position should be reset + dirtyMetadata.put(EntityData.ROW_TIME_LEFT, 0.0f); + } + } + + public void setPaddlingRight(BooleanEntityMetadata entityMetadata) { + isPaddlingRight = entityMetadata.getPrimitiveValue(); + if (isPaddlingRight) { + paddleTimeRight = 0f; + if (!this.passengers.isEmpty()) { + Entity entity = session.getEntityCache().getEntityByJavaId(this.passengers.iterator().nextLong()); + if (entity != null) { + updateRightPaddle(session, entity); + } + } + } else { + dirtyMetadata.put(EntityData.ROW_TIME_RIGHT, 0.0f); + } + } + + private void updateLeftPaddle(GeyserSession session, Entity rower) { + if (isPaddlingLeft) { + paddleTimeLeft += ROWING_SPEED; + sendAnimationPacket(session, rower, AnimatePacket.Action.ROW_LEFT, paddleTimeLeft); + + session.scheduleInEventLoop(() -> + updateLeftPaddle(session, rower), + 100, + TimeUnit.MILLISECONDS + ); + } + } + + private void updateRightPaddle(GeyserSession session, Entity rower) { + if (isPaddlingRight) { + paddleTimeRight += ROWING_SPEED; + sendAnimationPacket(session, rower, AnimatePacket.Action.ROW_RIGHT, paddleTimeRight); + + session.scheduleInEventLoop(() -> + updateRightPaddle(session, rower), + 100, + TimeUnit.MILLISECONDS + ); + } + } + + private void sendAnimationPacket(GeyserSession session, Entity rower, AnimatePacket.Action action, float rowTime) { + AnimatePacket packet = new AnimatePacket(); + packet.setRuntimeEntityId(rower.getGeyserId()); + packet.setAction(action); + packet.setRowingTime(rowTime); + session.sendUpstreamPacket(packet); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/CommandBlockMinecartEntity.java similarity index 53% rename from connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/CommandBlockMinecartEntity.java index 7d34cc795..1764c721e 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/CommandBlockMinecartEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,36 +23,28 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity; +package org.geysermc.geyser.entity.type; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.network.translators.chat.MessageTranslator; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity { - public CommandBlockMinecartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - // Required, or else the GUI will not open - metadata.put(EntityData.CONTAINER_TYPE, (byte) 16); - metadata.put(EntityData.CONTAINER_BASE_SIZE, 1); - // Required, or else the client does not bother to send a packet back with the new information - metadata.put(EntityData.COMMAND_BLOCK_ENABLED, (byte) 1); + public CommandBlockMinecartEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 13) { - metadata.put(EntityData.COMMAND_BLOCK_COMMAND, entityMetadata.getValue()); - } - if (entityMetadata.getId() == 14) { - metadata.put(EntityData.COMMAND_BLOCK_LAST_OUTPUT, MessageTranslator.convertMessage(entityMetadata.getValue().toString())); - } - super.updateBedrockMetadata(entityMetadata, session); + protected void initializeMetadata() { + // Required, or else the GUI will not open + dirtyMetadata.put(EntityData.CONTAINER_TYPE, (byte) 16); + dirtyMetadata.put(EntityData.CONTAINER_BASE_SIZE, 1); + // Required, or else the client does not bother to send a packet back with the new information + dirtyMetadata.put(EntityData.COMMAND_BLOCK_ENABLED, (byte) 1); } /** @@ -60,7 +52,7 @@ public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity { */ @Override public void updateDefaultBlockMetadata() { - metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.BEDROCK_RUNTIME_COMMAND_BLOCK_ID); - metadata.put(EntityData.DISPLAY_OFFSET, 6); + dirtyMetadata.put(EntityData.DISPLAY_ITEM, session.getBlockMappings().getCommandBlockRuntimeId()); + dirtyMetadata.put(EntityData.DISPLAY_OFFSET, 6); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/DefaultBlockMinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/DefaultBlockMinecartEntity.java new file mode 100644 index 000000000..ec00c30be --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/DefaultBlockMinecartEntity.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +/** + * This class is used as a base for minecarts with a default block to display like furnaces and spawners + */ +public class DefaultBlockMinecartEntity extends MinecartEntity { + + public int customBlock = 0; + public int customBlockOffset = 0; + public boolean showCustomBlock = false; + + public DefaultBlockMinecartEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + + dirtyMetadata.put(EntityData.CUSTOM_DISPLAY, (byte) 1); + } + + @Override + public void spawnEntity() { + updateDefaultBlockMetadata(); + super.spawnEntity(); + } + + @Override + public void setCustomBlock(IntEntityMetadata entityMetadata) { + customBlock = ((IntEntityMetadata) entityMetadata).getPrimitiveValue(); + + if (showCustomBlock) { + dirtyMetadata.put(EntityData.DISPLAY_ITEM, session.getBlockMappings().getBedrockBlockId(customBlock)); + } + } + + @Override + public void setCustomBlockOffset(IntEntityMetadata entityMetadata) { + customBlockOffset = entityMetadata.getPrimitiveValue(); + + if (showCustomBlock) { + dirtyMetadata.put(EntityData.DISPLAY_OFFSET, customBlockOffset); + } + } + + @Override + public void setShowCustomBlock(BooleanEntityMetadata entityMetadata) { + if (entityMetadata.getPrimitiveValue()) { + showCustomBlock = true; + dirtyMetadata.put(EntityData.DISPLAY_ITEM, session.getBlockMappings().getBedrockBlockId(customBlock)); + dirtyMetadata.put(EntityData.DISPLAY_OFFSET, customBlockOffset); + } else { + showCustomBlock = false; + updateDefaultBlockMetadata(); + } + } + + public void updateDefaultBlockMetadata() { + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/EnderCrystalEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/EnderCrystalEntity.java similarity index 58% rename from connector/src/main/java/org/geysermc/connector/entity/EnderCrystalEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/EnderCrystalEntity.java index 4b665683e..aa907b7c6 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/EnderCrystalEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/EnderCrystalEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity; +package org.geysermc.geyser.entity.type; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; @@ -31,32 +31,34 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.Optional; +import java.util.UUID; public class EnderCrystalEntity extends Entity { - public EnderCrystalEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - + public EnderCrystalEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + protected void initializeMetadata() { + super.initializeMetadata(); + // Bedrock 1.16.100+ - prevents the entity from appearing on fire itself when fire is underneath it + setFlag(EntityFlag.FIRE_IMMUNE, true); + } + + public void setBlockTarget(EntityMetadata, ?> entityMetadata) { // Show beam // Usually performed client-side on Bedrock except for Ender Dragon respawn event - if (entityMetadata.getId() == 7) { - if (entityMetadata.getValue() instanceof Position) { - Position pos = (Position) entityMetadata.getValue(); - metadata.put(EntityData.BLOCK_TARGET, Vector3i.from(pos.getX(), pos.getY(), pos.getZ())); - } else { - metadata.put(EntityData.BLOCK_TARGET, Vector3i.ZERO); - } + Optional optionalPos = entityMetadata.getValue(); + if (optionalPos.isPresent()) { + Position pos = optionalPos.get(); + dirtyMetadata.put(EntityData.BLOCK_TARGET, Vector3i.from(pos.getX(), pos.getY(), pos.getZ())); + } else { + dirtyMetadata.put(EntityData.BLOCK_TARGET, Vector3i.ZERO); } - // There is a base located on the ender crystal - if (entityMetadata.getId() == 8) { - metadata.getFlags().setFlag(EntityFlag.SHOW_BOTTOM, (boolean) entityMetadata.getValue()); - } - super.updateBedrockMetadata(entityMetadata, session); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java new file mode 100644 index 000000000..479ec2e8c --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java @@ -0,0 +1,446 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlags; +import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; +import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; +import com.nukkitx.protocol.bedrock.packet.RemoveEntityPacket; +import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import net.kyori.adventure.text.Component; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.GeyserDirtyMetadata; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.geyser.util.MathUtils; + +import java.util.Optional; +import java.util.UUID; + +@Getter +@Setter +public class Entity { + protected final GeyserSession session; + + protected long entityId; + protected final long geyserId; + protected UUID uuid; + + protected Vector3f position; + protected Vector3f motion; + + /** + * x = Yaw, y = Pitch, z = HeadYaw + */ + protected float yaw; + protected float pitch; + protected float headYaw; + + /** + * Saves if the entity should be on the ground. Otherwise entities like parrots are flapping when rotating + */ + protected boolean onGround; + + protected EntityDefinition definition; + + protected boolean valid; + + /* Metadata about this specific entity */ + @Setter(AccessLevel.NONE) + private float boundingBoxHeight; + @Setter(AccessLevel.NONE) + private float boundingBoxWidth; + @Setter(AccessLevel.NONE) + protected String nametag = ""; + /* Metadata end */ + + protected LongOpenHashSet passengers = new LongOpenHashSet(); + /** + * A container to store temporary metadata before it's sent to Bedrock. + */ + protected final GeyserDirtyMetadata dirtyMetadata = new GeyserDirtyMetadata(); + /** + * The entity flags for the Bedrock entity. + * These must always be saved - if flags are updated and the other values aren't present, the Bedrock client will + * think they are set to false. + */ + @Getter(AccessLevel.NONE) + protected final EntityFlags flags = new EntityFlags(); + /** + * Indicates if flags have been updated and need to be sent to the client. + */ + @Getter(AccessLevel.NONE) + @Setter(AccessLevel.PROTECTED) // For players + private boolean flagsDirty = false; + + public Entity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + this.session = session; + + this.entityId = entityId; + this.geyserId = geyserId; + this.uuid = uuid; + this.definition = definition; + this.motion = motion; + this.yaw = yaw; + this.pitch = pitch; + this.headYaw = headYaw; + + this.valid = false; + + setPosition(position); + setAirSupply(getMaxAir()); + + initializeMetadata(); + } + + /** + * Called on entity spawn. Used to populate the entity metadata and flags with default values. + */ + protected void initializeMetadata() { + dirtyMetadata.put(EntityData.SCALE, 1f); + dirtyMetadata.put(EntityData.COLOR, 0); + dirtyMetadata.put(EntityData.MAX_AIR_SUPPLY, getMaxAir()); + setDimensions(Pose.STANDING); + setFlag(EntityFlag.HAS_GRAVITY, true); + setFlag(EntityFlag.HAS_COLLISION, true); + setFlag(EntityFlag.CAN_SHOW_NAME, true); + setFlag(EntityFlag.CAN_CLIMB, true); + } + + public void spawnEntity() { + AddEntityPacket addEntityPacket = new AddEntityPacket(); + addEntityPacket.setIdentifier(definition.identifier()); + addEntityPacket.setRuntimeEntityId(geyserId); + addEntityPacket.setUniqueEntityId(geyserId); + addEntityPacket.setPosition(position); + addEntityPacket.setMotion(motion); + addEntityPacket.setRotation(getBedrockRotation()); + addEntityPacket.getMetadata().putFlags(flags); + dirtyMetadata.apply(addEntityPacket.getMetadata()); + addAdditionalSpawnData(addEntityPacket); + + valid = true; + session.sendUpstreamPacket(addEntityPacket); + + flagsDirty = false; + + if (session.getGeyser().getConfig().isDebugMode()) { + EntityType type = definition.entityType(); + String name = type != null ? type.name() : getClass().getSimpleName(); + session.getGeyser().getLogger().debug("Spawned entity " + name + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); + } + } + + /** + * To be overridden in other entity classes, if additional things need to be done to the spawn entity packet. + */ + public void addAdditionalSpawnData(AddEntityPacket addEntityPacket) { + } + + /** + * Despawns the entity + * + * @return can be deleted + */ + public boolean despawnEntity() { + if (!valid) return true; + + for (long passenger : passengers) { // Make sure all passengers on the despawned entity are updated + Entity entity = session.getEntityCache().getEntityByJavaId(passenger); + if (entity == null) continue; + entity.setFlag(EntityFlag.RIDING, false); + entity.updateBedrockMetadata(); + } + + RemoveEntityPacket removeEntityPacket = new RemoveEntityPacket(); + removeEntityPacket.setUniqueEntityId(geyserId); + session.sendUpstreamPacket(removeEntityPacket); + + valid = false; + return true; + } + + public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, boolean isOnGround) { + moveRelative(relX, relY, relZ, yaw, pitch, this.headYaw, isOnGround); + } + + public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) { + setYaw(yaw); + setPitch(pitch); + setHeadYaw(headYaw); + setOnGround(isOnGround); + this.position = Vector3f.from(position.getX() + relX, position.getY() + relY, position.getZ() + relZ); + + MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket(); + moveEntityPacket.setRuntimeEntityId(geyserId); + moveEntityPacket.setPosition(position); + moveEntityPacket.setRotation(getBedrockRotation()); + moveEntityPacket.setOnGround(isOnGround); + moveEntityPacket.setTeleported(false); + + session.sendUpstreamPacket(moveEntityPacket); + } + + public void moveAbsolute(Vector3f position, float yaw, float pitch, boolean isOnGround, boolean teleported) { + moveAbsolute(position, yaw, pitch, this.headYaw, isOnGround, teleported); + } + + public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { + setPosition(position); + // Setters are intentional so it can be overridden in places like AbstractArrowEntity + setYaw(yaw); + setPitch(pitch); + setHeadYaw(headYaw); + setOnGround(isOnGround); + + MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket(); + moveEntityPacket.setRuntimeEntityId(geyserId); + moveEntityPacket.setPosition(position); + moveEntityPacket.setRotation(getBedrockRotation()); + moveEntityPacket.setOnGround(isOnGround); + moveEntityPacket.setTeleported(teleported); + + session.sendUpstreamPacket(moveEntityPacket); + } + + /** + * Teleports an entity to a new location. Used in JavaTeleportEntityTranslator. + * @param position The new position of the entity. + * @param yaw The new yaw of the entity. + * @param pitch The new pitch of the entity. + * @param isOnGround Whether the entity is currently on the ground. + */ + public void teleport(Vector3f position, float yaw, float pitch, boolean isOnGround) { + moveAbsolute(position, yaw, pitch, isOnGround, false); + } + + /** + * Updates an entity's head position. Used in JavaRotateHeadTranslator. + * @param headYaw The new head rotation of the entity. + */ + public void updateHeadLookRotation(float headYaw) { + moveRelative(0, 0, 0, headYaw, pitch, this.headYaw, onGround); + } + + /** + * Updates an entity's position and rotation. Used in JavaMoveEntityPosRotTranslator. + * @param moveX The new X offset of the current position. + * @param moveY The new Y offset of the current position. + * @param moveZ The new Z offset of the current position. + * @param yaw The new yaw of the entity. + * @param pitch The new pitch of the entity. + * @param isOnGround Whether the entity is currently on the ground. + */ + public void updatePositionAndRotation(double moveX, double moveY, double moveZ, float yaw, float pitch, boolean isOnGround) { + moveRelative(moveX, moveY, moveZ, this.yaw, pitch, yaw, isOnGround); + } + + /** + * Updates an entity's rotation. Used in JavaMoveEntityRotTranslator. + * @param yaw The new yaw of the entity. + * @param pitch The new pitch of the entity. + * @param isOnGround Whether the entity is currently on the ground. + */ + public void updateRotation(float yaw, float pitch, boolean isOnGround) { + updatePositionAndRotation(0, 0, 0, yaw, pitch, isOnGround); + } + + public final boolean getFlag(EntityFlag flag) { + return flags.getFlag(flag); + } + + /** + * Updates a flag value and determines if the flags would need synced with the Bedrock client. + */ + public final void setFlag(EntityFlag flag, boolean value) { + flagsDirty |= flags.setFlag(flag, value); + } + + /** + * Sends the Bedrock metadata to the client + */ + public void updateBedrockMetadata() { + if (!valid) { + return; + } + + if (dirtyMetadata.hasEntries() || flagsDirty) { + SetEntityDataPacket entityDataPacket = new SetEntityDataPacket(); + entityDataPacket.setRuntimeEntityId(geyserId); + if (flagsDirty) { + entityDataPacket.getMetadata().putFlags(flags); + flagsDirty = false; + } + dirtyMetadata.apply(entityDataPacket.getMetadata()); + session.sendUpstreamPacket(entityDataPacket); + } + } + + public void setFlags(ByteEntityMetadata entityMetadata) { + byte xd = entityMetadata.getPrimitiveValue(); + setFlag(EntityFlag.ON_FIRE, ((xd & 0x01) == 0x01) && !getFlag(EntityFlag.FIRE_IMMUNE)); // Otherwise immune entities sometimes flicker onfire + setFlag(EntityFlag.SNEAKING, (xd & 0x02) == 0x02); + setFlag(EntityFlag.SPRINTING, (xd & 0x08) == 0x08); + // Swimming is ignored here and instead we rely on the pose + setFlag(EntityFlag.GLIDING, (xd & 0x80) == 0x80); + + setInvisible((xd & 0x20) == 0x20); + } + + /** + * Set a boolean - whether the entity is invisible or visible + * + * @param value true if the entity is invisible + */ + protected void setInvisible(boolean value) { + setFlag(EntityFlag.INVISIBLE, value); + } + + /** + * Set an int from 0 - this entity's maximum air - (air / maxAir) represents the percentage of bubbles left + */ + public final void setAir(IntEntityMetadata entityMetadata) { + setAirSupply(entityMetadata.getPrimitiveValue()); + } + + protected void setAirSupply(int amount) { + dirtyMetadata.put(EntityData.AIR_SUPPLY, (short) MathUtils.constrain(amount, 0, getMaxAir())); + } + + protected int getMaxAir() { + return 300; + } + + public void setDisplayName(EntityMetadata, ?> entityMetadata) { + Optional name = entityMetadata.getValue(); + if (name.isPresent()) { + nametag = MessageTranslator.convertMessage(name.get(), session.getLocale()); + dirtyMetadata.put(EntityData.NAMETAG, nametag); + } else if (!nametag.isEmpty()) { + // Clear nametag + dirtyMetadata.put(EntityData.NAMETAG, ""); + } + } + + public void setDisplayNameVisible(BooleanEntityMetadata entityMetadata) { + dirtyMetadata.put(EntityData.NAMETAG_ALWAYS_SHOW, (byte) (entityMetadata.getPrimitiveValue() ? 1 : 0)); + } + + public void setGravity(BooleanEntityMetadata entityMetadata) { + setFlag(EntityFlag.HAS_GRAVITY, !entityMetadata.getPrimitiveValue()); + } + + /** + * Usually used for bounding box and not animation. + */ + public void setPose(EntityMetadata entityMetadata) { + Pose pose = entityMetadata.getValue(); + + setFlag(EntityFlag.SLEEPING, pose.equals(Pose.SLEEPING)); + // Triggered when crawling + setFlag(EntityFlag.SWIMMING, pose.equals(Pose.SWIMMING)); + setDimensions(pose); + } + + /** + * Set the height and width of the entity's bounding box + */ + protected void setDimensions(Pose pose) { + // No flexibility options for basic entities + setBoundingBoxHeight(definition.height()); + setBoundingBoxWidth(definition.width()); + } + + public void setBoundingBoxHeight(float height) { + if (height != boundingBoxHeight) { + boundingBoxHeight = height; + dirtyMetadata.put(EntityData.BOUNDING_BOX_HEIGHT, boundingBoxHeight); + } + } + + public void setBoundingBoxWidth(float width) { + if (width != boundingBoxWidth) { + boundingBoxWidth = width; + dirtyMetadata.put(EntityData.BOUNDING_BOX_WIDTH, boundingBoxWidth); + } + } + + /** + * Set a float from 0-1 - how strong the "frozen" overlay should be on screen. + */ + public float setFreezing(IntEntityMetadata entityMetadata) { + // The value that Java edition gives us is in ticks, but Bedrock uses a float percentage of the strength 0.0 -> 1.0 + // The Java client caps its freezing tick percentage at 140 + int freezingTicks = Math.min(entityMetadata.getPrimitiveValue(), 140); + float freezingPercentage = freezingTicks / 140f; + dirtyMetadata.put(EntityData.FREEZING_EFFECT_STRENGTH, freezingPercentage); + return freezingPercentage; + } + + public void setRiderSeatPosition(Vector3f position) { + dirtyMetadata.put(EntityData.RIDER_SEAT_POSITION, position); + } + + /** + * If true, the entity should be shaking on the client's end. + * + * @return whether {@link EntityFlag#SHAKING} should be set to true. + */ + protected boolean isShaking() { + return false; + } + + /** + * x = Pitch, y = HeadYaw, z = Yaw + * + * @return the bedrock rotation + */ + public Vector3f getBedrockRotation() { + return Vector3f.from(pitch, headYaw, yaw); + } + + @SuppressWarnings("unchecked") + public I as(Class entityClass) { + return entityClass.isInstance(this) ? (I) this : null; + } + + public boolean is(Class entityClass) { + return entityClass.isInstance(this); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/GiantEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ExpOrbEntity.java similarity index 70% rename from connector/src/main/java/org/geysermc/connector/entity/living/monster/GiantEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/ExpOrbEntity.java index aa22d8d67..8196d03ff 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/GiantEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ExpOrbEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,17 +23,18 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.monster; +package org.geysermc.geyser.entity.type; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.session.GeyserSession; -public class GiantEntity extends MonsterEntity { +public class ExpOrbEntity extends Entity { - public GiantEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public ExpOrbEntity(GeyserSession session, int amount, long entityId, long geyserId, Vector3f position) { + super(session, entityId, geyserId, null, EntityDefinitions.EXPERIENCE_ORB, position, Vector3f.ZERO, 0, 0, 0); - metadata.put(EntityData.SCALE, 6f); + this.dirtyMetadata.put(EntityData.EXPERIENCE_VALUE, amount); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/FallingBlockEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FallingBlockEntity.java similarity index 62% rename from connector/src/main/java/org/geysermc/connector/entity/FallingBlockEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/FallingBlockEntity.java index f25162f56..1f690036f 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FallingBlockEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/FallingBlockEntity.java @@ -1,51 +1,51 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.entity; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; - -public class FallingBlockEntity extends Entity { - - public FallingBlockEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, int javaId) { - super(entityId, geyserId, entityType, position, motion, rotation); - - this.metadata.put(EntityData.VARIANT, BlockTranslator.getBedrockBlockId(javaId)); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - // Set the NO_AI flag based on the no gravity flag to prevent movement - if (entityMetadata.getId() == 5) { - this.metadata.getFlags().setFlag(EntityFlag.NO_AI, (boolean) entityMetadata.getValue()); - } - } -} +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +public class FallingBlockEntity extends Entity { + + public FallingBlockEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, Vector3f position, Vector3f motion, float yaw, float pitch, int javaId) { + super(session, entityId, geyserId, uuid, EntityDefinitions.FALLING_BLOCK, position, motion, yaw, pitch, 0f); + + this.dirtyMetadata.put(EntityData.VARIANT, session.getBlockMappings().getBedrockBlockId(javaId)); + } + + @Override + public void setGravity(BooleanEntityMetadata entityMetadata) { + super.setGravity(entityMetadata); + // Set the NO_AI flag based on the no gravity flag to prevent movement + setFlag(EntityFlag.NO_AI, entityMetadata.getPrimitiveValue()); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java new file mode 100644 index 000000000..52796d67b --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type; + +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +public class FireballEntity extends ThrowableEntity { + private final Vector3f acceleration; + + /** + * The number of ticks to advance movement before sending to Bedrock + */ + protected int futureTicks = 3; + + public FireballEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, Vector3f.ZERO, yaw, pitch, headYaw); + + float magnitude = motion.length(); + if (magnitude != 0) { + acceleration = motion.div(magnitude).mul(0.1f); + } else { + acceleration = Vector3f.ZERO; + } + } + + private Vector3f tickMovement(Vector3f position) { + position = position.add(motion); + float drag = getDrag(); + motion = motion.add(acceleration).mul(drag); + return position; + } + + @Override + protected void moveAbsoluteImmediate(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { + // Advance the position by a few ticks before sending it to Bedrock + Vector3f lastMotion = motion; + Vector3f newPosition = position; + for (int i = 0; i < futureTicks; i++) { + newPosition = tickMovement(newPosition); + } + super.moveAbsoluteImmediate(newPosition, yaw, pitch, headYaw, isOnGround, teleported); + this.position = position; + this.motion = lastMotion; + } + + @Override + public void tick() { + moveAbsoluteImmediate(tickMovement(position), yaw, pitch, headYaw, false, false); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FireworkEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FireworkEntity.java new file mode 100644 index 000000000..075178b55 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/FireworkEntity.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.nbt.NbtType; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.FireworkColor; +import org.geysermc.geyser.util.MathUtils; +import org.geysermc.floodgate.util.DeviceOs; + +import java.util.ArrayList; +import java.util.List; +import java.util.OptionalInt; +import java.util.UUID; + +public class FireworkEntity extends Entity { + + public FireworkEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + public void setFireworkItem(EntityMetadata entityMetadata) { + ItemStack item = entityMetadata.getValue(); + if (item == null) { + return; + } + CompoundTag tag = item.getNbt(); + + if (tag == null) { + return; + } + + // TODO: Remove once Mojang fixes bugs with fireworks crashing clients on these specific devices. + // https://bugs.mojang.com/browse/MCPE-89115 + if (session.getClientData().getDeviceOs() == DeviceOs.XBOX + || session.getClientData().getDeviceOs() == DeviceOs.PS4) { + return; + } + + CompoundTag fireworks = tag.get("Fireworks"); + if (fireworks == null) { + // Thank you Mineplex very cool + return; + } + + NbtMapBuilder fireworksBuilder = NbtMap.builder(); + if (fireworks.get("Flight") != null) { + fireworksBuilder.putByte("Flight", MathUtils.getNbtByte(fireworks.get("Flight").getValue())); + } + + List explosions = new ArrayList<>(); + if (fireworks.get("Explosions") != null) { + for (Tag effect : ((ListTag) fireworks.get("Explosions")).getValue()) { + CompoundTag effectData = (CompoundTag) effect; + NbtMapBuilder effectBuilder = NbtMap.builder(); + + if (effectData.get("Type") != null) { + effectBuilder.putByte("FireworkType", MathUtils.getNbtByte(effectData.get("Type").getValue())); + } + + if (effectData.get("Colors") != null) { + int[] oldColors = (int[]) effectData.get("Colors").getValue(); + byte[] colors = new byte[oldColors.length]; + + int i = 0; + for (int color : oldColors) { + colors[i++] = FireworkColor.fromJavaRGB(color); + } + + effectBuilder.putByteArray("FireworkColor", colors); + } + + if (effectData.get("FadeColors") != null) { + int[] oldColors = (int[]) effectData.get("FadeColors").getValue(); + byte[] colors = new byte[oldColors.length]; + + int i = 0; + for (int color : oldColors) { + colors[i++] = FireworkColor.fromJavaRGB(color); + } + + effectBuilder.putByteArray("FireworkFade", colors); + } + + if (effectData.get("Trail") != null) { + effectBuilder.putByte("FireworkTrail", MathUtils.getNbtByte(effectData.get("Trail").getValue())); + } + + if (effectData.get("Flicker") != null) { + effectBuilder.putByte("FireworkFlicker", MathUtils.getNbtByte(effectData.get("Flicker").getValue())); + } + + explosions.add(effectBuilder.build()); + } + } + + fireworksBuilder.putList("Explosions", NbtType.COMPOUND, explosions); + + NbtMapBuilder builder = NbtMap.builder(); + builder.put("Fireworks", fireworksBuilder.build()); + dirtyMetadata.put(EntityData.DISPLAY_ITEM, builder.build()); + } + + public void setPlayerGliding(EntityMetadata entityMetadata) { + OptionalInt optional = entityMetadata.getValue(); + // Checks if the firework has an entity ID (used when a player is gliding) + // and checks to make sure the player that is gliding is the one getting sent the packet + // or else every player near the gliding player will boost too. + if (optional.isPresent() && optional.getAsInt() == session.getPlayerEntity().getEntityId()) { + PlayerEntity entity = session.getPlayerEntity(); + float yaw = entity.getYaw(); + float pitch = entity.getPitch(); + // Uses math from NukkitX + entity.setMotion(Vector3f.from( + -Math.sin(Math.toRadians(yaw)) * Math.cos(Math.toRadians(pitch)) * 2, + -Math.sin(Math.toRadians(pitch)) * 2, + Math.cos(Math.toRadians(yaw)) * Math.cos(Math.toRadians(pitch)) * 2)); + // Need to update the EntityMotionPacket or else the player won't boost + SetEntityMotionPacket entityMotionPacket = new SetEntityMotionPacket(); + entityMotionPacket.setRuntimeEntityId(entity.getGeyserId()); + entityMotionPacket.setMotion(entity.getMotion()); + + session.sendUpstreamPacket(entityMotionPacket); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java new file mode 100644 index 000000000..b5774bd78 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.PlaySoundPacket; +import lombok.Getter; +import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.physics.BoundingBox; +import org.geysermc.geyser.translator.collision.BlockCollision; +import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.level.block.BlockPositionIterator; +import org.geysermc.geyser.util.BlockUtils; + +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; + +public class FishingHookEntity extends ThrowableEntity { + + private boolean hooked = false; + private boolean inWater = false; + + @Getter + private final boolean isOwnerSessionPlayer; + @Getter + private long bedrockTargetId; + + private final BoundingBox boundingBox; + + public FishingHookEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, Vector3f position, Vector3f motion, float yaw, float pitch, PlayerEntity owner) { + super(session, entityId, geyserId, uuid, EntityDefinitions.FISHING_BOBBER, position, motion, yaw, pitch, 0f); + + this.boundingBox = new BoundingBox(0.125, 0.125, 0.125, 0.25, 0.25, 0.25); + + // In Java, the splash sound depends on the entity's velocity, but in Bedrock the volume doesn't change. + // This splash can be confused with the sound from catching a fish. This silences the splash from Bedrock, + // so that it can be handled by moveAbsoluteImmediate. + setBoundingBoxHeight(128); + + isOwnerSessionPlayer = owner.getGeyserId() == session.getPlayerEntity().getGeyserId(); + this.dirtyMetadata.put(EntityData.OWNER_EID, owner.getGeyserId()); + } + + @Override + public void spawnEntity() { + + super.spawnEntity(); + } + + public void setHookedEntity(IntEntityMetadata entityMetadata) { + int hookedEntityId = entityMetadata.getPrimitiveValue() - 1; + Entity entity; + if (session.getPlayerEntity().getEntityId() == hookedEntityId) { + entity = session.getPlayerEntity(); + } else { + entity = session.getEntityCache().getEntityByJavaId(hookedEntityId); + } + + if (entity != null) { + bedrockTargetId = entity.getGeyserId(); + dirtyMetadata.put(EntityData.TARGET_EID, bedrockTargetId); + hooked = true; + } else { + hooked = false; + } + } + + @Override + protected void moveAbsoluteImmediate(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { + boundingBox.setMiddleX(position.getX()); + boundingBox.setMiddleY(position.getY() + boundingBox.getSizeY() / 2); + boundingBox.setMiddleZ(position.getZ()); + + boolean touchingWater = false; + boolean collided = false; + for (BlockPositionIterator iter = session.getCollisionManager().collidableBlocksIterator(boundingBox); iter.hasNext(); iter.next()) { + int blockID = session.getGeyser().getWorldManager().getBlockAt(session, iter.getX(), iter.getY(), iter.getZ()); + BlockCollision blockCollision = BlockUtils.getCollision(blockID); + if (blockCollision != null) { + if (blockCollision.checkIntersection(iter.getX(), iter.getY(), iter.getZ(), boundingBox)) { + // TODO Push bounding box out of collision to improve movement + collided = true; + } + } + + int waterLevel = BlockStateValues.getWaterLevel(blockID); + if (BlockRegistries.WATERLOGGED.get().contains(blockID)) { + waterLevel = 0; + } + if (waterLevel >= 0) { + double waterMaxY = iter.getY() + 1 - (waterLevel + 1) / 9.0; + // Falling water is a full block + if (waterLevel >= 8) { + waterMaxY = iter.getY() + 1; + } + if (position.getY() <= waterMaxY) { + touchingWater = true; + } + } + } + + if (!inWater && touchingWater) { + sendSplashSound(session); + } + inWater = touchingWater; + + if (!collided) { + super.moveAbsoluteImmediate(position, yaw, pitch, headYaw, isOnGround, teleported); + } else { + super.moveAbsoluteImmediate(this.position, yaw, pitch, headYaw, true, true); + } + } + + private void sendSplashSound(GeyserSession session) { + if (!getFlag(EntityFlag.SILENT)) { + float volume = (float) (0.2f * Math.sqrt(0.2 * (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) + motion.getY() * motion.getY())); + if (volume > 1) { + volume = 1; + } + PlaySoundPacket playSoundPacket = new PlaySoundPacket(); + playSoundPacket.setSound("random.splash"); + playSoundPacket.setPosition(position); + playSoundPacket.setVolume(volume); + playSoundPacket.setPitch(1f + ThreadLocalRandom.current().nextFloat() * 0.3f); + session.sendUpstreamPacket(playSoundPacket); + } + } + + @Override + public void tick() { + if (hooked || !isInAir() && !isInWater() || isOnGround()) { + motion = Vector3f.ZERO; + return; + } + float gravity = getGravity(); + motion = motion.down(gravity); + + moveAbsoluteImmediate(position.add(motion), yaw, pitch, headYaw, onGround, false); + + float drag = getDrag(); + motion = motion.mul(drag); + } + + @Override + protected float getGravity() { + if (!isInWater() && !onGround) { + return 0.03f; + } + return 0; + } + + /** + * @return true if this entity is currently in air. + */ + protected boolean isInAir() { + int block = session.getGeyser().getWorldManager().getBlockAt(session, position.toInt()); + return block == BlockStateValues.JAVA_AIR_ID; + } + + @Override + protected float getDrag() { + return 0.92f; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FurnaceMinecartEntity.java similarity index 57% rename from connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/FurnaceMinecartEntity.java index 8f0d97b09..ab34cb751 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/FurnaceMinecartEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,36 +23,32 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity; +package org.geysermc.geyser.entity.type; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.block.BlockStateValues; + +import java.util.UUID; public class FurnaceMinecartEntity extends DefaultBlockMinecartEntity { - private boolean hasFuel = false; - public FurnaceMinecartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public FurnaceMinecartEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 13 && !showCustomBlock) { - hasFuel = (boolean) entityMetadata.getValue(); - updateDefaultBlockMetadata(); - } - - super.updateBedrockMetadata(entityMetadata, session); + public void setHasFuel(BooleanEntityMetadata entityMetadata) { + hasFuel = entityMetadata.getPrimitiveValue(); + updateDefaultBlockMetadata(); } @Override public void updateDefaultBlockMetadata() { - metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId(hasFuel ? BlockTranslator.JAVA_RUNTIME_FURNACE_LIT_ID : BlockTranslator.JAVA_RUNTIME_FURNACE_ID)); - metadata.put(EntityData.DISPLAY_OFFSET, 6); + dirtyMetadata.put(EntityData.DISPLAY_ITEM, session.getBlockMappings().getBedrockBlockId(hasFuel ? BlockStateValues.JAVA_FURNACE_LIT_ID : BlockStateValues.JAVA_FURNACE_ID)); + dirtyMetadata.put(EntityData.DISPLAY_OFFSET, 6); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java new file mode 100644 index 000000000..dd98f9aba --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import com.nukkitx.protocol.bedrock.packet.AddItemEntityPacket; +import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.ItemTranslator; +import org.geysermc.geyser.level.block.BlockStateValues; + +import java.util.UUID; + +public class ItemEntity extends ThrowableEntity { + protected ItemData item; + + private int waterLevel = -1; + + public ItemEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + @Override + public void spawnEntity() { + if (item == null) { + return; + } + valid = true; + AddItemEntityPacket itemPacket = new AddItemEntityPacket(); + itemPacket.setRuntimeEntityId(geyserId); + itemPacket.setUniqueEntityId(geyserId); + itemPacket.setPosition(position.add(0d, this.definition.offset(), 0d)); + itemPacket.setMotion(motion); + itemPacket.setFromFishing(false); + itemPacket.setItemInHand(item); + itemPacket.getMetadata().putFlags(this.flags); + dirtyMetadata.apply(itemPacket.getMetadata()); + + setFlagsDirty(false); + + session.sendUpstreamPacket(itemPacket); + } + + @Override + public void tick() { + if (isInWater()) { + return; + } + if (!onGround || (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) > 0.00001) { + float gravity = getGravity(); + motion = motion.down(gravity); + moveAbsoluteImmediate(position.add(motion), yaw, pitch, headYaw, onGround, false); + float drag = getDrag(); + motion = motion.mul(drag, 0.98f, drag); + } + } + + public void setItem(EntityMetadata entityMetadata) { + ItemData item = ItemTranslator.translateToBedrock(session, entityMetadata.getValue()); + if (this.item == null) { + this.item = item; + spawnEntity(); + } else if (item.equals(this.item, false, true, true)) { + // Don't bother respawning the entity if items are equal + if (this.item.getCount() != item.getCount()) { + // Just item count updated; let's make this easy + this.item = item; + EntityEventPacket packet = new EntityEventPacket(); + packet.setRuntimeEntityId(geyserId); + packet.setType(EntityEventType.UPDATE_ITEM_STACK_SIZE); + packet.setData(this.item.getCount()); + session.sendUpstreamPacket(packet); + } + } else { + this.item = item; + despawnEntity(); + spawnEntity(); + } + } + + @Override + protected void moveAbsoluteImmediate(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { + float offset = definition.offset(); + if (waterLevel == 0) { // Item is in a full block of water + // Move the item entity down so it doesn't float above the water + offset = -definition.offset(); + } + super.moveAbsoluteImmediate(position.add(0, offset, 0), 0, 0, 0, isOnGround, teleported); + this.position = position; + + int block = session.getGeyser().getWorldManager().getBlockAt(session, position.toInt()); + waterLevel = BlockStateValues.getWaterLevel(block); + } + + @Override + protected float getGravity() { + if (getFlag(EntityFlag.HAS_GRAVITY) && !onGround && !isInWater()) { + // Gravity can change if the item is in water/lava, but + // the server calculates the motion & position for us + return 0.04f; + } + return 0; + } + + @Override + protected float getDrag() { + if (onGround) { + Vector3i groundBlockPos = position.toInt().down(1); + int blockState = session.getGeyser().getWorldManager().getBlockAt(session, groundBlockPos); + return BlockStateValues.getSlipperiness(blockState) * 0.98f; + } + return 0.98f; + } + + @Override + protected boolean isInWater() { + return waterLevel != -1; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java similarity index 56% rename from connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java index 972fa8d02..0f73de739 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,11 +23,13 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity; +package org.geysermc.geyser.entity.type; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.data.game.entity.object.HangingDirection; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.object.Direction; +import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; @@ -35,20 +37,19 @@ import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import com.nukkitx.protocol.bedrock.v465.Bedrock_v465; +import lombok.Getter; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.ItemTranslator; +import org.geysermc.geyser.registry.type.ItemMapping; -import java.util.concurrent.TimeUnit; +import java.util.UUID; /** * Item frames are an entity in Java but a block entity in Bedrock. */ public class ItemFrameEntity extends Entity { - /** * Used for getting the Bedrock block position. * Blocks deal with integers whereas entities deal with floats. @@ -66,82 +67,98 @@ public class ItemFrameEntity extends Entity { * Cached item frame's Bedrock compound tag. */ private NbtMap cachedTag; + /** + * The item currently in the item frame. Used for block picking. + */ + @Getter + private ItemStack heldItem = null; + /** + * Determines if this entity needs updated on the client end/ + */ + private boolean changed = true; + + public ItemFrameEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, Direction direction) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, 0f); - public ItemFrameEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, HangingDirection direction) { - super(entityId, geyserId, entityType, position, motion, rotation); NbtMapBuilder blockBuilder = NbtMap.builder() - .putString("name", "minecraft:frame") - .putInt("version", BlockTranslator.getBlockStateVersion()); - blockBuilder.put("states", NbtMap.builder() + .putString("name", this.definition.entityType() == EntityType.GLOW_ITEM_FRAME ? "minecraft:glow_frame" : "minecraft:frame") + .putInt("version", session.getBlockMappings().getBlockStateVersion()); + NbtMapBuilder statesBuilder = NbtMap.builder() .putInt("facing_direction", direction.ordinal()) - .putByte("item_frame_map_bit", (byte) 0) - .build()); - bedrockRuntimeId = BlockTranslator.getItemFrame(blockBuilder.build()); + .putByte("item_frame_map_bit", (byte) 0); + if (session.getUpstream().getProtocolVersion() >= Bedrock_v465.V465_CODEC.getProtocolVersion()) { + statesBuilder.putByte("item_frame_photo_bit", (byte) 0); + } + blockBuilder.put("states", statesBuilder.build()); + + bedrockRuntimeId = session.getBlockMappings().getItemFrame(blockBuilder.build()); bedrockPosition = Vector3i.from(position.getFloorX(), position.getFloorY(), position.getFloorZ()); + + session.getItemFrameCache().put(bedrockPosition, this); } @Override - public void spawnEntity(GeyserSession session) { - session.getItemFrameCache().put(bedrockPosition, entityId); - // Delay is required, or else loading in frames on chunk load is sketchy at best - session.getConnector().getGeneralThreadPool().schedule(() -> { - updateBlock(session); - session.getConnector().getLogger().debug("Spawned item frame at location " + bedrockPosition + " with java id " + entityId); - }, 500, TimeUnit.MILLISECONDS); + protected void initializeMetadata() { + // lol nah don't do anything + // This isn't a real entity for Bedrock so it isn't going to do anything + } + + @Override + public void spawnEntity() { + updateBlock(true); + session.getGeyser().getLogger().debug("Spawned item frame at location " + bedrockPosition + " with java id " + entityId); valid = true; } - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7 && entityMetadata.getValue() != null) { - ItemData itemData = ItemTranslator.translateToBedrock(session, (ItemStack) entityMetadata.getValue()); - ItemEntry itemEntry = ItemRegistry.getItem((ItemStack) entityMetadata.getValue()); + public void setItemInFrame(EntityMetadata entityMetadata) { + if (entityMetadata.getValue() != null) { + this.heldItem = entityMetadata.getValue(); + ItemData itemData = ItemTranslator.translateToBedrock(session, heldItem); + ItemMapping mapping = session.getItemMappings().getMapping(entityMetadata.getValue()); NbtMapBuilder builder = NbtMap.builder(); builder.putByte("Count", (byte) itemData.getCount()); if (itemData.getTag() != null) { - builder.put("tag", itemData.getTag().toBuilder().build()); + builder.put("tag", itemData.getTag()); } - builder.putShort("Damage", itemData.getDamage()); - builder.putString("Name", itemEntry.getBedrockIdentifier()); + builder.putShort("Damage", (short) itemData.getDamage()); + builder.putString("Name", mapping.getBedrockIdentifier()); NbtMapBuilder tag = getDefaultTag().toBuilder(); tag.put("Item", builder.build()); tag.putFloat("ItemDropChance", 1.0f); tag.putFloat("ItemRotation", rotation); cachedTag = tag.build(); - updateBlock(session); - } - else if (entityMetadata.getId() == 7 && entityMetadata.getValue() == null && cachedTag != null) { + changed = true; + } else if (cachedTag != null) { cachedTag = getDefaultTag(); - updateBlock(session); - } - else if (entityMetadata.getId() == 8) { - rotation = ((int) entityMetadata.getValue()) * 45; - if (cachedTag == null) { - updateBlock(session); - return; - } - NbtMapBuilder builder = cachedTag.toBuilder(); - builder.putFloat("ItemRotation", rotation); - cachedTag = builder.build(); - updateBlock(session); - } - else { - updateBlock(session); + changed = true; } } + public void setItemRotation(IntEntityMetadata entityMetadata) { + rotation = entityMetadata.getPrimitiveValue() * 45; + if (cachedTag == null) { + return; + } + NbtMapBuilder builder = cachedTag.toBuilder(); + builder.putFloat("ItemRotation", rotation); + cachedTag = builder.build(); + changed = true; + } + @Override - public boolean despawnEntity(GeyserSession session) { + public boolean despawnEntity() { UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); updateBlockPacket.setDataLayer(0); updateBlockPacket.setBlockPosition(bedrockPosition); - updateBlockPacket.setRuntimeId(BlockTranslator.BEDROCK_AIR_ID); + updateBlockPacket.setRuntimeId(session.getBlockMappings().getBedrockAirId()); //TODO maybe set this to the world block or another item frame? updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); session.sendUpstreamPacket(updateBlockPacket); - session.getItemFrameCache().remove(position, entityId); + + session.getItemFrameCache().remove(bedrockPosition, this); + valid = false; return true; } @@ -152,15 +169,23 @@ public class ItemFrameEntity extends Entity { builder.putInt("y", bedrockPosition.getY()); builder.putInt("z", bedrockPosition.getZ()); builder.putByte("isMovable", (byte) 1); - builder.putString("id", "ItemFrame"); + builder.putString("id", this.definition.entityType() == EntityType.GLOW_ITEM_FRAME ? "GlowItemFrame" : "ItemFrame"); return builder.build(); } + @Override + public void updateBedrockMetadata() { + updateBlock(false); + } + /** * Updates the item frame as a block - * @param session GeyserSession. */ - public void updateBlock(GeyserSession session) { + public void updateBlock(boolean force) { + if (!changed && !force) { + // Don't send a block update packet - nothing changed + return; + } UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); updateBlockPacket.setDataLayer(0); updateBlockPacket.setBlockPosition(bedrockPosition); @@ -179,24 +204,17 @@ public class ItemFrameEntity extends Entity { } session.sendUpstreamPacket(blockEntityDataPacket); + + changed = false; } /** * Finds the Java entity ID of an item frame from its Bedrock position. * @param position position of item frame in Bedrock. - * @param session GeyserSession. + * @param session GeyserConnection. * @return Java entity ID or -1 if not found. */ - public static long getItemFrameEntityId(GeyserSession session, Vector3i position) { - return session.getItemFrameCache().getOrDefault(position, -1); - } - - /** - * Force-remove from the position-to-ID map so it doesn't cause conflicts. - * @param session GeyserSession. - * @param position position of the removed item frame. - */ - public static void removePosition(GeyserSession session, Vector3i position) { - session.getItemFrameCache().remove(position); + public static ItemFrameEntity getItemFrameEntity(GeyserSession session, Vector3i position) { + return session.getItemFrameCache().get(position); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LeashKnotEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LeashKnotEntity.java new file mode 100644 index 000000000..63e964a55 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/LeashKnotEntity.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type; + +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +public class LeashKnotEntity extends Entity { + + public LeashKnotEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + // Position is incorrect by default + super(session, entityId, geyserId, uuid, definition, position.add(0.5f, 0.25f, 0.5f), motion, yaw, pitch, headYaw); + } + +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LightningEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LightningEntity.java new file mode 100644 index 000000000..1f5af0492 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/LightningEntity.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.packet.PlaySoundPacket; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; + +public class LightningEntity extends Entity { + + public LightningEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + @Override + public void spawnEntity() { + super.spawnEntity(); + + // Add these two sound effects - they're done completely clientside on Java Edition as of 1.17.1 + ThreadLocalRandom random = ThreadLocalRandom.current(); + + PlaySoundPacket thunderPacket = new PlaySoundPacket(); + thunderPacket.setPosition(this.position); + thunderPacket.setSound("ambient.weather.thunder"); + thunderPacket.setPitch(0.8f + random.nextFloat() * 0.2f); + thunderPacket.setVolume(10000f); // Really. + session.sendUpstreamPacket(thunderPacket); + + PlaySoundPacket impactPacket = new PlaySoundPacket(); + impactPacket.setPosition(this.position); + impactPacket.setSound("ambient.weather.lightning.impact"); + impactPacket.setPitch(0.5f + random.nextFloat() * 0.2f); + impactPacket.setVolume(2.0f); + session.sendUpstreamPacket(impactPacket); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java new file mode 100644 index 000000000..2aff7f9e1 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type; + +import com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute; +import com.github.steveice10.mc.protocol.data.game.entity.attribute.AttributeType; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.data.AttributeData; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import com.nukkitx.protocol.bedrock.packet.MobArmorEquipmentPacket; +import com.nukkitx.protocol.bedrock.packet.MobEquipmentPacket; +import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.attribute.GeyserAttributeType; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.util.AttributeUtils; +import org.geysermc.geyser.util.ChunkUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Getter +@Setter +public class LivingEntity extends Entity { + + protected ItemData helmet = ItemData.AIR; + protected ItemData chestplate = ItemData.AIR; + protected ItemData leggings = ItemData.AIR; + protected ItemData boots = ItemData.AIR; + protected ItemData hand = ItemData.AIR; + protected ItemData offHand = ItemData.AIR; + + @Getter(value = AccessLevel.NONE) + protected float health = 1f; // The default value in Java Edition before any entity metadata is sent + @Getter(value = AccessLevel.NONE) + protected float maxHealth = 20f; // The value Java Edition defaults to if no attribute is given + + /** + * A convenience variable for if the entity has reached the maximum frozen ticks and should be shaking + */ + private boolean isMaxFrozenState = false; + + public LivingEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + @Override + protected void initializeMetadata() { + super.initializeMetadata(); + // Matches Bedrock behavior; is always set to this + dirtyMetadata.put(EntityData.HEALTH, 1); + } + + public void setLivingEntityFlags(ByteEntityMetadata entityMetadata) { + byte xd = entityMetadata.getPrimitiveValue(); + + // Blocking gets triggered when using a bow, but if we set USING_ITEM for all items, it may look like + // you're "mining" with ex. a shield. + ItemMapping shield = session.getItemMappings().getStoredItems().shield(); + boolean isUsingShield = (getHand().getId() == shield.getBedrockId() || + getHand().equals(ItemData.AIR) && getOffHand().getId() == shield.getBedrockId()); + setFlag(EntityFlag.USING_ITEM, (xd & 0x01) == 0x01 && !isUsingShield); + setFlag(EntityFlag.BLOCKING, (xd & 0x01) == 0x01); + + // Riptide spin attack + setFlag(EntityFlag.DAMAGE_NEARBY_MOBS, (xd & 0x04) == 0x04); + } + + public void setHealth(FloatEntityMetadata entityMetadata) { + this.health = entityMetadata.getPrimitiveValue(); + + AttributeData healthData = createHealthAttribute(); + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); + attributesPacket.setRuntimeEntityId(geyserId); + attributesPacket.setAttributes(Collections.singletonList(healthData)); + session.sendUpstreamPacket(attributesPacket); + } + + public Vector3i setBedPosition(EntityMetadata, ?> entityMetadata) { + Optional optionalPos = entityMetadata.getValue(); + if (optionalPos.isPresent()) { + Position bedPosition = optionalPos.get(); + Vector3i vector = Vector3i.from(bedPosition.getX(), bedPosition.getY(), bedPosition.getZ()); + dirtyMetadata.put(EntityData.BED_POSITION, vector); + int bed = session.getGeyser().getWorldManager().getBlockAt(session, bedPosition); + // Bed has to be updated, or else player is floating in the air + ChunkUtils.updateBlock(session, bed, bedPosition); + // Indicate that the player should enter the sleep cycle + // Has to be a byte or it does not work + // (Bed position is what actually triggers sleep - "pose" is only optional) + dirtyMetadata.put(EntityData.PLAYER_FLAGS, (byte) 2); + return vector; + } else { + // Player is no longer sleeping + dirtyMetadata.put(EntityData.PLAYER_FLAGS, (byte) 0); + return null; + } + } + + @Override + protected boolean isShaking() { + return isMaxFrozenState; + } + + @Override + protected void setDimensions(Pose pose) { + if (pose == Pose.SLEEPING) { + setBoundingBoxWidth(0.2f); + setBoundingBoxHeight(0.2f); + } else { + super.setDimensions(pose); + } + } + + @Override + public float setFreezing(IntEntityMetadata entityMetadata) { + float freezingPercentage = super.setFreezing(entityMetadata); + this.isMaxFrozenState = freezingPercentage >= 1.0f; + setFlag(EntityFlag.SHAKING, isShaking()); + return freezingPercentage; + } + + /** + * @return a Bedrock health attribute constructed from the data sent from the server + */ + protected AttributeData createHealthAttribute() { + // Default health needs to be specified as the max health in order for maximum hearts to show correctly on mounted entities + // Round health value up, so that Bedrock doesn't consider the entity to be dead when health is between 0 and 1 + return new AttributeData(GeyserAttributeType.HEALTH.getBedrockIdentifier(), 0f, this.maxHealth, (float) Math.ceil(this.health), this.maxHealth); + } + + public void updateArmor(GeyserSession session) { + if (!valid) return; + + ItemData helmet = this.helmet; + ItemData chestplate = this.chestplate; + // If an entity has a banner on them, it will be in the helmet slot in Java but the chestplate spot in Bedrock + // But don't overwrite the chestplate if it isn't empty + ItemMapping banner = session.getItemMappings().getStoredItems().banner(); + if (chestplate.getId() == ItemData.AIR.getId() && helmet.getId() == banner.getBedrockId()) { + chestplate = this.helmet; + helmet = ItemData.AIR; + } else if (chestplate.getId() == banner.getBedrockId()) { + // Prevent chestplate banners from showing erroneously + chestplate = ItemData.AIR; + } + + MobArmorEquipmentPacket armorEquipmentPacket = new MobArmorEquipmentPacket(); + armorEquipmentPacket.setRuntimeEntityId(geyserId); + armorEquipmentPacket.setHelmet(helmet); + armorEquipmentPacket.setChestplate(chestplate); + armorEquipmentPacket.setLeggings(leggings); + armorEquipmentPacket.setBoots(boots); + + session.sendUpstreamPacket(armorEquipmentPacket); + } + + public void updateMainHand(GeyserSession session) { + if (!valid) return; + + MobEquipmentPacket handPacket = new MobEquipmentPacket(); + handPacket.setRuntimeEntityId(geyserId); + handPacket.setItem(hand); + handPacket.setHotbarSlot(-1); + handPacket.setInventorySlot(0); + handPacket.setContainerId(ContainerId.INVENTORY); + + session.sendUpstreamPacket(handPacket); + } + + public void updateOffHand(GeyserSession session) { + if (!valid) return; + + MobEquipmentPacket offHandPacket = new MobEquipmentPacket(); + offHandPacket.setRuntimeEntityId(geyserId); + offHandPacket.setItem(offHand); + offHandPacket.setHotbarSlot(-1); + offHandPacket.setInventorySlot(0); + offHandPacket.setContainerId(ContainerId.OFFHAND); + + session.sendUpstreamPacket(offHandPacket); + } + + /** + * Attributes are properties of an entity that are generally more runtime-based instead of permanent properties. + * Movement speed, current attack damage with a weapon, current knockback resistance. + * + * @param attributes the Java list of attributes sent from the server + */ + public void updateBedrockAttributes(GeyserSession session, List attributes) { + if (!valid) return; + + List newAttributes = new ArrayList<>(); + + for (Attribute attribute : attributes) { + // Convert the attribute to a Bedrock version, if relevant + updateAttribute(attribute, newAttributes); + } + + if (newAttributes.isEmpty()) { + // If there are Java-only attributes or only attributes that are not translated by us + return; + } + + UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket(); + updateAttributesPacket.setRuntimeEntityId(geyserId); + updateAttributesPacket.setAttributes(newAttributes); + session.sendUpstreamPacket(updateAttributesPacket); + } + + /** + * Takes the Java attribute and adds it to newAttributes as a Bedrock-formatted attribute + */ + protected void updateAttribute(Attribute javaAttribute, List newAttributes) { + if (javaAttribute.getType() instanceof AttributeType.Builtin type) { + switch (type) { + case GENERIC_MAX_HEALTH -> { + this.maxHealth = (float) AttributeUtils.calculateValue(javaAttribute); + newAttributes.add(createHealthAttribute()); + } + case GENERIC_ATTACK_DAMAGE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.ATTACK_DAMAGE)); + case GENERIC_FLYING_SPEED -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.FLYING_SPEED)); + case GENERIC_MOVEMENT_SPEED -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.MOVEMENT_SPEED)); + case GENERIC_FOLLOW_RANGE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.FOLLOW_RANGE)); + case GENERIC_KNOCKBACK_RESISTANCE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.KNOCKBACK_RESISTANCE)); + case HORSE_JUMP_STRENGTH -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.HORSE_JUMP_STRENGTH)); + } + } + } + + /** + * Calculates the complete attribute value to send to Bedrock. Will be overriden if attributes need to be cached. + */ + protected AttributeData calculateAttribute(Attribute javaAttribute, GeyserAttributeType type) { + return type.getAttribute((float) AttributeUtils.calculateValue(javaAttribute)); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java new file mode 100644 index 000000000..ffb5e4018 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +public class MinecartEntity extends Entity { + + public MinecartEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position.add(0d, definition.offset(), 0d), motion, yaw, pitch, headYaw); + } + + public void setCustomBlock(IntEntityMetadata entityMetadata) { + dirtyMetadata.put(EntityData.DISPLAY_ITEM, session.getBlockMappings().getBedrockBlockId(entityMetadata.getPrimitiveValue())); + } + + public void setCustomBlockOffset(IntEntityMetadata entityMetadata) { + dirtyMetadata.put(EntityData.DISPLAY_OFFSET, entityMetadata.getPrimitiveValue()); + } + + public void setShowCustomBlock(BooleanEntityMetadata entityMetadata) { + // If the custom block should be enabled + // Needs a byte based off of Java's boolean + dirtyMetadata.put(EntityData.CUSTOM_DISPLAY, (byte) (entityMetadata.getPrimitiveValue() ? 1 : 0)); + } + + @Override + public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { + super.moveAbsolute(position.add(0d, this.definition.offset(), 0d), yaw, pitch, headYaw, isOnGround, teleported); + } + + @Override + public Vector3f getBedrockRotation() { + // Note: minecart rotation on rails does not care about the actual rotation value + return Vector3f.from(0, yaw, 0); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/PaintingEntity.java similarity index 52% rename from connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/PaintingEntity.java index 8db7554b2..e97bb7090 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/PaintingEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,64 +23,59 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity; +package org.geysermc.geyser.entity.type; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.packet.AddPaintingPacket; +import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.PaintingType; -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.Accessors; +import java.util.UUID; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.utils.PaintingType; - -@Getter @Setter -@Accessors(chain = true) public class PaintingEntity extends Entity { private static final double OFFSET = -0.46875; - private PaintingType paintingName; - private int direction; + private final PaintingType paintingName; + private final int direction; - public PaintingEntity(long entityId, long geyserId, Vector3f position) { - super(entityId, geyserId, EntityType.PAINTING, position, Vector3f.ZERO, Vector3f.ZERO); + public PaintingEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, Vector3f position, PaintingType paintingName, int direction) { + super(session, entityId, geyserId, uuid, EntityDefinitions.PAINTING, position, Vector3f.ZERO, 0f, 0f, 0f); + this.paintingName = paintingName; + this.direction = direction; } @Override - public void spawnEntity(GeyserSession session) { + public void spawnEntity() { AddPaintingPacket addPaintingPacket = new AddPaintingPacket(); addPaintingPacket.setUniqueEntityId(geyserId); addPaintingPacket.setRuntimeEntityId(geyserId); addPaintingPacket.setMotive(paintingName.getBedrockName()); - addPaintingPacket.setPosition(fixOffset(true)); + addPaintingPacket.setPosition(fixOffset()); addPaintingPacket.setDirection(direction); session.sendUpstreamPacket(addPaintingPacket); valid = true; - session.getConnector().getLogger().debug("Spawned painting on " + position); + session.getGeyser().getLogger().debug("Spawned painting on " + position); } @Override - public void updateHeadLookRotation(GeyserSession session, float headYaw) { + public void updateHeadLookRotation(float headYaw) { // Do nothing, as head look messes up paintings } - public Vector3f fixOffset(boolean toBedrock) { - if (toBedrock) { - Vector3f position = super.position; - position = position.add(0.5, 0.5, 0.5); - double widthOffset = paintingName.getWidth() > 1 ? 0.5 : 0; - double heightOffset = paintingName.getHeight() > 1 && paintingName.getHeight() != 3 ? 0.5 : 0; + private Vector3f fixOffset() { + Vector3f position = super.position; + position = position.add(0.5, 0.5, 0.5); + double widthOffset = paintingName.getWidth() > 1 ? 0.5 : 0; + double heightOffset = paintingName.getHeight() > 1 && paintingName.getHeight() != 3 ? 0.5 : 0; - switch (direction) { - case 0: return position.add(widthOffset, heightOffset, OFFSET); - case 1: return position.add(-OFFSET, heightOffset, widthOffset); - case 2: return position.add(-widthOffset, heightOffset, -OFFSET); - case 3: return position.add(OFFSET, heightOffset, -widthOffset); - } - } - return position; + return switch (direction) { + case 0 -> position.add(widthOffset, heightOffset, OFFSET); + case 1 -> position.add(-OFFSET, heightOffset, widthOffset); + case 2 -> position.add(-widthOffset, heightOffset, -OFFSET); + case 3 -> position.add(OFFSET, heightOffset, -widthOffset); + default -> position; + }; } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/SpawnerMinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/SpawnerMinecartEntity.java similarity index 64% rename from connector/src/main/java/org/geysermc/connector/entity/SpawnerMinecartEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/SpawnerMinecartEntity.java index 56341b3b0..2cd4cf3f6 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/SpawnerMinecartEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/SpawnerMinecartEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,22 +23,25 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity; +package org.geysermc.geyser.entity.type; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.block.BlockStateValues; + +import java.util.UUID; public class SpawnerMinecartEntity extends DefaultBlockMinecartEntity { - public SpawnerMinecartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public SpawnerMinecartEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } @Override public void updateDefaultBlockMetadata() { - metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId(BlockTranslator.JAVA_RUNTIME_SPAWNER_ID)); - metadata.put(EntityData.DISPLAY_OFFSET, 6); + dirtyMetadata.put(EntityData.DISPLAY_ITEM, session.getBlockMappings().getBedrockBlockId(BlockStateValues.JAVA_SPAWNER_ID)); + dirtyMetadata.put(EntityData.DISPLAY_OFFSET, 6); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/TNTEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/TNTEntity.java new file mode 100644 index 000000000..e7edd32d5 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/TNTEntity.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +public class TNTEntity extends Entity implements Tickable { + private int currentTick; + + public TNTEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + public void setFuseLength(IntEntityMetadata entityMetadata) { + currentTick = ((IntEntityMetadata) entityMetadata).getPrimitiveValue(); + setFlag(EntityFlag.IGNITED, true); + dirtyMetadata.put(EntityData.FUSE_LENGTH, currentTick); + } + + @Override + public void tick() { + if (currentTick == 0) { + // No need to update the fuse when there is none + return; + } + + if (currentTick % 5 == 0) { + dirtyMetadata.put(EntityData.FUSE_LENGTH, currentTick); + + SetEntityDataPacket packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(geyserId); + packet.getMetadata().put(EntityData.FUSE_LENGTH, currentTick); + session.sendUpstreamPacket(packet); + } + currentTick--; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java new file mode 100644 index 000000000..d68779c9f --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type; + +import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; +import com.nukkitx.protocol.bedrock.packet.MoveEntityDeltaPacket; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.block.BlockStateValues; + +import java.util.UUID; + +/** + * Used as a class for any object-like entity that moves as a projectile + */ +public class ThrowableEntity extends Entity implements Tickable { + + protected Vector3f lastJavaPosition; + + public ThrowableEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + this.lastJavaPosition = position; + } + + /** + * Updates the position for the Bedrock client. + * + * Java clients assume the next positions of moving items. Bedrock needs to be explicitly told positions + */ + @Override + public void tick() { + moveAbsoluteImmediate(position.add(motion), yaw, pitch, headYaw, onGround, false); + float drag = getDrag(); + float gravity = getGravity(); + motion = motion.mul(drag).down(gravity); + } + + protected void moveAbsoluteImmediate(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { + MoveEntityDeltaPacket moveEntityDeltaPacket = new MoveEntityDeltaPacket(); + moveEntityDeltaPacket.setRuntimeEntityId(geyserId); + + if (isOnGround) { + moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.ON_GROUND); + } + setOnGround(isOnGround); + + if (teleported) { + moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.TELEPORTING); + } + + if (this.position.getX() != position.getX()) { + moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_X); + moveEntityDeltaPacket.setX(position.getX()); + } + if (this.position.getY() != position.getY()) { + moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Y); + moveEntityDeltaPacket.setY(position.getY()); + } + if (this.position.getZ() != position.getZ()) { + moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Z); + moveEntityDeltaPacket.setZ(position.getZ()); + } + setPosition(position); + + if (this.yaw != yaw) { + moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_YAW); + moveEntityDeltaPacket.setYaw(yaw); + this.yaw = yaw; + } + if (this.pitch != pitch) { + moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_PITCH); + moveEntityDeltaPacket.setPitch(pitch); + this.pitch = pitch; + } + if (this.headYaw != headYaw) { + moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_HEAD_YAW); + moveEntityDeltaPacket.setHeadYaw(headYaw); + this.headYaw = headYaw; + } + + if (!moveEntityDeltaPacket.getFlags().isEmpty()) { + session.sendUpstreamPacket(moveEntityDeltaPacket); + } + } + + /** + * Get the gravity of this entity type. Used for applying gravity while the entity is in motion. + * + * @return the amount of gravity to apply to this entity while in motion. + */ + protected float getGravity() { + if (getFlag(EntityFlag.HAS_GRAVITY)) { + switch (definition.entityType()) { + case POTION: + return 0.05f; + case EXPERIENCE_BOTTLE: + return 0.07f; + case FIREBALL: + case SHULKER_BULLET: + return 0; + case SNOWBALL: + case EGG: + case ENDER_PEARL: + return 0.03f; + case LLAMA_SPIT: + return 0.06f; + } + } + return 0; + } + + /** + * @return the drag that should be multiplied to the entity's motion + */ + protected float getDrag() { + if (isInWater()) { + return 0.8f; + } else { + switch (definition.entityType()) { + case POTION: + case EXPERIENCE_BOTTLE: + case SNOWBALL: + case EGG: + case ENDER_PEARL: + case LLAMA_SPIT: + return 0.99f; + case FIREBALL: + case SMALL_FIREBALL: + case DRAGON_FIREBALL: + return 0.95f; + case SHULKER_BULLET: + return 1; + } + } + return 1; + } + + /** + * @return true if this entity is currently in water. + */ + protected boolean isInWater() { + int block = session.getGeyser().getWorldManager().getBlockAt(session, position.toInt()); + return BlockStateValues.getWaterLevel(block) != -1; + } + + @Override + public boolean despawnEntity() { + if (definition.entityType() == EntityType.ENDER_PEARL) { + LevelEventPacket particlePacket = new LevelEventPacket(); + particlePacket.setType(LevelEventType.PARTICLE_TELEPORT); + particlePacket.setPosition(position); + session.sendUpstreamPacket(particlePacket); + } + return super.despawnEntity(); + } + + @Override + public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) { + moveAbsoluteImmediate(lastJavaPosition.add(relX, relY, relZ), yaw, pitch, headYaw, isOnGround, false); + lastJavaPosition = position; + } + + @Override + public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { + moveAbsoluteImmediate(position, yaw, pitch, headYaw, isOnGround, teleported); + lastJavaPosition = position; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableItemEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableItemEntity.java new file mode 100644 index 000000000..2cee252ec --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableItemEntity.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +/** + * Used as a class for any projectile entity that looks like an item + */ +public class ThrowableItemEntity extends ThrowableEntity { + /** + * Number of ticks since the entity was spawned by the Java server + */ + private int age; + private boolean invisible; + + public ThrowableItemEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + setFlag(EntityFlag.INVISIBLE, true); + invisible = false; + } + + private void checkVisibility() { + if (invisible != getFlag(EntityFlag.INVISIBLE)) { + if (!invisible) { + Vector3f playerPos = session.getPlayerEntity().getPosition(); + // Prevent projectiles from blocking the player's screen + if (age >= 4 || position.distanceSquared(playerPos) > 16) { + setFlag(EntityFlag.INVISIBLE, false); + updateBedrockMetadata(); + } + } else { + setFlag(EntityFlag.INVISIBLE, true); + updateBedrockMetadata(); + } + } + age++; + } + + @Override + public void tick() { + checkVisibility(); + super.tick(); + } + + @Override + protected void setInvisible(boolean value) { + invisible = value; + } + + public void setItem(EntityMetadata entityMetadata) { + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ThrownPotionEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ThrownPotionEntity.java new file mode 100644 index 000000000..3b4d5674a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ThrownPotionEntity.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.inventory.item.Potion; +import org.geysermc.geyser.registry.type.ItemMapping; + +import java.util.EnumSet; +import java.util.UUID; + +public class ThrownPotionEntity extends ThrowableItemEntity { + private static final EnumSet NON_ENCHANTED_POTIONS = EnumSet.of(Potion.WATER, Potion.MUNDANE, Potion.THICK, Potion.AWKWARD); + + public ThrownPotionEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + @Override + public void setItem(EntityMetadata entityMetadata) { + ItemStack itemStack = entityMetadata.getValue(); + if (itemStack == null) { + dirtyMetadata.put(EntityData.POTION_AUX_VALUE, 0); + setFlag(EntityFlag.ENCHANTED, false); + setFlag(EntityFlag.LINGERING, false); + } else { + ItemMapping mapping = session.getItemMappings().getMapping(itemStack); + if (mapping.getJavaIdentifier().endsWith("potion") && itemStack.getNbt() != null) { + Tag potionTag = itemStack.getNbt().get("Potion"); + if (potionTag instanceof StringTag) { + Potion potion = Potion.getByJavaIdentifier(((StringTag) potionTag).getValue()); + if (potion != null) { + dirtyMetadata.put(EntityData.POTION_AUX_VALUE, potion.getBedrockId()); + setFlag(EntityFlag.ENCHANTED, !NON_ENCHANTED_POTIONS.contains(potion)); + } else { + dirtyMetadata.put(EntityData.POTION_AUX_VALUE, 0); + GeyserImpl.getInstance().getLogger().debug("Unknown java potion: " + potionTag.getValue()); + } + } + + boolean isLingering = mapping.getJavaIdentifier().equals("minecraft:lingering_potion"); + setFlag(EntityFlag.LINGERING, isLingering); + } + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Tickable.java b/core/src/main/java/org/geysermc/geyser/entity/type/Tickable.java new file mode 100644 index 000000000..696f9008f --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Tickable.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type; + +/** + * Implemented onto anything that should have code ran every Minecraft tick - 50 milliseconds. + */ +public interface Tickable { + void tick(); +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/TippedArrowEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/TippedArrowEntity.java similarity index 53% rename from connector/src/main/java/org/geysermc/connector/entity/TippedArrowEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/TippedArrowEntity.java index 949764b92..a60c0ab33 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/TippedArrowEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/TippedArrowEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,41 +23,38 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity; +package org.geysermc.geyser.entity.type; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.item.TippedArrowPotion; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.inventory.item.TippedArrowPotion; + +import java.util.UUID; /** * Internally this is known as TippedArrowEntity but is used with tipped arrows and normal arrows */ public class TippedArrowEntity extends AbstractArrowEntity { - public TippedArrowEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public TippedArrowEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - // Arrow potion effect color - if (entityMetadata.getId() == 9) { - int potionColor = (int) entityMetadata.getValue(); - // -1 means no color - if (potionColor == -1) { - metadata.remove(EntityData.CUSTOM_DISPLAY); + public void setPotionEffectColor(IntEntityMetadata entityMetadata) { + int potionColor = entityMetadata.getPrimitiveValue(); + // -1 means no color + if (potionColor == -1) { + dirtyMetadata.put(EntityData.CUSTOM_DISPLAY, 0); + } else { + TippedArrowPotion potion = TippedArrowPotion.getByJavaColor(potionColor); + if (potion != null && potion.getJavaColor() != -1) { + dirtyMetadata.put(EntityData.CUSTOM_DISPLAY, (byte) potion.getBedrockId()); } else { - TippedArrowPotion potion = TippedArrowPotion.getByJavaColor(potionColor); - if (potion != null && potion.getJavaColor() != -1) { - metadata.put(EntityData.CUSTOM_DISPLAY, (byte) potion.getBedrockId()); - } else { - metadata.remove(EntityData.CUSTOM_DISPLAY); - } + dirtyMetadata.put(EntityData.CUSTOM_DISPLAY, 0); } } - super.updateBedrockMetadata(entityMetadata, session); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/TridentEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/TridentEntity.java new file mode 100644 index 000000000..65591fb50 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/TridentEntity.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type; + +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +public class TridentEntity extends AbstractArrowEntity { + + public TridentEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/WitherSkullEntity.java similarity index 51% rename from connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/WitherSkullEntity.java index 99b3df3d0..f645fae34 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/WitherSkullEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,36 +23,38 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity; +package org.geysermc.geyser.entity.type; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; import com.nukkitx.math.vector.Vector3f; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.session.GeyserSession; -public class WitherSkullEntity extends ItemedFireballEntity { +import java.util.UUID; + +public class WitherSkullEntity extends FireballEntity { private boolean isCharged; - public WitherSkullEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public WitherSkullEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + + this.futureTicks = 1; } - @Override - protected float getDrag(GeyserSession session) { - return isCharged ? 0.73f : super.getDrag(session); - } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7) { - boolean newIsCharged = (boolean) entityMetadata.getValue(); - if (newIsCharged != isCharged) { - isCharged = newIsCharged; - entityType = isCharged ? EntityType.WITHER_SKULL_DANGEROUS : EntityType.WITHER_SKULL; - despawnEntity(session); - spawnEntity(session); - } + public void setDangerous(BooleanEntityMetadata entityMetadata) { + boolean newDangerous = entityMetadata.getPrimitiveValue(); + if (newDangerous != isCharged) { + isCharged = newDangerous; + // Is an entirely new entity in Bedrock but just a metadata type in Java + definition = isCharged ? EntityDefinitions.WITHER_SKULL_DANGEROUS : EntityDefinitions.WITHER_SKULL; + despawnEntity(); + spawnEntity(); } - super.updateBedrockMetadata(entityMetadata, session); + } + + @Override + protected float getDrag() { + return isCharged ? 0.73f : super.getDrag(); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/AbstractFishEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/AbstractFishEntity.java similarity index 64% rename from connector/src/main/java/org/geysermc/connector/entity/living/AbstractFishEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/AbstractFishEntity.java index 9c3e20840..8fedce1e7 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/AbstractFishEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/AbstractFishEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,20 +23,23 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living; +package org.geysermc.geyser.entity.type.living; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; public class AbstractFishEntity extends WaterEntity { - public AbstractFishEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public AbstractFishEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); - metadata.getFlags().setFlag(EntityFlag.CAN_SWIM, true); - metadata.getFlags().setFlag(EntityFlag.BREATHING, true); - metadata.getFlags().setFlag(EntityFlag.CAN_CLIMB, false); - metadata.getFlags().setFlag(EntityFlag.HAS_GRAVITY, false); + setFlag(EntityFlag.CAN_SWIM, true); + setFlag(EntityFlag.BREATHING, true); + setFlag(EntityFlag.CAN_CLIMB, false); + setFlag(EntityFlag.HAS_GRAVITY, false); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/AgeableEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/AgeableEntity.java new file mode 100644 index 000000000..b04247a7a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/AgeableEntity.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +public class AgeableEntity extends CreatureEntity { + + public AgeableEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + public void setBaby(BooleanEntityMetadata entityMetadata) { + boolean isBaby = entityMetadata.getPrimitiveValue(); + dirtyMetadata.put(EntityData.SCALE, isBaby ? getBabySize() : getAdultSize()); + setFlag(EntityFlag.BABY, isBaby); + + setBoundingBoxHeight(definition.height() * (isBaby ? getBabySize() : getAdultSize())); + setBoundingBoxWidth(definition.width() * (isBaby ? getBabySize() : getAdultSize())); + } + + /** + * The scale that should be used when this entity is not a baby. + */ + protected float getAdultSize() { + return 1f; + } + + /** + * The scale that should be used when this entity is a baby. + */ + protected float getBabySize() { + return 0.55f; + } + + public boolean isBaby() { + return getFlag(EntityFlag.BABY); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/AmbientEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/AmbientEntity.java new file mode 100644 index 000000000..693a0cd46 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/AmbientEntity.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living; + +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +public class AmbientEntity extends MobEntity { + + public AmbientEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java new file mode 100644 index 000000000..fb459bf54 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java @@ -0,0 +1,433 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Rotation; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; +import lombok.Getter; +import net.kyori.adventure.text.Component; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.entity.type.LivingEntity; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.Optional; +import java.util.UUID; + +public class ArmorStandEntity extends LivingEntity { + + // These are used to store the state of the armour stand for use when handling invisibility + @Getter + private boolean isMarker = false; + private boolean isInvisible = false; + private boolean isSmall = false; + + /** + * On Java Edition, armor stands always show their name. Invisibility hides the name on Bedrock. + * By having a second entity, we can allow an invisible entity with the name tag. + * (This lets armor on armor stands still show) + */ + private ArmorStandEntity secondEntity = null; + /** + * Whether this is the primary armor stand that holds the armor and not the name tag. + */ + private boolean primaryEntity = true; + /** + * Whether the entity's position must be updated to included the offset. + * + * This should be true when the Java server marks the armor stand as invisible, but we shrink the entity + * to allow the nametag to appear. Basically: + * - Is visible: this is irrelevant (false) + * - Has armor, no name: false + * - Has armor, has name: false, with a second entity + * - No armor, no name: false + * - No armor, yes name: true + */ + private boolean positionRequiresOffset = false; + /** + * Whether we should update the position of this armor stand after metadata updates. + */ + private boolean positionUpdateRequired = false; + + public ArmorStandEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + @Override + public void spawnEntity() { + this.pitch = yaw; + this.headYaw = yaw; + super.spawnEntity(); + } + + @Override + public boolean despawnEntity() { + if (secondEntity != null) { + secondEntity.despawnEntity(); + } + return super.despawnEntity(); + } + + @Override + public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) { + if (secondEntity != null) { + secondEntity.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, isOnGround); + } + super.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, isOnGround); + } + + @Override + public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { + if (secondEntity != null) { + secondEntity.moveAbsolute(applyOffsetToPosition(position), yaw, pitch, headYaw, isOnGround, teleported); + } else if (positionRequiresOffset) { + // Fake the height to be above where it is so the nametag appears in the right location for invisible non-marker armour stands + position = applyOffsetToPosition(position); + } + + super.moveAbsolute(position, yaw, yaw, yaw, isOnGround, teleported); + } + + @Override + public void setDisplayName(EntityMetadata, ?> entityMetadata) { + super.setDisplayName(entityMetadata); + updateSecondEntityStatus(false); + } + + public void setArmorStandFlags(ByteEntityMetadata entityMetadata) { + byte xd = entityMetadata.getPrimitiveValue(); + + // isSmall + boolean newIsSmall = (xd & 0x01) == 0x01; + if (newIsSmall != isSmall) { + if (positionRequiresOffset) { + // Fix new inconsistency with offset + this.position = fixOffsetForSize(position, newIsSmall); + positionUpdateRequired = true; + } + + isSmall = newIsSmall; + if (!isMarker) { + toggleSmallStatus(); + } + } + + // setMarker + boolean oldIsMarker = isMarker; + isMarker = (xd & 0x10) == 0x10; + if (oldIsMarker != isMarker) { + if (isMarker) { + dirtyMetadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.0f); + dirtyMetadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.0f); + dirtyMetadata.put(EntityData.SCALE, 0f); + } else { + toggleSmallStatus(); + } + + updateSecondEntityStatus(false); + } + + // The following values don't do anything on normal Bedrock. + // But if given a resource pack, then we can use these values to control armor stand visual properties + setFlag(EntityFlag.ANGRY, (xd & 0x04) != 0x04); // Has arms + setFlag(EntityFlag.ADMIRING, (xd & 0x08) == 0x08); // Has no baseplate + } + + public void setHeadRotation(EntityMetadata entityMetadata) { + onRotationUpdate(EntityData.MARK_VARIANT, EntityFlag.INTERESTED, EntityFlag.CHARGED, EntityFlag.POWERED, entityMetadata.getValue()); + } + + public void setBodyRotation(EntityMetadata entityMetadata) { + onRotationUpdate(EntityData.VARIANT, EntityFlag.IN_LOVE, EntityFlag.CELEBRATING, EntityFlag.CELEBRATING_SPECIAL, entityMetadata.getValue()); + } + + public void setLeftArmRotation(EntityMetadata entityMetadata) { + onRotationUpdate(EntityData.TRADE_TIER, EntityFlag.CHARGING, EntityFlag.CRITICAL, EntityFlag.DANCING, entityMetadata.getValue()); + } + + public void setRightArmRotation(EntityMetadata entityMetadata) { + onRotationUpdate(EntityData.MAX_TRADE_TIER, EntityFlag.ELDER, EntityFlag.EMOTING, EntityFlag.IDLING, entityMetadata.getValue()); + } + + public void setLeftLegRotation(EntityMetadata entityMetadata) { + onRotationUpdate(EntityData.SKIN_ID, EntityFlag.IS_ILLAGER_CAPTAIN, EntityFlag.IS_IN_UI, EntityFlag.LINGERING, entityMetadata.getValue()); + } + + public void setRightLegRotation(EntityMetadata entityMetadata) { + onRotationUpdate(EntityData.HURT_DIRECTION, EntityFlag.IS_PREGNANT, EntityFlag.SHEARED, EntityFlag.STALKING, entityMetadata.getValue()); + } + + /** + * Updates rotation on the armor stand by hijacking other unused Bedrock entity data/flags. + * Do note: as of recent Bedrock versions there is a custom entity data system that can be replaced with this, + * but at this time there is no need to implement this. + * + * @param dataLeech the entity data to "leech" off of that stores a compressed version of the rotation + * @param negativeXToggle the flag to set true if the X value of rotation is negative + * @param negativeYToggle the flag to set true if the Y value of rotation is negative + * @param negativeZToggle the flag to set true if the Z value of rotation is negative + * @param rotation the Java rotation value + */ + private void onRotationUpdate(EntityData dataLeech, EntityFlag negativeXToggle, EntityFlag negativeYToggle, EntityFlag negativeZToggle, Rotation rotation) { + // Indicate that rotation should be checked + setFlag(EntityFlag.BRIBED, true); + + int rotationX = getRotation(rotation.getPitch()); + int rotationY = getRotation(rotation.getYaw()); + int rotationZ = getRotation(rotation.getRoll()); + // The top bit acts like binary and determines if each rotation goes above 100 + // We don't do this for the negative values out of concerns of the number being too big + int topBit = (Math.abs(rotationX) >= 100 ? 4 : 0) + (Math.abs(rotationY) >= 100 ? 2 : 0) + (Math.abs(rotationZ) >= 100 ? 1 : 0); + int value = (topBit * 1000000) + ((Math.abs(rotationX) % 100) * 10000) + ((Math.abs(rotationY) % 100) * 100) + (Math.abs(rotationZ) % 100); + dirtyMetadata.put(dataLeech, value); + // Set the entity flags if a value is negative + setFlag(negativeXToggle, rotationX < 0); + setFlag(negativeYToggle, rotationY < 0); + setFlag(negativeZToggle, rotationZ < 0); + } + + @Override + public void updateBedrockMetadata() { + if (secondEntity != null) { + secondEntity.updateBedrockMetadata(); + } + super.updateBedrockMetadata(); + if (positionUpdateRequired) { + positionUpdateRequired = false; + updatePosition(); + } + } + + @Override + protected void setInvisible(boolean value) { + // Check if the armour stand is invisible and store accordingly + if (primaryEntity) { + isInvisible = value; + updateSecondEntityStatus(false); + } + } + + @Override + public void setHelmet(ItemData helmet) { + super.setHelmet(helmet); + updateSecondEntityStatus(true); + } + + @Override + public void setChestplate(ItemData chestplate) { + super.setChestplate(chestplate); + updateSecondEntityStatus(true); + } + + @Override + public void setLeggings(ItemData leggings) { + super.setLeggings(leggings); + updateSecondEntityStatus(true); + } + + @Override + public void setBoots(ItemData boots) { + super.setBoots(boots); + updateSecondEntityStatus(true); + } + + @Override + public void setHand(ItemData hand) { + super.setHand(hand); + updateSecondEntityStatus(true); + } + + @Override + public void setOffHand(ItemData offHand) { + super.setOffHand(offHand); + updateSecondEntityStatus(true); + } + + /** + * Determine if we need to load or unload the second entity. + * + * @param sendMetadata whether to send a metadata update after a change. + */ + private void updateSecondEntityStatus(boolean sendMetadata) { + // A secondary entity always has to have the offset applied, so it remains invisible and the nametag shows. + if (!primaryEntity) return; + if (!isInvisible || isMarker) { + // It is either impossible to show armor, or the armor stand isn't invisible. We good. + setFlag(EntityFlag.INVISIBLE, false); + updateOffsetRequirement(false); + if (positionUpdateRequired) { + positionUpdateRequired = false; + updatePosition(); + } + + if (secondEntity != null) { + secondEntity.despawnEntity(); + secondEntity = null; + } + return; + } + boolean isNametagEmpty = nametag.isEmpty(); + if (!isNametagEmpty && (!helmet.equals(ItemData.AIR) || !chestplate.equals(ItemData.AIR) || !leggings.equals(ItemData.AIR) + || !boots.equals(ItemData.AIR) || !hand.equals(ItemData.AIR) || !offHand.equals(ItemData.AIR))) { + // If the second entity exists, no need to recreate it. + // We can't stuff this check above or else it'll fall into another else case and delete the second entity + if (secondEntity != null) return; + + // Create the second entity. It doesn't need to worry about the items, but it does need to worry about + // the metadata as it will hold the name tag. + secondEntity = new ArmorStandEntity(session, 0, session.getEntityCache().getNextEntityId().incrementAndGet(), null, + EntityDefinitions.ARMOR_STAND, position, motion, yaw, pitch, headYaw); + secondEntity.primaryEntity = false; + if (!this.positionRequiresOffset) { + // Ensure the offset is applied for the 0 scale + secondEntity.position = secondEntity.applyOffsetToPosition(secondEntity.position); + } + // Copy metadata + secondEntity.isSmall = isSmall; + //secondEntity.getDirtyMetadata().putAll(dirtyMetadata); //TODO check + secondEntity.flags.merge(this.flags); + // Guarantee this copy is NOT invisible + secondEntity.setFlag(EntityFlag.INVISIBLE, false); + // Scale to 0 to show nametag + secondEntity.getDirtyMetadata().put(EntityData.SCALE, 0.0f); + // No bounding box as we don't want to interact with this entity + secondEntity.getDirtyMetadata().put(EntityData.BOUNDING_BOX_WIDTH, 0.0f); + secondEntity.getDirtyMetadata().put(EntityData.BOUNDING_BOX_HEIGHT, 0.0f); + secondEntity.spawnEntity(); + + // Reset scale of the proper armor stand + this.dirtyMetadata.put(EntityData.SCALE, isSmall ? 0.55f : 1f); + // Set the proper armor stand to invisible to show armor + setFlag(EntityFlag.INVISIBLE, true); + // Update the position of the armor stand + updateOffsetRequirement(false); + } else if (isNametagEmpty) { + // We can just make an invisible entity + // Reset scale of the proper armor stand + dirtyMetadata.put(EntityData.SCALE, isSmall ? 0.55f : 1f); + // Set the proper armor stand to invisible to show armor + setFlag(EntityFlag.INVISIBLE, true); + // Update offset + updateOffsetRequirement(false); + + if (secondEntity != null) { + secondEntity.despawnEntity(); + secondEntity = null; + } + } else { + // Nametag is not empty and there is no armor + // We don't need to make a new entity + setFlag(EntityFlag.INVISIBLE, false); + dirtyMetadata.put(EntityData.SCALE, 0.0f); + // As the above is applied, we need an offset + updateOffsetRequirement(true); + + if (secondEntity != null) { + secondEntity.despawnEntity(); + secondEntity = null; + } + } + if (sendMetadata) { + this.updateBedrockMetadata(); + } + } + + private int getRotation(float rotation) { + rotation = rotation % 360f; + if (rotation < -180f) { + rotation += 360f; + } else if (rotation >= 180f) { + // 181 -> -179 + rotation = -(180 - (rotation - 180)); + } + return (int) rotation; + } + + /** + * If this armor stand is not a marker, set its bounding box size and scale. + */ + private void toggleSmallStatus() { + dirtyMetadata.put(EntityData.BOUNDING_BOX_WIDTH, isSmall ? 0.25f : definition.width()); + dirtyMetadata.put(EntityData.BOUNDING_BOX_HEIGHT, isSmall ? 0.9875f : definition.height()); + dirtyMetadata.put(EntityData.SCALE, isSmall ? 0.55f : 1f); + } + + /** + * @return the selected position with the position offset applied. + */ + private Vector3f applyOffsetToPosition(Vector3f position) { + return position.add(0d, definition.height() * (isSmall ? 0.55d : 1d), 0d); + } + + /** + * @return an adjusted offset for the new small status. + */ + private Vector3f fixOffsetForSize(Vector3f position, boolean isNowSmall) { + position = removeOffsetFromPosition(position); + return position.add(0d, definition.height() * (isNowSmall ? 0.55d : 1d), 0d); + } + + /** + * @return the selected position with the position offset removed. + */ + private Vector3f removeOffsetFromPosition(Vector3f position) { + return position.sub(0d, definition.height() * (isSmall ? 0.55d : 1d), 0d); + } + + /** + * Set the offset to a new value; if it changed, update the position, too. + */ + private void updateOffsetRequirement(boolean newValue) { + if (newValue != positionRequiresOffset) { + this.positionRequiresOffset = newValue; + if (positionRequiresOffset) { + this.position = applyOffsetToPosition(position); + } else { + this.position = removeOffsetFromPosition(position); + } + positionUpdateRequired = true; + } + } + + /** + * Updates position without calling movement code. + */ + private void updatePosition() { + MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket(); + moveEntityPacket.setRuntimeEntityId(geyserId); + moveEntityPacket.setPosition(position); + moveEntityPacket.setRotation(Vector3f.from(yaw, yaw, yaw)); + moveEntityPacket.setOnGround(onGround); + moveEntityPacket.setTeleported(false); + session.sendUpstreamPacket(moveEntityPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/BatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/BatEntity.java similarity index 63% rename from connector/src/main/java/org/geysermc/connector/entity/living/BatEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/BatEntity.java index b7b7534c8..0a72a431e 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/BatEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/BatEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,26 +23,24 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living; +package org.geysermc.geyser.entity.type.living; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; public class BatEntity extends AmbientEntity { - public BatEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public BatEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { - byte xd = (byte) entityMetadata.getValue(); - metadata.getFlags().setFlag(EntityFlag.RESTING, (xd & 0x01) == 0x01); - } - super.updateBedrockMetadata(entityMetadata, session); + public void setBatFlags(ByteEntityMetadata entityMetadata) { + byte xd = entityMetadata.getPrimitiveValue(); + setFlag(EntityFlag.RESTING, (xd & 0x01) == 0x01); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/CreatureEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/CreatureEntity.java new file mode 100644 index 000000000..b5e7557da --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/CreatureEntity.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living; + +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +public class CreatureEntity extends MobEntity { + + public CreatureEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/RaidParticipantEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/FlyingEntity.java similarity index 67% rename from connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/RaidParticipantEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/FlyingEntity.java index de102a6c7..91f839bc3 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/RaidParticipantEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/FlyingEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,15 +23,17 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.monster.raid; +package org.geysermc.geyser.entity.type.living; import com.nukkitx.math.vector.Vector3f; -import org.geysermc.connector.entity.living.monster.MonsterEntity; -import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; -public class RaidParticipantEntity extends MonsterEntity { +import java.util.UUID; - public RaidParticipantEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); +public class FlyingEntity extends MobEntity { + + public FlyingEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/GlowSquidEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/GlowSquidEntity.java new file mode 100644 index 000000000..b5950b7bc --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/GlowSquidEntity.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living; + +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +public class GlowSquidEntity extends SquidEntity { + public GlowSquidEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/GolemEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/GolemEntity.java similarity index 70% rename from connector/src/main/java/org/geysermc/connector/entity/living/GolemEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/GolemEntity.java index c1ab2f900..eb94e1406 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/GolemEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/GolemEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,14 +23,17 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living; +package org.geysermc.geyser.entity.type.living; import com.nukkitx.math.vector.Vector3f; -import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; public class GolemEntity extends CreatureEntity { - public GolemEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public GolemEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/IronGolemEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/IronGolemEntity.java new file mode 100644 index 000000000..f86392ed3 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/IronGolemEntity.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +public class IronGolemEntity extends GolemEntity { + + public IronGolemEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + // Indicate that we should show cracks through a resource pack + setFlag(EntityFlag.BRIBED, true); + // Required, or else the overlay is black + dirtyMetadata.put(EntityData.COLOR_2, (byte) 0); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/MagmaCubeEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/MagmaCubeEntity.java similarity index 55% rename from connector/src/main/java/org/geysermc/connector/entity/living/MagmaCubeEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/MagmaCubeEntity.java index 456e082bc..03cf9f3dc 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/MagmaCubeEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/MagmaCubeEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,36 +23,38 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living; +package org.geysermc.geyser.entity.type.living; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; public class MagmaCubeEntity extends SlimeEntity { - public MagmaCubeEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public MagmaCubeEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } @Override - public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) { - updateJump(session, isOnGround); - super.moveRelative(session, relX, relY, relZ, rotation, isOnGround); + public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) { + updateJump(isOnGround); + super.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, isOnGround); } @Override - public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { - updateJump(session, isOnGround); - super.moveAbsolute(session, position, rotation, isOnGround, teleported); + public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { + updateJump(isOnGround); + super.moveAbsolute(position, yaw, pitch, headYaw, isOnGround, teleported); } - public void updateJump(GeyserSession session, boolean newOnGround) { + public void updateJump(boolean newOnGround) { if (newOnGround != onGround) { // Add the jumping effect to the magma cube - metadata.put(EntityData.CLIENT_EVENT, (byte) (newOnGround ? 1 : 2)); - updateBedrockMetadata(session); + dirtyMetadata.put(EntityData.CLIENT_EVENT, (byte) (newOnGround ? 1 : 2)); + updateBedrockMetadata(); } } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/AgeableEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/MobEntity.java similarity index 51% rename from connector/src/main/java/org/geysermc/connector/entity/living/AgeableEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/MobEntity.java index 2175efcd6..e82b813d7 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/AgeableEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/MobEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,32 +23,43 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living; +package org.geysermc.geyser.entity.type.living; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import lombok.Getter; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.type.LivingEntity; +import org.geysermc.geyser.session.GeyserSession; -public class AgeableEntity extends CreatureEntity { +import java.util.UUID; - public AgeableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); +public class MobEntity extends LivingEntity { + /** + * If another mob is holding this mob by a leash, this variable tracks their Bedrock entity ID. + */ + @Getter + private long leashHolderBedrockId; + + public MobEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { - boolean isBaby = (boolean) entityMetadata.getValue(); - metadata.put(EntityData.SCALE, isBaby ? .55f : 1f); - metadata.getFlags().setFlag(EntityFlag.BABY, isBaby); + protected void initializeMetadata() { + super.initializeMetadata(); + setLeashHolderBedrockId(-1); + } - metadata.put(EntityData.BOUNDING_BOX_HEIGHT, entityType.getHeight() * (isBaby ? 0.55f : 1f)); - metadata.put(EntityData.BOUNDING_BOX_WIDTH, entityType.getWidth() * (isBaby ? 0.55f : 1f)); - } + public void setMobFlags(ByteEntityMetadata entityMetadata) { + byte xd = entityMetadata.getPrimitiveValue(); + setFlag(EntityFlag.NO_AI, (xd & 0x01) == 0x01); + } - super.updateBedrockMetadata(entityMetadata, session); + public void setLeashHolderBedrockId(long bedrockId) { + this.leashHolderBedrockId = bedrockId; + dirtyMetadata.put(EntityData.LEASH_HOLDER_EID, bedrockId); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/SlimeEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/SlimeEntity.java similarity index 62% rename from connector/src/main/java/org/geysermc/connector/entity/living/SlimeEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/SlimeEntity.java index faf993200..100ed764d 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/SlimeEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/SlimeEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,25 +23,23 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living; +package org.geysermc.geyser.entity.type.living; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; -public class SlimeEntity extends InsentientEntity { +import java.util.UUID; - public SlimeEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); +public class SlimeEntity extends MobEntity { + + public SlimeEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { - this.metadata.put(EntityData.SCALE, 0.10f + (int) entityMetadata.getValue()); - } - super.updateBedrockMetadata(entityMetadata, session); + public void setScale(IntEntityMetadata entityMetadata) { + dirtyMetadata.put(EntityData.SCALE, 0.10f + entityMetadata.getPrimitiveValue()); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/SnowGolemEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/SnowGolemEntity.java similarity index 61% rename from connector/src/main/java/org/geysermc/connector/entity/living/SnowGolemEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/SnowGolemEntity.java index 2f75e6458..1d8375529 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/SnowGolemEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/SnowGolemEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,27 +23,25 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living; +package org.geysermc.geyser.entity.type.living; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; public class SnowGolemEntity extends GolemEntity { - public SnowGolemEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public SnowGolemEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { - byte xd = (byte) entityMetadata.getValue(); - // Handle the visibility of the pumpkin - metadata.getFlags().setFlag(EntityFlag.SHEARED, (xd & 0x10) != 0x10); - } - super.updateBedrockMetadata(entityMetadata, session); + public void setSnowGolemFlags(ByteEntityMetadata entityMetadata) { + byte xd = entityMetadata.getPrimitiveValue(); + // Handle the visibility of the pumpkin + setFlag(EntityFlag.SHEARED, (xd & 0x10) != 0x10); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java new file mode 100644 index 000000000..b0e2fcb9e --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.MoveEntityDeltaPacket; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.type.Tickable; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.block.BlockStateValues; + +import java.util.UUID; + +public class SquidEntity extends WaterEntity implements Tickable { + private float targetPitch; + private float targetYaw; + + private boolean inWater; + + public SquidEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + @Override + public void tick() { + boolean pitchChanged; + boolean yawChanged; + float oldPitch = pitch; + if (inWater) { + float oldYaw = yaw; + pitch += (targetPitch - pitch) * 0.1f; + yaw += (targetYaw - yaw) * 0.1f; + yawChanged = oldYaw != yaw; + } else { + pitch += (-90 - pitch) * 0.02f; + yawChanged = false; + } + pitchChanged = oldPitch != pitch; + + if (pitchChanged || yawChanged) { + MoveEntityDeltaPacket packet = new MoveEntityDeltaPacket(); + packet.setRuntimeEntityId(geyserId); + + if (pitchChanged) { + packet.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_PITCH); + packet.setPitch(pitch); + } + if (yawChanged) { + packet.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_YAW); + packet.setYaw(yaw); + } + + session.sendUpstreamPacket(packet); + } + } + + @Override + public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) { + super.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, isOnGround); + checkInWater(); + } + + @Override + public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { + super.moveAbsolute(position, yaw, pitch, headYaw, isOnGround, teleported); + checkInWater(); + } + + @Override + public void setYaw(float yaw) { + // Let the Java server control yaw when the squid is out of water + if (!inWater) { + this.yaw = yaw; + } + } + + @Override + public void setPitch(float pitch) { + } + + @Override + public void setHeadYaw(float headYaw) { + } + + @Override + public void setMotion(Vector3f motion) { + super.setMotion(motion); + + double horizontalSpeed = Math.sqrt(motion.getX() * motion.getX() + motion.getZ() * motion.getZ()); + targetPitch = (float) Math.toDegrees(-Math.atan2(horizontalSpeed, motion.getY())); + targetYaw = (float) Math.toDegrees(-Math.atan2(motion.getX(), motion.getZ())); + } + + @Override + public Vector3f getBedrockRotation() { + return Vector3f.from(pitch, yaw, yaw); + } + + private void checkInWater() { + if (getFlag(EntityFlag.RIDING)) { + inWater = false; + } else { + int block = session.getGeyser().getWorldManager().getBlockAt(session, position.toInt()); + inWater = BlockStateValues.getWaterLevel(block) != -1; + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/WaterEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/WaterEntity.java similarity index 70% rename from connector/src/main/java/org/geysermc/connector/entity/living/WaterEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/WaterEntity.java index b0692eab0..acacd1f52 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/WaterEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/WaterEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,17 +23,17 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living; +package org.geysermc.geyser.entity.type.living; import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; public class WaterEntity extends CreatureEntity { - public WaterEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - - metadata.put(EntityData.AIR_SUPPLY, (short) 400); + public WaterEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/AnimalEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AnimalEntity.java similarity index 53% rename from connector/src/main/java/org/geysermc/connector/entity/living/animal/AnimalEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AnimalEntity.java index 3e363377f..c7c15b288 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/AnimalEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AnimalEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,15 +23,29 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.animal; +package org.geysermc.geyser.entity.type.living.animal; import com.nukkitx.math.vector.Vector3f; -import org.geysermc.connector.entity.living.AgeableEntity; -import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.type.living.AgeableEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.type.ItemMapping; + +import java.util.UUID; public class AnimalEntity extends AgeableEntity { - public AnimalEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public AnimalEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + /** + * @param javaIdentifierStripped the stripped Java identifier of the item that is potential breeding food. For example, + * wheat. + * @return true if this is a valid item to breed with for this animal. + */ + public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { + // This is what it defaults to. OK. + return javaIdentifierStripped.equals("wheat"); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java new file mode 100644 index 000000000..4dfa5fa8d --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.animal; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.type.ItemMapping; + +import java.util.UUID; + +public class AxolotlEntity extends AnimalEntity { + public AxolotlEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + public void setVariant(IntEntityMetadata entityMetadata) { + int variant = entityMetadata.getPrimitiveValue(); + switch (variant) { + case 1 -> variant = 3; // Java - "Wild" (brown) + case 3 -> variant = 1; // Java - cyan + } + dirtyMetadata.put(EntityData.VARIANT, variant); + } + + public void setPlayingDead(BooleanEntityMetadata entityMetadata) { + setFlag(EntityFlag.PLAYING_DEAD, entityMetadata.getPrimitiveValue()); + } + + @Override + public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { + return javaIdentifierStripped.equals("tropical_fish_bucket"); + } + + @Override + protected int getMaxAir() { + return 6000; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/BeeEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/BeeEntity.java new file mode 100644 index 000000000..7f9ec4255 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/BeeEntity.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.animal; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.type.ItemMapping; + +import java.util.UUID; + +public class BeeEntity extends AnimalEntity { + + public BeeEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + public void setBeeFlags(ByteEntityMetadata entityMetadata) { + byte xd = entityMetadata.getPrimitiveValue(); + // Bee is performing sting attack; trigger animation + if ((xd & 0x02) == 0x02) { + EntityEventPacket packet = new EntityEventPacket(); + packet.setRuntimeEntityId(geyserId); + packet.setType(EntityEventType.ATTACK_START); + packet.setData(0); + session.sendUpstreamPacket(packet); + } + // If the bee has stung + dirtyMetadata.put(EntityData.MARK_VARIANT, (xd & 0x04) == 0x04 ? 1 : 0); + // If the bee has nectar or not + setFlag(EntityFlag.POWERED, (xd & 0x08) == 0x08); + } + + public void setAngerTime(IntEntityMetadata entityMetadata) { + // Converting "anger time" to a boolean + setFlag(EntityFlag.ANGRY, entityMetadata.getPrimitiveValue() > 0); + } + + @Override + public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { + return session.getTagCache().isFlower(mapping); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/ChickenEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/ChickenEntity.java new file mode 100644 index 000000000..506714dbc --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/ChickenEntity.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.animal; + +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.type.ItemMapping; + +import java.util.UUID; + +public class ChickenEntity extends AnimalEntity { + + public ChickenEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + @Override + public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { + return javaIdentifierStripped.contains("seeds"); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieVillagerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/FoxEntity.java similarity index 52% rename from connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieVillagerEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/animal/FoxEntity.java index b8a628170..127a70a0f 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieVillagerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/FoxEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,34 +23,39 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.monster; +package org.geysermc.geyser.entity.type.living.animal; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.living.merchant.VillagerEntity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.type.ItemMapping; -public class ZombieVillagerEntity extends ZombieEntity { +import java.util.UUID; - public ZombieVillagerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); +public class FoxEntity extends AnimalEntity { + + public FoxEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + public void setFoxVariant(IntEntityMetadata entityMetadata) { + dirtyMetadata.put(EntityData.VARIANT, entityMetadata.getPrimitiveValue()); + } + + public void setFoxFlags(ByteEntityMetadata entityMetadata) { + byte xd = entityMetadata.getPrimitiveValue(); + setFlag(EntityFlag.SITTING, (xd & 0x01) == 0x01); + setFlag(EntityFlag.SNEAKING, (xd & 0x04) == 0x04); + setFlag(EntityFlag.INTERESTED, (xd & 0x08) == 0x08); + setFlag(EntityFlag.SLEEPING, (xd & 0x20) == 0x20); } @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 18) { - metadata.getFlags().setFlag(EntityFlag.IS_TRANSFORMING, (boolean) entityMetadata.getValue()); - metadata.getFlags().setFlag(EntityFlag.SHAKING, (boolean) entityMetadata.getValue()); - } - if (entityMetadata.getId() == 19) { - VillagerData villagerData = (VillagerData) entityMetadata.getValue(); - // Region - only one used on Bedrock - metadata.put(EntityData.MARK_VARIANT, VillagerEntity.VILLAGER_REGIONS.get(villagerData.getType())); - } - super.updateBedrockMetadata(entityMetadata, session); + public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { + return session.getTagCache().isFoxFood(mapping); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/InsentientEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/GoatEntity.java similarity index 52% rename from connector/src/main/java/org/geysermc/connector/entity/living/InsentientEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/animal/GoatEntity.java index 808eb3cbb..708a95134 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/InsentientEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/GoatEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,29 +23,40 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living; +package org.geysermc.geyser.entity.type.living.animal; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.LivingEntity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import lombok.Getter; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; -public class InsentientEntity extends LivingEntity { +import java.util.UUID; - public InsentientEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); +public class GoatEntity extends AnimalEntity { + private static final float LONG_JUMPING_HEIGHT = 1.3f * 0.7f; + private static final float LONG_JUMPING_WIDTH = 0.9f * 0.7f; + + @Getter + private boolean isScreamer; + + public GoatEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + public void setScreamer(BooleanEntityMetadata entityMetadata) { + // Metadata not used in Bedrock Edition + isScreamer = entityMetadata.getPrimitiveValue(); } @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 14 && entityMetadata.getType() == MetadataType.BYTE) { - byte xd = (byte) entityMetadata.getValue(); - metadata.getFlags().setFlag(EntityFlag.NO_AI, (xd & 0x01) == 0x01); + protected void setDimensions(Pose pose) { + if (pose == Pose.LONG_JUMPING) { + setBoundingBoxWidth(LONG_JUMPING_WIDTH); + setBoundingBoxHeight(LONG_JUMPING_HEIGHT); + } else { + super.setDimensions(pose); } - - super.updateBedrockMetadata(entityMetadata, session); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/HoglinEntity.java similarity index 51% rename from connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/animal/HoglinEntity.java index 3fd291724..ed0feed97 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/HoglinEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,28 +23,38 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.animal; +package org.geysermc.geyser.entity.type.living.animal; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.utils.DimensionUtils; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.util.DimensionUtils; + +import java.util.UUID; public class HoglinEntity extends AnimalEntity { + private boolean isImmuneToZombification; - public HoglinEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public HoglinEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + public void setImmuneToZombification(BooleanEntityMetadata entityMetadata) { + // Apply shaking effect if not in the nether and zombification is possible + this.isImmuneToZombification = entityMetadata.getPrimitiveValue(); + setFlag(EntityFlag.SHAKING, isShaking()); } @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { - // Immune to zombification? - // Apply shaking effect if not in the nether and zombification is possible - metadata.getFlags().setFlag(EntityFlag.SHAKING, !((boolean) entityMetadata.getValue()) && !session.getDimension().equals(DimensionUtils.NETHER)); - } - super.updateBedrockMetadata(entityMetadata, session); + protected boolean isShaking() { + return (!isImmuneToZombification && !session.getDimension().equals(DimensionUtils.NETHER)) || super.isShaking(); + } + + @Override + public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { + return javaIdentifierStripped.equals("crimson_fungus"); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/MooshroomEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/MooshroomEntity.java new file mode 100644 index 000000000..15473c8ac --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/MooshroomEntity.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.animal; + +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +public class MooshroomEntity extends AnimalEntity { + + public MooshroomEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/OcelotEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/OcelotEntity.java new file mode 100644 index 000000000..5244e3538 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/OcelotEntity.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.animal; + +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.type.ItemMapping; + +import java.util.UUID; + +public class OcelotEntity extends AnimalEntity { + + public OcelotEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + @Override + public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { + return javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon"); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PandaEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PandaEntity.java new file mode 100644 index 000000000..7548ccef1 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PandaEntity.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.animal; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.type.ItemMapping; + +import java.util.UUID; + +public class PandaEntity extends AnimalEntity { + private int mainGene; + private int hiddenGene; + + public PandaEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + public void setEatingCounter(IntEntityMetadata entityMetadata) { + int count = entityMetadata.getPrimitiveValue(); + setFlag(EntityFlag.EATING, count > 0); + dirtyMetadata.put(EntityData.EATING_COUNTER, count); + if (count != 0) { + // Particles and sound + EntityEventPacket packet = new EntityEventPacket(); + packet.setRuntimeEntityId(geyserId); + packet.setType(EntityEventType.EATING_ITEM); + packet.setData(session.getItemMappings().getStoredItems().bamboo().getBedrockId() << 16); + session.sendUpstreamPacket(packet); + } + } + + public void setMainGene(ByteEntityMetadata entityMetadata) { + mainGene = entityMetadata.getPrimitiveValue(); + updateAppearance(); + } + + public void setHiddenGene(ByteEntityMetadata entityMetadata) { + hiddenGene = entityMetadata.getPrimitiveValue(); + updateAppearance(); + } + + public void setPandaFlags(ByteEntityMetadata entityMetadata) { + byte xd = entityMetadata.getPrimitiveValue(); + setFlag(EntityFlag.SNEEZING, (xd & 0x02) == 0x02); + setFlag(EntityFlag.ROLLING, (xd & 0x04) == 0x04); + setFlag(EntityFlag.SITTING, (xd & 0x08) == 0x08); + // Required to put these both for sitting to actually show + dirtyMetadata.put(EntityData.SITTING_AMOUNT, (xd & 0x08) == 0x08 ? 1f : 0f); + dirtyMetadata.put(EntityData.SITTING_AMOUNT_PREVIOUS, (xd & 0x08) == 0x08 ? 1f : 0f); + setFlag(EntityFlag.LAYING_DOWN, (xd & 0x10) == 0x10); + } + + @Override + public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { + return javaIdentifierStripped.equals("bamboo"); + } + + /** + * Update the panda's appearance, and take into consideration the recessive brown and weak traits that only show up + * when both main and hidden genes match + */ + private void updateAppearance() { + if (mainGene == 4 || mainGene == 5) { + // Main gene is a recessive trait + if (mainGene == hiddenGene) { + // Main and hidden genes match; this is what the panda looks like. + dirtyMetadata.put(EntityData.VARIANT, mainGene); + } else { + // Genes have no effect on appearance + dirtyMetadata.put(EntityData.VARIANT, 0); + } + } else { + // No need to worry about hidden gene + dirtyMetadata.put(EntityData.VARIANT, mainGene); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PigEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PigEntity.java new file mode 100644 index 000000000..0be4c78f1 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PigEntity.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.animal; + +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.type.ItemMapping; + +import java.util.UUID; + +public class PigEntity extends AnimalEntity { + + public PigEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + @Override + public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { + return javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("potato") || javaIdentifierStripped.equals("beetroot"); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/AbstractMerchantEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PolarBearEntity.java similarity index 61% rename from connector/src/main/java/org/geysermc/connector/entity/living/merchant/AbstractMerchantEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PolarBearEntity.java index 11028b79e..727804dbc 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/AbstractMerchantEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PolarBearEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,21 +23,23 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.merchant; +package org.geysermc.geyser.entity.type.living.animal; import com.nukkitx.math.vector.Vector3f; -import org.geysermc.connector.entity.living.AgeableEntity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.type.ItemMapping; -public class AbstractMerchantEntity extends AgeableEntity { +import java.util.UUID; - public AbstractMerchantEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); +public class PolarBearEntity extends AnimalEntity { + + public PolarBearEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } @Override - public void teleport(GeyserSession session, Vector3f position, float yaw, float pitch, boolean isOnGround) { - super.teleport(session, position, yaw - 180, pitch, isOnGround); + public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { + return false; } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PufferFishEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PufferFishEntity.java similarity index 58% rename from connector/src/main/java/org/geysermc/connector/entity/living/animal/PufferFishEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PufferFishEntity.java index 407708a55..66853babf 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PufferFishEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PufferFishEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,29 +23,26 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.animal; +package org.geysermc.geyser.entity.type.living.animal; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.entity.living.AbstractFishEntity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.type.living.AbstractFishEntity; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; public class PufferFishEntity extends AbstractFishEntity { - public PufferFishEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public PufferFishEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { - // Transfers correctly but doesn't apply on the client - int puffsize = (int) entityMetadata.getValue(); - metadata.put(EntityData.PUFFERFISH_SIZE, puffsize); - metadata.put(EntityData.VARIANT, puffsize); - } - super.updateBedrockMetadata(entityMetadata, session); + public void setPufferfishSize(IntEntityMetadata entityMetadata) { + int puffsize = entityMetadata.getPrimitiveValue(); + dirtyMetadata.put(EntityData.PUFFERFISH_SIZE, (byte) puffsize); + dirtyMetadata.put(EntityData.VARIANT, puffsize); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/RabbitEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/RabbitEntity.java new file mode 100644 index 000000000..a1d80ac72 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/RabbitEntity.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.animal; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.type.ItemMapping; + +import java.util.UUID; + +public class RabbitEntity extends AnimalEntity { + + public RabbitEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + @Override + public void setBaby(BooleanEntityMetadata entityMetadata) { + super.setBaby(entityMetadata); + } + + public void setRabbitVariant(IntEntityMetadata entityMetadata) { + int variant = entityMetadata.getPrimitiveValue(); + + // Change the killer bunny to display as white since it only exists on Java Edition + boolean isKillerBunny = variant == 99; + if (isKillerBunny) { + variant = 1; + } + // Allow the resource pack to adjust to the killer bunny + setFlag(EntityFlag.BRIBED, isKillerBunny); + + dirtyMetadata.put(EntityData.VARIANT, variant); + } + + @Override + protected float getAdultSize() { + return 0.55f; + } + + @Override + protected float getBabySize() { + return 0.35f; + } + + @Override + public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { + return javaIdentifierStripped.equals("dandelion") || javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("golden_carrot"); + } +} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/SheepEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SheepEntity.java similarity index 62% rename from connector/src/main/java/org/geysermc/connector/entity/living/animal/SheepEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SheepEntity.java index 464377efd..757c5b574 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/SheepEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SheepEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,29 +23,26 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.animal; +package org.geysermc.geyser.entity.type.living.animal; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; public class SheepEntity extends AnimalEntity { - public SheepEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public SheepEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { - byte xd = (byte) entityMetadata.getValue(); - metadata.getFlags().setFlag(EntityFlag.SHEARED, (xd & 0x10) == 0x10); - metadata.put(EntityData.COLOR, xd); - } - - super.updateBedrockMetadata(entityMetadata, session); + public void setSheepFlags(ByteEntityMetadata entityMetadata) { + byte xd = ((ByteEntityMetadata) entityMetadata).getPrimitiveValue(); + setFlag(EntityFlag.SHEARED, (xd & 0x10) == 0x10); + dirtyMetadata.put(EntityData.COLOR, xd); } } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/StriderEntity.java similarity index 53% rename from connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/animal/StriderEntity.java index d1fdc63c4..d684fba06 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/StriderEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,66 +23,76 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.animal; +package org.geysermc.geyser.entity.type.living.animal; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.type.ItemMapping; + +import java.util.UUID; public class StriderEntity extends AnimalEntity { - private boolean shaking = false; + private boolean isCold = false; - public StriderEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public StriderEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); - metadata.getFlags().setFlag(EntityFlag.FIRE_IMMUNE, true); - metadata.getFlags().setFlag(EntityFlag.BREATHING, true); + setFlag(EntityFlag.FIRE_IMMUNE, true); + setFlag(EntityFlag.BREATHING, true); + } + + public void setCold(BooleanEntityMetadata entityMetadata) { + isCold = entityMetadata.getPrimitiveValue(); + } + + public void setSaddled(BooleanEntityMetadata entityMetadata) { + setFlag(EntityFlag.SADDLED, entityMetadata.getPrimitiveValue()); } @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 17) { - shaking = (boolean) entityMetadata.getValue(); - } - if (entityMetadata.getId() == 18) { - metadata.getFlags().setFlag(EntityFlag.SADDLED, (boolean) entityMetadata.getValue()); - } - - super.updateBedrockMetadata(entityMetadata, session); - } - - @Override - public void updateBedrockMetadata(GeyserSession session) { + public void updateBedrockMetadata() { // Make sure they are not shaking when riding another entity // Needs to copy the parent state - if (metadata.getFlags().getFlag(EntityFlag.RIDING)) { + if (getFlag(EntityFlag.RIDING)) { boolean parentShaking = false; + //TODO optimize for (Entity ent : session.getEntityCache().getEntities().values()) { if (ent.getPassengers().contains(entityId) && ent instanceof StriderEntity) { - parentShaking = ent.getMetadata().getFlags().getFlag(EntityFlag.SHAKING); + parentShaking = ent.getFlag(EntityFlag.SHAKING); break; } } - metadata.getFlags().setFlag(EntityFlag.BREATHING, !parentShaking); - metadata.getFlags().setFlag(EntityFlag.SHAKING, parentShaking); + setFlag(EntityFlag.BREATHING, !parentShaking); + setFlag(EntityFlag.SHAKING, parentShaking); } else { - metadata.getFlags().setFlag(EntityFlag.BREATHING, !shaking); - metadata.getFlags().setFlag(EntityFlag.SHAKING, shaking); + setFlag(EntityFlag.BREATHING, !isCold); + setFlag(EntityFlag.SHAKING, isShaking()); } // Update the passengers if we have any for (long passenger : passengers) { Entity passengerEntity = session.getEntityCache().getEntityByJavaId(passenger); if (passengerEntity != null) { - passengerEntity.updateBedrockMetadata(session); + passengerEntity.updateBedrockMetadata(); } } - super.updateBedrockMetadata(session); + super.updateBedrockMetadata(); + } + + @Override + protected boolean isShaking() { + return isCold || super.isShaking(); + } + + @Override + public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { + return javaIdentifierStripped.equals("warped_fungus"); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TropicalFishEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TropicalFishEntity.java new file mode 100644 index 000000000..b883c91a9 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TropicalFishEntity.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.animal; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.google.common.collect.ImmutableList; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import it.unimi.dsi.fastutil.ints.IntList; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.type.living.AbstractFishEntity; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.List; +import java.util.UUID; + +public class TropicalFishEntity extends AbstractFishEntity { + + /** + * A list of variant numbers that are given special names + * The index of the variant in this list is used as part of the locale key + */ + private static final IntList PREDEFINED_VARIANTS = IntList.of(117506305, 117899265, 185008129, 117441793, 118161664, 65536, 50726144, 67764993, 234882305, 67110144, 117441025, 16778497, 101253888, 50660352, 918529, 235340288, 918273, 67108865, 917504, 459008, 67699456, 67371009); + + private static final List VARIANT_NAMES = ImmutableList.of("kob", "sunstreak", "snooper", "dasher", "brinely", "spotty", "flopper", "stripey", "glitter", "blockfish", "betty", "clayfish"); + private static final List COLOR_NAMES = ImmutableList.of("white", "orange", "magenta", "light_blue", "yellow", "lime", "pink", "gray", "light_gray", "cyan", "purple", "blue", "brown", "green", "red", "black"); + + public TropicalFishEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + public void setFishVariant(IntEntityMetadata entityMetadata) { + int varNumber = entityMetadata.getPrimitiveValue(); + + dirtyMetadata.put(EntityData.VARIANT, getShape(varNumber)); // Shape 0-1 + dirtyMetadata.put(EntityData.MARK_VARIANT, getPattern(varNumber)); // Pattern 0-5 + dirtyMetadata.put(EntityData.COLOR, getBaseColor(varNumber)); // Base color 0-15 + dirtyMetadata.put(EntityData.COLOR_2, getPatternColor(varNumber)); // Pattern color 0-15 + } + + public static int getShape(int variant) { + return Math.min(variant & 0xFF, 1); + } + + public static int getPattern(int variant) { + return Math.min((variant >> 8) & 0xFF, 5); + } + + public static byte getBaseColor(int variant) { + byte color = (byte) ((variant >> 16) & 0xFF); + if (!(0 <= color && color <= 15)) { + return 0; + } + return color; + } + + public static byte getPatternColor(int variant) { + byte color = (byte) ((variant >> 24) & 0xFF); + if (!(0 <= color && color <= 15)) { + return 0; + } + return color; + } + + public static String getVariantName(int variant) { + int id = 6 * getShape(variant) + getPattern(variant); + return VARIANT_NAMES.get(id); + } + + public static String getColorName(byte colorId) { + return COLOR_NAMES.get(colorId); + } + + public static int getPredefinedId(int variant) { + return PREDEFINED_VARIANTS.indexOf(variant); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java similarity index 56% rename from connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java index 555e22684..e892d7d5e 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,27 +23,33 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.animal; +package org.geysermc.geyser.entity.type.living.animal; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.type.ItemMapping; + +import java.util.UUID; public class TurtleEntity extends AnimalEntity { - public TurtleEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public TurtleEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + public void setPregnant(BooleanEntityMetadata entityMetadata) { + setFlag(EntityFlag.IS_PREGNANT, entityMetadata.getPrimitiveValue()); + } + + public void setLayingEgg(BooleanEntityMetadata entityMetadata) { + setFlag(EntityFlag.LAYING_EGG, entityMetadata.getPrimitiveValue()); } @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 17) { - metadata.getFlags().setFlag(EntityFlag.IS_PREGNANT, (boolean) entityMetadata.getValue()); - } else if (entityMetadata.getId() == 18) { - metadata.getFlags().setFlag(EntityFlag.LAYING_EGG, (boolean) entityMetadata.getValue()); - } - super.updateBedrockMetadata(entityMetadata, session); + public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { + return javaIdentifierStripped.equals("seagrass"); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java new file mode 100644 index 000000000..7d0a3cf9a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.animal.horse; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; +import com.google.common.collect.ImmutableSet; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; +import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.attribute.GeyserAttributeType; +import org.geysermc.geyser.entity.type.living.animal.AnimalEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.type.ItemMapping; + +import java.util.Set; +import java.util.UUID; + +public class AbstractHorseEntity extends AnimalEntity { + /** + * A list of all foods a horse/donkey can eat on Java Edition. + * Used to display interactive tag if needed. + */ + private static final Set DONKEY_AND_HORSE_FOODS = ImmutableSet.of("golden_apple", "enchanted_golden_apple", + "golden_carrot", "sugar", "apple", "wheat", "hay_block"); + + public AbstractHorseEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + + // Specifies the size of the entity's inventory. Required to place slots in the entity. + dirtyMetadata.put(EntityData.CONTAINER_BASE_SIZE, getContainerBaseSize()); + + setFlag(EntityFlag.WASD_CONTROLLED, true); + } + + protected int getContainerBaseSize() { + return 2; + } + + @Override + public void spawnEntity() { + super.spawnEntity(); + + // Add horse jump strength attribute to allow donkeys and mules to jump, if they don't send the attribute themselves. + // Confirmed broken without this code by making a new donkey in vanilla 1.17.1 + // The spawn packet does have an attributes section, but adding the jump strength property there causes the + // donkey to jump very high. + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); + attributesPacket.setRuntimeEntityId(geyserId); + attributesPacket.getAttributes().add(GeyserAttributeType.HORSE_JUMP_STRENGTH.getAttribute(0.5f, 2)); + session.sendUpstreamPacket(attributesPacket); + } + + public void setHorseFlags(ByteEntityMetadata entityMetadata) { + byte xd = entityMetadata.getPrimitiveValue(); + boolean tamed = (xd & 0x02) == 0x02; + boolean saddled = (xd & 0x04) == 0x04; + setFlag(EntityFlag.TAMED, tamed); + setFlag(EntityFlag.SADDLED, saddled); + setFlag(EntityFlag.EATING, (xd & 0x10) == 0x10); + setFlag(EntityFlag.STANDING, (xd & 0x20) == 0x20); + + // HorseFlags + // Bred 0x10 + // Eating 0x20 + // Open mouth 0x80 + int horseFlags = 0x0; + horseFlags = (xd & 0x40) == 0x40 ? horseFlags | 0x80 : horseFlags; + + // Only set eating when we don't have mouth open so a player interaction doesn't trigger the eating animation + horseFlags = (xd & 0x10) == 0x10 && (xd & 0x40) != 0x40 ? horseFlags | 0x20 : horseFlags; + + // Set the flags into the display item + dirtyMetadata.put(EntityData.DISPLAY_ITEM, horseFlags); + + // Send the eating particles + // We use the wheat metadata as static particles since Java + // doesn't send over what item was used to feed the horse + if ((xd & 0x40) == 0x40) { + EntityEventPacket entityEventPacket = new EntityEventPacket(); + entityEventPacket.setRuntimeEntityId(geyserId); + entityEventPacket.setType(EntityEventType.EATING_ITEM); + entityEventPacket.setData(session.getItemMappings().getStoredItems().wheat().getBedrockId() << 16); + session.sendUpstreamPacket(entityEventPacket); + } + + // Set container type if tamed + dirtyMetadata.put(EntityData.CONTAINER_TYPE, tamed ? (byte) ContainerType.HORSE.getId() : (byte) 0); + + // Shows the jump meter + setFlag(EntityFlag.CAN_POWER_JUMP, saddled); + } + + @Override + public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { + return DONKEY_AND_HORSE_FOODS.contains(javaIdentifierStripped); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/TeleportCache.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/ChestedHorseEntity.java similarity index 63% rename from connector/src/main/java/org/geysermc/connector/network/session/cache/TeleportCache.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/ChestedHorseEntity.java index 475630dbb..ccf30dbc8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/TeleportCache.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/ChestedHorseEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,26 +23,22 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.session.cache; +package org.geysermc.geyser.entity.type.living.animal.horse; -import com.nukkitx.math.vector.Vector3d; import com.nukkitx.math.vector.Vector3f; -import lombok.AllArgsConstructor; -import lombok.Data; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; -@AllArgsConstructor -@Data -public class TeleportCache { +import java.util.UUID; - private static final double ERROR = 0.2; - private static final double ERROR_Y = 0.5; +public class ChestedHorseEntity extends AbstractHorseEntity { - private double x, y, z; - private int teleportConfirmId; + public ChestedHorseEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } - public boolean canConfirm(Vector3d position) { - return (Math.abs(this.x - position.getX()) < ERROR && - Math.abs(this.y - position.getY()) < ERROR_Y && - Math.abs(this.z - position.getZ()) < ERROR); + @Override + protected int getContainerBaseSize() { + return 16; } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/HorseEntity.java similarity index 61% rename from connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/HorseEntity.java index 349da5e05..31b5b7890 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/HorseEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,27 +23,25 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.animal.horse; +package org.geysermc.geyser.entity.type.living.animal.horse; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; public class HorseEntity extends AbstractHorseEntity { - public HorseEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public HorseEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 18) { - metadata.put(EntityData.VARIANT, entityMetadata.getValue()); - metadata.put(EntityData.MARK_VARIANT, (((int) entityMetadata.getValue()) >> 8) % 5); - } - super.updateBedrockMetadata(entityMetadata, session); + public void setHorseVariant(IntEntityMetadata entityMetadata) { + int value = entityMetadata.getPrimitiveValue(); + dirtyMetadata.put(EntityData.VARIANT, value & 255); + dirtyMetadata.put(EntityData.MARK_VARIANT, (value >> 8) % 5); } - } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/LlamaEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/LlamaEntity.java new file mode 100644 index 000000000..c18778c81 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/LlamaEntity.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.animal.horse; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import com.nukkitx.protocol.bedrock.packet.MobArmorEquipmentPacket; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.type.ItemMapping; + +import java.util.UUID; + +public class LlamaEntity extends ChestedHorseEntity { + + public LlamaEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + + dirtyMetadata.put(EntityData.CONTAINER_STRENGTH_MODIFIER, 3); // Presumably 3 slots for every 1 strength + } + + /** + * Color equipped on the llama + */ + public void setCarpetedColor(IntEntityMetadata entityMetadata) { + // Bedrock treats llama decoration as armor + MobArmorEquipmentPacket equipmentPacket = new MobArmorEquipmentPacket(); + equipmentPacket.setRuntimeEntityId(geyserId); + // -1 means no armor + int carpetIndex = entityMetadata.getPrimitiveValue(); + if (carpetIndex > -1 && carpetIndex <= 15) { + // The damage value is the dye color that Java sends us, for pre-1.16.220 + // The item is always going to be a carpet + equipmentPacket.setChestplate(session.getItemMappings().getCarpets().get(carpetIndex)); + } else { + equipmentPacket.setChestplate(ItemData.AIR); + } + // Required to fill out the rest of the equipment or Bedrock ignores it, including above else statement if removing armor + equipmentPacket.setBoots(ItemData.AIR); + equipmentPacket.setHelmet(ItemData.AIR); + equipmentPacket.setLeggings(ItemData.AIR); + + session.sendUpstreamPacket(equipmentPacket); + } + + @Override + public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { + return javaIdentifierStripped.equals("wheat") || javaIdentifierStripped.equals("hay_block"); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/TraderLlamaEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/TraderLlamaEntity.java similarity index 66% rename from connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/TraderLlamaEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/TraderLlamaEntity.java index f01326730..770d30a05 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/TraderLlamaEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/TraderLlamaEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,22 +23,24 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.animal.horse; +package org.geysermc.geyser.entity.type.living.animal.horse; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; public class TraderLlamaEntity extends LlamaEntity { - public TraderLlamaEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public TraderLlamaEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } @Override - public void spawnEntity(GeyserSession session) { - this.metadata.put(EntityData.MARK_VARIANT, 1); - super.spawnEntity(session); + protected void initializeMetadata() { + super.initializeMetadata(); + this.dirtyMetadata.put(EntityData.MARK_VARIANT, 1); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/CatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/CatEntity.java new file mode 100644 index 000000000..5538621d9 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/CatEntity.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.animal.tameable; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.type.ItemMapping; + +import java.util.UUID; + +public class CatEntity extends TameableEntity { + + private byte collarColor; + + public CatEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + @Override + public void updateRotation(float yaw, float pitch, boolean isOnGround) { + moveRelative(0, 0, 0, yaw, pitch, yaw, isOnGround); + } + + @Override + protected float getAdultSize() { + return 0.8f; + } + + @Override + protected float getBabySize() { + return 0.4f; + } + + @Override + public void setTameableFlags(ByteEntityMetadata entityMetadata) { + super.setTameableFlags(entityMetadata); + // Update collar color if tamed + if (getFlag(EntityFlag.TAMED)) { + dirtyMetadata.put(EntityData.COLOR, collarColor); + } + } + + public void setCatVariant(IntEntityMetadata entityMetadata) { + // Different colors in Java and Bedrock for some reason + int metadataValue = entityMetadata.getPrimitiveValue(); + int variantColor = switch (metadataValue) { + case 0 -> 8; + case 8 -> 0; + case 9 -> 10; + case 10 -> 9; + default -> metadataValue; + }; + dirtyMetadata.put(EntityData.VARIANT, variantColor); + } + + public void setResting(BooleanEntityMetadata entityMetadata) { + setFlag(EntityFlag.RESTING, entityMetadata.getPrimitiveValue()); + } + + public void setCollarColor(IntEntityMetadata entityMetadata) { + collarColor = (byte) entityMetadata.getPrimitiveValue(); + // Needed or else wild cats are a red color + if (getFlag(EntityFlag.TAMED)) { + dirtyMetadata.put(EntityData.COLOR, collarColor); + } + } + + @Override + public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { + return javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon"); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java new file mode 100644 index 000000000..05f0a6ad5 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.animal.tameable; + +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.type.ItemMapping; + +import java.util.UUID; + +public class ParrotEntity extends TameableEntity { + + public ParrotEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + @Override + public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { + return javaIdentifierStripped.contains("seeds") || javaIdentifierStripped.equals("cookie"); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java new file mode 100644 index 000000000..d12839e92 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.animal.tameable; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import lombok.Getter; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.type.living.animal.AnimalEntity; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.Optional; +import java.util.UUID; + +public class TameableEntity extends AnimalEntity { + /** + * Used in the interactive tag manager to track if the session player owns this entity + */ + @Getter + protected long ownerBedrockId; + + public TameableEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + public void setTameableFlags(ByteEntityMetadata entityMetadata) { + byte xd = entityMetadata.getPrimitiveValue(); + setFlag(EntityFlag.SITTING, (xd & 0x01) == 0x01); + setFlag(EntityFlag.ANGRY, (xd & 0x02) == 0x02); + setFlag(EntityFlag.TAMED, (xd & 0x04) == 0x04); + } + + public void setOwner(EntityMetadata, ?> entityMetadata) { + // Note: Must be set for wolf collar color to work + if (entityMetadata.getValue().isPresent()) { + // Owner UUID of entity + Entity entity = session.getEntityCache().getPlayerEntity(entityMetadata.getValue().get()); + // Used as both a check since the player isn't in the entity cache and a normal fallback + if (entity == null) { + entity = session.getPlayerEntity(); + } + // Translate to entity ID + ownerBedrockId = entity.getGeyserId(); + } else { + // Reset + ownerBedrockId = 0L; + } + dirtyMetadata.put(EntityData.OWNER_EID, ownerBedrockId); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java new file mode 100644 index 000000000..13bb8e17d --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.animal.tameable; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.google.common.collect.ImmutableSet; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.type.ItemMapping; + +import java.util.Set; +import java.util.UUID; + +public class WolfEntity extends TameableEntity { + /** + * A list of all foods a wolf can eat on Java Edition. + * Used to display interactive tag or particles if needed. + */ + private static final Set WOLF_FOODS = ImmutableSet.of("pufferfish", "tropical_fish", "chicken", "cooked_chicken", + "porkchop", "beef", "rabbit", "cooked_porkchop", "cooked_beef", "rotten_flesh", "mutton", "cooked_mutton", + "cooked_rabbit"); + + private byte collarColor; + + public WolfEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + @Override + public void setTameableFlags(ByteEntityMetadata entityMetadata) { + super.setTameableFlags(entityMetadata); + // Reset wolf color + byte xd = entityMetadata.getPrimitiveValue(); + boolean angry = (xd & 0x02) == 0x02; + if (angry) { + dirtyMetadata.put(EntityData.COLOR, (byte) 0); + } + } + + public void setCollarColor(IntEntityMetadata entityMetadata) { + collarColor = (byte) entityMetadata.getPrimitiveValue(); + if (getFlag(EntityFlag.ANGRY)) { + return; + } + + dirtyMetadata.put(EntityData.COLOR, collarColor); + if (ownerBedrockId == 0) { + // If a color is set and there is no owner entity ID, set one. + // Otherwise, the entire wolf is set to that color: https://user-images.githubusercontent.com/9083212/99209989-92691200-2792-11eb-911d-9a315c955be9.png + dirtyMetadata.put(EntityData.OWNER_EID, session.getPlayerEntity().getGeyserId()); + } + } + + // 1.16+ + public void setWolfAngerTime(IntEntityMetadata entityMetadata) { + int time = entityMetadata.getPrimitiveValue(); + setFlag(EntityFlag.ANGRY, time != 0); + dirtyMetadata.put(EntityData.COLOR, time != 0 ? (byte) 0 : collarColor); + } + + @Override + public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) { + // Cannot be a baby to eat these foods + return WOLF_FOODS.contains(javaIdentifierStripped) && !isBaby(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/AbstractMerchantEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/AbstractMerchantEntity.java new file mode 100644 index 000000000..7981c9b23 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/AbstractMerchantEntity.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.merchant; + +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.type.living.AgeableEntity; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +public class AbstractMerchantEntity extends AgeableEntity { + + public AbstractMerchantEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/VillagerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/VillagerEntity.java new file mode 100644 index 000000000..012fb05f2 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/VillagerEntity.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.merchant; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import lombok.Getter; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.BlockRegistries; + +import java.util.Optional; +import java.util.UUID; + +public class VillagerEntity extends AbstractMerchantEntity { + + /** + * A map of Java profession IDs to Bedrock IDs + */ + public static final Int2IntMap VILLAGER_PROFESSIONS = new Int2IntOpenHashMap(); + /** + * A map of all Java region IDs (plains, savanna...) to Bedrock + */ + public static final Int2IntMap VILLAGER_REGIONS = new Int2IntOpenHashMap(); + + static { + // Java villager profession IDs -> Bedrock + VILLAGER_PROFESSIONS.put(0, 0); + VILLAGER_PROFESSIONS.put(1, 8); + VILLAGER_PROFESSIONS.put(2, 11); + VILLAGER_PROFESSIONS.put(3, 6); + VILLAGER_PROFESSIONS.put(4, 7); + VILLAGER_PROFESSIONS.put(5, 1); + VILLAGER_PROFESSIONS.put(6, 2); + VILLAGER_PROFESSIONS.put(7, 4); + VILLAGER_PROFESSIONS.put(8, 12); + VILLAGER_PROFESSIONS.put(9, 5); + VILLAGER_PROFESSIONS.put(10, 13); + VILLAGER_PROFESSIONS.put(11, 14); + VILLAGER_PROFESSIONS.put(12, 3); + VILLAGER_PROFESSIONS.put(13, 10); + VILLAGER_PROFESSIONS.put(14, 9); + + VILLAGER_REGIONS.put(0, 1); + VILLAGER_REGIONS.put(1, 2); + VILLAGER_REGIONS.put(2, 0); + VILLAGER_REGIONS.put(3, 3); + VILLAGER_REGIONS.put(4, 4); + VILLAGER_REGIONS.put(5, 5); + VILLAGER_REGIONS.put(6, 6); + } + + private Vector3i bedPosition; + /** + * Used in the interactive tag manager + */ + @Getter + private boolean canTradeWith; + + public VillagerEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + public void setVillagerData(EntityMetadata entityMetadata) { + VillagerData villagerData = entityMetadata.getValue(); + // Profession + int profession = VILLAGER_PROFESSIONS.get(villagerData.getProfession()); + canTradeWith = profession != 14 && profession != 0; // Not a notwit and not professionless + dirtyMetadata.put(EntityData.VARIANT, profession); + //metadata.put(EntityData.SKIN_ID, villagerData.getType()); Looks like this is modified but for any reason? + // Region + dirtyMetadata.put(EntityData.MARK_VARIANT, VILLAGER_REGIONS.get(villagerData.getType())); + // Trade tier - different indexing in Bedrock + dirtyMetadata.put(EntityData.TRADE_TIER, villagerData.getLevel() - 1); + } + + @Override + public Vector3i setBedPosition(EntityMetadata, ?> entityMetadata) { + return bedPosition = super.setBedPosition(entityMetadata); + } + + @Override + public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) { + // The bed block position, if it exists + if (!getFlag(EntityFlag.SLEEPING) || bedPosition == null) { + // No need to worry about extra processing to compensate for sleeping + super.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, isOnGround); + return; + } + + // The bed block + int blockId = session.getGeyser().getWorldManager().getBlockAt(session, bedPosition); + String fullIdentifier = BlockRegistries.JAVA_IDENTIFIERS.get().get(blockId); + + // Set the correct position offset and rotation when sleeping + int bedRotation = 0; + float xOffset = 0; + float zOffset = 0; + if (fullIdentifier.contains("facing=south")) { + // bed is facing south + bedRotation = 180; + zOffset = -.5f; + } else if (fullIdentifier.contains("facing=east")) { + // bed is facing east + bedRotation = 90; + xOffset = -.5f; + } else if (fullIdentifier.contains("facing=west")) { + // bed is facing west + bedRotation = 270; + xOffset = .5f; + } else if (fullIdentifier.contains("facing=north")) { + // rotation does not change because north is 0 + zOffset = .5f; + } + + setYaw(yaw); + setPitch(pitch); + setHeadYaw(headYaw); + setOnGround(isOnGround); + this.position = Vector3f.from(position.getX() + relX, position.getY() + relY, position.getZ() + relZ); + + MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket(); + moveEntityPacket.setRuntimeEntityId(geyserId); + moveEntityPacket.setRotation(Vector3f.from(0, 0, bedRotation)); + moveEntityPacket.setPosition(Vector3f.from(position.getX() + xOffset, position.getY(), position.getZ() + zOffset)); + moveEntityPacket.setOnGround(isOnGround); + moveEntityPacket.setTeleported(false); + session.sendUpstreamPacket(moveEntityPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/AbstractSkeletonEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/AbstractSkeletonEntity.java similarity index 60% rename from connector/src/main/java/org/geysermc/connector/entity/living/monster/AbstractSkeletonEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/monster/AbstractSkeletonEntity.java index ff48d347f..ae13cfeae 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/AbstractSkeletonEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/AbstractSkeletonEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,27 +23,27 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.monster; +package org.geysermc.geyser.entity.type.living.monster; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; public class AbstractSkeletonEntity extends MonsterEntity { - public AbstractSkeletonEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public AbstractSkeletonEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 14) { - byte xd = (byte) entityMetadata.getValue(); - // A bit of a loophole so the hands get raised - set the target ID to its own ID - metadata.put(EntityData.TARGET_EID, (xd == 4) ? geyserId : 0); - } - super.updateBedrockMetadata(entityMetadata, session); + public void setMobFlags(ByteEntityMetadata entityMetadata) { + super.setMobFlags(entityMetadata); + byte xd = entityMetadata.getPrimitiveValue(); + // A bit of a loophole so the hands get raised - set the target ID to its own ID + dirtyMetadata.put(EntityData.TARGET_EID, ((xd & 4) == 4) ? geyserId : 0); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BasePiglinEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BasePiglinEntity.java new file mode 100644 index 000000000..2f315368f --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BasePiglinEntity.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.monster; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.DimensionUtils; + +import java.util.UUID; + +public class BasePiglinEntity extends MonsterEntity { + private boolean isImmuneToZombification; + + public BasePiglinEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + public void setImmuneToZombification(BooleanEntityMetadata entityMetadata) { + // Apply shaking effect if not in the nether and zombification is possible + this.isImmuneToZombification = entityMetadata.getPrimitiveValue(); + setFlag(EntityFlag.SHAKING, isShaking()); + } + + @Override + protected boolean isShaking() { + return (!isImmuneToZombification && !session.getDimension().equals(DimensionUtils.NETHER)) || super.isShaking(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/BlazeEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BlazeEntity.java similarity index 62% rename from connector/src/main/java/org/geysermc/connector/entity/living/monster/BlazeEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BlazeEntity.java index 75fec18fc..2303f8091 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/BlazeEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BlazeEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,27 +23,24 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.monster; +package org.geysermc.geyser.entity.type.living.monster; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; public class BlazeEntity extends MonsterEntity { - public BlazeEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public BlazeEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { - byte xd = (byte) entityMetadata.getValue(); - metadata.getFlags().setFlag(EntityFlag.ON_FIRE, (xd & 0x01) == 0x01); - } - - super.updateBedrockMetadata(entityMetadata, session); + public void setBlazeFlags(ByteEntityMetadata entityMetadata) { + byte xd = entityMetadata.getPrimitiveValue(); + setFlag(EntityFlag.ON_FIRE, (xd & 0x01) == 0x01); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/CreeperEntity.java similarity index 51% rename from connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/monster/CreeperEntity.java index f4931861c..f1e0b6a65 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/CreeperEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,41 +23,36 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.monster; +package org.geysermc.geyser.entity.type.living.monster; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; public class CreeperEntity extends MonsterEntity { - /** - * Whether the creeper has been ignited and is using ID 17. - * In this instance we ignore ID 15 since it's sending us -1 which confuses poor Bedrock. + * Whether the creeper has been ignited and is using {@link #setIgnited(BooleanEntityMetadata)}. + * In this instance we ignore {@link #setSwelling(IntEntityMetadata)} since it's sending us -1 which confuses poor Bedrock. */ private boolean ignitedByFlintAndSteel = false; - public CreeperEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public CreeperEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { - if (!ignitedByFlintAndSteel) { - metadata.getFlags().setFlag(EntityFlag.IGNITED, (int) entityMetadata.getValue() == 1); - } - } - if (entityMetadata.getId() == 16) { - metadata.getFlags().setFlag(EntityFlag.POWERED, (boolean) entityMetadata.getValue()); - } - if (entityMetadata.getId() == 17) { - ignitedByFlintAndSteel = (boolean) entityMetadata.getValue(); - metadata.getFlags().setFlag(EntityFlag.IGNITED, ignitedByFlintAndSteel); + public void setSwelling(IntEntityMetadata entityMetadata) { + if (!ignitedByFlintAndSteel) { + setFlag(EntityFlag.IGNITED, entityMetadata.getPrimitiveValue() == 1); } + } - super.updateBedrockMetadata(entityMetadata, session); + public void setIgnited(BooleanEntityMetadata entityMetadata) { + ignitedByFlintAndSteel = entityMetadata.getPrimitiveValue(); + setFlag(EntityFlag.IGNITED, ignitedByFlintAndSteel); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ElderGuardianEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ElderGuardianEntity.java similarity index 67% rename from connector/src/main/java/org/geysermc/connector/entity/living/monster/ElderGuardianEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ElderGuardianEntity.java index 8786a5b30..9c237f117 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ElderGuardianEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ElderGuardianEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,18 +23,25 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.monster; +package org.geysermc.geyser.entity.type.living.monster; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; public class ElderGuardianEntity extends GuardianEntity { - public ElderGuardianEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - // Otherwise it just looks like a normal guardian but bigger - metadata.getFlags().setFlag(EntityFlag.ELDER, true); + public ElderGuardianEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } + @Override + protected void initializeMetadata() { + super.initializeMetadata(); + // Otherwise it just looks like a normal guardian but bigger + setFlag(EntityFlag.ELDER, true); + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java new file mode 100644 index 000000000..f49b51e6c --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.monster; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.*; +import lombok.Data; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.type.Tickable; +import org.geysermc.geyser.entity.type.living.MobEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.DimensionUtils; + +import java.util.Random; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicLong; + +public class EnderDragonEntity extends MobEntity implements Tickable { + /** + * The Ender Dragon has multiple hit boxes, which + * are each its own invisible entity + */ + private EnderDragonPartEntity head; + private EnderDragonPartEntity neck; + private EnderDragonPartEntity body; + private EnderDragonPartEntity leftWing; + private EnderDragonPartEntity rightWing; + private EnderDragonPartEntity[] tail; + + private EnderDragonPartEntity[] allParts; + + /** + * A circular buffer that stores a history of + * y and yaw values. + */ + private final Segment[] segmentHistory = new Segment[19]; + private int latestSegment = -1; + + private int phase; + /** + * The number of ticks since the beginning of the phase + */ + private int phaseTicks; + + private int ticksTillNextGrowl = 100; + + /** + * Used to determine when the wing flap sound should be played + */ + private float wingPosition; + private float lastWingPosition; + + public EnderDragonEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + @Override + protected void initializeMetadata() { + super.initializeMetadata(); + setFlag(EntityFlag.FIRE_IMMUNE, true); + } + + @Override + public void setHealth(FloatEntityMetadata entityMetadata) { + super.setHealth(entityMetadata); + if (phase == 9 && this.health <= 0) { // Dying phase + EntityEventPacket entityEventPacket = new EntityEventPacket(); + entityEventPacket.setType(EntityEventType.ENDER_DRAGON_DEATH); + entityEventPacket.setRuntimeEntityId(geyserId); + entityEventPacket.setData(0); + session.sendUpstreamPacket(entityEventPacket); + } + } + + public void setPhase(IntEntityMetadata entityMetadata) { + phase = entityMetadata.getPrimitiveValue(); + phaseTicks = 0; + setFlag(EntityFlag.SITTING, isSitting()); + } + + @Override + public void spawnEntity() { + super.spawnEntity(); + + AtomicLong nextEntityId = session.getEntityCache().getNextEntityId(); + head = new EnderDragonPartEntity(session, entityId + 1, nextEntityId.incrementAndGet(), 1, 1); + neck = new EnderDragonPartEntity(session, entityId + 2, nextEntityId.incrementAndGet(), 3, 3); + body = new EnderDragonPartEntity(session, entityId + 3, nextEntityId.incrementAndGet(), 5, 3); + leftWing = new EnderDragonPartEntity(session, entityId + 4, nextEntityId.incrementAndGet(), 4, 2); + rightWing = new EnderDragonPartEntity(session, entityId + 5, nextEntityId.incrementAndGet(), 4, 2); + tail = new EnderDragonPartEntity[3]; + for (int i = 0; i < 3; i++) { + tail[i] = new EnderDragonPartEntity(session, entityId + 6 + i, nextEntityId.incrementAndGet(), 2, 2); + } + + allParts = new EnderDragonPartEntity[]{head, neck, body, leftWing, rightWing, tail[0], tail[1], tail[2]}; + + for (EnderDragonPartEntity part : allParts) { + session.getEntityCache().spawnEntity(part); + } + + for (int i = 0; i < segmentHistory.length; i++) { + segmentHistory[i] = new Segment(); + segmentHistory[i].yaw = headYaw; + segmentHistory[i].y = position.getY(); + } + } + + @Override + public void addAdditionalSpawnData(AddEntityPacket addEntityPacket) { + // Bedrock is EXTREMELY sensitive to the Ender Dragon's health - if it is dead once, it is dead for the rest of its life + // Ensure that the first spawn packet sent has health data so this cannot happen until it actually should + addEntityPacket.getAttributes().add(createHealthAttribute()); + } + + @Override + public boolean despawnEntity() { + for (EnderDragonPartEntity part : allParts) { + part.despawnEntity(); + } + return super.despawnEntity(); + } + + @Override + public void tick() { + effectTick(); + if (!getFlag(EntityFlag.NO_AI) && isAlive()) { + pushSegment(); + updateBoundingBoxes(); + } + } + + /** + * Updates the positions of the Ender Dragon's multiple bounding boxes + */ + private void updateBoundingBoxes() { + Vector3f facingDir = Vector3f.createDirectionDeg(0, headYaw); + Segment baseSegment = getSegment(5); + // Used to angle the head, neck, and tail when the dragon flies up/down + float pitch = (float) Math.toRadians(10 * (baseSegment.getY() - getSegment(10).getY())); + float pitchXZ = (float) Math.cos(pitch); + float pitchY = (float) Math.sin(pitch); + + // Lowers the head when the dragon sits/hovers + float headDuck; + if (isHovering() || isSitting()) { + headDuck = -1f; + } else { + headDuck = baseSegment.y - getSegment(0).y; + } + + head.setPosition(facingDir.up(pitchY).mul(pitchXZ, 1, -pitchXZ).mul(6.5f).up(headDuck)); + neck.setPosition(facingDir.up(pitchY).mul(pitchXZ, 1, -pitchXZ).mul(5.5f).up(headDuck)); + body.setPosition(facingDir.mul(0.5f, 0f, -0.5f)); + + Vector3f wingPos = Vector3f.createDirectionDeg(0, 90f - headYaw).mul(4.5f).up(2f); + rightWing.setPosition(wingPos); + leftWing.setPosition(wingPos.mul(-1, 1, -1)); // Mirror horizontally + + Vector3f tailBase = facingDir.mul(1.5f); + for (int i = 0; i < tail.length; i++) { + float distance = (i + 1) * 2f; + // Curls the tail when the dragon turns + Segment targetSegment = getSegment(12 + 2 * i); + float angle = headYaw + targetSegment.yaw - baseSegment.yaw; + + float tailYOffset = targetSegment.y - baseSegment.y - (distance + 1.5f) * pitchY + 1.5f; + tail[i].setPosition(Vector3f.createDirectionDeg(0, angle).mul(distance).add(tailBase).mul(-pitchXZ, 1, pitchXZ).up(tailYOffset)); + } + // Send updated positions + for (EnderDragonPartEntity part : allParts) { + part.moveAbsolute(part.getPosition().add(position), 0, 0, 0, false, false); + } + } + + /** + * Handles the particles and sounds of the Ender Dragon + */ + private void effectTick() { + Random random = ThreadLocalRandom.current(); + if (!getFlag(EntityFlag.SILENT)) { + if (Math.cos(wingPosition * 2f * Math.PI) <= -0.3f && Math.cos(lastWingPosition * 2f * Math.PI) >= -0.3f) { + PlaySoundPacket playSoundPacket = new PlaySoundPacket(); + playSoundPacket.setSound("mob.enderdragon.flap"); + playSoundPacket.setPosition(position); + playSoundPacket.setVolume(5f); + playSoundPacket.setPitch(0.8f + random.nextFloat() * 0.3f); + session.sendUpstreamPacket(playSoundPacket); + } + + if (!isSitting() && !isHovering() && ticksTillNextGrowl-- == 0) { + playGrowlSound(); + ticksTillNextGrowl = 200 + random.nextInt(200); + } + + lastWingPosition = wingPosition; + } + if (isAlive()) { + if (getFlag(EntityFlag.NO_AI)) { + wingPosition = 0.5f; + } else if (isHovering() || isSitting()) { + wingPosition += 0.1f; + } else { + double speed = motion.length(); + wingPosition += 0.2f / (speed * 10f + 1) * Math.pow(2, motion.getY()); + } + + phaseTicks++; + if (phase == 3) { // Landing Phase + float headHeight = head.getBoundingBoxHeight(); + Vector3f headCenter = head.getPosition().up(headHeight * 0.5f); + + for (int i = 0; i < 8; i++) { + Vector3f particlePos = headCenter.add(random.nextGaussian() / 2f, random.nextGaussian() / 2f, random.nextGaussian() / 2f); + // This is missing velocity information + LevelEventPacket particlePacket = new LevelEventPacket(); + particlePacket.setType(LevelEventType.PARTICLE_DRAGONS_BREATH); + particlePacket.setPosition(particlePos); + session.sendUpstreamPacket(particlePacket); + } + } else if (phase == 5) { // Sitting Flaming Phase + if (phaseTicks % 2 == 0 && phaseTicks < 10) { + // Performing breath attack + // Entity event DRAGON_FLAMING seems to create particles from the origin of the dragon, + // so we need to manually spawn particles + for (int i = 0; i < 8; i++) { + SpawnParticleEffectPacket spawnParticleEffectPacket = new SpawnParticleEffectPacket(); + spawnParticleEffectPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension())); + spawnParticleEffectPacket.setPosition(head.getPosition().add(random.nextGaussian() / 2f, random.nextGaussian() / 2f, random.nextGaussian() / 2f)); + spawnParticleEffectPacket.setIdentifier("minecraft:dragon_breath_fire"); + session.sendUpstreamPacket(spawnParticleEffectPacket); + } + } + } else if (phase == 7) { // Sitting Attacking Phase + playGrowlSound(); + } else if (phase == 9) { // Dying Phase + // Send explosion particles as the dragon move towards the end portal + if (phaseTicks % 10 == 0) { + float xOffset = 8f * (random.nextFloat() - 0.5f); + float yOffset = 4f * (random.nextFloat() - 0.5f) + 2f; + float zOffset = 8f * (random.nextFloat() - 0.5f); + Vector3f particlePos = position.add(xOffset, yOffset, zOffset); + LevelEventPacket particlePacket = new LevelEventPacket(); + particlePacket.setType(LevelEventType.PARTICLE_EXPLOSION); + particlePacket.setPosition(particlePos); + session.sendUpstreamPacket(particlePacket); + } + } + } + } + + private void playGrowlSound() { + Random random = ThreadLocalRandom.current(); + PlaySoundPacket playSoundPacket = new PlaySoundPacket(); + playSoundPacket.setSound("mob.enderdragon.growl"); + playSoundPacket.setPosition(position); + playSoundPacket.setVolume(2.5f); + playSoundPacket.setPitch(0.8f + random.nextFloat() * 0.3f); + session.sendUpstreamPacket(playSoundPacket); + } + + private boolean isAlive() { + return health > 0; + } + + private boolean isHovering() { + return phase == 10; + } + + private boolean isSitting() { + return phase == 5 || phase == 6 || phase == 7; + } + + /** + * Store the current yaw and y into the circular buffer + */ + private void pushSegment() { + latestSegment = (latestSegment + 1) % segmentHistory.length; + segmentHistory[latestSegment].yaw = headYaw; + segmentHistory[latestSegment].y = position.getY(); + } + + /** + * Gets the previous yaw and y + * Used to curl the tail and pitch the head and tail up/down + * + * @param index Number of ticks in the past + * @return Segment with the yaw and y + */ + private Segment getSegment(int index) { + index = (latestSegment - index) % segmentHistory.length; + if (index < 0) { + index += segmentHistory.length; + } + return segmentHistory[index]; + } + + @Data + private static class Segment { + private float yaw; + private float y; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonPartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonPartEntity.java similarity index 65% rename from connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonPartEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonPartEntity.java index bb5876ce8..7cd4bb6cf 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonPartEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonPartEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,20 +23,23 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.monster; +package org.geysermc.geyser.entity.type.living.monster; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.session.GeyserSession; public class EnderDragonPartEntity extends Entity { - public EnderDragonPartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, float width, float height) { - super(entityId, geyserId, entityType, position, motion, rotation); - metadata.put(EntityData.BOUNDING_BOX_WIDTH, width); - metadata.put(EntityData.BOUNDING_BOX_HEIGHT, height); - metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true); + public EnderDragonPartEntity(GeyserSession session, long entityId, long geyserId, float width, float height) { + super(session, entityId, geyserId, null, EntityDefinitions.ENDER_DRAGON_PART, Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0); + + dirtyMetadata.put(EntityData.BOUNDING_BOX_WIDTH, width); + dirtyMetadata.put(EntityData.BOUNDING_BOX_HEIGHT, height); + setFlag(EntityFlag.INVISIBLE, true); + setFlag(EntityFlag.FIRE_IMMUNE, true); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EndermanEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EndermanEntity.java new file mode 100644 index 000000000..469f48521 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EndermanEntity.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.monster; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.SoundEvent; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.LevelSoundEvent2Packet; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +public class EndermanEntity extends MonsterEntity { + + public EndermanEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + public void setCarriedBlock(IntEntityMetadata entityMetadata) { + dirtyMetadata.put(EntityData.CARRIED_BLOCK, session.getBlockMappings().getBedrockBlockId(entityMetadata.getPrimitiveValue())); + } + + /** + * Controls the screaming sound + */ + public void setScreaming(BooleanEntityMetadata entityMetadata) { + //TODO see if Bedrock controls this differently + // Java Edition this controls which ambient sound is used + if (entityMetadata.getPrimitiveValue()) { + LevelSoundEvent2Packet packet = new LevelSoundEvent2Packet(); + packet.setSound(SoundEvent.STARE); + packet.setPosition(this.position); + packet.setExtraData(-1); + packet.setIdentifier("minecraft:enderman"); + session.sendUpstreamPacket(packet); + } + } + + public void setAngry(BooleanEntityMetadata entityMetadata) { + // "Is staring/provoked" - controls visuals + setFlag(EntityFlag.ANGRY, entityMetadata.getPrimitiveValue()); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/GhastEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GhastEntity.java similarity index 61% rename from connector/src/main/java/org/geysermc/connector/entity/living/monster/GhastEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GhastEntity.java index 3d3be87ce..845a281d9 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/GhastEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GhastEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,27 +23,25 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.monster; +package org.geysermc.geyser.entity.type.living.monster; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.entity.living.FlyingEntity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.type.living.FlyingEntity; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; public class GhastEntity extends FlyingEntity { - public GhastEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public GhastEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { - // If the ghast is attacking - metadata.put(EntityData.CHARGE_AMOUNT, (byte) ((boolean) entityMetadata.getValue() ? 1 : 0)); - } - super.updateBedrockMetadata(entityMetadata, session); + public void setGhastAttacking(BooleanEntityMetadata entityMetadata) { + // If the ghast is attacking + dirtyMetadata.put(EntityData.CHARGE_AMOUNT, (byte) (entityMetadata.getPrimitiveValue() ? 1 : 0)); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/ExpOrbEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GiantEntity.java similarity index 64% rename from connector/src/main/java/org/geysermc/connector/entity/ExpOrbEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GiantEntity.java index c830d2596..6dd27cc39 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ExpOrbEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GiantEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,26 +23,20 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity; +package org.geysermc.geyser.entity.type.living.monster; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; -public class ExpOrbEntity extends Entity { +import java.util.UUID; - private int amount; +public class GiantEntity extends MonsterEntity { - public ExpOrbEntity(int amount, long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public GiantEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); - this.amount = amount; - } - - @Override - public void spawnEntity(GeyserSession session) { - this.metadata.put(EntityData.EXPERIENCE_VALUE, amount); - super.spawnEntity(session); + dirtyMetadata.put(EntityData.SCALE, 6f); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/GuardianEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GuardianEntity.java similarity index 53% rename from connector/src/main/java/org/geysermc/connector/entity/living/monster/GuardianEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GuardianEntity.java index aa9ce4ca5..0190f3c60 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/GuardianEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GuardianEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,36 +23,36 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.monster; +package org.geysermc.geyser.entity.type.living.monster; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; public class GuardianEntity extends MonsterEntity { - public GuardianEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public GuardianEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { - Entity entity = session.getEntityCache().getEntityByJavaId((int) entityMetadata.getValue()); - if (entity == null && session.getPlayerEntity().getEntityId() == (Integer) entityMetadata.getValue()) { - entity = session.getPlayerEntity(); - } - - if (entity != null) { - metadata.put(EntityData.TARGET_EID, entity.getGeyserId()); - } else { - metadata.put(EntityData.TARGET_EID, (long) 0); - } + public void setGuardianTarget(IntEntityMetadata entityMetadata) { + int entityId = entityMetadata.getPrimitiveValue(); + Entity entity; + if (session.getPlayerEntity().getEntityId() == entityId) { + entity = session.getPlayerEntity(); + } else { + entity = session.getEntityCache().getEntityByJavaId(entityId); } - super.updateBedrockMetadata(entityMetadata, session); + if (entity != null) { + dirtyMetadata.put(EntityData.TARGET_EID, entity.getGeyserId()); + } else { + dirtyMetadata.put(EntityData.TARGET_EID, (long) 0); + } } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/MonsterEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/MonsterEntity.java similarity index 67% rename from connector/src/main/java/org/geysermc/connector/entity/living/monster/MonsterEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/monster/MonsterEntity.java index 0edd1b987..fad45f982 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/MonsterEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/MonsterEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,15 +23,18 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.monster; +package org.geysermc.geyser.entity.type.living.monster; import com.nukkitx.math.vector.Vector3f; -import org.geysermc.connector.entity.living.CreatureEntity; -import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.type.living.CreatureEntity; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; public class MonsterEntity extends CreatureEntity { - public MonsterEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public MonsterEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/MooshroomEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PhantomEntity.java similarity index 54% rename from connector/src/main/java/org/geysermc/connector/entity/living/animal/MooshroomEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PhantomEntity.java index 69fb55fb4..8d3ccc71f 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/MooshroomEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PhantomEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,25 +23,29 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.animal; +package org.geysermc.geyser.entity.type.living.monster; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.type.living.FlyingEntity; +import org.geysermc.geyser.session.GeyserSession; -public class MooshroomEntity extends AnimalEntity { +import java.util.UUID; - public MooshroomEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); +public class PhantomEntity extends FlyingEntity { + public PhantomEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 16) { - metadata.put(EntityData.VARIANT, entityMetadata.getValue().equals("brown") ? 1 : 0); - } - super.updateBedrockMetadata(entityMetadata, session); + public void setPhantomScale(IntEntityMetadata entityMetadata) { + int size = entityMetadata.getPrimitiveValue(); + float modelScale = 1f + 0.15f * size; + float boundsScale = (1f + (0.2f * size) / definition.width()) / modelScale; + + setBoundingBoxWidth(boundsScale * definition.width()); + setBoundingBoxHeight(boundsScale * definition.height()); + dirtyMetadata.put(EntityData.SCALE, modelScale); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PiglinEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PiglinEntity.java new file mode 100644 index 000000000..b98d6eabc --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PiglinEntity.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.monster; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +public class PiglinEntity extends BasePiglinEntity { + + public PiglinEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + public void setBaby(BooleanEntityMetadata entityMetadata) { + boolean isBaby = entityMetadata.getPrimitiveValue(); + dirtyMetadata.put(EntityData.SCALE, isBaby? .55f : 1f); + setFlag(EntityFlag.BABY, isBaby); + } + + public void setChargingCrossbow(BooleanEntityMetadata entityMetadata) { + setFlag(EntityFlag.CHARGING, entityMetadata.getPrimitiveValue()); + } + + public void setDancing(BooleanEntityMetadata entityMetadata) { + setFlag(EntityFlag.DANCING, entityMetadata.getPrimitiveValue()); + } + + @Override + public void updateOffHand(GeyserSession session) { + // Check if the Piglin is holding Gold and set the ADMIRING flag accordingly so its pose updates + setFlag(EntityFlag.ADMIRING, session.getTagCache().shouldPiglinAdmire(session.getItemMappings().getMapping(this.offHand))); + super.updateBedrockMetadata(); + + super.updateOffHand(session); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ShulkerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ShulkerEntity.java new file mode 100644 index 000000000..e60f81d2f --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ShulkerEntity.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.monster; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.object.Direction; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.type.living.GolemEntity; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +public class ShulkerEntity extends GolemEntity { + + public ShulkerEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + // Indicate that invisibility should be fixed through the resource pack + setFlag(EntityFlag.BRIBED, true); + } + + public void setAttachedFace(EntityMetadata entityMetadata) { + Direction direction = entityMetadata.getValue(); + dirtyMetadata.put(EntityData.SHULKER_ATTACH_FACE, (byte) direction.ordinal()); + } + + public void setShulkerHeight(ByteEntityMetadata entityMetadata) { + int height = entityMetadata.getPrimitiveValue(); + dirtyMetadata.put(EntityData.SHULKER_PEEK_ID, height); + } + + public void setShulkerColor(ByteEntityMetadata entityMetadata) { + byte color = ((ByteEntityMetadata) entityMetadata).getPrimitiveValue(); + if (color == 16) { + // 16 is default on both editions + dirtyMetadata.put(EntityData.VARIANT, 16); + } else { + // Every other shulker color is offset 15 in bedrock edition + dirtyMetadata.put(EntityData.VARIANT, Math.abs(color - 15)); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/SkeletonEntity.java similarity index 58% rename from connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/monster/SkeletonEntity.java index f174747b7..b1f6939aa 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/SkeletonEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,28 +23,30 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity; +package org.geysermc.geyser.entity.type.living.monster; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; -public class AbstractArrowEntity extends Entity { +import java.util.UUID; - public AbstractArrowEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); +public class SkeletonEntity extends AbstractSkeletonEntity { + private boolean convertingToStray = false; + + public SkeletonEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + public void setConvertingToStray(BooleanEntityMetadata entityMetadata) { + this.convertingToStray = entityMetadata.getPrimitiveValue(); + setFlag(EntityFlag.SHAKING, isShaking()); } @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7) { - byte data = (byte) entityMetadata.getValue(); - - metadata.getFlags().setFlag(EntityFlag.CRITICAL, (data & 0x01) == 0x01); - } - - super.updateBedrockMetadata(entityMetadata, session); + protected boolean isShaking() { + return convertingToStray; } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/SpiderEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/SpiderEntity.java similarity index 62% rename from connector/src/main/java/org/geysermc/connector/entity/living/monster/SpiderEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/monster/SpiderEntity.java index f0ad6f058..4f7b02d73 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/SpiderEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/SpiderEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,27 +23,24 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.monster; +package org.geysermc.geyser.entity.type.living.monster; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; public class SpiderEntity extends MonsterEntity { - public SpiderEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public SpiderEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { - byte xd = (byte) entityMetadata.getValue(); - metadata.getFlags().setFlag(EntityFlag.WALL_CLIMBING, (xd & 0x01) == 0x01); - } - - super.updateBedrockMetadata(entityMetadata, session); + public void setSpiderFlags(ByteEntityMetadata entityMetadata) { + byte xd = entityMetadata.getPrimitiveValue(); + setFlag(EntityFlag.WALL_CLIMBING, (xd & 0x01) == 0x01); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/VexEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/VexEntity.java similarity index 57% rename from connector/src/main/java/org/geysermc/connector/entity/living/monster/VexEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/monster/VexEntity.java index 70e413298..938b18022 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/VexEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/VexEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,28 +23,26 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.monster; +package org.geysermc.geyser.entity.type.living.monster; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; public class VexEntity extends MonsterEntity { - public VexEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public VexEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { - byte xd = (byte) entityMetadata.getValue(); - // Set the target to the player to force the attack animation - // even if the player isn't the target as we dont get the target on Java - metadata.put(EntityData.TARGET_EID, (xd & 0x01) == 0x01 ? session.getPlayerEntity().getGeyserId() : 0); - } - super.updateBedrockMetadata(entityMetadata, session); + public void setVexFlags(ByteEntityMetadata entityMetadata) { + byte xd = entityMetadata.getPrimitiveValue(); + // Set the target to the player to force the attack animation + // even if the player isn't the target as we dont get the target on Java + dirtyMetadata.put(EntityData.TARGET_EID, (xd & 0x01) == 0x01 ? session.getPlayerEntity().getGeyserId() : 0); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/WitherEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/WitherEntity.java new file mode 100644 index 000000000..17da0a611 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/WitherEntity.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.monster; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +public class WitherEntity extends MonsterEntity { + + public WitherEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + @Override + protected void initializeMetadata() { + super.initializeMetadata(); + dirtyMetadata.put(EntityData.WITHER_AERIAL_ATTACK, (short) 1); + } + + public void setTarget1(IntEntityMetadata entityMetadata) { + setTargetId(EntityData.WITHER_TARGET_1, entityMetadata); + } + + public void setTarget2(IntEntityMetadata entityMetadata) { + setTargetId(EntityData.WITHER_TARGET_2, entityMetadata); + } + + public void setTarget3(IntEntityMetadata entityMetadata) { + setTargetId(EntityData.WITHER_TARGET_3, entityMetadata); + } + + private void setTargetId(EntityData entityData, IntEntityMetadata entityMetadata) { + int entityId = entityMetadata.getPrimitiveValue(); + Entity entity; + if (session.getPlayerEntity().getEntityId() == entityId) { + entity = session.getPlayerEntity(); + } else { + entity = session.getEntityCache().getEntityByJavaId(entityId); + } + + if (entity != null) { + dirtyMetadata.put(entityData, entity.getGeyserId()); + } + } + + public void setInvulnerableTicks(IntEntityMetadata entityMetadata) { + int value = entityMetadata.getPrimitiveValue(); + dirtyMetadata.put(EntityData.WITHER_INVULNERABLE_TICKS, value); + + // Show the shield for the first few seconds of spawning (like Java) + if (value >= 165) { + dirtyMetadata.put(EntityData.WITHER_AERIAL_ATTACK, (short) 0); + } else { + dirtyMetadata.put(EntityData.WITHER_AERIAL_ATTACK, (short) 1); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZoglinEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZoglinEntity.java similarity index 61% rename from connector/src/main/java/org/geysermc/connector/entity/living/monster/ZoglinEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZoglinEntity.java index 4ea842110..f0fe101da 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZoglinEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZoglinEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,30 +23,26 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.monster; +package org.geysermc.geyser.entity.type.living.monster; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; public class ZoglinEntity extends MonsterEntity { - public ZoglinEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public ZoglinEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { - boolean isBaby = (boolean) entityMetadata.getValue(); - if (isBaby) { - metadata.put(EntityData.SCALE, .55f); - metadata.getFlags().setFlag(EntityFlag.BABY, true); - } - } - super.updateBedrockMetadata(entityMetadata, session); + public void setBaby(BooleanEntityMetadata entityMetadata) { + boolean isBaby = entityMetadata.getPrimitiveValue(); + dirtyMetadata.put(EntityData.SCALE, isBaby? .55f : 1f); + setFlag(EntityFlag.BABY, isBaby); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombieEntity.java similarity index 55% rename from connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombieEntity.java index 218a2ca08..9e3301b48 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombieEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,30 +23,37 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.monster; +package org.geysermc.geyser.entity.type.living.monster; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; public class ZombieEntity extends MonsterEntity { + private boolean convertingToDrowned = false; - public ZombieEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public ZombieEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + public void setZombieBaby(BooleanEntityMetadata entityMetadata) { + boolean isBaby = entityMetadata.getPrimitiveValue(); + dirtyMetadata.put(EntityData.SCALE, isBaby ? .55f : 1.0f); + setFlag(EntityFlag.BABY, isBaby); + } + + public void setConvertingToDrowned(BooleanEntityMetadata entityMetadata) { + convertingToDrowned = entityMetadata.getPrimitiveValue(); + setFlag(EntityFlag.SHAKING, isShaking()); } @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { - boolean isBaby = (boolean) entityMetadata.getValue(); - if (isBaby) { - metadata.put(EntityData.SCALE, .55f); - metadata.getFlags().setFlag(EntityFlag.BABY, true); - } - } - super.updateBedrockMetadata(entityMetadata, session); + protected boolean isShaking() { + return convertingToDrowned || super.isShaking(); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombieVillagerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombieVillagerEntity.java new file mode 100644 index 000000000..54a5c4506 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombieVillagerEntity.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.monster; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.type.living.merchant.VillagerEntity; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +public class ZombieVillagerEntity extends ZombieEntity { + private boolean isTransforming; + + public ZombieVillagerEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + public void setTransforming(BooleanEntityMetadata entityMetadata) { + isTransforming = entityMetadata.getPrimitiveValue(); + setFlag(EntityFlag.IS_TRANSFORMING, isTransforming); + setFlag(EntityFlag.SHAKING, isShaking()); + } + + public void setZombieVillagerData(EntityMetadata entityMetadata) { + VillagerData villagerData = entityMetadata.getValue(); + dirtyMetadata.put(EntityData.VARIANT, VillagerEntity.VILLAGER_PROFESSIONS.get(villagerData.getProfession())); // Actually works properly with the OptionalPack + dirtyMetadata.put(EntityData.MARK_VARIANT, VillagerEntity.VILLAGER_REGIONS.get(villagerData.getType())); + // Used with the OptionalPack + dirtyMetadata.put(EntityData.TRADE_TIER, villagerData.getLevel() - 1); + } + + @Override + protected boolean isShaking() { + return isTransforming || super.isShaking(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombifiedPiglinEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombifiedPiglinEntity.java similarity index 69% rename from connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombifiedPiglinEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombifiedPiglinEntity.java index 01693170a..2604ce12e 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombifiedPiglinEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombifiedPiglinEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,17 +23,20 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.monster; +package org.geysermc.geyser.entity.type.living.monster; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; public class ZombifiedPiglinEntity extends ZombieEntity { - public ZombifiedPiglinEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public ZombifiedPiglinEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); - metadata.getFlags().setFlag(EntityFlag.FIRE_IMMUNE, true); + setFlag(EntityFlag.FIRE_IMMUNE, true); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/AbstractIllagerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/AbstractIllagerEntity.java similarity index 69% rename from connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/AbstractIllagerEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/AbstractIllagerEntity.java index b91871f07..15ac1a0d9 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/AbstractIllagerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/AbstractIllagerEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,14 +23,17 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.monster.raid; +package org.geysermc.geyser.entity.type.living.monster.raid; import com.nukkitx.math.vector.Vector3f; -import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; public class AbstractIllagerEntity extends RaidParticipantEntity { - public AbstractIllagerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + public AbstractIllagerEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/PillagerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/PillagerEntity.java new file mode 100644 index 000000000..477d9fef7 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/PillagerEntity.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.monster.raid; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.type.ItemMapping; + +import java.util.UUID; + +public class PillagerEntity extends AbstractIllagerEntity { + + public PillagerEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + @Override + public void updateMainHand(GeyserSession session) { //TODO + checkForCrossbow(); + + super.updateMainHand(session); + } + + @Override + public void updateOffHand(GeyserSession session) { + checkForCrossbow(); + + super.updateOffHand(session); + } + + /** + * Check for a crossbow in either the mainhand or offhand. If one exists, indicate that the pillager should be posing + */ + protected void checkForCrossbow() { + ItemMapping crossbow = session.getItemMappings().getStoredItems().crossbow(); + boolean hasCrossbow = this.hand.getId() == crossbow.getBedrockId() + || this.offHand.getId() == crossbow.getBedrockId(); + setFlag(EntityFlag.USING_ITEM, hasCrossbow); + setFlag(EntityFlag.CHARGED, hasCrossbow); + + updateBedrockMetadata(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/RaidParticipantEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/RaidParticipantEntity.java new file mode 100644 index 000000000..bd0f3ac5d --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/RaidParticipantEntity.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.monster.raid; + +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.type.living.monster.MonsterEntity; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +public class RaidParticipantEntity extends MonsterEntity { + + public RaidParticipantEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/SpellcasterIllagerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/SpellcasterIllagerEntity.java new file mode 100644 index 000000000..7c2a05de1 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/SpellcasterIllagerEntity.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.living.monster.raid; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +public class SpellcasterIllagerEntity extends AbstractIllagerEntity { + private static final int SUMMON_VEX_PARTICLE_COLOR = (179 << 16) | (179 << 8) | 204; + private static final int ATTACK_PARTICLE_COLOR = (102 << 16) | (77 << 8) | 89; + private static final int WOLOLO_PARTICLE_COLOR = (179 << 16) | (128 << 8) | 51; + + public SpellcasterIllagerEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + // OptionalPack usage + setFlag(EntityFlag.BRIBED, this.definition == EntityDefinitions.ILLUSIONER); + } + + public void setSpellType(ByteEntityMetadata entityMetadata) { + int spellType = entityMetadata.getPrimitiveValue(); + // Summon vex, attack, or wololo + setFlag(EntityFlag.CASTING, spellType == 1 || spellType == 2 || spellType == 3); + int rgbData = switch (spellType) { + // Set the spell color based on Java values + case 1 -> SUMMON_VEX_PARTICLE_COLOR; + case 2 -> ATTACK_PARTICLE_COLOR; + case 3 -> WOLOLO_PARTICLE_COLOR; + default -> 0; + }; + dirtyMetadata.put(EntityData.EVOKER_SPELL_COLOR, rgbData); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/VindicatorEntity.java similarity index 60% rename from connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java rename to core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/VindicatorEntity.java index 9b9701f8a..a3b85dc73 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/VindicatorEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,31 +23,27 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.animal; +package org.geysermc.geyser.entity.type.living.monster.raid; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; -public class PigEntity extends AnimalEntity { +import java.util.UUID; - public PigEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); +public class VindicatorEntity extends AbstractIllagerEntity { + + public VindicatorEntity(GeyserSession session, long entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); } @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - - if (entityMetadata.getId() == 16) { - metadata.getFlags().setFlag(EntityFlag.SADDLED, (boolean) entityMetadata.getValue()); - } - super.updateBedrockMetadata(entityMetadata, session); - } - - @Override - protected float getDefaultMaxHealth() { - return 10f; + public void setMobFlags(ByteEntityMetadata entityMetadata) { + super.setMobFlags(entityMetadata); + // Allow the axe to be shown if necessary + byte xd = entityMetadata.getPrimitiveValue(); + setFlag(EntityFlag.ANGRY, (xd & 4) == 4); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java new file mode 100644 index 000000000..1d59b83db --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -0,0 +1,429 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.player; + +import com.github.steveice10.mc.auth.data.GameProfile; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition; +import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.data.AttributeData; +import com.nukkitx.protocol.bedrock.data.PlayerPermission; +import com.nukkitx.protocol.bedrock.data.command.CommandPermission; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData; +import com.nukkitx.protocol.bedrock.packet.*; +import lombok.Getter; +import lombok.Setter; +import net.kyori.adventure.text.Component; +import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.type.LivingEntity; +import org.geysermc.geyser.entity.type.living.animal.tameable.ParrotEntity; +import org.geysermc.geyser.scoreboard.Objective; +import org.geysermc.geyser.scoreboard.Score; +import org.geysermc.geyser.scoreboard.Team; +import org.geysermc.geyser.scoreboard.UpdateType; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.text.MessageTranslator; + +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +@Getter @Setter +public class PlayerEntity extends LivingEntity { + private GameProfile profile; + private String username; + private boolean playerList = true; // Player is in the player list + + private Vector3i bedPosition; + + /** + * Saves the parrot currently on the player's left shoulder; otherwise null + */ + private ParrotEntity leftParrot; + /** + * Saves the parrot currently on the player's right shoulder; otherwise null + */ + private ParrotEntity rightParrot; + + public PlayerEntity(GeyserSession session, long entityId, long geyserId, GameProfile gameProfile, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, gameProfile.getId(), EntityDefinitions.PLAYER, position, motion, yaw, pitch, headYaw); + + profile = gameProfile; + username = gameProfile.getName(); + } + + @Override + protected void initializeMetadata() { + super.initializeMetadata(); + // For the OptionalPack, set all bits as invisible by default as this matches Java Edition behavior + dirtyMetadata.put(EntityData.MARK_VARIANT, 0xff); + } + + @Override + public void spawnEntity() { + // Check to see if the player should have a belowname counterpart added + Objective objective = session.getWorldCache().getScoreboard().getObjectiveSlots().get(ScoreboardPosition.BELOW_NAME); + if (objective != null) { + setBelowNameText(objective); + } + + // The name can't be updated later (the entity metadata for it is ignored), so we need to check for this now + updateDisplayName(null, false); + + AddPlayerPacket addPlayerPacket = new AddPlayerPacket(); + addPlayerPacket.setUuid(uuid); + addPlayerPacket.setUsername(username); + addPlayerPacket.setRuntimeEntityId(geyserId); + addPlayerPacket.setUniqueEntityId(geyserId); + addPlayerPacket.setPosition(position.sub(0, definition.offset(), 0)); + addPlayerPacket.setRotation(getBedrockRotation()); + addPlayerPacket.setMotion(motion); + addPlayerPacket.setHand(hand); + addPlayerPacket.getAdventureSettings().setCommandPermission(CommandPermission.NORMAL); + addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.MEMBER); + addPlayerPacket.setDeviceId(""); + addPlayerPacket.setPlatformChatId(""); + addPlayerPacket.getMetadata().putFlags(flags); + dirtyMetadata.apply(addPlayerPacket.getMetadata()); + + setFlagsDirty(false); + + long linkedEntityId = session.getEntityCache().getCachedPlayerEntityLink(entityId); + if (linkedEntityId != -1) { + Entity linkedEntity = session.getEntityCache().getEntityByJavaId(linkedEntityId); + if (linkedEntity != null) { + addPlayerPacket.getEntityLinks().add(new EntityLinkData(linkedEntity.getGeyserId(), geyserId, EntityLinkData.Type.RIDER, false, false)); + } + } + + valid = true; + session.sendUpstreamPacket(addPlayerPacket); + } + + public void sendPlayer() { + if (session.getEntityCache().getPlayerEntity(uuid) == null) + return; + + if (session.getEntityCache().getEntityByGeyserId(geyserId) == null) { + session.getEntityCache().spawnEntity(this); + } else { + spawnEntity(); + } + } + + @Override + public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { + setPosition(position); + setYaw(yaw); + setPitch(pitch); + setHeadYaw(headYaw); + + setOnGround(isOnGround); + + MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); + movePlayerPacket.setRuntimeEntityId(geyserId); + movePlayerPacket.setPosition(this.position); + movePlayerPacket.setRotation(getBedrockRotation()); + movePlayerPacket.setOnGround(isOnGround); + movePlayerPacket.setMode(teleported ? MovePlayerPacket.Mode.TELEPORT : MovePlayerPacket.Mode.NORMAL); + + if (teleported) { + movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.UNKNOWN); + } + + session.sendUpstreamPacket(movePlayerPacket); + if (leftParrot != null) { + leftParrot.moveAbsolute(position, yaw, pitch, headYaw, true, teleported); + } + if (rightParrot != null) { + rightParrot.moveAbsolute(position, yaw, pitch, headYaw, true, teleported); + } + } + + @Override + public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) { + setYaw(yaw); + setPitch(pitch); + setHeadYaw(headYaw); + this.position = Vector3f.from(position.getX() + relX, position.getY() + relY, position.getZ() + relZ); + + setOnGround(isOnGround); + + MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); + movePlayerPacket.setRuntimeEntityId(geyserId); + movePlayerPacket.setPosition(position); + movePlayerPacket.setRotation(getBedrockRotation()); + movePlayerPacket.setOnGround(isOnGround); + movePlayerPacket.setMode(MovePlayerPacket.Mode.NORMAL); + // If the player is moved while sleeping, we have to adjust their y, so it appears + // correctly on Bedrock. This fixes GSit's lay. + if (getFlag(EntityFlag.SLEEPING)) { + if (bedPosition != null && (bedPosition.getY() == 0 || bedPosition.distanceSquared(position.toInt()) > 4)) { + // Force the player movement by using a teleport + movePlayerPacket.setPosition(Vector3f.from(position.getX(), position.getY() - definition.offset() + 0.2f, position.getZ())); + movePlayerPacket.setMode(MovePlayerPacket.Mode.TELEPORT); + movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.UNKNOWN); + } + } + session.sendUpstreamPacket(movePlayerPacket); + if (leftParrot != null) { + leftParrot.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, true); + } + if (rightParrot != null) { + rightParrot.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, true); + } + } + + @Override + public void updateHeadLookRotation(float headYaw) { + moveRelative(0, 0, 0, yaw, pitch, headYaw, onGround); + MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); + movePlayerPacket.setRuntimeEntityId(geyserId); + movePlayerPacket.setPosition(position); + movePlayerPacket.setRotation(getBedrockRotation()); + movePlayerPacket.setMode(MovePlayerPacket.Mode.HEAD_ROTATION); + session.sendUpstreamPacket(movePlayerPacket); + } + + @Override + public void updatePositionAndRotation(double moveX, double moveY, double moveZ, float yaw, float pitch, boolean isOnGround) { + moveRelative(moveX, moveY, moveZ, yaw, pitch, isOnGround); + if (leftParrot != null) { + leftParrot.moveRelative(moveX, moveY, moveZ, yaw, pitch, isOnGround); + } + if (rightParrot != null) { + rightParrot.moveRelative(moveX, moveY, moveZ, yaw, pitch, isOnGround); + } + } + + @Override + public void updateRotation(float yaw, float pitch, boolean isOnGround) { + super.updateRotation(yaw, pitch, isOnGround); + // Both packets need to be sent or else player head rotation isn't correctly updated + MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); + movePlayerPacket.setRuntimeEntityId(geyserId); + movePlayerPacket.setPosition(position); + movePlayerPacket.setRotation(getBedrockRotation()); + movePlayerPacket.setOnGround(isOnGround); + movePlayerPacket.setMode(MovePlayerPacket.Mode.HEAD_ROTATION); + session.sendUpstreamPacket(movePlayerPacket); + if (leftParrot != null) { + leftParrot.updateRotation(yaw, pitch, isOnGround); + } + if (rightParrot != null) { + rightParrot.updateRotation(yaw, pitch, isOnGround); + } + } + + @Override + public void setPosition(Vector3f position) { + super.setPosition(position.add(0, definition.offset(), 0)); + } + + @Override + public Vector3i setBedPosition(EntityMetadata, ?> entityMetadata) { + return bedPosition = super.setBedPosition(entityMetadata); + } + + public void setAbsorptionHearts(FloatEntityMetadata entityMetadata) { + // Extra hearts - is not metadata but an attribute on Bedrock + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); + attributesPacket.setRuntimeEntityId(geyserId); + // Setting to a higher maximum since plugins/datapacks can probably extend the Bedrock soft limit + attributesPacket.setAttributes(Collections.singletonList( + new AttributeData("minecraft:absorption", 0.0f, 1024f, entityMetadata.getPrimitiveValue(), 0.0f))); + session.sendUpstreamPacket(attributesPacket); + } + + public void setSkinVisibility(ByteEntityMetadata entityMetadata) { + // OptionalPack usage for toggling skin bits + // In Java Edition, a bit being set means that part should be enabled + // However, to ensure that the pack still works on other servers, we invert the bit so all values by default + // are true (0). + dirtyMetadata.put(EntityData.MARK_VARIANT, ~entityMetadata.getPrimitiveValue() & 0xff); + } + + public void setLeftParrot(EntityMetadata entityMetadata) { + setParrot(entityMetadata.getValue(), true); + } + + public void setRightParrot(EntityMetadata entityMetadata) { + setParrot(entityMetadata.getValue(), false); + } + + /** + * Sets the parrot occupying the shoulder. Bedrock Edition requires a full entity whereas Java Edition just + * spawns it from the NBT data provided + */ + private void setParrot(CompoundTag tag, boolean isLeft) { + if (tag != null && !tag.isEmpty()) { + if ((isLeft && leftParrot != null) || (!isLeft && rightParrot != null)) { + // No need to update a parrot's data when it already exists + return; + } + // The parrot is a separate entity in Bedrock, but part of the player entity in Java //TODO is a UUID provided in NBT? + ParrotEntity parrot = new ParrotEntity(session, 0, session.getEntityCache().getNextEntityId().incrementAndGet(), + null, EntityDefinitions.PARROT, position, motion, yaw, pitch, headYaw); + parrot.spawnEntity(); + parrot.getDirtyMetadata().put(EntityData.VARIANT, tag.get("Variant").getValue()); + // Different position whether the parrot is left or right + float offset = isLeft ? 0.4f : -0.4f; + parrot.getDirtyMetadata().put(EntityData.RIDER_SEAT_POSITION, Vector3f.from(offset, -0.22, -0.1)); + parrot.getDirtyMetadata().put(EntityData.RIDER_ROTATION_LOCKED, 1); + parrot.updateBedrockMetadata(); + SetEntityLinkPacket linkPacket = new SetEntityLinkPacket(); + EntityLinkData.Type type = isLeft ? EntityLinkData.Type.RIDER : EntityLinkData.Type.PASSENGER; + linkPacket.setEntityLink(new EntityLinkData(geyserId, parrot.getGeyserId(), type, false, false)); + // Delay, or else spawned-in players won't get the link + // TODO: Find a better solution. + session.scheduleInEventLoop(() -> session.sendUpstreamPacket(linkPacket), 500, TimeUnit.MILLISECONDS); + if (isLeft) { + leftParrot = parrot; + } else { + rightParrot = parrot; + } + } else { + Entity parrot = isLeft ? leftParrot : rightParrot; + if (parrot != null) { + parrot.despawnEntity(); + if (isLeft) { + leftParrot = null; + } else { + rightParrot = null; + } + } + } + } + + @Override + public void setDisplayName(EntityMetadata, ?> entityMetadata) { + // Doesn't do anything for players + } + + //todo this will become common entity logic once UUID support is implemented for them + /** + * @param useGivenTeam even if there is no team, update the username in the entity metadata anyway, and don't look for a team + */ + public void updateDisplayName(@Nullable Team team, boolean useGivenTeam) { + if (team == null && !useGivenTeam) { + // Only search for the team if we are not supposed to use the given team + // If the given team is null, this is intentional that we are being removed from the team + team = session.getWorldCache().getScoreboard().getTeamFor(username); + } + + boolean needsUpdate; + String newDisplayName = this.username; + if (team != null) { + if (team.isVisibleFor(session.getPlayerEntity().getUsername())) { + TeamColor color = team.getColor(); + String chatColor = MessageTranslator.toChatColor(color); + // We have to emulate what modern Java text already does for us and add the color to each section + String prefix = team.getCurrentData().getPrefix(); + String suffix = team.getCurrentData().getSuffix(); + newDisplayName = chatColor + prefix + chatColor + this.username + chatColor + suffix; + } else { + // The name is not visible to the session player; clear name + newDisplayName = ""; + } + needsUpdate = useGivenTeam && !newDisplayName.equals(nametag); + nametag = newDisplayName; + dirtyMetadata.put(EntityData.NAMETAG, newDisplayName); + } else if (useGivenTeam) { + // The name has reset, if it was previously something else + needsUpdate = !newDisplayName.equals(nametag); + dirtyMetadata.put(EntityData.NAMETAG, this.username); + } else { + needsUpdate = false; + } + + if (needsUpdate) { + // Update the metadata as it won't be updated later + SetEntityDataPacket packet = new SetEntityDataPacket(); + packet.getMetadata().put(EntityData.NAMETAG, newDisplayName); + packet.setRuntimeEntityId(geyserId); + session.sendUpstreamPacket(packet); + } + } + + @Override + public void setDisplayNameVisible(BooleanEntityMetadata entityMetadata) { + // Doesn't do anything for players + } + + @Override + protected void setDimensions(Pose pose) { + float height; + switch (pose) { + case SNEAKING -> height = 1.5f; + case FALL_FLYING, SPIN_ATTACK, SWIMMING -> height = 0.6f; + default -> { + super.setDimensions(pose); + return; + } + } + setBoundingBoxWidth(definition.width()); + setBoundingBoxHeight(height); + } + + public void setBelowNameText(Objective objective) { + if (objective != null && objective.getUpdateType() != UpdateType.REMOVE) { + int amount; + Score score = objective.getScores().get(username); + if (score != null) { + amount = score.getCurrentData().getScore(); + } else { + amount = 0; + } + String displayString = amount + " " + objective.getDisplayName(); + + if (valid) { + // Already spawned - we still need to run the rest of this code because the spawn packet will be + // providing the information + SetEntityDataPacket packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(geyserId); + packet.getMetadata().put(EntityData.SCORE_TAG, displayString); + session.sendUpstreamPacket(packet); + } + } else if (valid) { + SetEntityDataPacket packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(geyserId); + packet.getMetadata().put(EntityData.SCORE_TAG, ""); + session.sendUpstreamPacket(packet); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java new file mode 100644 index 000000000..dff9fbfa2 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.player; + +import com.github.steveice10.mc.auth.data.GameProfile; +import com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute; +import com.github.steveice10.mc.protocol.data.game.entity.attribute.AttributeType; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.AttributeData; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import lombok.Getter; +import org.geysermc.geyser.entity.attribute.GeyserAttributeType; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.AttributeUtils; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * The entity class specifically for a {@link GeyserSession}'s player. + */ +public class SessionPlayerEntity extends PlayerEntity { + /** + * Used to fix some inconsistencies, especially in respawning. + */ + @Getter + protected final Map attributes = new Object2ObjectOpenHashMap<>(); + /** + * Whether to check for updated speed after all entity metadata has been processed + */ + private boolean refreshSpeed = false; + /** + * Used in PlayerInputTranslator for movement checks. + */ + @Getter + private boolean isRidingInFront; + /** + * Used for villager inventory emulation. + */ + private int fakeTradeXp; + + private final GeyserSession session; + + public SessionPlayerEntity(GeyserSession session) { + super(session, 1, 1, new GameProfile(UUID.randomUUID(), "unknown"), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0); + + valid = true; + this.session = session; + } + + @Override + public void spawnEntity() { + // Already logged in + } + + @Override + public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) { + super.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, isOnGround); + session.getCollisionManager().updatePlayerBoundingBox(this.position.down(definition.offset())); + } + + @Override + public void setPosition(Vector3f position) { + if (session != null) { // null during entity initialization + session.getCollisionManager().updatePlayerBoundingBox(position); + } + super.setPosition(position); + } + + /** + * Set the player's position without applying an offset or moving the bounding box + * This is used in BedrockMovePlayerTranslator which receives the player's position + * with the offset pre-applied + * + * @param position the new position of the Bedrock player + */ + public void setPositionManual(Vector3f position) { + this.position = position; + } + + @Override + public void setFlags(ByteEntityMetadata entityMetadata) { + super.setFlags(entityMetadata); + session.setSwimmingInWater((entityMetadata.getPrimitiveValue() & 0x10) == 0x10 && getFlag(EntityFlag.SPRINTING)); + refreshSpeed = true; + } + + @Override + public void setPose(EntityMetadata entityMetadata) { + super.setPose(entityMetadata); + session.setPose(entityMetadata.getValue()); + refreshSpeed = true; + } + + public float getMaxHealth() { + return maxHealth; + } + + public void setHealth(float health) { + this.health = health; + } + + @Override + protected void setAirSupply(int amount) { + if (amount == getMaxAir()) { + super.setAirSupply(0); // Hide the bubble counter from the UI for the player + } else { + super.setAirSupply(amount); + } + } + + @Override + public void setRiderSeatPosition(Vector3f position) { + super.setRiderSeatPosition(position); + this.isRidingInFront = position != null && position.getX() > 0; + } + + public void addFakeTradeExperience(int tradeXp) { + fakeTradeXp += tradeXp; + dirtyMetadata.put(EntityData.TRADE_XP, fakeTradeXp); + } + + @Override + public AttributeData createHealthAttribute() { + // Max health must be divisible by two in bedrock + if ((maxHealth % 2) == 1) { + maxHealth += 1; + } + return super.createHealthAttribute(); + } + + @Override + public void updateBedrockMetadata() { + super.updateBedrockMetadata(); + if (refreshSpeed) { + AttributeData speedAttribute = session.adjustSpeed(); + if (speedAttribute != null) { + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); + attributesPacket.setRuntimeEntityId(geyserId); + attributesPacket.setAttributes(Collections.singletonList(speedAttribute)); + session.sendUpstreamPacket(attributesPacket); + } + refreshSpeed = false; + } + } + + @Override + protected void updateAttribute(Attribute javaAttribute, List newAttributes) { + if (javaAttribute.getType() == AttributeType.Builtin.GENERIC_ATTACK_SPEED) { + session.setAttackSpeed(AttributeUtils.calculateValue(javaAttribute)); + } else { + super.updateAttribute(javaAttribute, newAttributes); + } + } + + @Override + protected AttributeData calculateAttribute(Attribute javaAttribute, GeyserAttributeType type) { + AttributeData attributeData = super.calculateAttribute(javaAttribute, type); + + if (javaAttribute.getType() == AttributeType.Builtin.GENERIC_MOVEMENT_SPEED) { + session.setOriginalSpeedAttribute(attributeData.getValue()); + AttributeData speedAttribute = session.adjustSpeed(); + if (speedAttribute != null) { + // Overwrite the attribute with our own + this.attributes.put(type, speedAttribute); + return speedAttribute; + } + } + + this.attributes.put(type, attributeData); + return attributeData; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java new file mode 100644 index 000000000..0afb3e8b5 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.type.player; + +import com.github.steveice10.mc.auth.data.GameProfile; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.data.PlayerPermission; +import com.nukkitx.protocol.bedrock.data.command.CommandPermission; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket; +import lombok.Getter; +import org.geysermc.geyser.session.GeyserSession; + +/** + * A wrapper to handle skulls more effectively - skulls have to be treated as entities since there are no + * custom player skulls in Bedrock. + */ +public class SkullPlayerEntity extends PlayerEntity { + /** + * Stores the block state that the skull is associated with. Used to determine if the block in the skull's position + * has changed + */ + @Getter + private final int blockState; + + public SkullPlayerEntity(GeyserSession session, long geyserId, GameProfile gameProfile, Vector3f position, float rotation, int blockState) { + super(session, 0, geyserId, gameProfile, position, Vector3f.ZERO, rotation, 0, rotation); + this.blockState = blockState; + setPlayerList(false); + } + + @Override + protected void initializeMetadata() { + // Deliberately do not call super + // Set bounding box to almost nothing so the skull is able to be broken and not cause entity to cast a shadow + dirtyMetadata.put(EntityData.SCALE, 1.08f); + dirtyMetadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.001f); + dirtyMetadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.001f); + setFlag(EntityFlag.CAN_SHOW_NAME, false); + setFlag(EntityFlag.INVISIBLE, true); // Until the skin is loaded + } + + /** + * Overwritten so each entity doesn't check for a linked entity + */ + @Override + public void spawnEntity() { + AddPlayerPacket addPlayerPacket = new AddPlayerPacket(); + addPlayerPacket.setUuid(getUuid()); + addPlayerPacket.setUsername(getUsername()); + addPlayerPacket.setRuntimeEntityId(geyserId); + addPlayerPacket.setUniqueEntityId(geyserId); + addPlayerPacket.setPosition(position.sub(0, definition.offset(), 0)); + addPlayerPacket.setRotation(getBedrockRotation()); + addPlayerPacket.setMotion(motion); + addPlayerPacket.setHand(hand); + addPlayerPacket.getAdventureSettings().setCommandPermission(CommandPermission.NORMAL); + addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.MEMBER); + addPlayerPacket.setDeviceId(""); + addPlayerPacket.setPlatformChatId(""); + addPlayerPacket.getMetadata().putFlags(flags); + dirtyMetadata.apply(addPlayerPacket.getMetadata()); + + setFlagsDirty(false); + + valid = true; + session.sendUpstreamPacket(addPlayerPacket); + } + + public void despawnEntity(Vector3i position) { + this.despawnEntity(); + session.getSkullCache().remove(position, this); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java new file mode 100644 index 000000000..343cff4ef --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.inventory; + +import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import lombok.Getter; +import lombok.Setter; + +/** + * Used to determine if rename packets should be sent and stores + * the expected level cost for AnvilInventoryUpdater + */ +@Getter @Setter +public class AnvilContainer extends Container { + /** + * Stores the level cost received as a window property from Java + */ + private int javaLevelCost = 0; + /** + * A flag to specify whether javaLevelCost can be used as it can + * be outdated or not sent at all. + */ + private boolean useJavaLevelCost = false; + + /** + * The new name of the item as received from Bedrock + */ + private String newName = null; + + private GeyserItemStack lastInput = GeyserItemStack.EMPTY; + private GeyserItemStack lastMaterial = GeyserItemStack.EMPTY; + + private int lastTargetSlot = -1; + + public AnvilContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) { + super(title, id, size, containerType, playerInventory); + } + + public GeyserItemStack getInput() { + return getItem(0); + } + + public GeyserItemStack getMaterial() { + return getItem(1); + } + + public GeyserItemStack getResult() { + return getItem(2); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/attribute/Attribute.java b/core/src/main/java/org/geysermc/geyser/inventory/BeaconContainer.java similarity index 72% rename from connector/src/main/java/org/geysermc/connector/entity/attribute/Attribute.java rename to core/src/main/java/org/geysermc/geyser/inventory/BeaconContainer.java index 71d25a36e..5e9282d7f 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/attribute/Attribute.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/BeaconContainer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,20 +23,19 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.attribute; +package org.geysermc.geyser.inventory; -import lombok.AllArgsConstructor; +import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; import lombok.Getter; import lombok.Setter; @Getter @Setter -@AllArgsConstructor -public class Attribute { +public class BeaconContainer extends Container { + private int primaryId; + private int secondaryId; - private AttributeType type; - private float minimum; - private float maximum; - private float value; - private float defaultValue; + public BeaconContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) { + super(title, id, size, containerType, playerInventory); + } } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/BedrockContainerSlot.java b/core/src/main/java/org/geysermc/geyser/inventory/BedrockContainerSlot.java new file mode 100644 index 000000000..0179bb277 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/BedrockContainerSlot.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.inventory; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import lombok.Value; + +@Value +public class BedrockContainerSlot { + ContainerSlotType container; + int slot; +} diff --git a/core/src/main/java/org/geysermc/geyser/inventory/CartographyContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/CartographyContainer.java new file mode 100644 index 000000000..de7a861d5 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/CartographyContainer.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.inventory; + +import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; + +public class CartographyContainer extends Container { + public CartographyContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) { + super(title, id, size, containerType, playerInventory); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/inventory/Container.java b/core/src/main/java/org/geysermc/geyser/inventory/Container.java new file mode 100644 index 000000000..464d683fa --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/Container.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.inventory; + +import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import lombok.Getter; +import lombok.NonNull; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.InventoryTranslator; + +/** + * Combination of {@link Inventory} and {@link PlayerInventory} + */ +@Getter +public class Container extends Inventory { + private final PlayerInventory playerInventory; + private final int containerSize; + + /** + * Whether we are using a real block when opening this inventory. + */ + private boolean isUsingRealBlock = false; + + public Container(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) { + super(title, id, size, containerType); + this.playerInventory = playerInventory; + this.containerSize = this.size + InventoryTranslator.PLAYER_INVENTORY_SIZE; + } + + @Override + public GeyserItemStack getItem(int slot) { + if (slot < this.size) { + return super.getItem(slot); + } else { + return playerInventory.getItem(slot - this.size + InventoryTranslator.PLAYER_INVENTORY_OFFSET); + } + } + + @Override + public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) { + if (slot < this.size) { + super.setItem(slot, newItem, session); + } else { + playerInventory.setItem(slot - this.size + InventoryTranslator.PLAYER_INVENTORY_OFFSET, newItem, session); + } + } + + @Override + public int getSize() { + return this.containerSize; + } + + /** + * Will be overwritten for droppers. + * + * @param usingRealBlock whether this container is using a real container or not + * @param javaBlockId the Java block string of the block, if real + */ + public void setUsingRealBlock(boolean usingRealBlock, String javaBlockId) { + isUsingRealBlock = usingRealBlock; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/inventory/EnchantingContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/EnchantingContainer.java new file mode 100644 index 000000000..b16a3a07c --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/EnchantingContainer.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.inventory; + +import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.EnchantOptionData; +import lombok.Getter; + +public class EnchantingContainer extends Container { + /** + * A cache of what Bedrock sees + */ + @Getter + private final EnchantOptionData[] enchantOptions; + /** + * A mutable cache of what the server sends us + */ + @Getter + private final GeyserEnchantOption[] geyserEnchantOptions; + + public EnchantingContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) { + super(title, id, size, containerType, playerInventory); + + enchantOptions = new EnchantOptionData[3]; + geyserEnchantOptions = new GeyserEnchantOption[3]; + for (int i = 0; i < geyserEnchantOptions.length; i++) { + geyserEnchantOptions[i] = new GeyserEnchantOption(i); + // Options cannot be null, so we build initial options + // GeyserConnection can be safely null here because it's only needed for net IDs + enchantOptions[i] = geyserEnchantOptions[i].build(null); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/inventory/Generic3X3Container.java b/core/src/main/java/org/geysermc/geyser/inventory/Generic3X3Container.java new file mode 100644 index 000000000..fc226f621 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/Generic3X3Container.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.inventory; + +import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import lombok.Getter; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.Generic3X3InventoryTranslator; + +public class Generic3X3Container extends Container { + /** + * Whether we need to set the container type as {@link com.nukkitx.protocol.bedrock.data.inventory.ContainerType#DROPPER}. + * + * Used at {@link Generic3X3InventoryTranslator#openInventory(GeyserSession, Inventory)} + */ + @Getter + private boolean isDropper = false; + + public Generic3X3Container(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) { + super(title, id, size, containerType, playerInventory); + } + + @Override + public void setUsingRealBlock(boolean usingRealBlock, String javaBlockId) { + super.setUsingRealBlock(usingRealBlock, javaBlockId); + if (usingRealBlock) { + isDropper = javaBlockId.startsWith("minecraft:dropper"); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/inventory/GeyserEnchantOption.java b/core/src/main/java/org/geysermc/geyser/inventory/GeyserEnchantOption.java new file mode 100644 index 000000000..8ecb3fe8d --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/GeyserEnchantOption.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.inventory; + +import com.nukkitx.protocol.bedrock.data.inventory.EnchantData; +import com.nukkitx.protocol.bedrock.data.inventory.EnchantOptionData; +import lombok.Getter; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * A mutable "wrapper" around {@link EnchantOptionData} + */ +public class GeyserEnchantOption { + private static final List EMPTY = Collections.emptyList(); + /** + * This: https://cdn.discordapp.com/attachments/613168850925649981/791030657169227816/unknown.png + * is controlled by the server. + * So, of course, we have to throw in some easter eggs. ;) + */ + private static final List ENCHANT_NAMES = Arrays.asList("tougher armor", "lukeeey", "fall better", + "explode less", "camo toy", "breathe better", "rtm five one six", "armor stab", "water walk", "you are elsa", + "tim two zero three", "fast walk nether", "oof ouch owie", "enemy on fire", "spider sad", "aj ferguson", "redned", + "more items thx", "long sword reach", "fast tool", "give me block", "less breaky break", "cube craft", + "strong arrow", "fist arrow", "spicy arrow", "many many arrows", "geyser", "come here fish", "i like this", + "stabby stab", "supreme mortal", "avatar i guess", "more arrows", "fly finder seventeen", "in and out", + "xp heals tools", "dragon proxy waz here"); + + @Getter + private final int javaIndex; + + /** + * Whether the enchantment details have actually changed. + * Used to mitigate weird packet spamming pre-1.14, causing the net ID to always update. + */ + private boolean hasChanged; + + private int xpCost = 0; + private int javaEnchantIndex = -1; + private int bedrockEnchantIndex = -1; + private int enchantLevel = -1; + + public GeyserEnchantOption(int javaIndex) { + this.javaIndex = javaIndex; + } + + public EnchantOptionData build(GeyserSession session) { + this.hasChanged = false; + return new EnchantOptionData(xpCost, javaIndex + 16, EMPTY, + enchantLevel == -1 ? EMPTY : Collections.singletonList(new EnchantData(bedrockEnchantIndex, enchantLevel)), EMPTY, + javaEnchantIndex == -1 ? "unknown" : ENCHANT_NAMES.get(javaEnchantIndex), enchantLevel == -1 ? 0 : session.getNextItemNetId()); + } + + public boolean hasChanged() { + return hasChanged; + } + + public void setXpCost(int xpCost) { + if (this.xpCost != xpCost) { + hasChanged = true; + this.xpCost = xpCost; + } + } + + public void setEnchantIndex(int javaEnchantIndex, int bedrockEnchantIndex) { + if (this.javaEnchantIndex != javaEnchantIndex) { + hasChanged = true; + this.javaEnchantIndex = javaEnchantIndex; + this.bedrockEnchantIndex = bedrockEnchantIndex; + } + } + + public void setEnchantLevel(int enchantLevel) { + if (this.enchantLevel != enchantLevel) { + hasChanged = true; + this.enchantLevel = enchantLevel; + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/inventory/GeyserItemStack.java b/core/src/main/java/org/geysermc/geyser/inventory/GeyserItemStack.java new file mode 100644 index 000000000..a75631db0 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/GeyserItemStack.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.inventory; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import lombok.Data; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.ItemTranslator; +import org.geysermc.geyser.registry.type.ItemMapping; + +import javax.annotation.Nonnull; + +@Data +public class GeyserItemStack { + public static final GeyserItemStack EMPTY = new GeyserItemStack(0, 0, null); + + private final int javaId; + private int amount; + private CompoundTag nbt; + private int netId; + + private GeyserItemStack(int javaId, int amount, CompoundTag nbt) { + this(javaId, amount, nbt, 1); + } + + private GeyserItemStack(int javaId, int amount, CompoundTag nbt, int netId) { + this.javaId = javaId; + this.amount = amount; + this.nbt = nbt; + this.netId = netId; + } + + public static @Nonnull GeyserItemStack from(ItemStack itemStack) { + return itemStack == null ? EMPTY : new GeyserItemStack(itemStack.getId(), itemStack.getAmount(), itemStack.getNbt()); + } + + public int getJavaId() { + return isEmpty() ? 0 : javaId; + } + + public int getAmount() { + return isEmpty() ? 0 : amount; + } + + public CompoundTag getNbt() { + return isEmpty() ? null : nbt; + } + + public int getNetId() { + return isEmpty() ? 0 : netId; + } + + public void add(int add) { + amount += add; + } + + public void sub(int sub) { + amount -= sub; + } + + public ItemStack getItemStack() { + return getItemStack(amount); + } + + public ItemStack getItemStack(int newAmount) { + return isEmpty() ? null : new ItemStack(javaId, newAmount, nbt); + } + + public ItemData getItemData(GeyserSession session) { + ItemData itemData = ItemTranslator.translateToBedrock(session, getItemStack()); + itemData.setNetId(getNetId()); + itemData.setUsingNetId(true); // Seems silly - this should probably be on the protocol level + return itemData; + } + + public ItemMapping getMapping(GeyserSession session) { + return session.getItemMappings().getMapping(this.javaId); + } + + public boolean isEmpty() { + return amount <= 0 || javaId == 0; + } + + public GeyserItemStack copy() { + return copy(amount); + } + + public GeyserItemStack copy(int newAmount) { + return isEmpty() ? EMPTY : new GeyserItemStack(javaId, newAmount, nbt == null ? null : nbt.clone(), netId); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java b/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java new file mode 100644 index 000000000..6eaaf84a2 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.inventory; + +import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import com.github.steveice10.opennbt.tag.builtin.ByteTag; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.nukkitx.math.vector.Vector3i; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.Arrays; + +public class Inventory { + + @Getter + protected final int id; + + /** + * If this is out of sync with the server, the server will resync items. + * Since Java Edition 1.17.1. + */ + @Getter + @Setter + private int stateId; + + @Getter + protected final int size; + + /** + * Used for smooth transitions between two windows of the same type. + */ + @Getter + protected final ContainerType containerType; + + @Getter + @Setter + protected String title; + + protected final GeyserItemStack[] items; + + /** + * The location of the inventory block. Will either be a fake block above the player's head, or the actual block location + */ + @Getter + @Setter + protected Vector3i holderPosition = Vector3i.ZERO; + + @Getter + @Setter + protected long holderId = -1; + + @Getter + @Setter + private boolean pending = false; + + protected Inventory(int id, int size, ContainerType containerType) { + this("Inventory", id, size, containerType); + } + + protected Inventory(String title, int id, int size, ContainerType containerType) { + this.title = title; + this.id = id; + this.size = size; + this.containerType = containerType; + this.items = new GeyserItemStack[size]; + Arrays.fill(items, GeyserItemStack.EMPTY); + } + + public GeyserItemStack getItem(int slot) { + if (slot > this.size) { + GeyserImpl.getInstance().getLogger().debug("Tried to get an item out of bounds! " + this); + return GeyserItemStack.EMPTY; + } + return items[slot]; + } + + public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) { + if (slot > this.size) { + session.getGeyser().getLogger().debug("Tried to set an item out of bounds! " + this); + return; + } + GeyserItemStack oldItem = items[slot]; + updateItemNetId(oldItem, newItem, session); + items[slot] = newItem; + + // Lodestone caching + if (newItem.getJavaId() == session.getItemMappings().getStoredItems().compass().getJavaId()) { + CompoundTag nbt = newItem.getNbt(); + if (nbt != null) { + Tag lodestoneTag = nbt.get("LodestoneTracked"); + if (lodestoneTag instanceof ByteTag) { + session.getLodestoneCache().cacheInventoryItem(newItem); + } + } + } + } + + protected static void updateItemNetId(GeyserItemStack oldItem, GeyserItemStack newItem, GeyserSession session) { + if (!newItem.isEmpty()) { + if (newItem.getItemData(session).equals(oldItem.getItemData(session), false, false, false)) { + newItem.setNetId(oldItem.getNetId()); + } else { + newItem.setNetId(session.getNextItemNetId()); + } + } + } + + @Override + public String toString() { + return "Inventory{" + + "id=" + id + + ", size=" + size + + ", title='" + title + '\'' + + ", items=" + Arrays.toString(items) + + ", holderPosition=" + holderPosition + + ", holderId=" + holderId + + '}'; + } +} diff --git a/common/src/main/java/org/geysermc/common/window/button/FormButton.java b/core/src/main/java/org/geysermc/geyser/inventory/LecternContainer.java similarity index 65% rename from common/src/main/java/org/geysermc/common/window/button/FormButton.java rename to core/src/main/java/org/geysermc/geyser/inventory/LecternContainer.java index 6daa2feae..74d853ab6 100644 --- a/common/src/main/java/org/geysermc/common/window/button/FormButton.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/LecternContainer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,35 +23,23 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.button; +package org.geysermc.geyser.inventory; +import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.NbtMap; import lombok.Getter; import lombok.Setter; -public class FormButton { +public class LecternContainer extends Container { + @Getter @Setter + private int currentBedrockPage = 0; + @Getter @Setter + private NbtMap blockEntityTag; + @Getter @Setter + private Vector3i position; - @Getter - @Setter - private String text; - - @Getter - private FormImage image; - - public FormButton(String text) { - this.text = text; - } - - public FormButton(String text, FormImage image) { - this.text = text; - - if (image.getData() != null && !image.getData().isEmpty()) { - this.image = image; - } - } - - public void setImage(FormImage image) { - if (image.getData() != null && !image.getData().isEmpty()) { - this.image = image; - } + public LecternContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) { + super(title, id, size, containerType, playerInventory); } } diff --git a/common/src/main/java/org/geysermc/common/window/FormWindow.java b/core/src/main/java/org/geysermc/geyser/inventory/MerchantContainer.java similarity index 60% rename from common/src/main/java/org/geysermc/common/window/FormWindow.java rename to core/src/main/java/org/geysermc/geyser/inventory/MerchantContainer.java index c3cc4258b..fecb79580 100644 --- a/common/src/main/java/org/geysermc/common/window/FormWindow.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/MerchantContainer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,37 +23,23 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window; +package org.geysermc.geyser.inventory; -import com.fasterxml.jackson.annotation.JsonIgnore; +import com.github.steveice10.mc.protocol.data.game.inventory.VillagerTrade; +import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.ClientboundMerchantOffersPacket; import lombok.Getter; import lombok.Setter; -import org.geysermc.common.window.response.FormResponse; +import org.geysermc.geyser.entity.type.Entity; -public abstract class FormWindow { +@Getter +@Setter +public class MerchantContainer extends Container { + private Entity villager; + private VillagerTrade[] villagerTrades; + private ClientboundMerchantOffersPacket pendingOffersPacket; - @Getter - private final String type; - - @Getter - protected FormResponse response; - - @Getter - @Setter - protected boolean closed; - - public FormWindow(String type) { - this.type = type; + public MerchantContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) { + super(title, id, size, containerType, playerInventory); } - - // Lombok won't work here, so we need to make our own method - public void setResponse(FormResponse response) { - this.response = response; - } - - @JsonIgnore - public abstract String getJSONData(); - - public abstract void setResponse(String response); - } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java b/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java new file mode 100644 index 000000000..36114ccba --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.inventory; + +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.session.GeyserSession; + +public class PlayerInventory extends Inventory { + + /** + * Stores the held item slot, starting at index 0. + * Add 36 in order to get the network item slot. + */ + @Getter + @Setter + private int heldItemSlot; + + @Getter + @NonNull + private GeyserItemStack cursor = GeyserItemStack.EMPTY; + + public PlayerInventory() { + super(0, 46, null); + heldItemSlot = 0; + } + + public void setCursor(@NonNull GeyserItemStack newCursor, GeyserSession session) { + updateItemNetId(cursor, newCursor, session); + cursor = newCursor; + } + + public GeyserItemStack getItemInHand() { + if (36 + heldItemSlot > this.size) { + GeyserImpl.getInstance().getLogger().debug("Held item slot was larger than expected!"); + return GeyserItemStack.EMPTY; + } + return items[36 + heldItemSlot]; + } + + public void setItemInHand(@NonNull GeyserItemStack item) { + if (36 + heldItemSlot > this.size) { + GeyserImpl.getInstance().getLogger().debug("Held item slot was larger than expected!"); + return; + } + items[36 + heldItemSlot] = item; + } + + public GeyserItemStack getOffhand() { + return items[45]; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SlotType.java b/core/src/main/java/org/geysermc/geyser/inventory/SlotType.java similarity index 91% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/SlotType.java rename to core/src/main/java/org/geysermc/geyser/inventory/SlotType.java index 045adbd32..17863ba66 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SlotType.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/SlotType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.inventory; +package org.geysermc.geyser.inventory; public enum SlotType { NORMAL, diff --git a/core/src/main/java/org/geysermc/geyser/inventory/StonecutterContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/StonecutterContainer.java new file mode 100644 index 000000000..3dc35f5df --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/StonecutterContainer.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.inventory; + +import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import org.geysermc.geyser.session.GeyserSession; + +public class StonecutterContainer extends Container { + /** + * The button that has currently been pressed Java-side + */ + @Getter + @Setter + private int stonecutterButton = -1; + + public StonecutterContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) { + super(title, id, size, containerType, playerInventory); + } + + @Override + public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) { + if (slot == 0 && newItem.getJavaId() != items[slot].getJavaId()) { + // The pressed stonecutter button output resets whenever the input item changes + this.stonecutterButton = -1; + } + super.setItem(slot, newItem, session); + } +} diff --git a/common/src/main/java/org/geysermc/common/window/response/CustomFormResponse.java b/core/src/main/java/org/geysermc/geyser/inventory/click/Click.java similarity index 53% rename from common/src/main/java/org/geysermc/common/window/response/CustomFormResponse.java rename to core/src/main/java/org/geysermc/geyser/inventory/click/Click.java index 6cdd70978..187a98842 100644 --- a/common/src/main/java/org/geysermc/common/window/response/CustomFormResponse.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/click/Click.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,24 +23,25 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.response; +package org.geysermc.geyser.inventory.click; +import com.github.steveice10.mc.protocol.data.game.inventory.ClickItemAction; +import com.github.steveice10.mc.protocol.data.game.inventory.ContainerActionType; +import com.github.steveice10.mc.protocol.data.game.inventory.*; import lombok.AllArgsConstructor; -import lombok.Getter; -import org.geysermc.common.window.response.FormResponse; -import org.geysermc.common.window.response.FormResponseData; -import java.util.Map; - -@Getter @AllArgsConstructor -public class CustomFormResponse implements FormResponse { +public enum Click { + LEFT(ContainerActionType.CLICK_ITEM, ClickItemAction.LEFT_CLICK), + RIGHT(ContainerActionType.CLICK_ITEM, ClickItemAction.RIGHT_CLICK), + LEFT_SHIFT(ContainerActionType.SHIFT_CLICK_ITEM, ShiftClickItemAction.LEFT_CLICK), + DROP_ONE(ContainerActionType.DROP_ITEM, DropItemAction.DROP_FROM_SELECTED), + DROP_ALL(ContainerActionType.DROP_ITEM, DropItemAction.DROP_SELECTED_STACK), + LEFT_OUTSIDE(ContainerActionType.CLICK_ITEM, ClickItemAction.LEFT_CLICK), + RIGHT_OUTSIDE(ContainerActionType.CLICK_ITEM, ClickItemAction.RIGHT_CLICK); - private Map responses; - private Map dropdownResponses; - private Map inputResponses; - private Map sliderResponses; - private Map stepSliderResponses; - private Map toggleResponses; - private Map labelResponses; + public static final int OUTSIDE_SLOT = -999; + + public final ContainerActionType actionType; + public final ContainerAction action; } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java b/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java new file mode 100644 index 000000000..c239cc778 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/click/ClickPlan.java @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.inventory.click; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.inventory.ContainerActionType; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClickPacket; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import lombok.Value; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.InventoryTranslator; +import org.geysermc.geyser.inventory.SlotType; +import org.geysermc.geyser.translator.inventory.CraftingInventoryTranslator; +import org.geysermc.geyser.translator.inventory.PlayerInventoryTranslator; +import org.geysermc.geyser.util.InventoryUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; + +public class ClickPlan { + private final List plan = new ArrayList<>(); + private final Int2ObjectMap simulatedItems; + private GeyserItemStack simulatedCursor; + private boolean simulating; + + private final GeyserSession session; + private final InventoryTranslator translator; + private final Inventory inventory; + private final int gridSize; + + public ClickPlan(GeyserSession session, InventoryTranslator translator, Inventory inventory) { + this.session = session; + this.translator = translator; + this.inventory = inventory; + + this.simulatedItems = new Int2ObjectOpenHashMap<>(inventory.getSize()); + this.simulatedCursor = session.getPlayerInventory().getCursor().copy(); + this.simulating = true; + + if (translator instanceof PlayerInventoryTranslator) { + gridSize = 4; + } else if (translator instanceof CraftingInventoryTranslator) { + gridSize = 9; + } else { + gridSize = -1; + } + } + + private void resetSimulation() { + this.simulatedItems.clear(); + this.simulatedCursor = session.getPlayerInventory().getCursor().copy(); + } + + public void add(Click click, int slot) { + add(click, slot, false); + } + + public void add(Click click, int slot, boolean force) { + if (!simulating) + throw new UnsupportedOperationException("ClickPlan already executed"); + + if (click == Click.LEFT_OUTSIDE || click == Click.RIGHT_OUTSIDE) { + slot = Click.OUTSIDE_SLOT; + } + + ClickAction action = new ClickAction(click, slot, force); + plan.add(action); + simulateAction(action); + } + + public void execute(boolean refresh) { + //update geyser inventory after simulation to avoid net id desync + resetSimulation(); + ListIterator planIter = plan.listIterator(); + while (planIter.hasNext()) { + ClickAction action = planIter.next(); + + if (action.slot != Click.OUTSIDE_SLOT && translator.getSlotType(action.slot) != SlotType.NORMAL) { + refresh = true; + } + + ItemStack clickedItemStack; + if (!planIter.hasNext() && refresh) { + clickedItemStack = InventoryUtils.REFRESH_ITEM; + } else if (action.click.actionType == ContainerActionType.DROP_ITEM || action.slot == Click.OUTSIDE_SLOT) { + clickedItemStack = null; + } else { + clickedItemStack = getItem(action.slot).getItemStack(); + } + + Int2ObjectMap affectedSlots = new Int2ObjectOpenHashMap<>(); + for (Int2ObjectMap.Entry simulatedSlot : simulatedItems.int2ObjectEntrySet()) { + affectedSlots.put(simulatedSlot.getIntKey(), simulatedSlot.getValue().getItemStack()); + } + + ServerboundContainerClickPacket clickPacket = new ServerboundContainerClickPacket( + inventory.getId(), + inventory.getStateId(), + action.slot, + action.click.actionType, + action.click.action, + clickedItemStack, + affectedSlots + ); + + simulateAction(action); + + session.sendDownstreamPacket(clickPacket); + } + + session.getPlayerInventory().setCursor(simulatedCursor, session); + for (Int2ObjectMap.Entry simulatedSlot : simulatedItems.int2ObjectEntrySet()) { + inventory.setItem(simulatedSlot.getIntKey(), simulatedSlot.getValue(), session); + } + simulating = false; + } + + public GeyserItemStack getItem(int slot) { + return getItem(slot, true); + } + + public GeyserItemStack getItem(int slot, boolean generate) { + if (generate) { + return simulatedItems.computeIfAbsent(slot, k -> inventory.getItem(slot).copy()); + } else { + return simulatedItems.getOrDefault(slot, inventory.getItem(slot)); + } + } + + public GeyserItemStack getCursor() { + return simulatedCursor; + } + + private void setItem(int slot, GeyserItemStack item) { + if (simulating) { + simulatedItems.put(slot, item); + } else { + inventory.setItem(slot, item, session); + } + } + + private void setCursor(GeyserItemStack item) { + if (simulating) { + simulatedCursor = item; + } else { + session.getPlayerInventory().setCursor(item, session); + } + } + + private void simulateAction(ClickAction action) { + GeyserItemStack cursor = simulating ? getCursor() : session.getPlayerInventory().getCursor(); + switch (action.click) { + case LEFT_OUTSIDE -> { + setCursor(GeyserItemStack.EMPTY); + return; + } + case RIGHT_OUTSIDE -> { + if (!cursor.isEmpty()) { + cursor.sub(1); + } + return; + } + } + + GeyserItemStack clicked = simulating ? getItem(action.slot) : inventory.getItem(action.slot); + if (translator.getSlotType(action.slot) == SlotType.OUTPUT) { + switch (action.click) { + case LEFT, RIGHT -> { + if (cursor.isEmpty() && !clicked.isEmpty()) { + setCursor(clicked.copy()); + } else if (InventoryUtils.canStack(cursor, clicked)) { + cursor.add(clicked.getAmount()); + } + reduceCraftingGrid(false); + } + case LEFT_SHIFT -> reduceCraftingGrid(true); + } + } else { + switch (action.click) { + case LEFT: + if (!InventoryUtils.canStack(cursor, clicked)) { + setCursor(clicked); + setItem(action.slot, cursor); + } else { + setCursor(GeyserItemStack.EMPTY); + clicked.add(cursor.getAmount()); + } + break; + case RIGHT: + if (cursor.isEmpty() && !clicked.isEmpty()) { + int half = clicked.getAmount() / 2; //smaller half + setCursor(clicked.copy(clicked.getAmount() - half)); //larger half + clicked.setAmount(half); + } else if (!cursor.isEmpty() && clicked.isEmpty()) { + cursor.sub(1); + setItem(action.slot, cursor.copy(1)); + } else if (InventoryUtils.canStack(cursor, clicked)) { + cursor.sub(1); + clicked.add(1); + } + break; + case LEFT_SHIFT: + //TODO + break; + case DROP_ONE: + if (!clicked.isEmpty()) { + clicked.sub(1); + } + break; + case DROP_ALL: + setItem(action.slot, GeyserItemStack.EMPTY); + break; + } + } + } + + //TODO + private void reduceCraftingGrid(boolean makeAll) { + if (gridSize == -1) + return; + + int crafted; + if (!makeAll) { + crafted = 1; + } else { + crafted = 0; + for (int i = 0; i < gridSize; i++) { + GeyserItemStack item = getItem(i + 1); + if (!item.isEmpty()) { + if (crafted == 0) { + crafted = item.getAmount(); + } + crafted = Math.min(crafted, item.getAmount()); + } + } + } + + for (int i = 0; i < gridSize; i++) { + GeyserItemStack item = getItem(i + 1); + if (!item.isEmpty()) + item.sub(crafted); + } + } + + /** + * @return a new set of all affected slots. This isn't a constant variable; it's newly generated each time it is run. + */ + public IntSet getAffectedSlots() { + IntSet affectedSlots = new IntOpenHashSet(); + for (ClickAction action : plan) { + if (translator.getSlotType(action.slot) == SlotType.NORMAL && action.slot != Click.OUTSIDE_SLOT) { + affectedSlots.add(action.slot); + } + } + return affectedSlots; + } + + @Value + private static class ClickAction { + Click click; + /** + * Java slot + */ + int slot; + boolean force; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/inventory/holder/BlockInventoryHolder.java b/core/src/main/java/org/geysermc/geyser/inventory/holder/BlockInventoryHolder.java new file mode 100644 index 000000000..8dba5a69d --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/holder/BlockInventoryHolder.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.inventory.holder; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.google.common.collect.ImmutableSet; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; +import com.nukkitx.protocol.bedrock.packet.ContainerClosePacket; +import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; +import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; +import org.geysermc.geyser.inventory.Container; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.InventoryTranslator; +import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.util.BlockUtils; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * Manages the fake block we implement for each inventory, should we need to. + * This class will attempt to use a real block first, if possible. + */ +public class BlockInventoryHolder extends InventoryHolder { + /** + * The default Java block ID to translate as a fake block + */ + private final int defaultJavaBlockState; + private final ContainerType containerType; + private final Set validBlocks; + + public BlockInventoryHolder(String javaBlockIdentifier, ContainerType containerType, String... validBlocks) { + this.defaultJavaBlockState = BlockRegistries.JAVA_IDENTIFIERS.get(javaBlockIdentifier); + this.containerType = containerType; + if (validBlocks != null) { + Set validBlocksTemp = new HashSet<>(validBlocks.length + 1); + Collections.addAll(validBlocksTemp, validBlocks); + validBlocksTemp.add(BlockUtils.getCleanIdentifier(javaBlockIdentifier)); + this.validBlocks = ImmutableSet.copyOf(validBlocksTemp); + } else { + this.validBlocks = Collections.singleton(BlockUtils.getCleanIdentifier(javaBlockIdentifier)); + } + } + + @Override + public void prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { + // Check to see if there is an existing block we can use that the player just selected. + // First, verify that the player's position has not changed, so we don't try to select a block wildly out of range. + // (This could be a virtual inventory that the player is opening) + if (checkInteractionPosition(session)) { + // Then, check to see if the interacted block is valid for this inventory by ensuring the block state identifier is valid + int javaBlockId = session.getGeyser().getWorldManager().getBlockAt(session, session.getLastInteractionBlockPosition()); + String[] javaBlockString = BlockRegistries.JAVA_IDENTIFIERS.get().getOrDefault(javaBlockId, "minecraft:air").split("\\["); + if (isValidBlock(javaBlockString)) { + // We can safely use this block + inventory.setHolderPosition(session.getLastInteractionBlockPosition()); + ((Container) inventory).setUsingRealBlock(true, javaBlockString[0]); + setCustomName(session, session.getLastInteractionBlockPosition(), inventory, javaBlockId); + return; + } + } + + // Otherwise, time to conjure up a fake block! + Vector3i position = session.getPlayerEntity().getPosition().toInt(); + position = position.add(Vector3i.UP); + UpdateBlockPacket blockPacket = new UpdateBlockPacket(); + blockPacket.setDataLayer(0); + blockPacket.setBlockPosition(position); + blockPacket.setRuntimeId(session.getBlockMappings().getBedrockBlockId(defaultJavaBlockState)); + blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); + session.sendUpstreamPacket(blockPacket); + inventory.setHolderPosition(position); + + setCustomName(session, position, inventory, defaultJavaBlockState); + } + + /** + * Will be overwritten in the beacon inventory translator to remove the check, since virtual inventories can't exist. + * + * @return if the player's last interaction position and current position match. Used to ensure that we don't select + * a block to hold the inventory that's wildly out of range. + */ + protected boolean checkInteractionPosition(GeyserSession session) { + return session.getLastInteractionPlayerPosition().equals(session.getPlayerEntity().getPosition()); + } + + /** + * @return true if this Java block ID can be used for player inventory. + */ + protected boolean isValidBlock(String[] javaBlockString) { + return this.validBlocks.contains(javaBlockString[0]); + } + + protected void setCustomName(GeyserSession session, Vector3i position, Inventory inventory, int javaBlockState) { + NbtMap tag = NbtMap.builder() + .putInt("x", position.getX()) + .putInt("y", position.getY()) + .putInt("z", position.getZ()) + .putString("CustomName", inventory.getTitle()).build(); + BlockEntityDataPacket dataPacket = new BlockEntityDataPacket(); + dataPacket.setData(tag); + dataPacket.setBlockPosition(position); + session.sendUpstreamPacket(dataPacket); + } + + @Override + public void openInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { + ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket(); + containerOpenPacket.setId((byte) inventory.getId()); + containerOpenPacket.setType(containerType); + containerOpenPacket.setBlockPosition(inventory.getHolderPosition()); + containerOpenPacket.setUniqueEntityId(inventory.getHolderId()); + session.sendUpstreamPacket(containerOpenPacket); + } + + @Override + public void closeInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { + if (((Container) inventory).isUsingRealBlock()) { + // No need to reset a block since we didn't change any blocks + // But send a container close packet because we aren't destroying the original. + ContainerClosePacket packet = new ContainerClosePacket(); + packet.setId((byte) inventory.getId()); + packet.setUnknownBool0(true); //TODO needs to be changed in Protocol to "server-side" or something + session.sendUpstreamPacket(packet); + return; + } + + Vector3i holderPos = inventory.getHolderPosition(); + Position pos = new Position(holderPos.getX(), holderPos.getY(), holderPos.getZ()); + int realBlock = session.getGeyser().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ()); + UpdateBlockPacket blockPacket = new UpdateBlockPacket(); + blockPacket.setDataLayer(0); + blockPacket.setBlockPosition(holderPos); + blockPacket.setRuntimeId(session.getBlockMappings().getBedrockBlockId(realBlock)); + blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); + session.sendUpstreamPacket(blockPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/InventoryHolder.java b/core/src/main/java/org/geysermc/geyser/inventory/holder/InventoryHolder.java similarity index 82% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/InventoryHolder.java rename to core/src/main/java/org/geysermc/geyser/inventory/holder/InventoryHolder.java index 5a9e736e9..845e645e3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/InventoryHolder.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/holder/InventoryHolder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,11 +23,11 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.inventory.holder; +package org.geysermc.geyser.inventory.holder; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.InventoryTranslator; public abstract class InventoryHolder { public abstract void prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/Enchantment.java b/core/src/main/java/org/geysermc/geyser/inventory/item/Enchantment.java similarity index 51% rename from connector/src/main/java/org/geysermc/connector/network/translators/item/Enchantment.java rename to core/src/main/java/org/geysermc/geyser/inventory/item/Enchantment.java index 31ec0c7fb..4abc7525e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/Enchantment.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/item/Enchantment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.item; +package org.geysermc.geyser.inventory.item; import lombok.Getter; @@ -69,6 +69,8 @@ public enum Enchantment { QUICK_CHARGE, SOUL_SPEED; + private static final Enchantment[] VALUES = values(); + private final String javaIdentifier; Enchantment() { @@ -76,7 +78,7 @@ public enum Enchantment { } public static Enchantment getByJavaIdentifier(String javaIdentifier) { - for (Enchantment enchantment : Enchantment.values()) { + for (Enchantment enchantment : VALUES) { if (enchantment.javaIdentifier.equals(javaIdentifier) || enchantment.name().toLowerCase(Locale.ENGLISH).equalsIgnoreCase(javaIdentifier)) { return enchantment; } @@ -85,9 +87,83 @@ public enum Enchantment { } public static Enchantment getByBedrockId(int bedrockId) { - if (bedrockId >= 0 && bedrockId < Enchantment.values().length) { - return Enchantment.values()[bedrockId]; + if (bedrockId >= 0 && bedrockId < VALUES.length) { + return VALUES[bedrockId]; } return null; } + + /** + * Enchantments classified by their Java index + */ + public enum JavaEnchantment { + PROTECTION, + FIRE_PROTECTION, + FEATHER_FALLING, + BLAST_PROTECTION, + PROJECTILE_PROTECTION, + RESPIRATION, + AQUA_AFFINITY, + THORNS, + DEPTH_STRIDER, + FROST_WALKER, + BINDING_CURSE, + SOUL_SPEED, + SHARPNESS, + SMITE, + BANE_OF_ARTHROPODS, + KNOCKBACK, + FIRE_ASPECT, + LOOTING, + SWEEPING, + EFFICIENCY, + SILK_TOUCH, + UNBREAKING, + FORTUNE, + POWER, + PUNCH, + FLAME, + INFINITY, + LUCK_OF_THE_SEA, + LURE, + LOYALTY, + IMPALING, + RIPTIDE, + CHANNELING, + MULTISHOT, + QUICK_CHARGE, + PIERCING, + MENDING, + VANISHING_CURSE; + + private static final JavaEnchantment[] VALUES = JavaEnchantment.values(); + + public static JavaEnchantment of(int index) { + return VALUES[index]; + } + + /** + * A list of all enchantment Java identifiers for use with command suggestions. + */ + public static final String[] ALL_JAVA_IDENTIFIERS; + + public static JavaEnchantment getByJavaIdentifier(String javaIdentifier) { + if (!javaIdentifier.startsWith("minecraft:")) { + javaIdentifier = "minecraft:" + javaIdentifier; + } + for (int i = 0; i < ALL_JAVA_IDENTIFIERS.length; i++) { + if (ALL_JAVA_IDENTIFIERS[i].equalsIgnoreCase(javaIdentifier)) { + return VALUES[i]; + } + } + return null; + } + + static { + ALL_JAVA_IDENTIFIERS = new String[VALUES.length]; + for (int i = 0; i < ALL_JAVA_IDENTIFIERS.length; i++) { + ALL_JAVA_IDENTIFIERS[i] = "minecraft:" + VALUES[i].name().toLowerCase(Locale.ENGLISH); + } + } + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/Potion.java b/core/src/main/java/org/geysermc/geyser/inventory/item/Potion.java similarity index 90% rename from connector/src/main/java/org/geysermc/connector/network/translators/item/Potion.java rename to core/src/main/java/org/geysermc/geyser/inventory/item/Potion.java index b9a213d84..1efb77ab0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/Potion.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/item/Potion.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.item; +package org.geysermc.geyser.inventory.item; import lombok.Getter; @@ -74,6 +74,8 @@ public enum Potion { SLOW_FALLING(40), LONG_SLOW_FALLING(41); + public static final Potion[] VALUES = values(); + private final String javaIdentifier; private final short bedrockId; @@ -83,7 +85,7 @@ public enum Potion { } public static Potion getByJavaIdentifier(String javaIdentifier) { - for (Potion potion : Potion.values()) { + for (Potion potion : VALUES) { if (potion.javaIdentifier.equals(javaIdentifier)) { return potion; } @@ -91,8 +93,8 @@ public enum Potion { return null; } - public static Potion getByBedrockId(short bedrockId) { - for (Potion potion : Potion.values()) { + public static Potion getByBedrockId(int bedrockId) { + for (Potion potion : VALUES) { if (potion.bedrockId == bedrockId) { return potion; } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java b/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java new file mode 100644 index 000000000..940436106 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.inventory.item; + +import lombok.Getter; +import lombok.experimental.Accessors; +import org.geysermc.geyser.registry.type.ItemMapping; + +import javax.annotation.Nonnull; +import java.util.Map; + +/** + * A class to have easy access to specific item mappings per-version. + */ +@Getter +@Accessors(fluent = true) +public class StoredItemMappings { + private final ItemMapping bamboo; + private final ItemMapping banner; + private final ItemMapping barrier; + private final ItemMapping compass; + private final ItemMapping crossbow; + private final ItemMapping enchantedBook; + private final ItemMapping fishingRod; + private final ItemMapping lodestoneCompass; + private final ItemMapping milkBucket; + private final ItemMapping powderSnowBucket; + private final ItemMapping playerHead; + private final ItemMapping egg; + private final ItemMapping shield; + private final ItemMapping wheat; + private final ItemMapping writableBook; + + public StoredItemMappings(Map itemMappings) { + this.bamboo = load(itemMappings, "bamboo"); + this.banner = load(itemMappings, "white_banner"); // As of 1.17.10, all banners have the same Bedrock ID + this.barrier = load(itemMappings, "barrier"); + this.compass = load(itemMappings, "compass"); + this.crossbow = load(itemMappings, "crossbow"); + this.enchantedBook = load(itemMappings, "enchanted_book"); + this.fishingRod = load(itemMappings, "fishing_rod"); + this.lodestoneCompass = load(itemMappings, "lodestone_compass"); + this.milkBucket = load(itemMappings, "milk_bucket"); + this.powderSnowBucket = load(itemMappings, "powder_snow_bucket"); + this.playerHead = load(itemMappings, "player_head"); + this.egg = load(itemMappings, "egg"); + this.shield = load(itemMappings, "shield"); + this.wheat = load(itemMappings, "wheat"); + this.writableBook = load(itemMappings, "writable_book"); + } + + @Nonnull + private ItemMapping load(Map itemMappings, String cleanIdentifier) { + ItemMapping mapping = itemMappings.get("minecraft:" + cleanIdentifier); + if (mapping == null) { + throw new RuntimeException("Could not find item " + cleanIdentifier); + } + + return mapping; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/TippedArrowPotion.java b/core/src/main/java/org/geysermc/geyser/inventory/item/TippedArrowPotion.java similarity index 93% rename from connector/src/main/java/org/geysermc/connector/network/translators/item/TippedArrowPotion.java rename to core/src/main/java/org/geysermc/geyser/inventory/item/TippedArrowPotion.java index 7a5b576be..91d0f526f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/TippedArrowPotion.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/item/TippedArrowPotion.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.item; +package org.geysermc.geyser.inventory.item; import lombok.Getter; @@ -77,6 +77,8 @@ public enum TippedArrowPotion { SLOW_FALLING(41, ArrowParticleColors.SLOW_FALLING), LONG_SLOW_FALLING(42, ArrowParticleColors.SLOW_FALLING); + private static final TippedArrowPotion[] VALUES = values(); + private final String javaIdentifier; private final short bedrockId; /** @@ -92,7 +94,7 @@ public enum TippedArrowPotion { } public static TippedArrowPotion getByJavaIdentifier(String javaIdentifier) { - for (TippedArrowPotion potion : TippedArrowPotion.values()) { + for (TippedArrowPotion potion : VALUES) { if (potion.javaIdentifier.equals(javaIdentifier)) { return potion; } @@ -100,8 +102,8 @@ public enum TippedArrowPotion { return null; } - public static TippedArrowPotion getByBedrockId(short bedrockId) { - for (TippedArrowPotion potion : TippedArrowPotion.values()) { + public static TippedArrowPotion getByBedrockId(int bedrockId) { + for (TippedArrowPotion potion : VALUES) { if (potion.bedrockId == bedrockId) { return potion; } @@ -114,7 +116,7 @@ public enum TippedArrowPotion { * @return the tipped arrow potion that most closely resembles that color. */ public static TippedArrowPotion getByJavaColor(int color) { - for (TippedArrowPotion potion : TippedArrowPotion.values()) { + for (TippedArrowPotion potion : VALUES) { if (potion.javaColor == color) { return potion; } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java b/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java new file mode 100644 index 000000000..f95633768 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java @@ -0,0 +1,460 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.inventory.updater; + +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundRenameItemPacket; +import com.github.steveice10.opennbt.tag.builtin.*; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntMaps; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.inventory.AnvilContainer; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.geyser.translator.inventory.InventoryTranslator; +import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.registry.type.EnchantmentData; +import org.geysermc.geyser.util.ItemUtils; + +import java.util.Objects; +import java.util.Set; + +public class AnvilInventoryUpdater extends InventoryUpdater { + public static final AnvilInventoryUpdater INSTANCE = new AnvilInventoryUpdater(); + + private static final int MAX_LEVEL_COST = 40; + + @Override + public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { + super.updateInventory(translator, session, inventory); + AnvilContainer anvilContainer = (AnvilContainer) inventory; + updateInventoryState(session, anvilContainer); + int targetSlot = getTargetSlot(session, anvilContainer); + for (int i = 0; i < translator.size; i++) { + final int bedrockSlot = translator.javaSlotToBedrock(i); + if (bedrockSlot == 50) + continue; + if (i == targetSlot) { + updateTargetSlot(translator, session, anvilContainer, targetSlot); + } else { + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.UI); + slotPacket.setSlot(bedrockSlot); + slotPacket.setItem(inventory.getItem(i).getItemData(session)); + session.sendUpstreamPacket(slotPacket); + } + } + } + + @Override + public boolean updateSlot(InventoryTranslator translator, GeyserSession session, Inventory inventory, int javaSlot) { + if (super.updateSlot(translator, session, inventory, javaSlot)) + return true; + AnvilContainer anvilContainer = (AnvilContainer) inventory; + updateInventoryState(session, anvilContainer); + + int lastTargetSlot = anvilContainer.getLastTargetSlot(); + int targetSlot = getTargetSlot(session, anvilContainer); + if (targetSlot != javaSlot) { + // Update the requested slot + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.UI); + slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); + slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session)); + session.sendUpstreamPacket(slotPacket); + } else if (lastTargetSlot != javaSlot) { + // Update the previous target slot to remove repair cost changes + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.UI); + slotPacket.setSlot(translator.javaSlotToBedrock(lastTargetSlot)); + slotPacket.setItem(inventory.getItem(lastTargetSlot).getItemData(session)); + session.sendUpstreamPacket(slotPacket); + } + + updateTargetSlot(translator, session, anvilContainer, targetSlot); + return true; + } + + private void updateInventoryState(GeyserSession session, AnvilContainer anvilContainer) { + GeyserItemStack input = anvilContainer.getInput(); + if (!input.equals(anvilContainer.getLastInput())) { + anvilContainer.setLastInput(input.copy()); + anvilContainer.setUseJavaLevelCost(false); + + // Changing the item in the input slot resets the name field on Bedrock, but + // does not result in a FilterTextPacket + String originalName = MessageTranslator.convertToPlainText(ItemUtils.getCustomName(input.getNbt()), session.getLocale()); + ServerboundRenameItemPacket renameItemPacket = new ServerboundRenameItemPacket(originalName); + session.sendDownstreamPacket(renameItemPacket); + + anvilContainer.setNewName(null); + } + + GeyserItemStack material = anvilContainer.getMaterial(); + if (!material.equals(anvilContainer.getLastMaterial())) { + anvilContainer.setLastMaterial(material.copy()); + anvilContainer.setUseJavaLevelCost(false); + } + } + + /** + * @param anvilContainer the anvil inventory + * @return the slot to change the repair cost + */ + private int getTargetSlot(GeyserSession session, AnvilContainer anvilContainer) { + GeyserItemStack input = anvilContainer.getInput(); + GeyserItemStack material = anvilContainer.getMaterial(); + + if (!material.isEmpty()) { + if (!input.isEmpty() && isRepairing(session, input, material)) { + // Changing the repair cost on the material item makes it non-stackable + return 0; + } + // Prefer changing the material item because it does not reset the name field + return 1; + } + return 0; + } + + private void updateTargetSlot(InventoryTranslator translator, GeyserSession session, AnvilContainer anvilContainer, int slot) { + ItemData itemData = anvilContainer.getItem(slot).getItemData(session); + itemData = hijackRepairCost(session, anvilContainer, itemData); + + if (slot == 0 && isRenaming(session, anvilContainer, true)) { + // Can't change the RepairCost because it resets the name field on Bedrock + return; + } + + anvilContainer.setLastTargetSlot(slot); + + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.UI); + slotPacket.setSlot(translator.javaSlotToBedrock(slot)); + slotPacket.setItem(itemData); + session.sendUpstreamPacket(slotPacket); + } + + private ItemData hijackRepairCost(GeyserSession session, AnvilContainer anvilContainer, ItemData itemData) { + if (itemData.isNull()) { + return itemData; + } + // Fix level count by adjusting repair cost + int newRepairCost; + if (anvilContainer.isUseJavaLevelCost()) { + newRepairCost = anvilContainer.getJavaLevelCost(); + } else { + // Did not receive a ServerWindowPropertyPacket with the level cost + newRepairCost = calcLevelCost(session, anvilContainer, false); + } + + int bedrockLevelCost = calcLevelCost(session, anvilContainer, true); + if (bedrockLevelCost == -1) { + // Bedrock is unable to combine/repair the items + return itemData; + } + + newRepairCost -= bedrockLevelCost; + if (newRepairCost == 0) { + // No change to the repair cost needed + return itemData; + } + + NbtMapBuilder tagBuilder = NbtMap.builder(); + if (itemData.getTag() != null) { + newRepairCost += itemData.getTag().getInt("RepairCost", 0); + tagBuilder.putAll(itemData.getTag()); + } + tagBuilder.put("RepairCost", newRepairCost); + return itemData.toBuilder().tag(tagBuilder.build()).build(); + } + + /** + * Calculate the number of levels needed to combine/rename an item + * + * @param session the geyser session + * @param anvilContainer the anvil container + * @param bedrock True to count enchantments like Bedrock + * @return the number of levels needed + */ + public int calcLevelCost(GeyserSession session, AnvilContainer anvilContainer, boolean bedrock) { + GeyserItemStack input = anvilContainer.getInput(); + GeyserItemStack material = anvilContainer.getMaterial(); + + if (input.isEmpty()) { + return 0; + } + int totalRepairCost = getRepairCost(input); + int cost = 0; + if (!material.isEmpty()) { + totalRepairCost += getRepairCost(material); + if (isCombining(session, input, material)) { + if (hasDurability(session, input) && input.getJavaId() == material.getJavaId()) { + cost += calcMergeRepairCost(session, input, material); + } + + int enchantmentLevelCost = calcMergeEnchantmentCost(session, input, material, bedrock); + if (enchantmentLevelCost != -1) { + cost += enchantmentLevelCost; + } else if (cost == 0) { + // Can't repair or merge enchantments + return -1; + } + } else if (hasDurability(session, input) && isRepairing(session, input, material)) { + cost = calcRepairLevelCost(session, input, material); + if (cost == -1) { + // No damage to repair + return -1; + } + } else { + return -1; + } + } + + int totalCost = totalRepairCost + cost; + if (isRenaming(session, anvilContainer, bedrock)) { + totalCost++; + if (cost == 0 && totalCost >= MAX_LEVEL_COST) { + // Items can still be renamed when the level cost for renaming exceeds 40 + totalCost = MAX_LEVEL_COST - 1; + } + } + return totalCost; + } + + /** + * Calculate the levels needed to repair an item with its repair material + * E.g. iron_sword + iron_ingot + * + * @param session Geyser session + * @param input an item with durability + * @param material the item's respective repair material + * @return the number of levels needed or 0 if it is not possible to repair any further + */ + private int calcRepairLevelCost(GeyserSession session, GeyserItemStack input, GeyserItemStack material) { + int newDamage = getDamage(input); + int unitRepair = Math.min(newDamage, input.getMapping(session).getMaxDamage() / 4); + if (unitRepair <= 0) { + // No damage to repair + return -1; + } + for (int i = 0; i < material.getAmount(); i++) { + newDamage -= unitRepair; + unitRepair = Math.min(newDamage, input.getMapping(session).getMaxDamage() / 4); + if (unitRepair <= 0) { + return i + 1; + } + } + return material.getAmount(); + } + + /** + * Calculate the levels cost for repairing items by combining two of the same item + * + * @param session Geyser session + * @param input an item with durability + * @param material a matching item + * @return the number of levels needed or 0 if it is not possible to repair any further + */ + private int calcMergeRepairCost(GeyserSession session, GeyserItemStack input, GeyserItemStack material) { + // If the material item is damaged 112% or more, then the input item will not be repaired + if (getDamage(input) > 0 && getDamage(material) < (material.getMapping(session).getMaxDamage() * 112 / 100)) { + return 2; + } + return 0; + } + + /** + * Calculate the levels needed for combining the enchantments of two items + * + * @param session Geyser session + * @param input an item with durability + * @param material a matching item + * @param bedrock True to count enchantments like Bedrock, False to count like Java + * @return the number of levels needed or -1 if no enchantments can be applied + */ + private int calcMergeEnchantmentCost(GeyserSession session, GeyserItemStack input, GeyserItemStack material, boolean bedrock) { + boolean hasCompatible = false; + Object2IntMap combinedEnchantments = getEnchantments(session, input, bedrock); + int cost = 0; + for (Object2IntMap.Entry entry : getEnchantments(session, material, bedrock).object2IntEntrySet()) { + JavaEnchantment enchantment = entry.getKey(); + EnchantmentData data = Registries.ENCHANTMENTS.get(enchantment); + if (data == null) { + GeyserImpl.getInstance().getLogger().debug("Java enchantment not in registry: " + enchantment); + continue; + } + + boolean canApply = isEnchantedBook(session, input) || data.validItems().contains(input.getJavaId()); + for (JavaEnchantment incompatible : data.incompatibleEnchantments()) { + if (combinedEnchantments.containsKey(incompatible)) { + canApply = false; + if (!bedrock) { + cost++; + } + } + } + + if (canApply || (!bedrock && session.getGameMode() == GameMode.CREATIVE)) { + int currentLevel = combinedEnchantments.getOrDefault(enchantment, 0); + int newLevel = entry.getIntValue(); + if (newLevel == currentLevel) { + newLevel++; + } + newLevel = Math.max(currentLevel, newLevel); + if (newLevel > data.maxLevel()) { + newLevel = data.maxLevel(); + } + combinedEnchantments.put(enchantment, newLevel); + + int rarityMultiplier = data.rarityMultiplier(); + if (isEnchantedBook(session, material) && rarityMultiplier > 1) { + rarityMultiplier /= 2; + } + if (bedrock) { + if (newLevel > currentLevel) { + hasCompatible = true; + } + if (enchantment == JavaEnchantment.IMPALING) { + // Multiplier is halved on Bedrock for some reason + rarityMultiplier /= 2; + } else if (enchantment == JavaEnchantment.SWEEPING) { + // Doesn't exist on Bedrock + rarityMultiplier = 0; + } + cost += rarityMultiplier * (newLevel - currentLevel); + } else { + hasCompatible = true; + cost += rarityMultiplier * newLevel; + } + } + } + + if (!hasCompatible) { + return -1; + } + return cost; + } + + private Object2IntMap getEnchantments(GeyserSession session, GeyserItemStack itemStack, boolean bedrock) { + if (itemStack.getNbt() == null) { + return Object2IntMaps.emptyMap(); + } + Object2IntMap enchantments = new Object2IntOpenHashMap<>(); + Tag enchantmentTag; + if (isEnchantedBook(session, itemStack)) { + enchantmentTag = itemStack.getNbt().get("StoredEnchantments"); + } else { + enchantmentTag = itemStack.getNbt().get("Enchantments"); + } + if (enchantmentTag instanceof ListTag listTag) { + for (Tag tag : listTag.getValue()) { + if (tag instanceof CompoundTag enchantTag) { + if (enchantTag.get("id") instanceof StringTag javaEnchId) { + JavaEnchantment enchantment = JavaEnchantment.getByJavaIdentifier(javaEnchId.getValue()); + if (enchantment == null) { + GeyserImpl.getInstance().getLogger().debug("Unknown java enchantment: " + javaEnchId.getValue()); + continue; + } + + Tag javaEnchLvl = enchantTag.get("lvl"); + if (!(javaEnchLvl instanceof ShortTag || javaEnchLvl instanceof IntTag)) + continue; + + // Handle duplicate enchantments + if (bedrock) { + enchantments.putIfAbsent(enchantment, ((Number) javaEnchLvl.getValue()).intValue()); + } else { + enchantments.mergeInt(enchantment, ((Number) javaEnchLvl.getValue()).intValue(), Math::max); + } + } + } + } + } + return enchantments; + } + + private boolean isEnchantedBook(GeyserSession session, GeyserItemStack itemStack) { + return itemStack.getJavaId() == session.getItemMappings().getStoredItems().enchantedBook().getJavaId(); + } + + private boolean isCombining(GeyserSession session, GeyserItemStack input, GeyserItemStack material) { + return isEnchantedBook(session, material) || (input.getJavaId() == material.getJavaId() && hasDurability(session, input)); + } + + private boolean isRepairing(GeyserSession session, GeyserItemStack input, GeyserItemStack material) { + Set repairMaterials = input.getMapping(session).getRepairMaterials(); + return repairMaterials != null && repairMaterials.contains(material.getMapping(session).getJavaIdentifier()); + } + + private boolean isRenaming(GeyserSession session, AnvilContainer anvilContainer, boolean bedrock) { + if (anvilContainer.getResult().isEmpty()) { + return false; + } + // This should really check the name field in all cases, but that requires the localized name + // of the item which can change depending on NBT and Minecraft Edition + String originalName = ItemUtils.getCustomName(anvilContainer.getInput().getNbt()); + if (bedrock && originalName != null && anvilContainer.getNewName() != null) { + // Check text and formatting + String legacyOriginalName = MessageTranslator.convertMessageLenient(originalName, session.getLocale()); + return !legacyOriginalName.equals(anvilContainer.getNewName()); + } + return !Objects.equals(originalName, ItemUtils.getCustomName(anvilContainer.getResult().getNbt())); + } + + private int getTagIntValueOr(GeyserItemStack itemStack, String tagName, int defaultValue) { + if (itemStack.getNbt() != null) { + Tag tag = itemStack.getNbt().get(tagName); + if (tag != null && tag.getValue() instanceof Number value) { + return value.intValue(); + } + } + return defaultValue; + } + + private int getRepairCost(GeyserItemStack itemStack) { + return getTagIntValueOr(itemStack, "RepairCost", 0); + } + + private boolean hasDurability(GeyserSession session, GeyserItemStack itemStack) { + if (itemStack.getMapping(session).getMaxDamage() > 0) { + return getTagIntValueOr(itemStack, "Unbreakable", 0) == 0; + } + return false; + } + + private int getDamage(GeyserItemStack itemStack) { + return getTagIntValueOr(itemStack, "Damage", 0); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java b/core/src/main/java/org/geysermc/geyser/inventory/updater/ChestInventoryUpdater.java similarity index 70% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java rename to core/src/main/java/org/geysermc/geyser/inventory/updater/ChestInventoryUpdater.java index e45945bc3..65147abb6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/updater/ChestInventoryUpdater.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,22 +23,25 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.inventory.updater; +package org.geysermc.geyser.inventory.updater; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; import lombok.AllArgsConstructor; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.InventoryTranslator; -import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.geysermc.connector.utils.InventoryUtils; -import org.geysermc.connector.utils.LanguageUtils; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.InventoryTranslator; +import org.geysermc.geyser.util.InventoryUtils; +import org.geysermc.geyser.text.GeyserLocale; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.IntFunction; @AllArgsConstructor public class ChestInventoryUpdater extends InventoryUpdater { - private static final ItemData UNUSUABLE_SPACE_BLOCK = InventoryUtils.createUnusableSpaceBlock(LanguageUtils.getLocaleStringLog("geyser.inventory.unusable_item.slot")); + private static final IntFunction UNUSUABLE_SPACE_BLOCK = InventoryUtils.createUnusableSpaceBlock(GeyserLocale.getLocaleStringLog("geyser.inventory.unusable_item.slot")); private final int paddedSize; @@ -46,12 +49,12 @@ public class ChestInventoryUpdater extends InventoryUpdater { public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { super.updateInventory(translator, session, inventory); - ItemData[] bedrockItems = new ItemData[paddedSize]; - for (int i = 0; i < bedrockItems.length; i++) { + List bedrockItems = new ArrayList<>(paddedSize); + for (int i = 0; i < paddedSize; i++) { if (i < translator.size) { - bedrockItems[i] = ItemTranslator.translateToBedrock(session, inventory.getItem(i)); + bedrockItems.add(inventory.getItem(i).getItemData(session)); } else { - bedrockItems[i] = UNUSUABLE_SPACE_BLOCK; + bedrockItems.add(UNUSUABLE_SPACE_BLOCK.apply(session.getUpstream().getProtocolVersion())); } } @@ -69,7 +72,7 @@ public class ChestInventoryUpdater extends InventoryUpdater { InventorySlotPacket slotPacket = new InventorySlotPacket(); slotPacket.setContainerId(inventory.getId()); slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); - slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(javaSlot))); + slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session)); session.sendUpstreamPacket(slotPacket); return true; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ContainerInventoryUpdater.java b/core/src/main/java/org/geysermc/geyser/inventory/updater/ContainerInventoryUpdater.java similarity index 78% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ContainerInventoryUpdater.java rename to core/src/main/java/org/geysermc/geyser/inventory/updater/ContainerInventoryUpdater.java index 51f33ac43..dd1e810ca 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ContainerInventoryUpdater.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/updater/ContainerInventoryUpdater.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,29 +23,32 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.inventory.updater; +package org.geysermc.geyser.inventory.updater; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.InventoryTranslator; -import org.geysermc.connector.network.translators.item.ItemTranslator; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.InventoryTranslator; + +import java.util.Arrays; public class ContainerInventoryUpdater extends InventoryUpdater { + public static final ContainerInventoryUpdater INSTANCE = new ContainerInventoryUpdater(); + @Override public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { super.updateInventory(translator, session, inventory); ItemData[] bedrockItems = new ItemData[translator.size]; for (int i = 0; i < bedrockItems.length; i++) { - bedrockItems[translator.javaSlotToBedrock(i)] = ItemTranslator.translateToBedrock(session, inventory.getItem(i)); + bedrockItems[translator.javaSlotToBedrock(i)] = inventory.getItem(i).getItemData(session); } InventoryContentPacket contentPacket = new InventoryContentPacket(); contentPacket.setContainerId(inventory.getId()); - contentPacket.setContents(bedrockItems); + contentPacket.setContents(Arrays.asList(bedrockItems)); session.sendUpstreamPacket(contentPacket); } @@ -57,7 +60,7 @@ public class ContainerInventoryUpdater extends InventoryUpdater { InventorySlotPacket slotPacket = new InventorySlotPacket(); slotPacket.setContainerId(inventory.getId()); slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); - slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(javaSlot))); + slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session)); session.sendUpstreamPacket(slotPacket); return true; } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/updater/HorseInventoryUpdater.java b/core/src/main/java/org/geysermc/geyser/inventory/updater/HorseInventoryUpdater.java new file mode 100644 index 000000000..10a556c81 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/updater/HorseInventoryUpdater.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.inventory.updater; + +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; +import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.InventoryTranslator; + +import java.util.Arrays; + +public class HorseInventoryUpdater extends InventoryUpdater { + public static final HorseInventoryUpdater INSTANCE = new HorseInventoryUpdater(); + + @Override + public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { + super.updateInventory(translator, session, inventory); + + ItemData[] bedrockItems = new ItemData[translator.size]; + for (int i = 0; i < bedrockItems.length; i++) { + bedrockItems[translator.javaSlotToBedrock(i)] = inventory.getItem(i).getItemData(session); + } + + InventoryContentPacket contentPacket = new InventoryContentPacket(); + contentPacket.setContainerId(inventory.getId()); + contentPacket.setContents(Arrays.asList(bedrockItems)); + session.sendUpstreamPacket(contentPacket); + } + + @Override + public boolean updateSlot(InventoryTranslator translator, GeyserSession session, Inventory inventory, int javaSlot) { + if (super.updateSlot(translator, session, inventory, javaSlot)) + return true; + + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(4); // Horse GUI? + slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); + slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session)); + session.sendUpstreamPacket(slotPacket); + return true; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java b/core/src/main/java/org/geysermc/geyser/inventory/updater/InventoryUpdater.java similarity index 76% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java rename to core/src/main/java/org/geysermc/geyser/inventory/updater/InventoryUpdater.java index 57601a9e0..6910aa447 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/updater/InventoryUpdater.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,27 +23,28 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.inventory.updater; +package org.geysermc.geyser.inventory.updater; import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.InventoryTranslator; -import org.geysermc.connector.network.translators.item.ItemTranslator; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.InventoryTranslator; -public abstract class InventoryUpdater { +import java.util.Arrays; + +public class InventoryUpdater { public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { ItemData[] bedrockItems = new ItemData[36]; for (int i = 0; i < 36; i++) { final int offset = i < 9 ? 27 : -9; - bedrockItems[i] = ItemTranslator.translateToBedrock(session, inventory.getItem(translator.size + i + offset)); + bedrockItems[i] = inventory.getItem(translator.size + i + offset).getItemData(session); } InventoryContentPacket contentPacket = new InventoryContentPacket(); contentPacket.setContainerId(ContainerId.INVENTORY); - contentPacket.setContents(bedrockItems); + contentPacket.setContents(Arrays.asList(bedrockItems)); session.sendUpstreamPacket(contentPacket); } @@ -52,7 +53,7 @@ public abstract class InventoryUpdater { InventorySlotPacket slotPacket = new InventorySlotPacket(); slotPacket.setContainerId(ContainerId.INVENTORY); slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); - slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(javaSlot))); + slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session)); session.sendUpstreamPacket(slotPacket); return true; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/CursorInventoryUpdater.java b/core/src/main/java/org/geysermc/geyser/inventory/updater/UIInventoryUpdater.java similarity index 76% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/CursorInventoryUpdater.java rename to core/src/main/java/org/geysermc/geyser/inventory/updater/UIInventoryUpdater.java index 26d889900..79b6bffc0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/CursorInventoryUpdater.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/updater/UIInventoryUpdater.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,18 +23,17 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.inventory.updater; +package org.geysermc.geyser.inventory.updater; import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.InventoryTranslator; -import org.geysermc.connector.network.translators.item.ItemTranslator; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.InventoryTranslator; -public class CursorInventoryUpdater extends InventoryUpdater { +public class UIInventoryUpdater extends InventoryUpdater { + public static final UIInventoryUpdater INSTANCE = new UIInventoryUpdater(); - //TODO: Consider renaming this? Since the Protocol enum updated @Override public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { super.updateInventory(translator, session, inventory); @@ -46,7 +45,7 @@ public class CursorInventoryUpdater extends InventoryUpdater { InventorySlotPacket slotPacket = new InventorySlotPacket(); slotPacket.setContainerId(ContainerId.UI); slotPacket.setSlot(bedrockSlot); - slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(i))); + slotPacket.setItem(inventory.getItem(i).getItemData(session)); session.sendUpstreamPacket(slotPacket); } } @@ -59,7 +58,7 @@ public class CursorInventoryUpdater extends InventoryUpdater { InventorySlotPacket slotPacket = new InventorySlotPacket(); slotPacket.setContainerId(ContainerId.UI); slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); - slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(javaSlot))); + slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session)); session.sendUpstreamPacket(slotPacket); return true; } diff --git a/connector/src/main/java/org/geysermc/connector/utils/BedrockMapIcon.java b/core/src/main/java/org/geysermc/geyser/level/BedrockMapIcon.java similarity index 88% rename from connector/src/main/java/org/geysermc/connector/utils/BedrockMapIcon.java rename to core/src/main/java/org/geysermc/geyser/level/BedrockMapIcon.java index 7815e36b7..002753407 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/BedrockMapIcon.java +++ b/core/src/main/java/org/geysermc/geyser/level/BedrockMapIcon.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,9 +23,9 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.utils; +package org.geysermc.geyser.level; -import com.github.steveice10.mc.protocol.data.game.world.map.MapIconType; +import com.github.steveice10.mc.protocol.data.game.level.map.MapIconType; import lombok.Getter; public enum BedrockMapIcon { @@ -58,22 +58,17 @@ public enum BedrockMapIcon { private static final BedrockMapIcon[] VALUES = values(); - private MapIconType iconType; + private final MapIconType iconType; @Getter - private int iconID; + private final int iconID; - private int red; - private int green; - private int blue; + private final int red; + private final int green; + private final int blue; BedrockMapIcon(MapIconType iconType, int iconID) { - this.iconType = iconType; - this.iconID = iconID; - - this.red = 255; - this.green = 255; - this.blue = 255; + this(iconType, iconID, 255, 255, 255); } BedrockMapIcon(MapIconType iconType, int iconID, int red, int green, int blue) { @@ -107,11 +102,11 @@ public enum BedrockMapIcon { * @return ARGB as an int */ public int toARGB() { - int alpha = 255; + final int alpha = 255; return ((alpha & 0xFF) << 24) | ((red & 0xFF) << 16) | ((green & 0xFF) << 8) | - ((blue & 0xFF) << 0); + (blue & 0xFF); } } diff --git a/core/src/main/java/org/geysermc/geyser/level/FireworkColor.java b/core/src/main/java/org/geysermc/geyser/level/FireworkColor.java new file mode 100644 index 000000000..cb8c57517 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/level/FireworkColor.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.level; + +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.util.HSVLike; + +public enum FireworkColor { + BLACK(1973019), + RED(11743532), + GREEN(3887386), + BROWN(5320730), + BLUE(2437522), + PURPLE(8073150), + CYAN(2651799), + LIGHT_GRAY(11250603), + GRAY(4408131), + PINK(14188952), + LIME(4312372), + YELLOW(14602026), + LIGHT_BLUE(6719955), + MAGENTA(12801229), + ORANGE(15435844), + WHITE(15790320); + + private static final FireworkColor[] VALUES = values(); + + private final TextColor color; + + FireworkColor(int rgbValue) { + this.color = TextColor.color(rgbValue); + } + + private static HSVLike toHSV(int rgbValue) { + int r = (rgbValue & (255 << 16)) >> 16; + int g = (rgbValue & (255 << 8)) >> 8; + int b = rgbValue & 255; + return HSVLike.fromRGB(r, g, b); + } + + public static byte fromJavaRGB(int rgbValue) { + HSVLike hsv = toHSV(rgbValue); + return (byte) nearestTo(hsv).ordinal(); + } + + // The following two methods were adapted from the Adventure project: + // https://github.com/KyoriPowered/adventure/blob/09edf74409feb52d9147a5a811910de0721acf95/api/src/main/java/net/kyori/adventure/text/format/NamedTextColor.java#L193-L237 + /** + * Find the firework color nearest to the provided color. + * + * @param any color to match + * @return nearest named color. will always return a value + * @since 4.0.0 + */ + private static FireworkColor nearestTo(final HSVLike any) { + float matchedDistance = Float.MAX_VALUE; + FireworkColor match = VALUES[0]; + for (final FireworkColor potential : VALUES) { + final float distance = distance(any, potential.color.asHSV()); + if (distance < matchedDistance) { + match = potential; + matchedDistance = distance; + } + if (distance == 0) { + break; // same colour! whoo! + } + } + return match; + } + + /** + * Returns a distance metric to the other color. + * + *

This value is unitless and should only be used to compare with other firework colors.

+ * + * @param other color to compare to + * @return distance metric + */ + private static float distance(final HSVLike self, final HSVLike other) { + // weight hue more heavily than saturation and brightness. kind of magic numbers, but is fine for our use case of downsampling to a set of colors + final float hueDistance = 3 * Math.min(Math.abs(self.h() - other.h()), 1f - Math.abs(self.h() - other.h())); + final float saturationDiff = self.s() - other.s(); + final float valueDiff = self.v() - other.v(); + return hueDistance * hueDistance + saturationDiff * saturationDiff + valueDiff * valueDiff; + } + + public static int fromBedrockId(int id) { + for (FireworkColor fireworkColor : VALUES) { + if (fireworkColor.ordinal() == id) { + return fireworkColor.color.value(); + } + } + + return WHITE.color.value(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/GameRule.java b/core/src/main/java/org/geysermc/geyser/level/GameRule.java similarity index 92% rename from connector/src/main/java/org/geysermc/connector/utils/GameRule.java rename to core/src/main/java/org/geysermc/geyser/level/GameRule.java index 48feb1c18..d11288c6f 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/GameRule.java +++ b/core/src/main/java/org/geysermc/geyser/level/GameRule.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.utils; +package org.geysermc.geyser.level; import lombok.Getter; @@ -51,6 +51,7 @@ public enum GameRule { DROWNINGDAMAGE("drowningDamage", Boolean.class, true), FALLDAMAGE("fallDamage", Boolean.class, true), FIREDAMAGE("fireDamage", Boolean.class, true), + FREEZEDAMAGE("freezeDamage", Boolean.class, true), FORGIVEDEADPLAYERS("forgiveDeadPlayers", Boolean.class, true), // JE only KEEPINVENTORY("keepInventory", Boolean.class, false), LOGADMINCOMMANDS("logAdminCommands", Boolean.class, true), // JE only @@ -58,6 +59,7 @@ public enum GameRule { MAXENTITYCRAMMING("maxEntityCramming", Integer.class, 24), // JE only MOBGRIEFING("mobGriefing", Boolean.class, true), NATURALREGENERATION("naturalRegeneration", Boolean.class, true), + PLAYERSSLEEPINGPERCENTAGE("playersSleepingPercentage", Integer.class, 100), // JE only RANDOMTICKSPEED("randomTickSpeed", Integer.class, 3), REDUCEDDEBUGINFO("reducedDebugInfo", Boolean.class, false), // JE only SENDCOMMANDFEEDBACK("sendCommandFeedback", Boolean.class, true), @@ -68,16 +70,16 @@ public enum GameRule { UNKNOWN("unknown", Object.class); - private static final GameRule[] VALUES = values(); + public static final GameRule[] VALUES = values(); @Getter - private String javaID; + private final String javaID; @Getter - private Class type; + private final Class type; @Getter - private Object defaultValue; + private final Object defaultValue; GameRule(String javaID, Class type) { this(javaID, type, null); diff --git a/core/src/main/java/org/geysermc/geyser/level/GeyserAdvancement.java b/core/src/main/java/org/geysermc/geyser/level/GeyserAdvancement.java new file mode 100644 index 000000000..24bebf10a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/level/GeyserAdvancement.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.level; + +import com.github.steveice10.mc.protocol.data.game.advancement.Advancement; +import lombok.NonNull; +import org.geysermc.geyser.session.cache.AdvancementsCache; + +import java.util.List; + +/** + * A wrapper around MCProtocolLib's {@link Advancement} class so we can control the parent of an advancement + */ +public class GeyserAdvancement { + private final Advancement advancement; + private String rootId = null; + + public static GeyserAdvancement from(Advancement advancement) { + return new GeyserAdvancement(advancement); + } + + private GeyserAdvancement(Advancement advancement) { + this.advancement = advancement; + } + + @NonNull + public String getId() { + return this.advancement.getId(); + } + + @NonNull + public List getCriteria() { + return this.advancement.getCriteria(); + } + + @NonNull + public List> getRequirements() { + return this.advancement.getRequirements(); + } + + public String getParentId() { + return this.advancement.getParentId(); + } + + public Advancement.DisplayData getDisplayData() { + return this.advancement.getDisplayData(); + } + + public String getRootId(AdvancementsCache advancementsCache) { + if (rootId == null) { + if (this.advancement.getParentId() == null) { + // We are the root ID + this.rootId = this.advancement.getId(); + } else { + // Go through our cache, and descend until we find the root ID + GeyserAdvancement advancement = advancementsCache.getStoredAdvancements().get(this.advancement.getParentId()); + if (advancement.getParentId() == null) { + this.rootId = advancement.getId(); + } else { + this.rootId = advancement.getRootId(advancementsCache); + } + } + } + return rootId; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java b/core/src/main/java/org/geysermc/geyser/level/GeyserWorldManager.java similarity index 58% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java rename to core/src/main/java/org/geysermc/geyser/level/GeyserWorldManager.java index 2ab3c0108..ac7084980 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java +++ b/core/src/main/java/org/geysermc/geyser/level/GeyserWorldManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,18 +23,21 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world; +package org.geysermc.geyser.level; -import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; -import com.github.steveice10.mc.protocol.data.game.chunk.Column; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; -import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundChatPacket; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.session.cache.ChunkCache; -import org.geysermc.connector.utils.GameRule; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.ChunkCache; +import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator; +import org.geysermc.geyser.level.block.BlockStateValues; + +import java.util.Locale; public class GeyserWorldManager extends WorldManager { @@ -46,51 +49,41 @@ public class GeyserWorldManager extends WorldManager { if (chunkCache != null) { // Chunk cache can be null if the session is closed asynchronously return chunkCache.getBlockAt(x, y, z); } - return 0; + return BlockStateValues.JAVA_AIR_ID; } @Override - public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) { - ChunkCache chunkCache = session.getChunkCache(); - Column cachedColumn; - Chunk cachedChunk; - if (chunkCache == null || (cachedColumn = chunkCache.getChunk(x, z)) == null || (cachedChunk = cachedColumn.getChunks()[y]) == null) { - return; - } - - // Copy state IDs from cached chunk to output chunk - for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order - for (int blockZ = 0; blockZ < 16; blockZ++) { - for (int blockX = 0; blockX < 16; blockX++) { - chunk.set(blockX, blockY, blockZ, cachedChunk.get(blockX, blockY, blockZ)); - } - } - } - } - - @Override - public boolean hasMoreBlockDataThanChunkCache() { + public boolean hasOwnChunkCache() { // This implementation can only fetch data from the session chunk cache return false; } @Override - public int[] getBiomeDataAt(GeyserSession session, int x, int z) { - if (session.getConnector().getConfig().isCacheChunks()) { - ChunkCache chunkCache = session.getChunkCache(); - if (chunkCache != null) { // Chunk cache can be null if the session is closed asynchronously - Column column = chunkCache.getChunk(x, z); - if (column != null) { // Column can be null if the server sent a partial chunk update before the first ground-up-continuous one - return column.getBiomeData(); - } - } - } - return new int[1024]; + public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad) { + // Without direct server access, we can't get lectern information on-the-fly. + // I should have set this up so it's only called when there is a book in the block state. - Camotoy + NbtMapBuilder lecternTag = LecternInventoryTranslator.getBaseLecternTag(x, y, z, 1); + lecternTag.putCompound("book", NbtMap.builder() + .putByte("Count", (byte) 1) + .putShort("Damage", (short) 0) + .putString("Name", "minecraft:written_book") + .putCompound("tag", NbtMap.builder() + .putString("photoname", "") + .putString("text", "") + .build()) + .build()); + lecternTag.putInt("page", -1); // I'm surprisingly glad this exists - it forces Bedrock to stop reading immediately. Usually. + return lecternTag.build(); + } + + @Override + public boolean shouldExpectLecternHandled() { + return false; } @Override public void setGameRule(GeyserSession session, String name, Object value) { - session.sendDownstreamPacket(new ClientChatPacket("/gamerule " + name + " " + value)); + session.sendDownstreamPacket(new ServerboundChatPacket("/gamerule " + name + " " + value)); gameruleCache.put(name, String.valueOf(value)); } @@ -116,12 +109,12 @@ public class GeyserWorldManager extends WorldManager { @Override public void setPlayerGameMode(GeyserSession session, GameMode gameMode) { - session.sendDownstreamPacket(new ClientChatPacket("/gamemode " + gameMode.name().toLowerCase())); + session.sendDownstreamPacket(new ServerboundChatPacket("/gamemode " + gameMode.name().toLowerCase(Locale.ROOT))); } @Override public void setDifficulty(GeyserSession session, Difficulty difficulty) { - session.sendDownstreamPacket(new ClientChatPacket("/difficulty " + difficulty.name().toLowerCase())); + session.sendDownstreamPacket(new ServerboundChatPacket("/difficulty " + difficulty.name().toLowerCase(Locale.ROOT))); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/level/MapColor.java b/core/src/main/java/org/geysermc/geyser/level/MapColor.java new file mode 100644 index 000000000..1b2de18d1 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/level/MapColor.java @@ -0,0 +1,300 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.level; + +public enum MapColor { + COLOR_0(-1, -1, -1), + COLOR_1(-1, -1, -1), + COLOR_2(-1, -1, -1), + COLOR_3(-1, -1, -1), + COLOR_4(39, 125, 89), + COLOR_5(48, 153, 109), + COLOR_6(56, 178, 127), + COLOR_7(29, 94, 67), + COLOR_8(115, 164, 174), + COLOR_9(140, 201, 213), + COLOR_10(163, 233, 247), + COLOR_11(86, 123, 130), + COLOR_12(140, 140, 140), + COLOR_13(171, 171, 171), + COLOR_14(199, 199, 199), + COLOR_15(105, 105, 105), + COLOR_16(0, 0, 180), + COLOR_17(0, 0, 220), + COLOR_18(0, 0, 255), + COLOR_19(0, 0, 135), + COLOR_20(180, 112, 112), + COLOR_21(220, 138, 138), + COLOR_22(255, 160, 160), + COLOR_23(135, 84, 84), + COLOR_24(117, 117, 117), + COLOR_25(144, 144, 144), + COLOR_26(167, 167, 167), + COLOR_27(88, 88, 88), + COLOR_28(0, 87, 0), + COLOR_29(0, 106, 0), + COLOR_30(0, 124, 0), + COLOR_31(0, 65, 0), + COLOR_32(180, 180, 180), + COLOR_33(220, 220, 220), + COLOR_34(255, 255, 255), + COLOR_35(135, 135, 135), + COLOR_36(129, 118, 115), + COLOR_37(158, 144, 141), + COLOR_38(184, 168, 164), + COLOR_39(97, 88, 86), + COLOR_40(54, 76, 106), + COLOR_41(66, 94, 130), + COLOR_42(77, 109, 151), + COLOR_43(40, 57, 79), + COLOR_44(79, 79, 79), + COLOR_45(96, 96, 96), + COLOR_46(112, 112, 112), + COLOR_47(59, 59, 59), + COLOR_48(180, 45, 45), + COLOR_49(220, 55, 55), + COLOR_50(255, 64, 64), + COLOR_51(135, 33, 33), + COLOR_52(50, 84, 100), + COLOR_53(62, 102, 123), + COLOR_54(72, 119, 143), + COLOR_55(38, 63, 75), + COLOR_56(172, 177, 180), + COLOR_57(211, 217, 220), + COLOR_58(245, 252, 255), + COLOR_59(129, 133, 135), + COLOR_60(36, 89, 152), + COLOR_61(44, 109, 186), + COLOR_62(51, 127, 216), + COLOR_63(27, 67, 114), + COLOR_64(152, 53, 125), + COLOR_65(186, 65, 153), + COLOR_66(216, 76, 178), + COLOR_67(114, 40, 94), + COLOR_68(152, 108, 72), + COLOR_69(186, 132, 88), + COLOR_70(216, 153, 102), + COLOR_71(114, 81, 54), + COLOR_72(36, 161, 161), + COLOR_73(44, 197, 197), + COLOR_74(51, 229, 229), + COLOR_75(27, 121, 121), + COLOR_76(17, 144, 89), + COLOR_77(21, 176, 109), + COLOR_78(25, 204, 127), + COLOR_79(13, 108, 67), + COLOR_80(116, 89, 170), + COLOR_81(142, 109, 208), + COLOR_82(165, 127, 242), + COLOR_83(87, 67, 128), + COLOR_84(53, 53, 53), + COLOR_85(65, 65, 65), + COLOR_86(76, 76, 76), + COLOR_87(40, 40, 40), + COLOR_88(108, 108, 108), + COLOR_89(132, 132, 132), + COLOR_90(153, 153, 153), + COLOR_91(81, 81, 81), + COLOR_92(108, 89, 53), + COLOR_93(132, 109, 65), + COLOR_94(153, 127, 76), + COLOR_95(81, 67, 40), + COLOR_96(125, 44, 89), + COLOR_97(153, 54, 109), + COLOR_98(178, 63, 127), + COLOR_99(94, 33, 67), + COLOR_100(125, 53, 36), + COLOR_101(153, 65, 44), + COLOR_102(178, 76, 51), + COLOR_103(94, 40, 27), + COLOR_104(36, 53, 72), + COLOR_105(44, 65, 88), + COLOR_106(51, 76, 102), + COLOR_107(27, 40, 54), + COLOR_108(36, 89, 72), + COLOR_109(44, 109, 88), + COLOR_110(51, 127, 102), + COLOR_111(27, 67, 54), + COLOR_112(36, 36, 108), + COLOR_113(44, 44, 132), + COLOR_114(51, 51, 153), + COLOR_115(27, 27, 81), + COLOR_116(17, 17, 17), + COLOR_117(21, 21, 21), + COLOR_118(25, 25, 25), + COLOR_119(13, 13, 13), + COLOR_120(54, 168, 176), + COLOR_121(66, 205, 215), + COLOR_122(77, 238, 250), + COLOR_123(40, 126, 132), + COLOR_124(150, 154, 64), + COLOR_125(183, 188, 79), + COLOR_126(213, 219, 92), + COLOR_127(112, 115, 48), + COLOR_128(180, 90, 52), + COLOR_129(220, 110, 63), + COLOR_130(255, 128, 74), + COLOR_131(135, 67, 39), + COLOR_132(40, 153, 0), + COLOR_133(50, 187, 0), + COLOR_134(58, 217, 0), + COLOR_135(30, 114, 0), + COLOR_136(34, 60, 91), + COLOR_137(42, 74, 111), + COLOR_138(49, 86, 129), + COLOR_139(25, 45, 68), + COLOR_140(0, 1, 79), + COLOR_141(0, 1, 96), + COLOR_142(0, 2, 112), + COLOR_143(0, 1, 59), + COLOR_144(113, 124, 147), + COLOR_145(138, 152, 180), + COLOR_146(161, 177, 209), + COLOR_147(85, 93, 110), + COLOR_148(25, 57, 112), + COLOR_149(31, 70, 137), + COLOR_150(36, 82, 159), + COLOR_151(19, 43, 84), + COLOR_152(76, 61, 105), + COLOR_153(93, 75, 128), + COLOR_154(108, 87, 149), + COLOR_155(57, 46, 78), + COLOR_156(97, 76, 79), + COLOR_157(119, 93, 96), + COLOR_158(138, 108, 112), + COLOR_159(73, 57, 59), + COLOR_160(25, 93, 131), + COLOR_161(31, 114, 160), + COLOR_162(36, 133, 186), + COLOR_163(19, 70, 98), + COLOR_164(37, 82, 72), + COLOR_165(45, 100, 88), + COLOR_166(53, 117, 103), + COLOR_167(28, 61, 54), + COLOR_168(55, 54, 112), + COLOR_169(67, 66, 138), + COLOR_170(78, 77, 160), + COLOR_171(41, 40, 84), + COLOR_172(24, 28, 40), + COLOR_173(30, 35, 49), + COLOR_174(35, 41, 57), + COLOR_175(18, 21, 30), + COLOR_176(69, 75, 95), + COLOR_177(84, 92, 116), + COLOR_178(98, 107, 135), + COLOR_179(51, 56, 71), + COLOR_180(64, 64, 61), + COLOR_181(79, 79, 75), + COLOR_182(92, 92, 87), + COLOR_183(48, 48, 46), + COLOR_184(62, 51, 86), + COLOR_185(75, 62, 105), + COLOR_186(88, 73, 122), + COLOR_187(46, 38, 64), + COLOR_188(64, 43, 53), + COLOR_189(79, 53, 65), + COLOR_190(92, 62, 76), + COLOR_191(48, 32, 40), + COLOR_192(24, 35, 53), + COLOR_193(30, 43, 65), + COLOR_194(35, 50, 76), + COLOR_195(18, 26, 40), + COLOR_196(29, 57, 53), + COLOR_197(36, 70, 65), + COLOR_198(42, 82, 76), + COLOR_199(22, 43, 40), + COLOR_200(32, 42, 100), + COLOR_201(39, 51, 122), + COLOR_202(46, 60, 142), + COLOR_203(24, 31, 75), + COLOR_204(11, 15, 26), + COLOR_205(13, 18, 31), + COLOR_206(16, 22, 37), + COLOR_207(8, 11, 19), + COLOR_208(34, 33, 133), + COLOR_209(42, 41, 163), + COLOR_210(49, 48, 189), + COLOR_211(25, 25, 100), + COLOR_212(68, 44, 104), + COLOR_213(83, 54, 127), + COLOR_214(97, 63, 148), + COLOR_215(51, 33, 78), + COLOR_216(20, 17, 64), + COLOR_217(25, 21, 79), + COLOR_218(29, 25, 92), + COLOR_219(15, 13, 48), + COLOR_220(94, 88, 15), + COLOR_221(115, 108, 18), + COLOR_222(134, 126, 22), + COLOR_223(70, 66, 11), + COLOR_224(98, 100, 40), + COLOR_225(120, 122, 50), + COLOR_226(140, 142, 58), + COLOR_227(74, 75, 30), + COLOR_228(43, 31, 60), + COLOR_229(53, 37, 74), + COLOR_230(62, 44, 86), + COLOR_231(32, 23, 45), + COLOR_232(93, 127, 14), + COLOR_233(114, 155, 17), + COLOR_234(133, 180, 20), + COLOR_235(70, 95, 10), + COLOR_236(70, 70, 70), + COLOR_237(86, 86, 86), + COLOR_238(100, 100, 100), + COLOR_239(52, 52, 52), + COLOR_240(103, 123, 152), + COLOR_241(126, 150, 186), + COLOR_242(147, 175, 216), + COLOR_243(77, 92, 114), + COLOR_244(105, 117, 89), + COLOR_245(129, 144, 109), + COLOR_246(150, 167, 127), + COLOR_247(79, 88, 67); + + private static final MapColor[] VALUES = values(); + + private final int value; + + MapColor(int red, int green, int blue) { + int alpha = 255; + if (red == -1 && green == -1 && blue == -1) + alpha = 0; // transparent + + this.value = ((alpha & 0xFF) << 24) | + ((red & 0xFF) << 16) | + ((green & 0xFF) << 8) | + (blue & 0xFF); + } + + public static MapColor fromId(int id) { + return id >= 0 && id < VALUES.length ? VALUES[id] : COLOR_0; + } + + public int getARGB() { + return value; + } +} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/utils/PaintingType.java b/core/src/main/java/org/geysermc/geyser/level/PaintingType.java similarity index 96% rename from connector/src/main/java/org/geysermc/connector/utils/PaintingType.java rename to core/src/main/java/org/geysermc/geyser/level/PaintingType.java index 63f1119c6..952828b17 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/PaintingType.java +++ b/core/src/main/java/org/geysermc/geyser/level/PaintingType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.utils; +package org.geysermc.geyser.level; import lombok.AccessLevel; import lombok.AllArgsConstructor; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java b/core/src/main/java/org/geysermc/geyser/level/WorldManager.java similarity index 75% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java rename to core/src/main/java/org/geysermc/geyser/level/WorldManager.java index aaafe2fe9..9aaf323c0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java +++ b/core/src/main/java/org/geysermc/geyser/level/WorldManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,15 +23,14 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world; +package org.geysermc.geyser.level; -import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; import com.nukkitx.math.vector.Vector3i; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.utils.GameRule; +import com.nukkitx.nbt.NbtMap; +import org.geysermc.geyser.session.GeyserSession; /** * Class that manages or retrieves various information @@ -76,35 +75,40 @@ public abstract class WorldManager { public abstract int getBlockAt(GeyserSession session, int x, int y, int z); /** - * Gets all block states in the specified chunk section. - * - * @param session the session - * @param x the chunk's X coordinate - * @param y the chunk's Y coordinate - * @param z the chunk's Z coordinate - * @param section the chunk section to store the block data in - */ - public abstract void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk section); - - /** - * Checks whether or not this world manager has access to more block data than the chunk cache. + * Checks whether or not this world manager requires a separate chunk cache/has access to more block data than the chunk cache. *

* Some world managers (e.g. Spigot) can provide access to block data outside of the chunk cache, and even with chunk caching disabled. This * method provides a means to check if this manager has this capability. * * @return whether or not this world manager has access to more block data than the chunk cache */ - public abstract boolean hasMoreBlockDataThanChunkCache(); + public abstract boolean hasOwnChunkCache(); /** - * Gets the Java biome data for the specified chunk. + * Sigh.
+ * + * So, on Java Edition, the lectern is an inventory. Java opens it and gets the contents of the book there. + * On Bedrock, the lectern contents are part of the block entity tag. Therefore, Bedrock expects to have the contents + * of the lectern ready and present in the world. If the contents are not there, it takes at least two clicks for the + * lectern to update the tag and then present itself.
+ * + * We solve this problem by querying all loaded lecterns, where possible, and sending their information in a block entity + * tag. * * @param session the session of the player - * @param x the chunk's X coordinate - * @param z the chunk's Z coordinate - * @return the biome data for the specified region with a length of 1024. + * @param x the x coordinate of the lectern + * @param y the y coordinate of the lectern + * @param z the z coordinate of the lectern + * @param isChunkLoad if this is called during a chunk load or not. Changes behavior in certain instances. + * @return the Bedrock lectern block entity tag. This may not be the exact block entity tag - for example, Spigot's + * block handled must be done on the server thread, so we send the tag manually there. */ - public abstract int[] getBiomeDataAt(GeyserSession session, int x, int z); + public abstract NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad); + + /** + * @return whether we should expect lectern data to update, or if we have to fall back on a workaround. + */ + public abstract boolean shouldExpectLecternHandled(); /** * Updates a gamerule value on the Java server diff --git a/core/src/main/java/org/geysermc/geyser/level/block/BlockPositionIterator.java b/core/src/main/java/org/geysermc/geyser/level/block/BlockPositionIterator.java new file mode 100644 index 000000000..0c7235f6c --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/level/block/BlockPositionIterator.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.level.block; + +import com.nukkitx.network.util.Preconditions; +import lombok.Getter; + + +public class BlockPositionIterator { + private final int minX; + private final int minY; + private final int minZ; + + private final int sizeX; + private final int sizeZ; + + private int i = 0; + private final int maxI; + + public BlockPositionIterator(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) { + Preconditions.checkArgument(maxX >= minX, "maxX is not greater than or equal to minX"); + Preconditions.checkArgument(maxY >= minY, "maxY is not greater than or equal to minY"); + Preconditions.checkArgument(maxZ >= minZ, "maxZ is not greater than or equal to minZ"); + + this.minX = minX; + this.minY = minY; + this.minZ = minZ; + + this.sizeX = maxX - minX + 1; + int sizeY = maxY - minY + 1; + this.sizeZ = maxZ - minZ + 1; + this.maxI = sizeX * sizeY * sizeZ; + } + + public boolean hasNext() { + return i < maxI; + } + + public void next() { + // Iterate in zxy order + i++; + } + + public void reset() { + i = 0; + } + + public int getX() { + return ((i / sizeZ) % sizeX) + minX; + } + + public int getY() { + return (i / sizeZ / sizeX) + minY; + } + + public int getZ() { + return (i % sizeZ) + minZ; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java b/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java new file mode 100644 index 000000000..432ea1081 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/level/block/BlockStateValues.java @@ -0,0 +1,449 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.level.block; + +import com.fasterxml.jackson.databind.JsonNode; +import it.unimi.dsi.fastutil.ints.*; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntityTranslator; +import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.registry.type.BlockMapping; +import org.geysermc.geyser.level.physics.Direction; +import org.geysermc.geyser.level.physics.PistonBehavior; +import org.geysermc.geyser.util.collection.FixedInt2ByteMap; +import org.geysermc.geyser.util.collection.FixedInt2IntMap; +import org.geysermc.geyser.util.collection.LecternHasBookMap; + +/** + * Used for block entities if the Java block state contains Bedrock block information. + */ +public final class BlockStateValues { + private static final Int2IntMap BANNER_COLORS = new FixedInt2IntMap(); + private static final Int2ByteMap BED_COLORS = new FixedInt2ByteMap(); + private static final Int2ByteMap COMMAND_BLOCK_VALUES = new Int2ByteOpenHashMap(); + private static final Int2ObjectMap DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>(); + private static final LecternHasBookMap LECTERN_BOOK_STATES = new LecternHasBookMap(); + private static final Int2IntMap NOTEBLOCK_PITCHES = new FixedInt2IntMap(); + private static final Int2BooleanMap PISTON_VALUES = new Int2BooleanOpenHashMap(); + private static final IntSet STICKY_PISTONS = new IntOpenHashSet(); + private static final Object2IntMap PISTON_HEADS = new Object2IntOpenHashMap<>(); + private static final Int2ObjectMap PISTON_ORIENTATION = new Int2ObjectOpenHashMap<>(); + private static final IntSet ALL_PISTON_HEADS = new IntOpenHashSet(); + private static final IntSet MOVING_PISTONS = new IntOpenHashSet(); + private static final Int2ByteMap SKULL_VARIANTS = new FixedInt2ByteMap(); + private static final Int2ByteMap SKULL_ROTATIONS = new Int2ByteOpenHashMap(); + private static final Int2IntMap SKULL_WALL_DIRECTIONS = new Int2IntOpenHashMap(); + private static final Int2ByteMap SHULKERBOX_DIRECTIONS = new FixedInt2ByteMap(); + private static final Int2IntMap WATER_LEVEL = new Int2IntOpenHashMap(); + + public static final int JAVA_AIR_ID = 0; + + public static int JAVA_BELL_ID; + public static int JAVA_COBWEB_ID; + public static int JAVA_FURNACE_ID; + public static int JAVA_FURNACE_LIT_ID; + public static int JAVA_HONEY_BLOCK_ID; + public static int JAVA_SLIME_BLOCK_ID; + public static int JAVA_SPAWNER_ID; + public static int JAVA_WATER_ID; + + /** + * Determines if the block state contains Bedrock block information + * + * @param javaId The Java Identifier of the block + * @param javaBlockState the Java Block State of the block + * @param blockData JsonNode of info about the block from blocks.json + */ + public static void storeBlockStateValues(String javaId, int javaBlockState, JsonNode blockData) { + JsonNode bannerColor = blockData.get("banner_color"); + if (bannerColor != null) { + BANNER_COLORS.put(javaBlockState, (byte) bannerColor.intValue()); + return; // There will never be a banner color and a skull variant + } + + JsonNode bedColor = blockData.get("bed_color"); + if (bedColor != null) { + BED_COLORS.put(javaBlockState, (byte) bedColor.intValue()); + return; + } + + if (javaId.contains("command_block")) { + COMMAND_BLOCK_VALUES.put(javaBlockState, javaId.contains("conditional=true") ? (byte) 1 : (byte) 0); + return; + } + + if (blockData.get("double_chest_position") != null) { + boolean isX = (blockData.get("x") != null); + boolean isDirectionPositive = ((blockData.get("x") != null && blockData.get("x").asBoolean()) || + (blockData.get("z") != null && blockData.get("z").asBoolean())); + boolean isLeft = (blockData.get("double_chest_position").asText().contains("left")); + DOUBLE_CHEST_VALUES.put(javaBlockState, new DoubleChestValue(isX, isDirectionPositive, isLeft)); + return; + } + + if (javaId.startsWith("minecraft:potted_") || javaId.equals("minecraft:flower_pot")) { + String name = javaId.replace("potted_", ""); + if (name.contains("azalea")) { + // Exception to the rule + name = name.replace("_bush", ""); + } + FLOWER_POT_VALUES.put(javaBlockState, name); + return; + } + + if (javaId.startsWith("minecraft:lectern")) { + LECTERN_BOOK_STATES.put(javaBlockState, javaId.contains("has_book=true")); + return; + } + + JsonNode notePitch = blockData.get("note_pitch"); + if (notePitch != null) { + NOTEBLOCK_PITCHES.put(javaBlockState, blockData.get("note_pitch").intValue()); + return; + } + + if (javaId.contains("piston[")) { // minecraft:moving_piston, minecraft:sticky_piston, minecraft:piston + if (javaId.startsWith("minecraft:moving_piston")) { + MOVING_PISTONS.add(javaBlockState); + } else { + PISTON_VALUES.put(javaBlockState, javaId.contains("extended=true")); + } + if (javaId.contains("sticky")) { + STICKY_PISTONS.add(javaBlockState); + } + PISTON_ORIENTATION.put(javaBlockState, getBlockDirection(javaId)); + return; + } else if (javaId.startsWith("minecraft:piston_head")) { + ALL_PISTON_HEADS.add(javaBlockState); + if (javaId.contains("short=false")) { + PISTON_HEADS.put(getBlockDirection(javaId), javaBlockState); + } + return; + } + + JsonNode skullVariation = blockData.get("variation"); + if (skullVariation != null) { + SKULL_VARIANTS.put(javaBlockState, (byte) skullVariation.intValue()); + } + + JsonNode skullRotation = blockData.get("skull_rotation"); + if (skullRotation != null) { + SKULL_ROTATIONS.put(javaBlockState, (byte) skullRotation.intValue()); + } + + if (javaId.contains("wall_skull") || javaId.contains("wall_head")) { + String direction = javaId.substring(javaId.lastIndexOf("facing=") + 7); + int rotation = switch (direction.substring(0, direction.length() - 1)) { + case "north" -> 180; + case "west" -> 90; + case "east" -> 270; + default -> 0; // Also south + }; + SKULL_WALL_DIRECTIONS.put(javaBlockState, rotation); + } + + JsonNode shulkerDirection = blockData.get("shulker_direction"); + if (shulkerDirection != null) { + BlockStateValues.SHULKERBOX_DIRECTIONS.put(javaBlockState, (byte) shulkerDirection.intValue()); + } + + if (javaId.startsWith("minecraft:water")) { + String strLevel = javaId.substring(javaId.lastIndexOf("level=") + 6, javaId.length() - 1); + int level = Integer.parseInt(strLevel); + WATER_LEVEL.put(javaBlockState, level); + } + } + + /** + * Banner colors are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. + * This gives an integer color that Bedrock can use. + * + * @param state BlockState of the block + * @return Banner color integer or -1 if no color + */ + public static int getBannerColor(int state) { + return BANNER_COLORS.getOrDefault(state, -1); + } + + /** + * Bed colors are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. + * This gives a byte color that Bedrock can use - Bedrock needs a byte in the final tag. + * + * @param state BlockState of the block + * @return Bed color byte or -1 if no color + */ + public static byte getBedColor(int state) { + return BED_COLORS.getOrDefault(state, (byte) -1); + } + + /** + * The block state in Java and Bedrock both contain the conditional bit, however command block block entity tags + * in Bedrock need the conditional information. + * + * @return the list of all command blocks and if they are conditional (1 or 0) + */ + public static Int2ByteMap getCommandBlockValues() { + return COMMAND_BLOCK_VALUES; + } + + /** + * All double chest values are part of the block state in Java and part of the block entity tag in Bedrock. + * This gives the DoubleChestValue that can be calculated into the final tag. + * + * @return The map of all DoubleChestValues. + */ + public static Int2ObjectMap getDoubleChestValues() { + return DOUBLE_CHEST_VALUES; + } + + /** + * Get the Int2ObjectMap of flower pot block states to containing plant + * + * @return Int2ObjectMap of flower pot values + */ + public static Int2ObjectMap getFlowerPotValues() { + return FLOWER_POT_VALUES; + } + + /** + * @return the lectern book state map pointing to book present state + */ + public static LecternHasBookMap getLecternBookStates() { + return LECTERN_BOOK_STATES; + } + + /** + * The note that noteblocks output when hit is part of the block state in Java but sent as a BlockEventPacket in Bedrock. + * This gives an integer pitch that Bedrock can use. + * + * @param state BlockState of the block + * @return note block note integer or -1 if not present + */ + public static int getNoteblockPitch(int state) { + return NOTEBLOCK_PITCHES.getOrDefault(state, -1); + } + + /** + * Get the Int2BooleanMap showing if a piston block state is extended or not. + * + * @return the Int2BooleanMap of piston extensions. + */ + public static Int2BooleanMap getPistonValues() { + return PISTON_VALUES; + } + + public static boolean isStickyPiston(int blockState) { + return STICKY_PISTONS.contains(blockState); + } + + public static boolean isPistonHead(int state) { + return ALL_PISTON_HEADS.contains(state); + } + + /** + * Get the Java Block State for a piston head for a specific direction + * This is used in PistonBlockEntity to get the BlockCollision for the piston head. + * + * @param direction Direction the piston head points in + * @return Block state for the piston head + */ + public static int getPistonHead(Direction direction) { + return PISTON_HEADS.getOrDefault(direction, BlockStateValues.JAVA_AIR_ID); + } + + /** + * Check if a block is a minecraft:moving_piston + * This is used in ChunkUtils to prevent them from being placed as it causes + * pistons to flicker and it is not needed + * + * @param state Block state of the block + * @return True if the block is a moving_piston + */ + public static boolean isMovingPiston(int state) { + return MOVING_PISTONS.contains(state); + } + + /** + * This is used in GeyserPistonEvents.java and accepts minecraft:piston, + * minecraft:sticky_piston, and minecraft:moving_piston. + * + * @param state The block state of the piston base + * @return The direction in which the piston faces + */ + public static Direction getPistonOrientation(int state) { + return PISTON_ORIENTATION.get(state); + } + + /** + * Checks if a block sticks to other blocks + * (Slime and honey blocks) + * + * @param state The block state + * @return True if the block sticks to adjacent blocks + */ + public static boolean isBlockSticky(int state) { + return state == JAVA_SLIME_BLOCK_ID || state == JAVA_HONEY_BLOCK_ID; + } + + /** + * Check if two blocks are attached to each other. + * + * @param stateA The block state of block a + * @param stateB The block state of block b + * @return True if the blocks are attached to each other + */ + public static boolean isBlockAttached(int stateA, int stateB) { + boolean aSticky = isBlockSticky(stateA); + boolean bSticky = isBlockSticky(stateB); + if (aSticky && bSticky) { + // Only matching sticky blocks are attached together + // Honey + Honey & Slime + Slime + return stateA == stateB; + } + return aSticky || bSticky; + } + + /** + * @param state The block state of the block + * @return true if a piston can break the block + */ + public static boolean canPistonDestroyBlock(int state) { + return BlockRegistries.JAVA_BLOCKS.getOrDefault(state, BlockMapping.AIR).getPistonBehavior() == PistonBehavior.DESTROY; + } + + public static boolean canPistonMoveBlock(int javaId, boolean isPushing) { + if (javaId == JAVA_AIR_ID) { + return true; + } + // Pistons can only be moved if they aren't extended + if (PistonBlockEntityTranslator.isBlock(javaId)) { + return !PISTON_VALUES.get(javaId); + } + BlockMapping block = BlockRegistries.JAVA_BLOCKS.getOrDefault(javaId, BlockMapping.AIR); + // Bedrock, End portal frames, etc. can't be moved + if (block.getHardness() == -1.0d) { + return false; + } + return switch (block.getPistonBehavior()) { + case BLOCK, DESTROY -> false; + case PUSH_ONLY -> isPushing; // Glazed terracotta can only be pushed + default -> !block.isBlockEntity(); // Pistons can't move block entities + }; + } + + /** + * Skull variations are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. + * This gives a byte variant ID that Bedrock can use. + * + * @param state BlockState of the block + * @return Skull variant byte or -1 if no variant + */ + public static byte getSkullVariant(int state) { + return SKULL_VARIANTS.getOrDefault(state, (byte) -1); + } + + /** + * Skull rotations are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. + * This gives a byte rotation that Bedrock can use. + * + * @param state BlockState of the block + * @return Skull rotation value or -1 if no value + */ + public static byte getSkullRotation(int state) { + return SKULL_ROTATIONS.getOrDefault(state, (byte) -1); + } + + /** + * Skull rotations are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. + * This gives a integer rotation that Bedrock can use. + * + * @return Skull wall rotation value with the blockstate + */ + public static Int2IntMap getSkullWallDirections() { + return SKULL_WALL_DIRECTIONS; + } + + /** + * Shulker box directions are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. + * This gives a byte direction that Bedrock can use. + * + * @param state BlockState of the block + * @return Shulker direction value or -1 if no value + */ + public static byte getShulkerBoxDirection(int state) { + return SHULKERBOX_DIRECTIONS.getOrDefault(state, (byte) -1); + } + + /** + * Get the level of water from the block state. + * This is used in FishingHookEntity to create splash sounds when the hook hits the water. + * + * @param state BlockState of the block + * @return The water level or -1 if the block isn't water + */ + public static int getWaterLevel(int state) { + return WATER_LEVEL.getOrDefault(state, -1); + } + + /** + * Get the slipperiness of a block. + * This is used in ItemEntity to calculate the friction on an item as it slides across the ground + * + * @param state BlockState of the block + * @return The block's slipperiness + */ + public static float getSlipperiness(int state) { + String blockIdentifier = BlockRegistries.JAVA_BLOCKS.getOrDefault(state, BlockMapping.AIR).getJavaIdentifier(); + return switch (blockIdentifier) { + case "minecraft:slime_block" -> 0.8f; + case "minecraft:ice", "minecraft:packed_ice" -> 0.98f; + case "minecraft:blue_ice" -> 0.989f; + default -> 0.6f; + }; + } + + private static Direction getBlockDirection(String javaId) { + if (javaId.contains("down")) { + return Direction.DOWN; + } else if (javaId.contains("up")) { + return Direction.UP; + } else if (javaId.contains("south")) { + return Direction.SOUTH; + } else if (javaId.contains("west")) { + return Direction.WEST; + } else if (javaId.contains("north")) { + return Direction.NORTH; + } else if (javaId.contains("east")) { + return Direction.EAST; + } + throw new IllegalStateException(); + } + + private BlockStateValues() { + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/DoubleChestValue.java b/core/src/main/java/org/geysermc/geyser/level/block/DoubleChestValue.java similarity index 67% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/block/DoubleChestValue.java rename to core/src/main/java/org/geysermc/geyser/level/block/DoubleChestValue.java index 284f8c57a..bd4128abc 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/DoubleChestValue.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/DoubleChestValue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,29 +23,23 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.block; - -import lombok.AllArgsConstructor; +package org.geysermc.geyser.level.block; /** * This stores all values of double chests that are part of the Java block state. */ -@AllArgsConstructor -public class DoubleChestValue { - - /** - * If true, then chest is facing east/west; if false, south/north - */ - public boolean isFacingEast; - - /** - * If true, direction is positive (east/south); if false, direction is negative (west/north) - */ - public boolean isDirectionPositive; - - /** - * If true, chest is the left of a pair; if false, chest is the right of a pair. - */ - public boolean isLeft; +public record DoubleChestValue( + /** + * If true, then chest is facing east/west; if false, south/north + */ + boolean isFacingEast, + /** + * If true, direction is positive (east/south); if false, direction is negative (west/north) + */ + boolean isDirectionPositive, + /** + * If true, chest is the left of a pair; if false, chest is the right of a pair. + */ + boolean isLeft) { } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java b/core/src/main/java/org/geysermc/geyser/level/chunk/BlockStorage.java similarity index 81% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java rename to core/src/main/java/org/geysermc/geyser/level/chunk/BlockStorage.java index f195394db..62d7df413 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java +++ b/core/src/main/java/org/geysermc/geyser/level/chunk/BlockStorage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,16 +23,15 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.chunk; +package org.geysermc.geyser.level.chunk; import com.nukkitx.network.VarInts; import io.netty.buffer.ByteBuf; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; import lombok.Getter; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArray; -import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArrayVersion; +import org.geysermc.geyser.level.chunk.bitarray.BitArray; +import org.geysermc.geyser.level.chunk.bitarray.BitArrayVersion; import java.util.function.IntConsumer; @@ -44,14 +43,14 @@ public class BlockStorage { private final IntList palette; private BitArray bitArray; - public BlockStorage() { - this(BitArrayVersion.V2); + public BlockStorage(int airBlockId) { + this(airBlockId, BitArrayVersion.V2); } - public BlockStorage(BitArrayVersion version) { + public BlockStorage(int airBlockId, BitArrayVersion version) { this.bitArray = version.createArray(SIZE); this.palette = new IntArrayList(16); - this.palette.add(BlockTranslator.BEDROCK_AIR_ID); // Air is at the start of every palette and controls what the default block is in second-layer non-air block spaces. + this.palette.add(airBlockId); // Air is at the start of every palette and controls what the default block is in second-layer non-air block spaces. } public BlockStorage(BitArray bitArray, IntList palette) { @@ -63,10 +62,6 @@ public class BlockStorage { return (version.getId() << 1) | (runtime ? 1 : 0); } - private static BitArrayVersion getVersionFromHeader(byte header) { - return BitArrayVersion.get(header >> 1, true); - } - public int getFullBlock(int index) { return this.palette.getInt(this.bitArray.get(index)); } @@ -83,7 +78,7 @@ public class BlockStorage { buffer.writeIntLE(word); } - VarInts.writeInt(buffer, palette.size()); + bitArray.writeSizeToNetwork(buffer, palette.size()); palette.forEach((IntConsumer) id -> VarInts.writeInt(buffer, id)); } @@ -106,7 +101,7 @@ public class BlockStorage { this.bitArray = newBitArray; } - private int idFor(int runtimeId) { + public int idFor(int runtimeId) { // Set to public so we can reuse the palette ID for biomes int index = this.palette.indexOf(runtimeId); if (index != -1) { return index; diff --git a/core/src/main/java/org/geysermc/geyser/level/chunk/GeyserChunk.java b/core/src/main/java/org/geysermc/geyser/level/chunk/GeyserChunk.java new file mode 100644 index 000000000..365d196a3 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/level/chunk/GeyserChunk.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.level.chunk; + +import com.github.steveice10.mc.protocol.data.game.chunk.DataPalette; + +/** + * Acts as a lightweight chunk class that doesn't store biomes, heightmaps or block entities. + */ +public record GeyserChunk(DataPalette[] sections) { + + public static GeyserChunk from(DataPalette[] sections) { + return new GeyserChunk(sections); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkSection.java b/core/src/main/java/org/geysermc/geyser/level/chunk/GeyserChunkSection.java similarity index 88% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkSection.java rename to core/src/main/java/org/geysermc/geyser/level/chunk/GeyserChunkSection.java index 979b79c93..6daf13996 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkSection.java +++ b/core/src/main/java/org/geysermc/geyser/level/chunk/GeyserChunkSection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,22 +23,22 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.chunk; +package org.geysermc.geyser.level.chunk; import com.nukkitx.network.util.Preconditions; import io.netty.buffer.ByteBuf; -public class ChunkSection { +public class GeyserChunkSection { private static final int CHUNK_SECTION_VERSION = 8; private final BlockStorage[] storage; - public ChunkSection() { - this(new BlockStorage[]{new BlockStorage(), new BlockStorage()}); + public GeyserChunkSection(int airBlockId) { + this(new BlockStorage[]{new BlockStorage(airBlockId), new BlockStorage(airBlockId)}); } - public ChunkSection(BlockStorage[] storage) { + public GeyserChunkSection(BlockStorage[] storage) { this.storage = storage; } @@ -83,12 +83,12 @@ public class ChunkSection { return true; } - public ChunkSection copy() { + public GeyserChunkSection copy() { BlockStorage[] storage = new BlockStorage[this.storage.length]; for (int i = 0; i < storage.length; i++) { storage[i] = this.storage[i].copy(); } - return new ChunkSection(storage); + return new GeyserChunkSection(storage); } public static int blockPosition(int x, int y, int z) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/NibbleArray.java b/core/src/main/java/org/geysermc/geyser/level/chunk/NibbleArray.java similarity index 96% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/NibbleArray.java rename to core/src/main/java/org/geysermc/geyser/level/chunk/NibbleArray.java index 37203377f..43dbd9fdc 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/NibbleArray.java +++ b/core/src/main/java/org/geysermc/geyser/level/chunk/NibbleArray.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.chunk; +package org.geysermc.geyser.level.chunk; import com.nukkitx.network.util.Preconditions; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArray.java b/core/src/main/java/org/geysermc/geyser/level/chunk/bitarray/BitArray.java similarity index 78% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArray.java rename to core/src/main/java/org/geysermc/geyser/level/chunk/bitarray/BitArray.java index d3f8927d2..cf69e697f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArray.java +++ b/core/src/main/java/org/geysermc/geyser/level/chunk/bitarray/BitArray.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,10 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.chunk.bitarray; +package org.geysermc.geyser.level.chunk.bitarray; + +import com.nukkitx.network.VarInts; +import io.netty.buffer.ByteBuf; public interface BitArray { @@ -33,6 +36,13 @@ public interface BitArray { int size(); + /** + * Overridden if the bit array implementation does not require size. + */ + default void writeSizeToNetwork(ByteBuf buffer, int size) { + VarInts.writeInt(buffer, size); + } + int[] getWords(); BitArrayVersion getVersion(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArrayVersion.java b/core/src/main/java/org/geysermc/geyser/level/chunk/bitarray/BitArrayVersion.java similarity index 92% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArrayVersion.java rename to core/src/main/java/org/geysermc/geyser/level/chunk/bitarray/BitArrayVersion.java index 47a73f7c1..cd54ed73f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArrayVersion.java +++ b/core/src/main/java/org/geysermc/geyser/level/chunk/bitarray/BitArrayVersion.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,9 +23,9 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.chunk.bitarray; +package org.geysermc.geyser.level.chunk.bitarray; -import org.geysermc.connector.utils.MathUtils; +import org.geysermc.geyser.util.MathUtils; public enum BitArrayVersion { V16(16, 2, null), @@ -35,7 +35,8 @@ public enum BitArrayVersion { V4(4, 8, V5), V3(3, 10, V4), // 2 bit padding V2(2, 16, V3), - V1(1, 32, V2); + V1(1, 32, V2), + V0(0, 0, V1); private static final BitArrayVersion[] VALUES = values(); @@ -94,6 +95,8 @@ public enum BitArrayVersion { if (this == V3 || this == V5 || this == V6) { // Padded palettes aren't able to use bitwise operations due to their padding. return new PaddedBitArray(this, size, words); + } else if (this == V0) { + return new SingletonBitArray(); } else { return new Pow2BitArray(this, size, words); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/PaddedBitArray.java b/core/src/main/java/org/geysermc/geyser/level/chunk/bitarray/PaddedBitArray.java similarity index 94% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/PaddedBitArray.java rename to core/src/main/java/org/geysermc/geyser/level/chunk/bitarray/PaddedBitArray.java index 3068bd681..3686ed4e0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/PaddedBitArray.java +++ b/core/src/main/java/org/geysermc/geyser/level/chunk/bitarray/PaddedBitArray.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,10 +23,10 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.chunk.bitarray; +package org.geysermc.geyser.level.chunk.bitarray; import com.nukkitx.network.util.Preconditions; -import org.geysermc.connector.utils.MathUtils; +import org.geysermc.geyser.util.MathUtils; import java.util.Arrays; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/Pow2BitArray.java b/core/src/main/java/org/geysermc/geyser/level/chunk/bitarray/Pow2BitArray.java similarity index 95% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/Pow2BitArray.java rename to core/src/main/java/org/geysermc/geyser/level/chunk/bitarray/Pow2BitArray.java index dd679b71b..be5567e15 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/Pow2BitArray.java +++ b/core/src/main/java/org/geysermc/geyser/level/chunk/bitarray/Pow2BitArray.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,10 +23,10 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.chunk.bitarray; +package org.geysermc.geyser.level.chunk.bitarray; import com.nukkitx.network.util.Preconditions; -import org.geysermc.connector.utils.MathUtils; +import org.geysermc.geyser.util.MathUtils; import java.util.Arrays; diff --git a/core/src/main/java/org/geysermc/geyser/level/chunk/bitarray/SingletonBitArray.java b/core/src/main/java/org/geysermc/geyser/level/chunk/bitarray/SingletonBitArray.java new file mode 100644 index 000000000..0c64ad76b --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/level/chunk/bitarray/SingletonBitArray.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.level.chunk.bitarray; + +import io.netty.buffer.ByteBuf; + +public class SingletonBitArray implements BitArray { + public static final SingletonBitArray INSTANCE = new SingletonBitArray(); + + private static final int[] EMPTY_ARRAY = new int[0]; + + public SingletonBitArray() { + } + + @Override + public void set(int index, int value) { + } + + @Override + public int get(int index) { + return 0; + } + + @Override + public int size() { + return 1; + } + + @Override + public void writeSizeToNetwork(ByteBuf buffer, int size) { + // no-op - size is fixed + } + + @Override + public int[] getWords() { + return EMPTY_ARRAY; + } + + @Override + public BitArrayVersion getVersion() { + return BitArrayVersion.V0; + } + + @Override + public SingletonBitArray copy() { + return new SingletonBitArray(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/ping/IGeyserPingPassthrough.java b/core/src/main/java/org/geysermc/geyser/level/physics/Axis.java similarity index 69% rename from connector/src/main/java/org/geysermc/connector/ping/IGeyserPingPassthrough.java rename to core/src/main/java/org/geysermc/geyser/level/physics/Axis.java index 049f85046..d9334368f 100644 --- a/connector/src/main/java/org/geysermc/connector/ping/IGeyserPingPassthrough.java +++ b/core/src/main/java/org/geysermc/geyser/level/physics/Axis.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,19 +23,24 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.ping; +package org.geysermc.geyser.level.physics; -import org.geysermc.connector.common.ping.GeyserPingInfo; +import com.nukkitx.math.vector.Vector3d; -/** - * Interface that retrieves ping passthrough information from the Java server - */ -public interface IGeyserPingPassthrough { +public enum Axis { + X, Y, Z; + + public static final Axis[] VALUES = values(); /** - * Get the MOTD of the server displayed on the multiplayer screen - * @return string of the MOTD + * @param vector The vector + * @return The component of the vector in this axis */ - GeyserPingInfo getPingInformation(); - + public double choose(Vector3d vector) { + return switch (this) { + case X -> vector.getX(); + case Y -> vector.getY(); + case Z -> vector.getZ(); + }; + } } diff --git a/core/src/main/java/org/geysermc/geyser/level/physics/BoundingBox.java b/core/src/main/java/org/geysermc/geyser/level/physics/BoundingBox.java new file mode 100644 index 000000000..6600c5abc --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/level/physics/BoundingBox.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.level.physics; + +import com.nukkitx.math.vector.Vector3d; +import lombok.*; + +@Data +@AllArgsConstructor +public class BoundingBox implements Cloneable { + private double middleX; + private double middleY; + private double middleZ; + + private double sizeX; + private double sizeY; + private double sizeZ; + + public void translate(double x, double y, double z) { + middleX += x; + middleY += y; + middleZ += z; + } + + public void extend(double x, double y, double z) { + middleX += x / 2; + middleY += y / 2; + middleZ += z / 2; + + sizeX += Math.abs(x); + sizeY += Math.abs(y); + sizeZ += Math.abs(z); + } + + public void extend(Vector3d extend) { + extend(extend.getX(), extend.getY(), extend.getZ()); + } + + public boolean checkIntersection(double offsetX, double offsetY, double offsetZ, BoundingBox otherBox) { + return (Math.abs((middleX + offsetX) - otherBox.getMiddleX()) * 2 < (sizeX + otherBox.getSizeX())) && + (Math.abs((middleY + offsetY) - otherBox.getMiddleY()) * 2 < (sizeY + otherBox.getSizeY())) && + (Math.abs((middleZ + offsetZ) - otherBox.getMiddleZ()) * 2 < (sizeZ + otherBox.getSizeZ())); + } + + public boolean checkIntersection(Vector3d offset, BoundingBox otherBox) { + return checkIntersection(offset.getX(), offset.getY(), offset.getZ(), otherBox); + } + + public Vector3d getMin() { + double x = middleX - sizeX / 2; + double y = middleY - sizeY / 2; + double z = middleZ - sizeZ / 2; + return Vector3d.from(x, y, z); + } + + public Vector3d getMax() { + double x = middleX + sizeX / 2; + double y = middleY + sizeY / 2; + double z = middleZ + sizeZ / 2; + return Vector3d.from(x, y, z); + } + + public Vector3d getBottomCenter() { + return Vector3d.from(middleX, middleY - sizeY / 2, middleZ); + } + + private boolean checkOverlapInAxis(double xOffset, double yOffset, double zOffset, BoundingBox otherBox, Axis axis) { + return switch (axis) { + case X -> Math.abs((middleX + xOffset) - otherBox.getMiddleX()) * 2 < (sizeX + otherBox.getSizeX()); + case Y -> Math.abs((middleY + yOffset) - otherBox.getMiddleY()) * 2 < (sizeY + otherBox.getSizeY()); + case Z -> Math.abs((middleZ + zOffset) - otherBox.getMiddleZ()) * 2 < (sizeZ + otherBox.getSizeZ()); + }; + } + + /** + * Find the maximum offset of another bounding box in an axis that will not collide with this bounding box + * + * @param xOffset The x offset of this bounding box + * @param yOffset The y offset of this bounding box + * @param zOffset The z offset of this bounding box + * @param otherBoundingBox The bounding box that is moving + * @param axis The axis of movement + * @param offset The current max offset + * @return The new max offset + */ + public double getMaxOffset(double xOffset, double yOffset, double zOffset, BoundingBox otherBoundingBox, Axis axis, double offset) { + // Make sure that the bounding box overlaps in the other axes + for (Axis a : Axis.VALUES) { + if (a != axis && !checkOverlapInAxis(xOffset, yOffset, zOffset, otherBoundingBox, a)) { + return offset; + } + } + if (offset > 0) { + double min = axis.choose(getMin().add(xOffset, yOffset, zOffset)); + double max = axis.choose(otherBoundingBox.getMax()); + if ((min - max) >= -2.0 * CollisionManager.COLLISION_TOLERANCE) { + offset = Math.min(min - max, offset); + } + } else if (offset < 0) { + double min = axis.choose(otherBoundingBox.getMin()); + double max = axis.choose(getMax().add(xOffset, yOffset, zOffset)); + if ((min - max) >= -2.0 * CollisionManager.COLLISION_TOLERANCE) { + offset = Math.max(max - min, offset); + } + } + return offset; + } + + /** + * Get the distance required to move this bounding box to one of otherBoundingBox's sides + * + * @param otherBoundingBox The stationary bounding box + * @param side The side of otherBoundingBox to snap this bounding box to + * @return The distance to move in the direction of {@code side} + */ + public double getIntersectionSize(BoundingBox otherBoundingBox, Direction side) { + return switch (side) { + case DOWN -> getMax().getY() - otherBoundingBox.getMin().getY(); + case UP -> otherBoundingBox.getMax().getY() - getMin().getY(); + case NORTH -> getMax().getZ() - otherBoundingBox.getMin().getZ(); + case SOUTH -> otherBoundingBox.getMax().getZ() - getMin().getZ(); + case WEST -> getMax().getX() - otherBoundingBox.getMin().getX(); + case EAST -> otherBoundingBox.getMax().getX() - getMin().getX(); + }; + } + + @SneakyThrows(CloneNotSupportedException.class) + @Override + public BoundingBox clone() { + BoundingBox clone = (BoundingBox) super.clone(); + clone.middleX = middleX; + clone.middleY = middleY; + clone.middleZ = middleZ; + + clone.sizeX = sizeX; + clone.sizeY = sizeY; + clone.sizeZ = sizeZ; + return clone; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java b/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java new file mode 100644 index 000000000..5bd72182f --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java @@ -0,0 +1,413 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.level.physics; + +import com.nukkitx.math.vector.Vector3d; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; +import lombok.Getter; +import lombok.Setter; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.PistonCache; +import org.geysermc.geyser.translator.collision.BlockCollision; +import org.geysermc.geyser.translator.collision.ScaffoldingCollision; +import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.level.block.BlockPositionIterator; +import org.geysermc.geyser.util.BlockUtils; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; + +public class CollisionManager { + + private final GeyserSession session; + + @Getter + private final BoundingBox playerBoundingBox; + + /** + * Whether the player is inside scaffolding + */ + @Setter + private boolean touchingScaffolding; + + /** + * Whether the player is on top of scaffolding + */ + @Setter + private boolean onScaffolding; + + /** + * Additional space where blocks are checked, which is helpful for fixing NoCheatPlus's Passable check. + * This check doesn't allow players right up against the block, so they must be pushed slightly away. + */ + public static final double COLLISION_TOLERANCE = 0.00001; + /** + * Trims Y coordinates when jumping to prevent rounding issues being sent to the server. + * The locale used is necessary so other regions don't use , as their decimal separator. + */ + private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.#####", new DecimalFormatSymbols(Locale.ENGLISH)); + + private static final double PLAYER_STEP_UP = 0.6; + + /** + * The maximum squared distance between a Bedrock players' movement and our predicted movement before + * the player is teleported to the correct position + */ + private static final double INCORRECT_MOVEMENT_THRESHOLD = 0.08; + + public CollisionManager(GeyserSession session) { + this.session = session; + this.playerBoundingBox = new BoundingBox(0, 0, 0, 0.6, 1.8, 0.6); + } + + /** + * Updates the stored bounding box + * @param position The new position of the player + */ + public void updatePlayerBoundingBox(Vector3f position) { + updatePlayerBoundingBox(position.toDouble()); + } + + /** + * Updates the stored bounding box + * @param position The new position of the player + */ + public void updatePlayerBoundingBox(Vector3d position) { + updatePlayerBoundingBox(); + + playerBoundingBox.setMiddleX(position.getX()); + playerBoundingBox.setMiddleY(position.getY() + (playerBoundingBox.getSizeY() / 2)); + playerBoundingBox.setMiddleZ(position.getZ()); + } + + /** + * Updates the height of the stored bounding box + */ + public void updatePlayerBoundingBox() { + // According to the Minecraft Wiki, when sneaking: + // - In Bedrock Edition, the height becomes 1.65 blocks, allowing movement through spaces as small as 1.75 (2 - 1⁄4) blocks high. + // - In Java Edition, the height becomes 1.5 blocks. + // Other instances have the player's bounding box become as small as 0.6 or 0.2. + double playerHeight = session.getPlayerEntity().getBoundingBoxHeight(); + playerBoundingBox.setMiddleY(playerBoundingBox.getMiddleY() - (playerBoundingBox.getSizeY() / 2.0) + (playerHeight / 2.0)); + playerBoundingBox.setSizeY(playerHeight); + } + + /** + * Adjust the Bedrock position before sending to the Java server to account for inaccuracies in movement between + * the two versions. Will also send corrected movement packets back to Bedrock if they collide with pistons. + * + * @param bedrockPosition the current Bedrock position of the client + * @param onGround whether the Bedrock player is on the ground + * @param teleported whether the Bedrock player has teleported to a new position. If true, movement correction is skipped. + * @return the position to send to the Java server, or null to cancel sending the packet + */ + public Vector3d adjustBedrockPosition(Vector3f bedrockPosition, boolean onGround, boolean teleported) { + PistonCache pistonCache = session.getPistonCache(); + // Bedrock clients tend to fall off of honey blocks, so we need to teleport them to the new position + if (pistonCache.isPlayerAttachedToHoney()) { + return null; + } + // We need to parse the float as a string since casting a float to a double causes us to + // lose precision and thus, causes players to get stuck when walking near walls + double javaY = bedrockPosition.getY() - EntityDefinitions.PLAYER.offset(); + + Vector3d position = Vector3d.from(Double.parseDouble(Float.toString(bedrockPosition.getX())), javaY, + Double.parseDouble(Float.toString(bedrockPosition.getZ()))); + + Vector3d startingPos = playerBoundingBox.getBottomCenter(); + Vector3d movement = position.sub(startingPos); + Vector3d adjustedMovement = correctPlayerMovement(movement, false, teleported); + playerBoundingBox.translate(adjustedMovement.getX(), adjustedMovement.getY(), adjustedMovement.getZ()); + playerBoundingBox.translate(pistonCache.getPlayerMotion().getX(), pistonCache.getPlayerMotion().getY(), pistonCache.getPlayerMotion().getZ()); + // Correct player position + if (!correctPlayerPosition()) { + // Cancel the movement if it needs to be cancelled + recalculatePosition(); + return null; + } + // The server can't complain about our movement if we never send it + // TODO get rid of this and handle teleports smoothly + if (pistonCache.isPlayerCollided()) { + return null; + } + + position = playerBoundingBox.getBottomCenter(); + + boolean newOnGround = adjustedMovement.getY() != movement.getY() && movement.getY() < 0 || onGround; + // Send corrected position to Bedrock if they differ by too much to prevent de-syncs + if (onGround != newOnGround || movement.distanceSquared(adjustedMovement) > INCORRECT_MOVEMENT_THRESHOLD) { + PlayerEntity playerEntity = session.getPlayerEntity(); + if (pistonCache.getPlayerMotion().equals(Vector3f.ZERO) && !pistonCache.isPlayerSlimeCollision()) { + playerEntity.moveAbsolute(position.toFloat(), playerEntity.getYaw(), playerEntity.getPitch(), playerEntity.getHeadYaw(), newOnGround, true); + } + } + + if (!onGround) { + // Trim the position to prevent rounding errors that make Java think we are clipping into a block + position = Vector3d.from(position.getX(), Double.parseDouble(DECIMAL_FORMAT.format(position.getY())), position.getZ()); + } + + return position; + } + + // TODO: This makes the player look upwards for some reason, rotation values must be wrong + public void recalculatePosition() { + PlayerEntity entity = session.getPlayerEntity(); + // Gravity might need to be reset... + entity.updateBedrockMetadata(); // TODO may not be necessary + + MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); + movePlayerPacket.setRuntimeEntityId(entity.getGeyserId()); + movePlayerPacket.setPosition(entity.getPosition()); + movePlayerPacket.setRotation(entity.getBedrockRotation()); + movePlayerPacket.setMode(MovePlayerPacket.Mode.NORMAL); + session.sendUpstreamPacket(movePlayerPacket); + } + + public BlockPositionIterator collidableBlocksIterator(BoundingBox box) { + Vector3d position = Vector3d.from(box.getMiddleX(), + box.getMiddleY() - (box.getSizeY() / 2), + box.getMiddleZ()); + + // Expand volume by 1 in each direction to include moving blocks + double pistonExpand = session.getPistonCache().getPistons().isEmpty() ? 0 : 1; + + // Loop through all blocks that could collide + int minCollisionX = (int) Math.floor(position.getX() - ((box.getSizeX() / 2) + COLLISION_TOLERANCE + pistonExpand)); + int maxCollisionX = (int) Math.floor(position.getX() + (box.getSizeX() / 2) + COLLISION_TOLERANCE + pistonExpand); + + // Y extends 0.5 blocks down because of fence hitboxes + int minCollisionY = (int) Math.floor(position.getY() - 0.5 - COLLISION_TOLERANCE - pistonExpand / 2.0); + int maxCollisionY = (int) Math.floor(position.getY() + box.getSizeY() + pistonExpand); + + int minCollisionZ = (int) Math.floor(position.getZ() - ((box.getSizeZ() / 2) + COLLISION_TOLERANCE + pistonExpand)); + int maxCollisionZ = (int) Math.floor(position.getZ() + (box.getSizeZ() / 2) + COLLISION_TOLERANCE + pistonExpand); + + return new BlockPositionIterator(minCollisionX, minCollisionY, minCollisionZ, maxCollisionX, maxCollisionY, maxCollisionZ); + } + + public BlockPositionIterator playerCollidableBlocksIterator() { + return collidableBlocksIterator(playerBoundingBox); + } + + /** + * Returns false if the movement is invalid, and in this case it shouldn't be sent to the server and should be + * cancelled + * See {@link BlockCollision#correctPosition(GeyserSession, int, int, int, BoundingBox)} for more info + */ + public boolean correctPlayerPosition() { + + // These may be set to true by the correctPosition method in ScaffoldingCollision + touchingScaffolding = false; + onScaffolding = false; + + // Used when correction code needs to be run before the main correction + BlockPositionIterator iter = session.getCollisionManager().playerCollidableBlocksIterator(); + for (; iter.hasNext(); iter.next()) { + BlockCollision blockCollision = BlockUtils.getCollisionAt(session, iter.getX(), iter.getY(), iter.getZ()); + if (blockCollision != null) { + blockCollision.beforeCorrectPosition(iter.getX(), iter.getY(), iter.getZ(), playerBoundingBox); + } + } + + // Main correction code + for (iter.reset(); iter.hasNext(); iter.next()) { + BlockCollision blockCollision = BlockUtils.getCollisionAt(session, iter.getX(), iter.getY(), iter.getZ()); + if (blockCollision != null) { + if (!blockCollision.correctPosition(session, iter.getX(), iter.getY(), iter.getZ(), playerBoundingBox)) { + return false; + } + } + } + + updateScaffoldingFlags(true); + + return true; + } + + public Vector3d correctPlayerMovement(Vector3d movement, boolean checkWorld, boolean teleported) { + // On the teleported check: see https://github.com/GeyserMC/Geyser/issues/2540 + // As of this commit we don't know how it happens but we don't need to check movement here anyway in that case + if (teleported || (!checkWorld && session.getPistonCache().getPistons().isEmpty())) { // There is nothing to check + return movement; + } + return correctMovement(movement, playerBoundingBox, session.getPlayerEntity().isOnGround(), PLAYER_STEP_UP, checkWorld); + } + + public Vector3d correctMovement(Vector3d movement, BoundingBox boundingBox, boolean onGround, double stepUp, boolean checkWorld) { + Vector3d adjustedMovement = movement; + if (!movement.equals(Vector3d.ZERO)) { + adjustedMovement = correctMovementForCollisions(movement, boundingBox, checkWorld); + } + + boolean verticalCollision = adjustedMovement.getY() != movement.getY(); + boolean horizontalCollision = adjustedMovement.getX() != movement.getX() || adjustedMovement.getZ() != movement.getZ(); + boolean falling = movement.getY() < 0; + onGround = onGround || (verticalCollision && falling); + if (onGround && horizontalCollision) { + Vector3d horizontalMovement = Vector3d.from(movement.getX(), 0, movement.getZ()); + Vector3d stepUpMovement = correctMovementForCollisions(horizontalMovement.up(stepUp), boundingBox, checkWorld); + + BoundingBox stretchedBoundingBox = boundingBox.clone(); + stretchedBoundingBox.extend(horizontalMovement); + double maxStepUp = correctMovementForCollisions(Vector3d.from(0, stepUp, 0), stretchedBoundingBox, checkWorld).getY(); + if (maxStepUp < stepUp) { // The player collided with a block above them + boundingBox.translate(0, maxStepUp, 0); + Vector3d adjustedStepUpMovement = correctMovementForCollisions(horizontalMovement, boundingBox, checkWorld); + boundingBox.translate(0, -maxStepUp, 0); + + if (squaredHorizontalLength(adjustedStepUpMovement) > squaredHorizontalLength(stepUpMovement)) { + stepUpMovement = adjustedStepUpMovement.up(maxStepUp); + } + } + + if (squaredHorizontalLength(stepUpMovement) > squaredHorizontalLength(adjustedMovement)) { + boundingBox.translate(stepUpMovement.getX(), stepUpMovement.getY(), stepUpMovement.getZ()); + // Apply the player's remaining vertical movement + double verticalMovement = correctMovementForCollisions(Vector3d.from(0, movement.getY() - stepUpMovement.getY(), 0), boundingBox, checkWorld).getY(); + boundingBox.translate(-stepUpMovement.getX(), -stepUpMovement.getY(), -stepUpMovement.getZ()); + + stepUpMovement = stepUpMovement.up(verticalMovement); + adjustedMovement = stepUpMovement; + } + } + return adjustedMovement; + } + + private double squaredHorizontalLength(Vector3d vector) { + return vector.getX() * vector.getX() + vector.getZ() * vector.getZ(); + } + + private Vector3d correctMovementForCollisions(Vector3d movement, BoundingBox boundingBox, boolean checkWorld) { + double movementX = movement.getX(); + double movementY = movement.getY(); + double movementZ = movement.getZ(); + + BoundingBox movementBoundingBox = boundingBox.clone(); + movementBoundingBox.extend(movement); + BlockPositionIterator iter = collidableBlocksIterator(movementBoundingBox); + if (Math.abs(movementY) > CollisionManager.COLLISION_TOLERANCE) { + movementY = computeCollisionOffset(boundingBox, Axis.Y, movementY, iter, checkWorld); + boundingBox.translate(0, movementY, 0); + } + boolean checkZFirst = Math.abs(movementZ) > Math.abs(movementX); + if (checkZFirst && Math.abs(movementZ) > CollisionManager.COLLISION_TOLERANCE) { + movementZ = computeCollisionOffset(boundingBox, Axis.Z, movementZ, iter, checkWorld); + boundingBox.translate(0, 0, movementZ); + } + if (Math.abs(movementX) > CollisionManager.COLLISION_TOLERANCE) { + movementX = computeCollisionOffset(boundingBox, Axis.X, movementX, iter, checkWorld); + boundingBox.translate(movementX, 0, 0); + } + if (!checkZFirst && Math.abs(movementZ) > CollisionManager.COLLISION_TOLERANCE) { + movementZ = computeCollisionOffset(boundingBox, Axis.Z, movementZ, iter, checkWorld); + boundingBox.translate(0, 0, movementZ); + } + + boundingBox.translate(-movementX, -movementY, -movementZ); + return Vector3d.from(movementX, movementY, movementZ); + } + + private double computeCollisionOffset(BoundingBox boundingBox, Axis axis, double offset, BlockPositionIterator iter, boolean checkWorld) { + for (iter.reset(); iter.hasNext(); iter.next()) { + int x = iter.getX(); + int y = iter.getY(); + int z = iter.getZ(); + if (checkWorld) { + BlockCollision blockCollision = BlockUtils.getCollisionAt(session, x, y, z); + if (blockCollision != null && !(blockCollision instanceof ScaffoldingCollision)) { + offset = blockCollision.computeCollisionOffset(x, y, z, boundingBox, axis, offset); + } + } + offset = session.getPistonCache().computeCollisionOffset(Vector3i.from(x, y, z), boundingBox, axis, offset); + if (Math.abs(offset) < COLLISION_TOLERANCE) { + return 0; + } + } + return offset; + } + + /** + * @return true if the block located at the player's floor position plus 1 would intersect with the player, + * were they not sneaking + */ + public boolean isUnderSlab() { + Vector3i position = session.getPlayerEntity().getPosition().toInt(); + BlockCollision collision = BlockUtils.getCollisionAt(session, position); + if (collision != null) { + // Determine, if the player's bounding box *were* at full height, if it would intersect with the block + // at the current location. + double originalY = playerBoundingBox.getMiddleY(); + double originalHeight = playerBoundingBox.getSizeY(); + double standingY = originalY - (originalHeight / 2.0) + (EntityDefinitions.PLAYER.height() / 2.0); + + playerBoundingBox.setSizeY(EntityDefinitions.PLAYER.height()); + playerBoundingBox.setMiddleY(standingY); + boolean result = collision.checkIntersection(position, playerBoundingBox); + result |= session.getPistonCache().checkCollision(position, playerBoundingBox); + playerBoundingBox.setSizeY(originalHeight); + playerBoundingBox.setMiddleY(originalY); + return result; + } + return false; + } + + /** + * @return if the player is currently in a water block + */ + public boolean isPlayerInWater() { + return session.getGeyser().getWorldManager().getBlockAt(session, session.getPlayerEntity().getPosition().toInt()) == BlockStateValues.JAVA_WATER_ID; + } + + /** + * Updates scaffolding entity flags + * Scaffolding needs to be checked per-move since it's a flag in Bedrock but Java does it client-side + * + * @param updateMetadata whether we should update metadata if something changed + */ + public void updateScaffoldingFlags(boolean updateMetadata) { + Entity entity = session.getPlayerEntity(); + boolean isSneakingWithScaffolding = (touchingScaffolding || onScaffolding) && session.isSneaking(); + + entity.setFlag(EntityFlag.OVER_DESCENDABLE_BLOCK, onScaffolding); + entity.setFlag(EntityFlag.IN_ASCENDABLE_BLOCK, touchingScaffolding); + entity.setFlag(EntityFlag.OVER_SCAFFOLDING, isSneakingWithScaffolding); + + entity.setFlag(EntityFlag.IN_SCAFFOLDING, touchingScaffolding); + + if (updateMetadata) { + session.getPlayerEntity().updateBedrockMetadata(); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/level/physics/Direction.java b/core/src/main/java/org/geysermc/geyser/level/physics/Direction.java new file mode 100644 index 000000000..fa3234460 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/level/physics/Direction.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.level.physics; + +import com.github.steveice10.mc.protocol.data.game.level.block.value.PistonValue; +import com.nukkitx.math.vector.Vector3i; +import lombok.Getter; + +import javax.annotation.Nonnull; + +public enum Direction { + DOWN(1, Vector3i.from(0, -1, 0), Axis.Y, PistonValue.DOWN), + UP(0, Vector3i.UNIT_Y, Axis.Y, PistonValue.UP), + NORTH(3, Vector3i.from(0, 0, -1), Axis.Z, PistonValue.NORTH), + SOUTH(2, Vector3i.UNIT_Z, Axis.Z, PistonValue.SOUTH), + WEST(5, Vector3i.from(-1, 0, 0), Axis.X, PistonValue.WEST), + EAST(4, Vector3i.UNIT_X, Axis.X, PistonValue.EAST); + + public static final Direction[] VALUES = values(); + + private final int reversedId; + @Getter + private final Vector3i unitVector; + @Getter + private final Axis axis; + @Getter + private final PistonValue pistonValue; + + Direction(int reversedId, Vector3i unitVector, Axis axis, PistonValue pistonValue) { + this.reversedId = reversedId; + this.unitVector = unitVector; + this.axis = axis; + this.pistonValue = pistonValue; + } + + public Direction reversed() { + return VALUES[reversedId]; + } + + public boolean isVertical() { + return axis == Axis.Y; + } + + public boolean isHorizontal() { + return axis == Axis.X || axis == Axis.Z; + } + + @Nonnull + public static Direction fromPistonValue(PistonValue pistonValue) { + for (Direction direction : VALUES) { + if (direction.pistonValue == pistonValue) { + return direction; + } + } + throw new IllegalStateException(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/level/physics/PistonBehavior.java b/core/src/main/java/org/geysermc/geyser/level/physics/PistonBehavior.java new file mode 100644 index 000000000..2a818d2d8 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/level/physics/PistonBehavior.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.level.physics; + +import java.util.Locale; + +public enum PistonBehavior { + NORMAL, + BLOCK, + DESTROY, + PUSH_ONLY; + + public static final PistonBehavior[] VALUES = values(); + + public static PistonBehavior getByName(String name) { + String upperCase = name.toUpperCase(Locale.ROOT); + for (PistonBehavior type : VALUES) { + if (type.name().equals(upperCase)) { + return type; + } + } + return NORMAL; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/network/CIDRMatcher.java b/core/src/main/java/org/geysermc/geyser/network/CIDRMatcher.java new file mode 100644 index 000000000..b29b12873 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/network/CIDRMatcher.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.network; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +/* + * Taken & modified from TCPShield, licensed under MIT. See https://github.com/TCPShield/RealIP/blob/master/LICENSE + * + * https://github.com/TCPShield/RealIP/blob/32d422a9523cb6e25b571072851f3306bb8bbc4f/src/main/java/net/tcpshield/tcpshield/validation/cidr/CIDRMatcher.java + */ +public class CIDRMatcher { + private final int maskBits; + private final int maskBytes; + private final boolean simpleCIDR; + private final InetAddress cidrAddress; + + public CIDRMatcher(String ipAddress) { + String[] split = ipAddress.split("/", 2); + + String parsedIPAddress; + if (split.length == 2) { + parsedIPAddress = split[0]; + + this.maskBits = Integer.parseInt(split[1]); + this.simpleCIDR = maskBits == 32; + } else { + parsedIPAddress = ipAddress; + + this.maskBits = -1; + this.simpleCIDR = true; + } + + this.maskBytes = simpleCIDR ? -1 : maskBits / 8; + + try { + cidrAddress = InetAddress.getByName(parsedIPAddress); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } + + public boolean matches(InetAddress inetAddress) { + // check if IP is IPv4 or IPv6 + if (cidrAddress.getClass() != inetAddress.getClass()) { + return false; + } + + // check for equality if it's a simple CIDR + if (simpleCIDR) { + return inetAddress.equals(cidrAddress); + } + + byte[] inetAddressBytes = inetAddress.getAddress(); + byte[] requiredAddressBytes = cidrAddress.getAddress(); + + byte finalByte = (byte) (0xFF00 >> (maskBits & 0x07)); + + for (int i = 0; i < maskBytes; i++) { + if (inetAddressBytes[i] != requiredAddressBytes[i]) { + return false; + } + } + + if (finalByte != 0) { + return (inetAddressBytes[maskBytes] & finalByte) == (requiredAddressBytes[maskBytes] & finalByte); + } + + return true; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java b/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java new file mode 100644 index 000000000..97ed35785 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.network; + +import com.nukkitx.protocol.bedrock.BedrockPong; +import com.nukkitx.protocol.bedrock.BedrockServerEventHandler; +import com.nukkitx.protocol.bedrock.BedrockServerSession; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.DefaultEventLoopGroup; +import io.netty.channel.socket.DatagramPacket; +import io.netty.util.concurrent.DefaultThreadFactory; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.ping.GeyserPingInfo; +import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.geyser.ping.IGeyserPingPassthrough; +import org.geysermc.geyser.text.GeyserLocale; + +import javax.annotation.Nonnull; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.List; + +public class ConnectorServerEventHandler implements BedrockServerEventHandler { + /* + The following constants are all used to ensure the ping does not reach a length where it is unparsable by the Bedrock client + */ + private static final int MINECRAFT_VERSION_BYTES_LENGTH = MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion().getBytes(StandardCharsets.UTF_8).length; + private static final int BRAND_BYTES_LENGTH = GeyserImpl.NAME.getBytes(StandardCharsets.UTF_8).length; + /** + * The MOTD, sub-MOTD and Minecraft version ({@link #MINECRAFT_VERSION_BYTES_LENGTH}) combined cannot reach this length. + */ + private static final int MAGIC_RAKNET_LENGTH = 338; + + private final GeyserImpl geyser; + // There is a constructor that doesn't require inputting threads, but older Netty versions don't have it + private final DefaultEventLoopGroup eventLoopGroup = new DefaultEventLoopGroup(0, new DefaultThreadFactory("Geyser player thread")); + + public ConnectorServerEventHandler(GeyserImpl geyser) { + this.geyser = geyser; + } + + @Override + public boolean onConnectionRequest(InetSocketAddress inetSocketAddress) { + List allowedProxyIPs = geyser.getConfig().getBedrock().getProxyProtocolWhitelistedIPs(); + if (geyser.getConfig().getBedrock().isEnableProxyProtocol() && !allowedProxyIPs.isEmpty()) { + boolean isWhitelistedIP = false; + for (CIDRMatcher matcher : geyser.getConfig().getBedrock().getWhitelistedIPsMatchers()) { + if (matcher.matches(inetSocketAddress.getAddress())) { + isWhitelistedIP = true; + break; + } + } + + if (!isWhitelistedIP) { + return false; + } + } + + geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.attempt_connect", inetSocketAddress)); + return true; + } + + @Override + public BedrockPong onQuery(InetSocketAddress inetSocketAddress) { + if (geyser.getConfig().isDebugMode()) { + geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.network.pinged", inetSocketAddress)); + } + + GeyserConfiguration config = geyser.getConfig(); + + GeyserPingInfo pingInfo = null; + if (config.isPassthroughMotd() || config.isPassthroughPlayerCounts()) { + IGeyserPingPassthrough pingPassthrough = geyser.getBootstrap().getGeyserPingPassthrough(); + pingInfo = pingPassthrough.getPingInformation(inetSocketAddress); + } + + BedrockPong pong = new BedrockPong(); + pong.setEdition("MCPE"); + pong.setGameType("Survival"); // Can only be Survival or Creative as of 1.16.210.59 + pong.setNintendoLimited(false); + pong.setProtocolVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()); + pong.setVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()); // Required to not be empty as of 1.16.210.59. Can only contain . and numbers. + pong.setIpv4Port(config.getBedrock().getPort()); + + if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) { + String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n"); + String mainMotd = motd[0]; // First line of the motd. + String subMotd = (motd.length != 1) ? motd[1] : GeyserImpl.NAME; // Second line of the motd if present, otherwise default. + + pong.setMotd(mainMotd.trim()); + pong.setSubMotd(subMotd.trim()); // Trimmed to shift it to the left, prevents the universe from collapsing on us just because we went 2 characters over the text box's limit. + } else { + pong.setMotd(config.getBedrock().getMotd1()); + pong.setSubMotd(config.getBedrock().getMotd2()); + } + + if (config.isPassthroughPlayerCounts() && pingInfo != null) { + pong.setPlayerCount(pingInfo.getPlayers().getOnline()); + pong.setMaximumPlayerCount(pingInfo.getPlayers().getMax()); + } else { + pong.setPlayerCount(geyser.getSessionManager().getSessions().size()); + pong.setMaximumPlayerCount(config.getMaxPlayers()); + } + + // Fallbacks to prevent errors and allow Bedrock to see the server + if (pong.getMotd() == null || pong.getMotd().isBlank()) { + pong.setMotd(GeyserImpl.NAME); + } + if (pong.getSubMotd() == null || pong.getSubMotd().isBlank()) { + // Sub-MOTD cannot be empty as of 1.16.210.59 + pong.setSubMotd(GeyserImpl.NAME); + } + + // The ping will not appear if the MOTD + sub-MOTD is of a certain length. + // We don't know why, though + byte[] motdArray = pong.getMotd().getBytes(StandardCharsets.UTF_8); + int subMotdLength = pong.getSubMotd().getBytes(StandardCharsets.UTF_8).length; + if (motdArray.length + subMotdLength > (MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH)) { + // Shorten the sub-MOTD first since that only appears locally + if (subMotdLength > BRAND_BYTES_LENGTH) { + pong.setSubMotd(GeyserImpl.NAME); + subMotdLength = BRAND_BYTES_LENGTH; + } + if (motdArray.length > (MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH - subMotdLength)) { + // If the top MOTD is still too long, we chop it down + byte[] newMotdArray = new byte[MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH - subMotdLength]; + System.arraycopy(motdArray, 0, newMotdArray, 0, newMotdArray.length); + pong.setMotd(new String(newMotdArray, StandardCharsets.UTF_8)); + } + } + + //Bedrock will not even attempt a connection if the client thinks the server is full + //so we have to fake it not being full + if (pong.getPlayerCount() >= pong.getMaximumPlayerCount()) { + pong.setMaximumPlayerCount(pong.getPlayerCount() + 1); + } + + return pong; + } + + @Override + public void onSessionCreation(@Nonnull BedrockServerSession bedrockServerSession) { + try { + bedrockServerSession.setPacketCodec(MinecraftProtocol.DEFAULT_BEDROCK_CODEC); + bedrockServerSession.setLogging(true); + bedrockServerSession.setCompressionLevel(geyser.getConfig().getBedrock().getCompressionLevel()); + bedrockServerSession.setPacketHandler(new UpstreamPacketHandler(geyser, new GeyserSession(geyser, bedrockServerSession, eventLoopGroup.next()))); + // Set the packet codec to default just in case we need to send disconnect packets. + } catch (Throwable e) { + // Error must be caught or it will be swallowed + geyser.getLogger().error("Error occurred while initializing player!", e); + bedrockServerSession.disconnect(e.getMessage()); + } + } + + @Override + public void onUnhandledDatagram(@Nonnull ChannelHandlerContext ctx, @Nonnull DatagramPacket packet) { + try { + ByteBuf content = packet.content(); + if (QueryPacketHandler.isQueryPacket(content)) { + new QueryPacketHandler(geyser, packet.sender(), content); + } + } catch (Throwable e) { + // Error must be caught or it will be swallowed + if (geyser.getConfig().isDebugMode()) { + geyser.getLogger().error("Error occurred during unhandled datagram!", e); + } + } + } +} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/LoggingPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java similarity index 93% rename from connector/src/main/java/org/geysermc/connector/network/LoggingPacketHandler.java rename to core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java index c41c64c71..0dceed7e9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/LoggingPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,13 +23,13 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network; +package org.geysermc.geyser.network; import com.nukkitx.protocol.bedrock.BedrockPacket; import com.nukkitx.protocol.bedrock.handler.BedrockPacketHandler; import com.nukkitx.protocol.bedrock.packet.*; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.session.GeyserSession; /** * Bare bones implementation of BedrockPacketHandler suitable for extension. @@ -38,17 +38,16 @@ import org.geysermc.connector.network.session.GeyserSession; * packets of interest and limit boilerplate code. */ public class LoggingPacketHandler implements BedrockPacketHandler { + protected final GeyserImpl geyser; + protected final GeyserSession session; - protected GeyserConnector connector; - protected GeyserSession session; - - LoggingPacketHandler(GeyserConnector connector, GeyserSession session) { - this.connector = connector; + LoggingPacketHandler(GeyserImpl geyser, GeyserSession session) { + this.geyser = geyser; this.session = session; } boolean defaultHandler(BedrockPacket packet) { - connector.getLogger().debug("Handled packet: " + packet.getClass().getSimpleName()); + geyser.getLogger().debug("Handled packet: " + packet.getClass().getSimpleName()); return false; } @@ -132,11 +131,6 @@ public class LoggingPacketHandler implements BedrockPacketHandler { return defaultHandler(packet); } - @Override - public boolean handle(EntityFallPacket packet) { - return defaultHandler(packet); - } - @Override public boolean handle(EntityPickRequestPacket packet) { return defaultHandler(packet); @@ -764,9 +758,6 @@ public class LoggingPacketHandler implements BedrockPacketHandler { return defaultHandler(packet); } - // I question if God exists because of this packet - God does not exist if I find out there's a built-in dab - // TODO for the future: redirect this as a /me command - // TODO for the far future: should we have a client mod that handles skins, handle these too @Override public boolean handle(EmoteListPacket packet) { return defaultHandler(packet); @@ -826,4 +817,43 @@ public class LoggingPacketHandler implements BedrockPacketHandler { public boolean handle(PositionTrackingDBServerBroadcastPacket packet) { return defaultHandler(packet); } + + // 1.16.100 new packets + + @Override + public boolean handle(MotionPredictionHintsPacket packet) { + return defaultHandler(packet); + } + + @Override + public boolean handle(AnimateEntityPacket packet) { + return defaultHandler(packet); + } + + @Override + public boolean handle(CameraShakePacket packet) { + return defaultHandler(packet); + } + + @Override + public boolean handle(PlayerFogPacket packet) { + return defaultHandler(packet); + } + + @Override + public boolean handle(CorrectPlayerMovePredictionPacket packet) { + return defaultHandler(packet); + } + + @Override + public boolean handle(ItemComponentPacket packet) { + return defaultHandler(packet); + } + + // 1.16.200 new packet + + @Override + public boolean handle(FilterTextPacket packet) { + return defaultHandler(packet); + } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java new file mode 100644 index 000000000..868f87a5d --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/network/MinecraftProtocol.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.network; + +import com.github.steveice10.mc.protocol.codec.MinecraftCodec; +import com.github.steveice10.mc.protocol.codec.PacketCodec; +import com.nukkitx.protocol.bedrock.BedrockPacketCodec; +import com.nukkitx.protocol.bedrock.v465.Bedrock_v465; +import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; +import com.nukkitx.protocol.bedrock.v475.Bedrock_v475; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.StringJoiner; + +/** + * Contains information about the supported protocols in Geyser. + */ +public final class MinecraftProtocol { + /** + * Default Bedrock codec that should act as a fallback. Should represent the latest available + * release of the game that Geyser supports. + */ + public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v475.V475_CODEC; + /** + * A list of all supported Bedrock versions that can join Geyser + */ + public static final List SUPPORTED_BEDROCK_CODECS = new ArrayList<>(); + + /** + * Java codec that is supported. We only ever support one version for + * Java Edition. + */ + private static final PacketCodec DEFAULT_JAVA_CODEC = MinecraftCodec.CODEC; + + static { + SUPPORTED_BEDROCK_CODECS.add(Bedrock_v465.V465_CODEC); + SUPPORTED_BEDROCK_CODECS.add(Bedrock_v471.V471_CODEC); + SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); + } + + /** + * Gets the {@link BedrockPacketCodec} of the given protocol version. + * @param protocolVersion The protocol version to attempt to find + * @return The packet codec, or null if the client's protocol is unsupported + */ + public static BedrockPacketCodec getBedrockCodec(int protocolVersion) { + for (BedrockPacketCodec packetCodec : SUPPORTED_BEDROCK_CODECS) { + if (packetCodec.getProtocolVersion() == protocolVersion) { + return packetCodec; + } + } + return null; + } + + /** + * Gets the {@link PacketCodec} for Minecraft: Java Edition. + * + * @return the packet codec for Minecraft: Java Edition + */ + public static PacketCodec getJavaCodec() { + return DEFAULT_JAVA_CODEC; + } + + /** + * Gets the supported Minecraft: Java Edition version names. + * + * @return the supported Minecraft: Java Edition version names + */ + public static List getJavaVersions() { + return Arrays.asList("1.18", "1.18.1"); + } + + /** + * Gets the supported Minecraft: Java Edition protocol version. + * + * @return the supported Minecraft: Java Edition protocol version + */ + public static int getJavaProtocolVersion() { + return DEFAULT_JAVA_CODEC.getProtocolVersion(); + } + + /** + * @return a string showing all supported Bedrock versions for this Geyser instance + */ + public static String getAllSupportedBedrockVersions() { + StringJoiner joiner = new StringJoiner(", "); + for (BedrockPacketCodec packetCodec : SUPPORTED_BEDROCK_CODECS) { + joiner.add(packetCodec.getMinecraftVersion()); + } + + return joiner.toString(); + } + + /** + * @return a string showing all supported Java versions for this Geyser instance + */ + public static String getAllSupportedJavaVersions() { + StringJoiner joiner = new StringJoiner(", "); + for (String version : getJavaVersions()) { + joiner.add(version); + } + + return joiner.toString(); + } + + private MinecraftProtocol() { + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/QueryPacketHandler.java similarity index 66% rename from connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java rename to core/src/main/java/org/geysermc/geyser/network/QueryPacketHandler.java index 510bba2d2..c8d1959ef 100644 --- a/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/QueryPacketHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,16 +23,17 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network; +package org.geysermc.geyser.network; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; -import org.geysermc.connector.common.ping.GeyserPingInfo; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.network.translators.chat.MessageTranslator; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.ping.GeyserPingInfo; +import org.geysermc.geyser.translator.text.MessageTranslator; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.ByteBuffer; @@ -42,32 +43,28 @@ import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.HashMap; import java.util.Map; -import java.util.Random; import java.util.concurrent.ThreadLocalRandom; public class QueryPacketHandler { - public static final byte HANDSHAKE = 0x09; public static final byte STATISTICS = 0x00; - private GeyserConnector connector; - private InetSocketAddress sender; - private byte type; - private int sessionId; + private final GeyserImpl geyser; + private final InetSocketAddress sender; + private final byte type; + private final int sessionId; private byte[] token; /** - * The Query packet handler instance + * The Query packet handler instance. The unsigned short magic handshake should already be read at this point, + * and the packet should be verified to have enough buffer space to be a qualified query packet. * - * @param connector Geyser Connector + * @param geyser Geyser * @param sender The Sender IP/Port for the Query * @param buffer The Query data */ - public QueryPacketHandler(GeyserConnector connector, InetSocketAddress sender, ByteBuf buffer) { - if(!isQueryPacket(buffer)) - return; - - this.connector = connector; + public QueryPacketHandler(GeyserImpl geyser, InetSocketAddress sender, ByteBuf buffer) { + this.geyser = geyser; this.sender = sender; this.type = buffer.readByte(); this.sessionId = buffer.readInt(); @@ -82,8 +79,9 @@ public class QueryPacketHandler { * @param buffer Query data * @return if the packet is a query packet */ - private boolean isQueryPacket(ByteBuf buffer) { - return (buffer.readableBytes() >= 2) ? buffer.readUnsignedShort() == 0xFEFD : false; + public static boolean isQueryPacket(ByteBuf buffer) { + // 2 for magic short, 1 for type byte and 4 for session ID int + return buffer.readableBytes() >= (2 + 1 + 4) && buffer.readUnsignedShort() == 0xFEFD; } /** @@ -115,15 +113,18 @@ public class QueryPacketHandler { * Sends the query data to the sender */ private void sendQueryData() { - ByteBuf reply = ByteBufAllocator.DEFAULT.ioBuffer(64); + byte[] gameData = getGameData(); + byte[] playerData = getPlayers(); + + ByteBuf reply = ByteBufAllocator.DEFAULT.ioBuffer(1 + 4 + gameData.length + playerData.length); reply.writeByte(STATISTICS); reply.writeInt(sessionId); // Game Info - reply.writeBytes(getGameData()); + reply.writeBytes(gameData); // Players - reply.writeBytes(getPlayers()); + reply.writeBytes(playerData); sendPacket(reply); } @@ -142,59 +143,55 @@ public class QueryPacketHandler { String maxPlayerCount; String map; - if (connector.getConfig().isPassthroughMotd() || connector.getConfig().isPassthroughPlayerCounts()) { - pingInfo = connector.getBootstrap().getGeyserPingPassthrough().getPingInformation(); + if (geyser.getConfig().isPassthroughMotd() || geyser.getConfig().isPassthroughPlayerCounts()) { + pingInfo = geyser.getBootstrap().getGeyserPingPassthrough().getPingInformation(); } - if (connector.getConfig().isPassthroughMotd() && pingInfo != null) { + if (geyser.getConfig().isPassthroughMotd() && pingInfo != null) { String[] javaMotd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n"); motd = javaMotd[0].trim(); // First line of the motd. } else { - motd = connector.getConfig().getBedrock().getMotd1(); + motd = geyser.getConfig().getBedrock().getMotd1(); } // If passthrough player counts is enabled lets get players from the server - if (connector.getConfig().isPassthroughPlayerCounts() && pingInfo != null) { + if (geyser.getConfig().isPassthroughPlayerCounts() && pingInfo != null) { currentPlayerCount = String.valueOf(pingInfo.getPlayers().getOnline()); maxPlayerCount = String.valueOf(pingInfo.getPlayers().getMax()); } else { - currentPlayerCount = String.valueOf(connector.getPlayers().size()); - maxPlayerCount = String.valueOf(connector.getConfig().getMaxPlayers()); + currentPlayerCount = String.valueOf(geyser.getSessionManager().getSessions().size()); + maxPlayerCount = String.valueOf(geyser.getConfig().getMaxPlayers()); } // If passthrough protocol name is enabled let's get the protocol name from the ping response. - if (connector.getConfig().isPassthroughProtocolName() && pingInfo != null) { - map = String.valueOf((pingInfo.getVersion().getName())); + if (geyser.getConfig().isPassthroughProtocolName() && pingInfo != null) { + map = pingInfo.getVersion().getName(); } else { - map = GeyserConnector.NAME; + map = GeyserImpl.NAME; } // Create a hashmap of all game data needed in the query - Map gameData = new HashMap(); + Map gameData = new HashMap<>(); gameData.put("hostname", motd); gameData.put("gametype", "SMP"); gameData.put("game_id", "MINECRAFT"); - gameData.put("version", BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()); + gameData.put("version", GeyserImpl.NAME + " (" + GeyserImpl.GIT_VERSION + ") " + MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()); gameData.put("plugins", ""); gameData.put("map", map); gameData.put("numplayers", currentPlayerCount); gameData.put("maxplayers", maxPlayerCount); - gameData.put("hostport", String.valueOf(connector.getConfig().getBedrock().getPort())); - gameData.put("hostip", connector.getConfig().getBedrock().getAddress()); + gameData.put("hostport", String.valueOf(geyser.getConfig().getBedrock().getPort())); + gameData.put("hostip", geyser.getConfig().getBedrock().getAddress()); try { - // Blank Buffer Bytes - query.write("GeyserMC".getBytes()); - query.write((byte) 0x00); + writeString(query, "GeyserMC"); query.write((byte) 0x80); query.write((byte) 0x00); // Fills the game data - for(Map.Entry entry : gameData.entrySet()) { - query.write(entry.getKey().getBytes()); - query.write((byte) 0x00); - query.write(entry.getValue().getBytes()); - query.write((byte) 0x00); + for (Map.Entry entry : gameData.entrySet()) { + writeString(query, entry.getKey()); + writeString(query, entry.getValue()); } // Final byte to show the end of the game data @@ -215,20 +212,19 @@ public class QueryPacketHandler { ByteArrayOutputStream query = new ByteArrayOutputStream(); GeyserPingInfo pingInfo = null; - if (connector.getConfig().isPassthroughMotd() || connector.getConfig().isPassthroughPlayerCounts()) { - pingInfo = connector.getBootstrap().getGeyserPingPassthrough().getPingInformation(); + if (geyser.getConfig().isPassthroughMotd() || geyser.getConfig().isPassthroughPlayerCounts()) { + pingInfo = geyser.getBootstrap().getGeyserPingPassthrough().getPingInformation(); } try { // Start the player section - query.write("player_".getBytes()); - query.write(new byte[] { 0x00, 0x00 }); + writeString(query, "player_"); + query.write((byte) 0x00); // Fill player names - if(pingInfo != null) { + if (pingInfo != null) { for (String username : pingInfo.getPlayerList()) { - query.write(username.getBytes()); - query.write((byte) 0x00); + writeString(query, username); } } @@ -241,13 +237,25 @@ public class QueryPacketHandler { } } + /** + * Partially mimics {@link java.io.DataOutputStream#writeBytes(String)} which is what the Minecraft server uses as of 1.17.1. + */ + private void writeString(OutputStream stream, String value) throws IOException { + int length = value.length(); + for (int i = 0; i < length; i++) { + stream.write((byte) value.charAt(i)); + } + // Padding to indicate the end of the string + stream.write((byte) 0x00); + } + /** * Sends a packet to the sender * * @param data packet data */ private void sendPacket(ByteBuf data) { - connector.getBedrockServer().getRakNet().send(sender, data); + geyser.getBedrockServer().getRakNet().send(sender, data); } /** @@ -256,7 +264,7 @@ public class QueryPacketHandler { public void regenerateToken() { byte[] token = new byte[16]; for (int i = 0; i < 16; i++) { - token[i] = (byte) new Random().nextInt(255); + token[i] = (byte) ThreadLocalRandom.current().nextInt(255); } this.token = token; diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java similarity index 58% rename from connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java rename to core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java index f4e3ca214..781f58ef5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,69 +23,96 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network; +package org.geysermc.geyser.network; import com.nukkitx.protocol.bedrock.BedrockPacket; -import com.nukkitx.protocol.bedrock.data.ResourcePackType; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; +import com.nukkitx.protocol.bedrock.data.ExperimentData; +import com.nukkitx.protocol.bedrock.data.ResourcePackType; import com.nukkitx.protocol.bedrock.packet.*; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.common.AuthType; -import org.geysermc.connector.configuration.GeyserConfiguration; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslatorRegistry; -import org.geysermc.connector.utils.LanguageUtils; -import org.geysermc.connector.utils.LoginEncryptionUtils; -import org.geysermc.connector.utils.MathUtils; -import org.geysermc.connector.utils.ResourcePack; -import org.geysermc.connector.utils.ResourcePackManifest; -import org.geysermc.connector.utils.SettingsUtils; -import org.geysermc.connector.utils.StatisticsUtils; +import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.pack.ResourcePack; +import org.geysermc.geyser.pack.ResourcePackManifest; +import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.util.*; import java.io.FileInputStream; import java.io.InputStream; public class UpstreamPacketHandler extends LoggingPacketHandler { - public UpstreamPacketHandler(GeyserConnector connector, GeyserSession session) { - super(connector, session); + public UpstreamPacketHandler(GeyserImpl geyser, GeyserSession session) { + super(geyser, session); } private boolean translateAndDefault(BedrockPacket packet) { - return PacketTranslatorRegistry.BEDROCK_TRANSLATOR.translate(packet.getClass(), packet, session); + return Registries.BEDROCK_PACKET_TRANSLATORS.translate(packet.getClass(), packet, session); + } + + @Override + boolean defaultHandler(BedrockPacket packet) { + return translateAndDefault(packet); } @Override public boolean handle(LoginPacket loginPacket) { - BedrockPacketCodec packetCodec = BedrockProtocol.getBedrockCodec(loginPacket.getProtocolVersion()); + if (geyser.isShuttingDown()) { + // Don't allow new players in if we're no longer operating + session.disconnect(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.kick.message")); + return true; + } + + BedrockPacketCodec packetCodec = MinecraftProtocol.getBedrockCodec(loginPacket.getProtocolVersion()); if (packetCodec == null) { - if (loginPacket.getProtocolVersion() > BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { + String supportedVersions = MinecraftProtocol.getAllSupportedBedrockVersions(); + if (loginPacket.getProtocolVersion() > MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { // Too early to determine session locale - session.getConnector().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.outdated.server", BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion())); - session.disconnect(LanguageUtils.getLocaleStringLog("geyser.network.outdated.server", BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion())); + session.getGeyser().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.outdated.server", supportedVersions)); + session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.server", supportedVersions)); return true; - } else if (loginPacket.getProtocolVersion() < BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { - session.getConnector().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.outdated.client", BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion())); - session.disconnect(LanguageUtils.getLocaleStringLog("geyser.network.outdated.client", BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion())); + } else if (loginPacket.getProtocolVersion() < MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { + session.getGeyser().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.outdated.client", supportedVersions)); + session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.client", supportedVersions)); return true; } } session.getUpstream().getSession().setPacketCodec(packetCodec); - LoginEncryptionUtils.encryptPlayerConnection(connector, session, loginPacket); + // Set the block translation based off of version + session.setBlockMappings(BlockRegistries.BLOCKS.forVersion(loginPacket.getProtocolVersion())); + session.setItemMappings(Registries.ITEMS.forVersion(loginPacket.getProtocolVersion())); + + LoginEncryptionUtils.encryptPlayerConnection(session, loginPacket); + + if (session.isClosed()) { + // Can happen if Xbox validation fails + return true; + } PlayStatusPacket playStatus = new PlayStatusPacket(); playStatus.setStatus(PlayStatusPacket.Status.LOGIN_SUCCESS); session.sendUpstreamPacket(playStatus); + geyser.getSessionManager().addPendingSession(session); + ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket(); for(ResourcePack resourcePack : ResourcePack.PACKS.values()) { ResourcePackManifest.Header header = resourcePack.getManifest().getHeader(); - resourcePacksInfo.getResourcePackInfos().add(new ResourcePacksInfoPacket.Entry(header.getUuid().toString(), header.getVersionString(), resourcePack.getFile().length(), "", "", "", false)); + resourcePacksInfo.getResourcePackInfos().add(new ResourcePacksInfoPacket.Entry( + header.getUuid().toString(), header.getVersionString(), resourcePack.getFile().length(), + "", "", "", false, false)); } - resourcePacksInfo.setForcedToAccept(GeyserConnector.getInstance().getConfig().isForceResourcePacks()); + resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().getConfig().isForceResourcePacks()); session.sendUpstreamPacket(resourcePacksInfo); + + GeyserLocale.loadGeyserLocale(session.getLocale()); return true; } @@ -93,8 +120,13 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { public boolean handle(ResourcePackClientResponsePacket packet) { switch (packet.getStatus()) { case COMPLETED: - session.connect(connector.getRemoteServer()); - connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.connect", session.getAuthData().getName())); + if (geyser.getConfig().getRemote().getAuthType() != AuthType.ONLINE) { + session.authenticate(session.getAuthData().name()); + } else if (!couldLoginUserByName(session.getAuthData().name())) { + // We must spawn the white world + session.connect(); + } + geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.connect", session.getAuthData().name())); break; case SEND_PACKS: @@ -129,6 +161,16 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { stackPacket.getResourcePacks().add(new ResourcePackStackPacket.Entry(header.getUuid().toString(), header.getVersionString(), "")); } + if (session.getItemMappings().getFurnaceMinecartData() != null) { + // Allow custom items to work + stackPacket.getExperiments().add(new ExperimentData("data_driven_items", true)); + } + + if (session.getUpstream().getProtocolVersion() <= Bedrock_v471.V471_CODEC.getProtocolVersion()) { + // Allow extended world height in the overworld to work for pre-1.18 clients + stackPacket.getExperiments().add(new ExperimentData("caves_and_cliffs", true)); + } + session.sendUpstreamPacket(stackPacket); break; @@ -142,27 +184,18 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public boolean handle(ModalFormResponsePacket packet) { - if (packet.getFormId() == SettingsUtils.SETTINGS_FORM_ID) { - return SettingsUtils.handleSettingsForm(session, packet.getFormData()); - } else if (packet.getFormId() == StatisticsUtils.STATISTICS_MENU_FORM_ID) { - return StatisticsUtils.handleMenuForm(session, packet.getFormData()); - } else if (packet.getFormId() == StatisticsUtils.STATISTICS_LIST_FORM_ID) { - return StatisticsUtils.handleListForm(session, packet.getFormData()); - } - - return LoginEncryptionUtils.authenticateFromForm(session, connector, packet.getFormId(), packet.getFormData()); + session.executeInEventLoop(() -> session.getFormCache().handleResponse(packet)); + return true; } private boolean couldLoginUserByName(String bedrockUsername) { - if (connector.getConfig().getUserAuths() != null) { - GeyserConfiguration.IUserAuthenticationInfo info = connector.getConfig().getUserAuths().get(bedrockUsername); + if (geyser.getConfig().getUserAuths() != null) { + GeyserConfiguration.IUserAuthenticationInfo info = geyser.getConfig().getUserAuths().get(bedrockUsername); if (info != null) { - connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().getName())); + geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().name())); + session.setMicrosoftAccount(info.isMicrosoftAccount()); session.authenticate(info.getEmail(), info.getPassword()); - - // TODO send a message to bedrock user telling them they are connected (if nothing like a motd - // somes from the Java server w/in a few seconds) return true; } } @@ -170,40 +203,23 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { return false; } - @Override - public boolean handle(SetLocalPlayerAsInitializedPacket packet) { - LanguageUtils.loadGeyserLocale(session.getLocale()); - - if (!session.isLoggedIn() && !session.isLoggingIn() && session.getConnector().getAuthType() == AuthType.ONLINE) { - // TODO it is safer to key authentication on something that won't change (UUID, not username) - if (!couldLoginUserByName(session.getAuthData().getName())) { - LoginEncryptionUtils.showLoginWindow(session); - } - // else we were able to log the user in - } - return translateAndDefault(packet); - } - @Override public boolean handle(MovePlayerPacket packet) { if (session.isLoggingIn()) { SetTitlePacket titlePacket = new SetTitlePacket(); titlePacket.setType(SetTitlePacket.Type.ACTIONBAR); - titlePacket.setText(LanguageUtils.getPlayerLocaleString("geyser.auth.login.wait", session.getLocale())); + titlePacket.setText(GeyserLocale.getPlayerLocaleString("geyser.auth.login.wait", session.getLocale())); titlePacket.setFadeInTime(0); titlePacket.setFadeOutTime(1); titlePacket.setStayTime(2); + titlePacket.setXuid(""); + titlePacket.setPlatformOnlineId(""); session.sendUpstreamPacket(titlePacket); } return translateAndDefault(packet); } - @Override - boolean defaultHandler(BedrockPacket packet) { - return translateAndDefault(packet); - } - @Override public boolean handle(ResourcePackChunkRequestPacket packet) { ResourcePackChunkDataPacket data = new ResourcePackChunkDataPacket(); diff --git a/core/src/main/java/org/geysermc/geyser/network/netty/ChannelWrapper.java b/core/src/main/java/org/geysermc/geyser/network/netty/ChannelWrapper.java new file mode 100644 index 000000000..ec23c4149 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/network/netty/ChannelWrapper.java @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.network.netty; + +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.*; +import io.netty.util.Attribute; +import io.netty.util.AttributeKey; + +import java.net.SocketAddress; + +public class ChannelWrapper implements Channel { + protected final Channel source; + private volatile SocketAddress remoteAddress; + + public ChannelWrapper(Channel channel) { + this.source = channel; + } + + @Override + public SocketAddress localAddress() { + return source.localAddress(); + } + + @Override + public SocketAddress remoteAddress() { + if (remoteAddress == null) { + return source.remoteAddress(); + } + return remoteAddress; + } + + public void remoteAddress(SocketAddress socketAddress) { + remoteAddress = socketAddress; + } + + @Override + public ChannelId id() { + return source.id(); + } + + @Override + public EventLoop eventLoop() { + return source.eventLoop(); + } + + @Override + public Channel parent() { + return source.parent(); + } + + @Override + public ChannelConfig config() { + return source.config(); + } + + @Override + public boolean isOpen() { + return source.isOpen(); + } + + @Override + public boolean isRegistered() { + return source.isRegistered(); + } + + @Override + public boolean isActive() { + return source.isActive(); + } + + @Override + public ChannelMetadata metadata() { + return source.metadata(); + } + + @Override + public ChannelFuture closeFuture() { + return source.closeFuture(); + } + + @Override + public boolean isWritable() { + return source.isWritable(); + } + + @Override + public long bytesBeforeUnwritable() { + return source.bytesBeforeUnwritable(); + } + + @Override + public long bytesBeforeWritable() { + return source.bytesBeforeWritable(); + } + + @Override + public Unsafe unsafe() { + return source.unsafe(); + } + + @Override + public ChannelPipeline pipeline() { + return source.pipeline(); + } + + @Override + public ByteBufAllocator alloc() { + return source.alloc(); + } + + @Override + public ChannelFuture bind(SocketAddress socketAddress) { + return source.bind(socketAddress); + } + + @Override + public ChannelFuture connect(SocketAddress socketAddress) { + return source.connect(socketAddress); + } + + @Override + public ChannelFuture connect(SocketAddress socketAddress, SocketAddress socketAddress1) { + return source.connect(socketAddress, socketAddress1); + } + + @Override + public ChannelFuture disconnect() { + return source.disconnect(); + } + + @Override + public ChannelFuture close() { + return source.disconnect(); + } + + @Override + public ChannelFuture deregister() { + return source.deregister(); + } + + @Override + public ChannelFuture bind(SocketAddress socketAddress, ChannelPromise channelPromise) { + return source.bind(socketAddress, channelPromise); + } + + @Override + public ChannelFuture connect(SocketAddress socketAddress, ChannelPromise channelPromise) { + return source.connect(socketAddress, channelPromise); + } + + @Override + public ChannelFuture connect(SocketAddress socketAddress, SocketAddress socketAddress1, ChannelPromise channelPromise) { + return source.connect(socketAddress, socketAddress1, channelPromise); + } + + @Override + public ChannelFuture disconnect(ChannelPromise channelPromise) { + return source.disconnect(channelPromise); + } + + @Override + public ChannelFuture close(ChannelPromise channelPromise) { + return source.close(channelPromise); + } + + @Override + public ChannelFuture deregister(ChannelPromise channelPromise) { + return source.deregister(channelPromise); + } + + @Override + public Channel read() { + source.read(); + return this; + } + + @Override + public ChannelFuture write(Object o) { + return source.write(o); + } + + @Override + public ChannelFuture write(Object o, ChannelPromise channelPromise) { + return source.write(o, channelPromise); + } + + @Override + public Channel flush() { + return source.flush(); + } + + @Override + public ChannelFuture writeAndFlush(Object o, ChannelPromise channelPromise) { + return source.writeAndFlush(o, channelPromise); + } + + @Override + public ChannelFuture writeAndFlush(Object o) { + return source.writeAndFlush(o); + } + + @Override + public ChannelPromise newPromise() { + return source.newPromise(); + } + + @Override + public ChannelProgressivePromise newProgressivePromise() { + return source.newProgressivePromise(); + } + + @Override + public ChannelFuture newSucceededFuture() { + return source.newSucceededFuture(); + } + + @Override + public ChannelFuture newFailedFuture(Throwable throwable) { + return source.newFailedFuture(throwable); + } + + @Override + public ChannelPromise voidPromise() { + return source.voidPromise(); + } + + @Override + public Attribute attr(AttributeKey attributeKey) { + return source.attr(attributeKey); + } + + @Override + public boolean hasAttr(AttributeKey attributeKey) { + return source.hasAttr(attributeKey); + } + + @Override + public int compareTo(Channel o) { + return source.compareTo(o); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/network/netty/DefaultChannelPipelinePublic.java b/core/src/main/java/org/geysermc/geyser/network/netty/DefaultChannelPipelinePublic.java new file mode 100644 index 000000000..67bbf6427 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/network/netty/DefaultChannelPipelinePublic.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.network.netty; + +import io.netty.channel.Channel; +import io.netty.channel.DefaultChannelPipeline; + +/** + * Exists solely to make DefaultChannelPipeline's protected constructor public + */ +public class DefaultChannelPipelinePublic extends DefaultChannelPipeline { + public DefaultChannelPipelinePublic(Channel channel) { + super(channel); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/network/netty/GeyserInjector.java b/core/src/main/java/org/geysermc/geyser/network/netty/GeyserInjector.java new file mode 100644 index 000000000..b87c41ba6 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/network/netty/GeyserInjector.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.network.netty; + +import io.netty.channel.ChannelFuture; +import lombok.Getter; +import org.geysermc.geyser.GeyserBootstrap; + +import java.net.SocketAddress; + +/** + * Used to inject Geyser clients directly into the server, bypassing the need to implement a complete TCP connection, + * by creating a local channel. + */ +public abstract class GeyserInjector { + /** + * The local channel we can use to inject ourselves into the server without creating a TCP connection. + */ + protected ChannelFuture localChannel; + /** + * The LocalAddress to use to connect to the server without connecting over TCP. + */ + @Getter + protected SocketAddress serverSocketAddress; + + /** + * @param bootstrap the bootstrap of the Geyser instance. + */ + public void initializeLocalChannel(GeyserBootstrap bootstrap) { + if (!bootstrap.getGeyserConfig().isUseDirectConnection()) { + bootstrap.getGeyserLogger().debug("Disabling direct injection!"); + return; + } + + if (this.localChannel != null) { + bootstrap.getGeyserLogger().warning("Geyser attempted to inject into the server connection handler twice! Please ensure you aren't using /reload or any plugin that (re)loads Geyser after the server has started."); + return; + } + + try { + initializeLocalChannel0(bootstrap); + bootstrap.getGeyserLogger().debug("Local injection succeeded!"); + } catch (Exception e) { + e.printStackTrace(); + // If the injector partially worked, undo it + shutdown(); + } + } + + /** + * The method to implement that is called by {@link #initializeLocalChannel(GeyserBootstrap)} wrapped around a try/catch. + */ + protected abstract void initializeLocalChannel0(GeyserBootstrap bootstrap) throws Exception; + + public void shutdown() { + if (localChannel != null && localChannel.channel().isOpen()) { + try { + localChannel.channel().close().sync(); + localChannel = null; + } catch (Exception e) { + e.printStackTrace(); + } + } else if (localChannel != null) { + localChannel = null; + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/network/netty/LocalChannelWithRemoteAddress.java b/core/src/main/java/org/geysermc/geyser/network/netty/LocalChannelWithRemoteAddress.java new file mode 100644 index 000000000..96dcca77b --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/network/netty/LocalChannelWithRemoteAddress.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.network.netty; + +import io.netty.channel.local.LocalChannel; + +import java.net.InetSocketAddress; + +/** + * Client -> server storing the spoofed remote address. + */ +public class LocalChannelWithRemoteAddress extends LocalChannel { + private InetSocketAddress spoofedAddress; + + public InetSocketAddress spoofedRemoteAddress() { + return spoofedAddress; + } + + public void spoofedRemoteAddress(InetSocketAddress socketAddress) { + this.spoofedAddress = socketAddress; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/network/netty/LocalChannelWrapper.java b/core/src/main/java/org/geysermc/geyser/network/netty/LocalChannelWrapper.java new file mode 100644 index 000000000..f80e85d70 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/network/netty/LocalChannelWrapper.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.network.netty; + +import io.netty.channel.DefaultChannelPipeline; +import io.netty.channel.local.LocalChannel; +import io.netty.channel.local.LocalServerChannel; + +import java.net.InetSocketAddress; + +public class LocalChannelWrapper extends LocalChannel { + private final ChannelWrapper wrapper; + /** + * {@link #newChannelPipeline()} is called during super, so this exists until the wrapper can be initialized. + */ + private volatile ChannelWrapper tempWrapper; + + public LocalChannelWrapper() { + wrapper = new ChannelWrapper(this); + } + + public LocalChannelWrapper(LocalServerChannel parent, LocalChannel peer) { + super(parent, peer); + if (tempWrapper == null) { + this.wrapper = new ChannelWrapper(this); + } else { + this.wrapper = tempWrapper; + } + wrapper.remoteAddress(new InetSocketAddress(0)); + } + + public ChannelWrapper wrapper() { + return wrapper; + } + + @Override + protected DefaultChannelPipeline newChannelPipeline() { + if (wrapper != null) { + return new DefaultChannelPipelinePublic(wrapper); + } else if (tempWrapper != null) { + return new DefaultChannelPipelinePublic(tempWrapper); + } else { + tempWrapper = new ChannelWrapper(this); + return new DefaultChannelPipelinePublic(tempWrapper); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/ChestedHorseEntity.java b/core/src/main/java/org/geysermc/geyser/network/netty/LocalServerChannelWrapper.java similarity index 55% rename from connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/ChestedHorseEntity.java rename to core/src/main/java/org/geysermc/geyser/network/netty/LocalServerChannelWrapper.java index 7343f5e84..7bb11e01b 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/ChestedHorseEntity.java +++ b/core/src/main/java/org/geysermc/geyser/network/netty/LocalServerChannelWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,25 +23,24 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living.animal.horse; +package org.geysermc.geyser.network.netty; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; - -public class ChestedHorseEntity extends AbstractHorseEntity { - - public ChestedHorseEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); - } +import io.netty.channel.local.LocalChannel; +import io.netty.channel.local.LocalServerChannel; +/** + * If the incoming channel if an instance of LocalChannelWithRemoteAddress, this server creates a LocalChannelWrapper + * for the other end and attaches the spoofed remote address + */ +public class LocalServerChannelWrapper extends LocalServerChannel { @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 18) { - metadata.getFlags().setFlag(EntityFlag.CHESTED, (boolean) entityMetadata.getValue()); + protected LocalChannel newLocalChannel(LocalChannel peer) { + // LocalChannel here should be an instance of LocalChannelWithRemoteAddress, which we can use to set the "remote address" on the other end + if (peer instanceof LocalChannelWithRemoteAddress) { + LocalChannelWrapper channel = new LocalChannelWrapper(this, peer); + channel.wrapper().remoteAddress(((LocalChannelWithRemoteAddress) peer).spoofedRemoteAddress()); + return channel; } - super.updateBedrockMetadata(entityMetadata, session); + return super.newLocalChannel(peer); } } diff --git a/core/src/main/java/org/geysermc/geyser/network/netty/LocalSession.java b/core/src/main/java/org/geysermc/geyser/network/netty/LocalSession.java new file mode 100644 index 000000000..7fa28ebf2 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/network/netty/LocalSession.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.network.netty; + +import com.github.steveice10.packetlib.BuiltinFlags; +import com.github.steveice10.packetlib.packet.PacketProtocol; +import com.github.steveice10.packetlib.tcp.*; +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.*; +import io.netty.channel.unix.PreferredDirectByteBufAllocator; +import io.netty.handler.codec.haproxy.*; + +import java.net.Inet4Address; +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +/** + * Manages a Minecraft Java session over our LocalChannel implementations. + */ +public final class LocalSession extends TcpSession { + private static DefaultEventLoopGroup DEFAULT_EVENT_LOOP_GROUP; + private static PreferredDirectByteBufAllocator PREFERRED_DIRECT_BYTE_BUF_ALLOCATOR = null; + + private final SocketAddress targetAddress; + private final String clientIp; + + public LocalSession(String host, int port, SocketAddress targetAddress, String clientIp, PacketProtocol protocol) { + super(host, port, protocol); + this.targetAddress = targetAddress; + this.clientIp = clientIp; + } + + @Override + public void connect() { + if (this.disconnected) { + throw new IllegalStateException("Connection has already been disconnected."); + } + + if (DEFAULT_EVENT_LOOP_GROUP == null) { + DEFAULT_EVENT_LOOP_GROUP = new DefaultEventLoopGroup(); + } + + try { + final Bootstrap bootstrap = new Bootstrap(); + bootstrap.channel(LocalChannelWithRemoteAddress.class); + bootstrap.handler(new ChannelInitializer() { + @Override + public void initChannel(LocalChannelWithRemoteAddress channel) { + channel.spoofedRemoteAddress(new InetSocketAddress(clientIp, 0)); + PacketProtocol protocol = getPacketProtocol(); + protocol.newClientSession(LocalSession.this); + + refreshReadTimeoutHandler(channel); + refreshWriteTimeoutHandler(channel); + + ChannelPipeline pipeline = channel.pipeline(); + pipeline.addLast("sizer", new TcpPacketSizer(LocalSession.this, protocol.getPacketHeader().getLengthSize())); + pipeline.addLast("codec", new TcpPacketCodec(LocalSession.this, true)); + pipeline.addLast("manager", LocalSession.this); + + addHAProxySupport(pipeline); + } + }).group(DEFAULT_EVENT_LOOP_GROUP).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getConnectTimeout() * 1000); + + if (PREFERRED_DIRECT_BYTE_BUF_ALLOCATOR != null) { + bootstrap.option(ChannelOption.ALLOCATOR, PREFERRED_DIRECT_BYTE_BUF_ALLOCATOR); + } + + bootstrap.remoteAddress(targetAddress); + + bootstrap.connect().addListener((future) -> { + if (!future.isSuccess()) { + exceptionCaught(null, future.cause()); + } + }); + } catch (Throwable t) { + exceptionCaught(null, t); + } + } + + // TODO duplicate code + private void addHAProxySupport(ChannelPipeline pipeline) { + InetSocketAddress clientAddress = getFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS); + if (getFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, false) && clientAddress != null) { + pipeline.addFirst("proxy-protocol-packet-sender", new ChannelInboundHandlerAdapter() { + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + HAProxyProxiedProtocol proxiedProtocol = clientAddress.getAddress() instanceof Inet4Address ? HAProxyProxiedProtocol.TCP4 : HAProxyProxiedProtocol.TCP6; + InetSocketAddress remoteAddress; + if (ctx.channel().remoteAddress() instanceof InetSocketAddress) { + remoteAddress = (InetSocketAddress) ctx.channel().remoteAddress(); + } else { + remoteAddress = new InetSocketAddress(host, port); + } + ctx.channel().writeAndFlush(new HAProxyMessage( + HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, proxiedProtocol, + clientAddress.getAddress().getHostAddress(), remoteAddress.getAddress().getHostAddress(), + clientAddress.getPort(), remoteAddress.getPort() + )); + ctx.pipeline().remove(this); + ctx.pipeline().remove("proxy-protocol-encoder"); + super.channelActive(ctx); + } + }); + pipeline.addFirst("proxy-protocol-encoder", HAProxyMessageEncoder.INSTANCE); + } + } + + /** + * Should only be called when direct ByteBufs should be preferred. At this moment, this should only be called on BungeeCord. + */ + public static void createDirectByteBufAllocator() { + if (PREFERRED_DIRECT_BYTE_BUF_ALLOCATOR == null) { + PREFERRED_DIRECT_BYTE_BUF_ALLOCATOR = new PreferredDirectByteBufAllocator(); + PREFERRED_DIRECT_BYTE_BUF_ALLOCATOR.updateAllocator(ByteBufAllocator.DEFAULT); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java b/core/src/main/java/org/geysermc/geyser/pack/ResourcePack.java similarity index 68% rename from connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java rename to core/src/main/java/org/geysermc/geyser/pack/ResourcePack.java index 3e9848dbe..1f69fbdbc 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java +++ b/core/src/main/java/org/geysermc/geyser/pack/ResourcePack.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,13 +23,17 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.utils; +package org.geysermc.geyser.pack; -import org.geysermc.connector.GeyserConnector; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.util.FileUtils; +import org.geysermc.geyser.text.GeyserLocale; import java.io.File; import java.util.HashMap; import java.util.Map; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** @@ -55,7 +59,7 @@ public class ResourcePack { * Loop through the packs directory and locate valid resource pack files */ public static void loadPacks() { - File directory = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("packs").toFile(); + File directory = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("packs").toFile(); if (!directory.exists()) { directory.mkdir(); @@ -63,34 +67,43 @@ public class ResourcePack { // As we just created the directory it will be empty return; } - + for (File file : directory.listFiles()) { if (file.getName().endsWith(".zip") || file.getName().endsWith(".mcpack")) { ResourcePack pack = new ResourcePack(); pack.sha256 = FileUtils.calculateSHA256(file); + Stream stream = null; try { ZipFile zip = new ZipFile(file); - zip.stream().forEach((x) -> { + stream = zip.stream(); + stream.forEach((x) -> { if (x.getName().contains("manifest.json")) { try { ResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), ResourcePackManifest.class); + // Sometimes a pack_manifest file is present and not in a valid format, + // but a manifest file is, so we null check through that one + if (manifest.getHeader().getUuid() != null) { + pack.file = file; + pack.manifest = manifest; + pack.version = ResourcePackManifest.Version.fromArray(manifest.getHeader().getVersion()); - pack.file = file; - pack.manifest = manifest; - pack.version = ResourcePackManifest.Version.fromArray(manifest.getHeader().getVersion()); - - PACKS.put(pack.getManifest().getHeader().getUuid().toString(), pack); + PACKS.put(pack.getManifest().getHeader().getUuid().toString(), pack); + } } catch (Exception e) { e.printStackTrace(); } } }); } catch (Exception e) { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.resource_pack.broken", file.getName())); + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", file.getName())); e.printStackTrace(); + } finally { + if (stream != null) { + stream.close(); + } } } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/ResourcePackManifest.java b/core/src/main/java/org/geysermc/geyser/pack/ResourcePackManifest.java similarity index 97% rename from connector/src/main/java/org/geysermc/connector/utils/ResourcePackManifest.java rename to core/src/main/java/org/geysermc/geyser/pack/ResourcePackManifest.java index 6a08c4dbc..1e79b330b 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ResourcePackManifest.java +++ b/core/src/main/java/org/geysermc/geyser/pack/ResourcePackManifest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.utils; +package org.geysermc.geyser.pack; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.EqualsAndHashCode; diff --git a/connector/src/main/java/org/geysermc/connector/ping/GeyserLegacyPingPassthrough.java b/core/src/main/java/org/geysermc/geyser/ping/GeyserLegacyPingPassthrough.java similarity index 71% rename from connector/src/main/java/org/geysermc/connector/ping/GeyserLegacyPingPassthrough.java rename to core/src/main/java/org/geysermc/geyser/ping/GeyserLegacyPingPassthrough.java index 519d847db..9d36b0274 100644 --- a/connector/src/main/java/org/geysermc/connector/ping/GeyserLegacyPingPassthrough.java +++ b/core/src/main/java/org/geysermc/geyser/ping/GeyserLegacyPingPassthrough.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,14 +23,13 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.ping; +package org.geysermc.geyser.ping; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonMappingException; -import com.github.steveice10.mc.protocol.MinecraftConstants; import com.nukkitx.nbt.util.VarInts; -import org.geysermc.connector.common.ping.GeyserPingInfo; -import org.geysermc.connector.GeyserConnector; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.network.MinecraftProtocol; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; @@ -43,34 +42,33 @@ import java.net.SocketTimeoutException; import java.util.concurrent.TimeUnit; public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runnable { + private final GeyserImpl geyser; - private GeyserConnector connector; - - public GeyserLegacyPingPassthrough(GeyserConnector connector) { - this.connector = connector; + public GeyserLegacyPingPassthrough(GeyserImpl geyser) { + this.geyser = geyser; } private GeyserPingInfo pingInfo; /** * Start legacy ping passthrough thread - * @param connector GeyserConnector + * @param geyser Geyser * @return GeyserPingPassthrough, or null if not initialized */ - public static IGeyserPingPassthrough init(GeyserConnector connector) { - if (connector.getConfig().isPassthroughMotd() || connector.getConfig().isPassthroughPlayerCounts()) { - GeyserLegacyPingPassthrough pingPassthrough = new GeyserLegacyPingPassthrough(connector); + public static IGeyserPingPassthrough init(GeyserImpl geyser) { + if (geyser.getConfig().isPassthroughMotd() || geyser.getConfig().isPassthroughPlayerCounts()) { + GeyserLegacyPingPassthrough pingPassthrough = new GeyserLegacyPingPassthrough(geyser); // Ensure delay is not zero - int interval = (connector.getConfig().getPingPassthroughInterval() == 0) ? 1 : connector.getConfig().getPingPassthroughInterval(); - connector.getLogger().debug("Scheduling ping passthrough at an interval of " + interval + " second(s)."); - connector.getGeneralThreadPool().scheduleAtFixedRate(pingPassthrough, 1, interval, TimeUnit.SECONDS); + int interval = (geyser.getConfig().getPingPassthroughInterval() == 0) ? 1 : geyser.getConfig().getPingPassthroughInterval(); + geyser.getLogger().debug("Scheduling ping passthrough at an interval of " + interval + " second(s)."); + geyser.getScheduledThread().scheduleAtFixedRate(pingPassthrough, 1, interval, TimeUnit.SECONDS); return pingPassthrough; } return null; } @Override - public GeyserPingInfo getPingInformation() { + public GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) { return pingInfo; } @@ -78,14 +76,14 @@ public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runn public void run() { try { Socket socket = new Socket(); - String address = connector.getConfig().getRemote().getAddress(); - int port = connector.getConfig().getRemote().getPort(); + String address = geyser.getConfig().getRemote().getAddress(); + int port = geyser.getConfig().getRemote().getPort(); socket.connect(new InetSocketAddress(address, port), 5000); ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream(); DataOutputStream handshake = new DataOutputStream(byteArrayStream); handshake.write(0x0); - VarInts.writeUnsignedInt(handshake, MinecraftConstants.PROTOCOL_VERSION); + VarInts.writeUnsignedInt(handshake, MinecraftProtocol.getJavaProtocolVersion()); VarInts.writeUnsignedInt(handshake, address.length()); handshake.writeBytes(address); handshake.writeShort(port); @@ -110,7 +108,7 @@ public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runn VarInts.readUnsignedInt(dataInputStream); String json = new String(buffer); - this.pingInfo = GeyserConnector.JSON_MAPPER.readValue(json, GeyserPingInfo.class); + this.pingInfo = GeyserImpl.JSON_MAPPER.readValue(json, GeyserPingInfo.class); byteArrayStream.close(); handshake.close(); @@ -119,9 +117,9 @@ public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runn socket.close(); } catch (SocketTimeoutException | ConnectException ex) { this.pingInfo = null; - this.connector.getLogger().debug("Connection timeout for ping passthrough."); + this.geyser.getLogger().debug("Connection timeout for ping passthrough."); } catch (JsonParseException | JsonMappingException ex) { - this.connector.getLogger().error("Failed to parse json when pinging server!", ex); + this.geyser.getLogger().error("Failed to parse json when pinging server!", ex); } catch (IOException e) { e.printStackTrace(); } diff --git a/connector/src/main/java/org/geysermc/connector/common/ping/GeyserPingInfo.java b/core/src/main/java/org/geysermc/geyser/ping/GeyserPingInfo.java similarity index 96% rename from connector/src/main/java/org/geysermc/connector/common/ping/GeyserPingInfo.java rename to core/src/main/java/org/geysermc/geyser/ping/GeyserPingInfo.java index eb2ee46ce..ac3c61980 100644 --- a/connector/src/main/java/org/geysermc/connector/common/ping/GeyserPingInfo.java +++ b/core/src/main/java/org/geysermc/geyser/ping/GeyserPingInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.common.ping; +package org.geysermc.geyser.ping; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; diff --git a/core/src/main/java/org/geysermc/geyser/ping/IGeyserPingPassthrough.java b/core/src/main/java/org/geysermc/geyser/ping/IGeyserPingPassthrough.java new file mode 100644 index 000000000..d2ebbe4ac --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/ping/IGeyserPingPassthrough.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.ping; + +import java.net.Inet4Address; +import java.net.InetSocketAddress; + +/** + * Interface that retrieves ping passthrough information from the Java server + */ +public interface IGeyserPingPassthrough { + + /** + * Get the MOTD of the server displayed on the multiplayer screen. It uses a fake remote, as the remote isn't important in this context. + * + * @return string of the MOTD + */ + default GeyserPingInfo getPingInformation() { + return this.getPingInformation(new InetSocketAddress(Inet4Address.getLoopbackAddress(), 69)); + } + + /** + * Get the MOTD of the server displayed on the multiplayer screen + * + * @param inetSocketAddress the ip address of the client pinging the server + * @return string of the MOTD + */ + GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress); + +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/AbstractMappedRegistry.java b/core/src/main/java/org/geysermc/geyser/registry/AbstractMappedRegistry.java new file mode 100644 index 000000000..ce64ee265 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/AbstractMappedRegistry.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry; + +import org.geysermc.geyser.registry.loader.RegistryLoader; + +import javax.annotation.Nullable; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; + +/** + * An abstract registry holding a map of various registrations as defined by {@link M}. + * The M represents the map class, which can be anything that extends {@link Map}. The + * {@link K} and {@link V} generics are the key and value respectively. + * + * @param the key + * @param the value + * @param the map + */ +public abstract class AbstractMappedRegistry> extends Registry { + protected AbstractMappedRegistry(I input, RegistryLoader registryLoader) { + super(input, registryLoader); + } + + /** + * Returns the value registered by the given key. + * + * @param key the key + * @return the value registered by the given key. + */ + @Nullable + public V get(K key) { + return this.mappings.get(key); + } + + /** + * Returns and maps the value by the given key if present. + * + * @param key the key + * @param mapper the mapper + * @param the type + * @return the mapped value from the given key if present + */ + public Optional map(K key, Function mapper) { + V value = this.get(key); + if (value == null) { + return Optional.empty(); + } else { + return Optional.ofNullable(mapper.apply(value)); + } + } + + /** + * Returns the value registered by the given key or the default value + * specified if null. + * + * @param key the key + * @param defaultValue the default value + * @return the value registered by the given key or the default value + * specified if null. + */ + public V getOrDefault(K key, V defaultValue) { + return this.mappings.getOrDefault(key, defaultValue); + } + + /** + * Registers a new value into this registry with the given key. + * + * @param key the key + * @param value the value + * @return a new value into this registry with the given key. + */ + public V register(K key, V value) { + return this.mappings.put(key, value); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/registry/ArrayRegistry.java b/core/src/main/java/org/geysermc/geyser/registry/ArrayRegistry.java new file mode 100644 index 000000000..5ef0aeba0 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/ArrayRegistry.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry; + +import org.geysermc.geyser.registry.loader.RegistryLoader; + +import javax.annotation.Nullable; +import java.util.function.Supplier; + +/** + * An array registry that stores mappings as an array defined by {@link M}. + * The M represents the value that is to be stored as part of this array. + * + * @param the mapping type + */ +public class ArrayRegistry extends Registry { + + /** + * Creates a new array registry of this class with the given input and + * {@link RegistryLoader}. The input specified is what the registry + * loader needs to take in. + * + * @param input the input + * @param registryLoader the registry loader + */ + protected ArrayRegistry(I input, RegistryLoader registryLoader) { + super(input, registryLoader); + } + + /** + * Returns the value registered by the given index. + * + * @param index the index + * @return the value registered by the given index. + */ + @Nullable + public M get(int index) { + if (index >= this.mappings.length) { + return null; + } + + return this.mappings[index]; + } + + /** + * Returns the value registered by the given index or the default value + * specified if null. + * + * @param index the index + * @param defaultValue the default value + * @return the value registered by the given key or the default value + * specified if null. + */ + public M getOrDefault(int index, M defaultValue) { + M value = this.get(index); + if (value == null) { + return defaultValue; + } + + return value; + } + + /** + * Registers a new value into this registry with the given index. + * + * @param index the index + * @param value the value + * @return a new value into this registry with the given index. + */ + public M register(int index, M value) { + return this.mappings[index] = value; + } + + /** + * Creates a new array registry with the given {@link RegistryLoader} supplier. The + * input type is not specified here, meaning the loader return type is either + * predefined, or the registry is populated at a later point. + * + * @param registryLoader the registry loader supplier + * @param the input type + * @param the returned mappings type + * @return a new registry with the given RegistryLoader supplier + */ + public static ArrayRegistry create(Supplier> registryLoader) { + return new ArrayRegistry<>(null, registryLoader.get()); + } + + /** + * Creates a new array registry with the given {@link RegistryLoader} supplier + * and input. + * + * @param input the input + * @param registryLoader the registry loader supplier + * @param the input type + * @param the returned mappings type + * @return a new registry with the given RegistryLoader supplier + */ + public static ArrayRegistry create(I input, Supplier> registryLoader) { + return new ArrayRegistry<>(input, registryLoader.get()); + } + + /** + * Creates a new array registry with the given {@link RegistryLoader}. The + * input type is not specified here, meaning the loader return type is either + * predefined, or the registry is populated at a later point. + * + * @param registryLoader the registry loader + * @param the input type + * @param the returned mappings type + * @return a new registry with the given RegistryLoader supplier + */ + public static ArrayRegistry create(RegistryLoader registryLoader) { + return new ArrayRegistry<>(null, registryLoader); + } + + /** + * Creates a new array registry with the given {@link RegistryLoader} and input. + * + * @param input the input + * @param registryLoader the registry loader + * @param the input type + * @param the returned mappings type + * @return a new registry with the given RegistryLoader supplier + */ + public static ArrayRegistry create(I input, RegistryLoader registryLoader) { + return new ArrayRegistry<>(input, registryLoader); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/BlockRegistries.java b/core/src/main/java/org/geysermc/geyser/registry/BlockRegistries.java new file mode 100644 index 000000000..98d3aa341 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/BlockRegistries.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry; + +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import org.geysermc.geyser.registry.loader.RegistryLoaders; +import org.geysermc.geyser.registry.populator.BlockRegistryPopulator; +import org.geysermc.geyser.registry.type.BlockMapping; +import org.geysermc.geyser.registry.type.BlockMappings; +import org.geysermc.geyser.util.collection.Object2IntBiMap; + +/** + * Holds all the block registries in Geyser. + */ +public class BlockRegistries { + /** + * A versioned registry which holds {@link BlockMappings} for each version. These block mappings contain + * primarily Bedrock version-specific data. + */ + public static final VersionedRegistry BLOCKS = VersionedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new)); + + /** + * A mapped registry which stores Java to Bedrock block identifiers. + */ + public static final SimpleMappedRegistry JAVA_TO_BEDROCK_IDENTIFIERS = SimpleMappedRegistry.create(RegistryLoaders.empty(Object2ObjectOpenHashMap::new)); + + /** + * A registry which stores Java IDs to {@link BlockMapping}, containing miscellaneous information about + * blocks and their behavior in many cases. + */ + public static final ArrayRegistry JAVA_BLOCKS = ArrayRegistry.create(RegistryLoaders.empty(() -> new BlockMapping[] {})); + + /** + * A (bi)mapped registry containing the Java IDs to identifiers. + */ + public static final MappedRegistry> JAVA_IDENTIFIERS = MappedRegistry.create(RegistryLoaders.empty(Object2IntBiMap::new)); + + /** + * A registry which stores unique Java IDs to its clean identifier + * This is used in the statistics form. + */ + public static final ArrayRegistry CLEAN_JAVA_IDENTIFIERS = ArrayRegistry.create(RegistryLoaders.empty(() -> new String[] {})); + + /** + * A registry containing all the waterlogged blockstates. + */ + public static final SimpleRegistry WATERLOGGED = SimpleRegistry.create(RegistryLoaders.empty(IntOpenHashSet::new)); + + static { + BlockRegistryPopulator.populate(); + } + + public static void init() { + // no-op + } +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/registry/MappedRegistry.java b/core/src/main/java/org/geysermc/geyser/registry/MappedRegistry.java new file mode 100644 index 000000000..9860eda78 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/MappedRegistry.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry; + +import org.geysermc.geyser.registry.loader.RegistryLoader; + +import java.util.Map; +import java.util.function.Supplier; + +/** + * An public registry holding a map of various registrations as defined by {@link M}. + * The M represents the map class, which can be anything that extends {@link Map}. The + * {@link K} and {@link V} generics are the key and value respectively. + * + * @param the key + * @param the value + * @param the map + */ +public class MappedRegistry> extends AbstractMappedRegistry { + protected MappedRegistry(I input, RegistryLoader registryLoader) { + super(input, registryLoader); + } + + /** + * Creates a new mapped registry with the given {@link RegistryLoader}. The + * input type is not specified here, meaning the loader return type is either + * predefined, or the registry is populated at a later point. + * + * @param registryLoader the registry loader + * @param the input + * @param the map key + * @param the map value + * @param the returned mappings type, a map in this case + * @return a new registry with the given RegistryLoader + */ + public static > MappedRegistry create(RegistryLoader registryLoader) { + return new MappedRegistry<>(null, registryLoader); + } + + /** + * Creates a new mapped registry with the given {@link RegistryLoader} and input. + * + * @param input the input + * @param registryLoader the registry loader + * @param the input + * @param the map key + * @param the map value + * @param the returned mappings type, a map in this case + * @return a new registry with the given RegistryLoader + */ + public static > MappedRegistry create(I input, RegistryLoader registryLoader) { + return new MappedRegistry<>(input, registryLoader); + } + + /** + * Creates a new mapped registry with the given {@link RegistryLoader} supplier. + * The input type is not specified here, meaning the loader return type is either + * predefined, or the registry is populated at a later point. + * + * @param registryLoader the registry loader supplier + * @param the input + * @param the map key + * @param the map value + * @param the returned mappings type, a map in this case + * @return a new registry with the given RegistryLoader supplier + */ + public static > MappedRegistry create(Supplier> registryLoader) { + return new MappedRegistry<>(null, registryLoader.get()); + } + + /** + * Creates a new mapped registry with the given {@link RegistryLoader} and input. + * + * @param registryLoader the registry loader + * @param the input + * @param the map key + * @param the map value + * @param the returned mappings type, a map in this case + * @return a new registry with the given RegistryLoader supplier + */ + public static > MappedRegistry create(I input, Supplier> registryLoader) { + return new MappedRegistry<>(input, registryLoader.get()); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java b/core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java new file mode 100644 index 000000000..e74d15001 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry; + +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundTabListPacket; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundLightUpdatePacket; +import com.nukkitx.protocol.bedrock.BedrockPacket; +import io.netty.channel.EventLoop; +import org.geysermc.common.PlatformType; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.loader.RegistryLoaders; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.text.GeyserLocale; + +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Set; + +public class PacketTranslatorRegistry extends AbstractMappedRegistry, PacketTranslator, IdentityHashMap, PacketTranslator>> { + private static final Set> IGNORED_PACKETS = Collections.newSetFromMap(new IdentityHashMap<>()); + + static { + IGNORED_PACKETS.add(ClientboundLightUpdatePacket.class); // Light is handled on Bedrock for us + IGNORED_PACKETS.add(ClientboundTabListPacket.class); // Cant be implemented in Bedrock + } + + protected PacketTranslatorRegistry() { + super(null, RegistryLoaders.empty(IdentityHashMap::new)); + } + + @SuppressWarnings("unchecked") + public

boolean translate(Class clazz, P packet, GeyserSession session) { + if (session.getUpstream().isClosed() || session.isClosed()) { + return false; + } + + PacketTranslator

translator = (PacketTranslator

) this.mappings.get(clazz); + if (translator != null) { + EventLoop eventLoop = session.getEventLoop(); + if (!translator.shouldExecuteInEventLoop() || eventLoop.inEventLoop()) { + translate0(session, translator, packet); + } else { + eventLoop.execute(() -> translate0(session, translator, packet)); + } + return true; + } else { + if ((GeyserImpl.getInstance().getPlatformType() != PlatformType.STANDALONE || !(packet instanceof BedrockPacket)) && !IGNORED_PACKETS.contains(clazz)) { + // Other debug logs already take care of Bedrock packets for us if on standalone + GeyserImpl.getInstance().getLogger().debug("Could not find packet for " + (packet.toString().length() > 25 ? packet.getClass().getSimpleName() : packet)); + } + + return false; + } + } + + private

void translate0(GeyserSession session, PacketTranslator

translator, P packet) { + if (session.isClosed()) { + return; + } + + try { + translator.translate(session, packet); + } catch (Throwable ex) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.network.translator.packet.failed", packet.getClass().getSimpleName()), ex); + ex.printStackTrace(); + } + } + + public static PacketTranslatorRegistry create() { + return new PacketTranslatorRegistry<>(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/Registries.java b/core/src/main/java/org/geysermc/geyser/registry/Registries.java new file mode 100644 index 000000000..ce63c2c5b --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/Registries.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry; + +import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; +import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; +import com.github.steveice10.mc.protocol.data.game.level.event.SoundEvent; +import com.github.steveice10.mc.protocol.data.game.level.particle.ParticleType; +import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; +import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; +import com.github.steveice10.mc.protocol.data.game.statistic.CustomStatistic; +import com.github.steveice10.packetlib.packet.Packet; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.protocol.bedrock.BedrockPacket; +import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; +import com.nukkitx.protocol.bedrock.data.inventory.PotionMixData; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.registry.populator.PacketRegistryPopulator; +import org.geysermc.geyser.translator.collision.BlockCollision; +import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment; +import org.geysermc.geyser.translator.sound.SoundTranslator; +import org.geysermc.geyser.translator.sound.SoundInteractionTranslator; +import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator; +import org.geysermc.geyser.translator.level.event.LevelEventTranslator; +import org.geysermc.geyser.registry.loader.*; +import org.geysermc.geyser.registry.populator.ItemRegistryPopulator; +import org.geysermc.geyser.registry.populator.RecipeRegistryPopulator; +import org.geysermc.geyser.registry.type.EnchantmentData; +import org.geysermc.geyser.registry.type.ItemMappings; +import org.geysermc.geyser.registry.type.ParticleMapping; +import org.geysermc.geyser.registry.type.SoundMapping; + +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.IntFunction; + +/** + * Holds all the common registries in Geyser. + */ +public final class Registries { + /** + * A registry holding a CompoundTag of the known entity identifiers. + */ + public static final SimpleRegistry BEDROCK_ENTITY_IDENTIFIERS = SimpleRegistry.create("bedrock/entity_identifiers.dat", RegistryLoaders.NBT); + + /** + * A registry containing all the Bedrock packet translators. + */ + public static final PacketTranslatorRegistry BEDROCK_PACKET_TRANSLATORS = PacketTranslatorRegistry.create(); + + /** + * A registry holding a CompoundTag of all the known biomes. + */ + public static final SimpleRegistry BIOMES_NBT = SimpleRegistry.create("bedrock/biome_definitions.dat", RegistryLoaders.NBT); + + /** + * A mapped registry which stores Java biome identifiers and their Bedrock biome identifier. + */ + public static final SimpleRegistry> BIOME_IDENTIFIERS = SimpleRegistry.create("mappings/biomes.json", BiomeIdentifierRegistryLoader::new); + + /** + * A mapped registry which stores a block entity identifier to its {@link BlockEntityTranslator}. + */ + public static final SimpleMappedRegistry BLOCK_ENTITIES = SimpleMappedRegistry.create("org.geysermc.geyser.translator.level.block.entity.BlockEntity", BlockEntityRegistryLoader::new); + + /** + * A mapped registry containing which holds block IDs to its {@link BlockCollision}. + */ + public static final SimpleMappedRegistry COLLISIONS = SimpleMappedRegistry.create(Pair.of("org.geysermc.geyser.translator.collision.CollisionRemapper", "mappings/collision.json"), CollisionRegistryLoader::new); + + /** + * A versioned registry which holds a {@link RecipeType} to a corresponding list of {@link CraftingData}. + */ + public static final VersionedRegistry>> CRAFTING_DATA = VersionedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new)); + + /** + * A registry holding data of all the known enchantments. + */ + public static final SimpleMappedRegistry ENCHANTMENTS; + + /** + * A map containing all entity types and their respective Geyser definitions + */ + public static final SimpleMappedRegistry> ENTITY_DEFINITIONS = SimpleMappedRegistry.create(RegistryLoaders.empty(() -> new EnumMap<>(EntityType.class))); + + /** + * A map containing all Java entity identifiers and their respective Geyser definitions + */ + public static final SimpleMappedRegistry> JAVA_ENTITY_IDENTIFIERS = SimpleMappedRegistry.create(RegistryLoaders.empty(Object2ObjectOpenHashMap::new)); + + /** + * A registry containing all the Java packet translators. + */ + public static final PacketTranslatorRegistry JAVA_PACKET_TRANSLATORS = PacketTranslatorRegistry.create(); + + /** + * A versioned registry which holds {@link ItemMappings} for each version. These item mappings contain + * primarily Bedrock version-specific data. + */ + public static final VersionedRegistry ITEMS = VersionedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new)); + + /** + * A mapped registry holding the {@link ParticleType} to a corresponding {@link ParticleMapping}, containing various pieces of + * data primarily for how Bedrock should handle the particle. + */ + public static final SimpleMappedRegistry PARTICLES = SimpleMappedRegistry.create("mappings/particles.json", ParticleTypesRegistryLoader::new); + + /** + * A registry holding all the potion mixes. + */ + public static final SimpleRegistry> POTION_MIXES; + + /** + * A versioned registry holding all the recipes, with the net ID being the key, and {@link Recipe} as the value. + */ + public static final VersionedRegistry> RECIPES = VersionedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new)); + + /** + * A mapped registry holding the available records, with the ID of the record being the key, and the {@link com.nukkitx.protocol.bedrock.data.SoundEvent} + * as the value. + */ + public static final SimpleMappedRegistry RECORDS = SimpleMappedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new)); + + /** + * A mapped registry holding sound identifiers to their corresponding {@link SoundMapping}. + */ + public static final SimpleMappedRegistry SOUNDS = SimpleMappedRegistry.create("mappings/sounds.json", SoundRegistryLoader::new); + + /** + * A mapped registry holding {@link SoundEvent}s to their corresponding {@link LevelEventTranslator}. + */ + public static final SimpleMappedRegistry SOUND_EVENTS = SimpleMappedRegistry.create("mappings/effects.json", SoundEventsRegistryLoader::new); + + /** + * A mapped registry holding {@link SoundTranslator}s to their corresponding {@link SoundInteractionTranslator}. + */ + public static final SimpleMappedRegistry> SOUND_TRANSLATORS = SimpleMappedRegistry.create("org.geysermc.geyser.translator.sound.SoundTranslator", SoundTranslatorRegistryLoader::new); + + public static void init() { + // no-op + } + + static { + PacketRegistryPopulator.populate(); + ItemRegistryPopulator.populate(); + RecipeRegistryPopulator.populate(); + + // Create registries that require other registries to load first + POTION_MIXES = SimpleRegistry.create(PotionMixRegistryLoader::new); + ENCHANTMENTS = SimpleMappedRegistry.create("mappings/enchantments.json", EnchantmentRegistryLoader::new); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/registry/Registry.java b/core/src/main/java/org/geysermc/geyser/registry/Registry.java new file mode 100644 index 000000000..15a18e3b1 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/Registry.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry; + +import org.geysermc.geyser.registry.loader.RegistryLoader; + +import java.util.function.Consumer; + +/** + * A wrapper around a value which is loaded based on the output from the provided + * {@link RegistryLoader}. This class is primarily designed to hold a registration + * of some kind, however no limits are set on what it can hold, as long as the + * specified RegistryLoader returns the same value type that is specified in the + * generic. + * + *

+ * Below, a RegistryLoader is taken in the constructor. RegistryLoaders have two + * generic types: the input, and the output. The input is what it takes in, whether + * it be a string which references to a file, or nothing more than an integer. The + * output is what it generates based on the input, and should be the same type as + * the {@link M} generic specified in the registry. + * + *

+ * Registries can be very simple to create. Here is an example that simply parses a + * number given a string: + * + *

+ * {@code
+ *     public static final SimpleRegistry STRING_TO_INT = SimpleRegistry.create("5", Integer::parseInt);
+ * }
+ * 
+ * + *

+ * This is a simple example which really wouldn't have much of a practical use, + * however it demonstrates a fairly basic use case of how this system works. Typically + * though, the first parameter would be a location of some sort, such as a file path + * where the loader will load the mappings from. The NBT registry is a good reference + * point for something both simple and practical. See {@link Registries#BIOMES_NBT} and + * {@link org.geysermc.geyser.registry.loader.NbtRegistryLoader}. + * + * @param the value being held by the registry + */ +public abstract class Registry { + protected M mappings; + + /** + * Creates a new instance of this class with the given input and + * {@link RegistryLoader}. The input specified is what the registry + * loader needs to take in. + * + * @param input the input + * @param registryLoader the registry loader + * @param the input type + */ + protected Registry(I input, RegistryLoader registryLoader) { + this.mappings = registryLoader.load(input); + } + + /** + * Gets the underlying value held by this registry. + * + * @return the underlying value held by this registry. + */ + public M get() { + return this.mappings; + } + + /** + * Sets the underlying value held by this registry. + * Clears any existing data associated with the previous + * value. + * + * @param mappings the underlying value held by this registry + */ + public void set(M mappings) { + this.mappings = mappings; + } + + /** + * Registers what is specified in the given + * {@link Consumer} into the underlying value. + * + * @param consumer the consumer + */ + public void register(Consumer consumer) { + consumer.accept(this.mappings); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/registry/SimpleMappedRegistry.java b/core/src/main/java/org/geysermc/geyser/registry/SimpleMappedRegistry.java new file mode 100644 index 000000000..9eddab92f --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/SimpleMappedRegistry.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry; + +import org.geysermc.geyser.registry.loader.RegistryLoader; + +import java.util.Map; +import java.util.function.Supplier; + +/** + * A variant of {@link AbstractMappedRegistry} with {@link Map} as the defined type. Unlike + * {@link MappedRegistry}, this registry does not support specifying your own Map class, + * and only permits operations the {@link Map} interface does, unless you manually cast. + * + * @param the key + * @param the value + */ +public class SimpleMappedRegistry extends AbstractMappedRegistry> { + protected SimpleMappedRegistry(I input, RegistryLoader> registryLoader) { + super(input, registryLoader); + } + + /** + * Creates a new mapped registry with the given {@link RegistryLoader}. The + * input type is not specified here, meaning the loader return type is either + * predefined, or the registry is populated at a later point. + * + * @param registryLoader the registry loader + * @param the input + * @param the map key + * @param the map value + * @return a new registry with the given RegistryLoader + */ + public static SimpleMappedRegistry create(RegistryLoader> registryLoader) { + return new SimpleMappedRegistry<>(null, registryLoader); + } + + /** + * Creates a new mapped registry with the given {@link RegistryLoader} and input. + * + * @param input the input + * @param registryLoader the registry loader + * @param the input + * @param the map key + * @param the map value + * @return a new registry with the given RegistryLoader + */ + public static SimpleMappedRegistry create(I input, RegistryLoader> registryLoader) { + return new SimpleMappedRegistry<>(input, registryLoader); + } + + /** + * Creates a new mapped registry with the given {@link RegistryLoader} supplier. + * The input type is not specified here, meaning the loader return type is either + * predefined, or the registry is populated at a later point. + * + * @param registryLoader the registry loader supplier + * @param the input + * @param the map key + * @param the map value + * @return a new registry with the given RegistryLoader supplier + */ + public static SimpleMappedRegistry create(Supplier>> registryLoader) { + return new SimpleMappedRegistry<>(null, registryLoader.get()); + } + + /** + * Creates a new mapped registry with the given {@link RegistryLoader} and input. + * + * @param registryLoader the registry loader + * @param the input + * @param the map key + * @param the map value + * @return a new registry with the given RegistryLoader supplier + */ + public static SimpleMappedRegistry create(I input, Supplier>> registryLoader) { + return new SimpleMappedRegistry<>(input, registryLoader.get()); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/registry/SimpleRegistry.java b/core/src/main/java/org/geysermc/geyser/registry/SimpleRegistry.java new file mode 100644 index 000000000..b39aae450 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/SimpleRegistry.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry; + +import org.geysermc.geyser.registry.loader.RegistryLoader; + +import java.util.function.Supplier; + +/** + * A simple registry with no defined mapping or input type. Designed to allow + * for simple registrations of any given type without restrictions on what + * the input or output can be. + * + * @param the value being held by the registry + */ +public class SimpleRegistry extends Registry { + private SimpleRegistry(I input, RegistryLoader registryLoader) { + super(input, registryLoader); + } + + /** + * Creates a new registry with the given {@link RegistryLoader} supplier. The + * input type is not specified here, meaning the loader return type is either + * predefined, or the registry is populated at a later point. + * + * @param registryLoader the registry loader supplier + * @param the input type + * @param the returned mappings type + * @return a new registry with the given RegistryLoader supplier + */ + public static SimpleRegistry create(Supplier> registryLoader) { + return new SimpleRegistry<>(null, registryLoader.get()); + } + + /** + * Creates a new registry with the given {@link RegistryLoader} supplier + * and input. + * + * @param input the input + * @param registryLoader the registry loader supplier + * @param the input type + * @param the returned mappings type + * @return a new registry with the given RegistryLoader supplier + */ + public static SimpleRegistry create(I input, Supplier> registryLoader) { + return new SimpleRegistry<>(input, registryLoader.get()); + } + + /** + * Creates a new registry with the given {@link RegistryLoader}. The + * input type is not specified here, meaning the loader return type is either + * predefined, or the registry is populated at a later point. + * + * @param registryLoader the registry loader + * @param the input type + * @param the returned mappings type + * @return a new registry with the given RegistryLoader supplier + */ + public static SimpleRegistry create(RegistryLoader registryLoader) { + return new SimpleRegistry<>(null, registryLoader); + } + + /** + * Creates a new registry with the given {@link RegistryLoader} and input. + * + * @param input the input + * @param registryLoader the registry loader + * @param the input type + * @param the returned mappings type + * @return a new registry with the given RegistryLoader supplier + */ + public static SimpleRegistry create(I input, RegistryLoader registryLoader) { + return new SimpleRegistry<>(input, registryLoader); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/registry/VersionedRegistry.java b/core/src/main/java/org/geysermc/geyser/registry/VersionedRegistry.java new file mode 100644 index 000000000..93387a770 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/VersionedRegistry.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import org.geysermc.geyser.registry.loader.RegistryLoader; + +import java.util.Map; +import java.util.function.Supplier; + +/** + * A versioned, mapped registry. Like {@link SimpleMappedRegistry}, the {@link Map} interface is + * not able to be specified here, but unlike it, it does not have support for specialized + * instances, and ONLY supports {@link Int2ObjectMap} for optimal performance to prevent boxing + * of integers. + * + * @param the value + */ +public class VersionedRegistry extends AbstractMappedRegistry> { + protected VersionedRegistry(I input, RegistryLoader> registryLoader) { + super(input, registryLoader); + } + + /** + * Gets the closest value for the specified version. Only + * returns versions higher up than the specified if one + * does not exist for the given one. Useful in the event + * that you want to get a resource which is guaranteed for + * older versions, but not on newer ones. + * + * @param version the version + * @return the closest value for the specified version + */ + public V forVersion(int version) { + Int2ObjectMap.Entry current = null; + for (Int2ObjectMap.Entry entry : this.mappings.int2ObjectEntrySet()) { + int currentVersion = entry.getIntKey(); + if (version < currentVersion) { + continue; + } + if (version == currentVersion) { + return entry.getValue(); + } + if (current == null || current.getIntKey() < currentVersion) { + // This version is newer and should be prioritized + current = entry; + } + } + return current == null ? null : current.getValue(); + } + + /** + * Creates a new versioned registry with the given {@link RegistryLoader}. The + * input type is not specified here, meaning the loader return type is either + * predefined, or the registry is populated at a later point. + * + * @param registryLoader the registry loader + * @param the input + * @param the map value + * @return a new registry with the given RegistryLoader + */ + public static VersionedRegistry create(RegistryLoader> registryLoader) { + return new VersionedRegistry<>(null, registryLoader); + } + + /** + * Creates a new versioned registry with the given {@link RegistryLoader} and input. + * + * @param registryLoader the registry loader + * @param the input + * @param the map value + * @return a new registry with the given RegistryLoader + */ + public static VersionedRegistry create(I input, RegistryLoader> registryLoader) { + return new VersionedRegistry<>(input, registryLoader); + } + + /** + * Creates a new versioned registry with the given {@link RegistryLoader} supplier. + * The input type is not specified here, meaning the loader return type is either + * predefined, or the registry is populated at a later point. + * + * @param registryLoader the registry loader + * @param the input + * @param the map value + * @return a new registry with the given RegistryLoader supplier + */ + public static VersionedRegistry< V> create(Supplier>> registryLoader) { + return new VersionedRegistry<>(null, registryLoader.get()); + } + + /** + * Creates a new versioned registry with the given {@link RegistryLoader} supplier and input. + * + * @param registryLoader the registry loader + * @param the input + * @param the map value + * @return a new registry with the given RegistryLoader supplier + */ + public static VersionedRegistry< V> create(I input, Supplier>> registryLoader) { + return new VersionedRegistry<>(input, registryLoader.get()); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/AnnotatedRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/AnnotatedRegistryLoader.java new file mode 100644 index 000000000..d8314965f --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/AnnotatedRegistryLoader.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.loader; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import org.geysermc.geyser.util.FileUtils; + +import java.lang.annotation.Annotation; +import java.util.Map; +import java.util.function.Function; + +/** + * A mapped registry loader which takes in a {@link String} and returns a transformed + * {@link Annotation} as the value. The {@link R} represents the final result as mapped + * by the {@link A}, the annotation. This function exists in this registry loader for + * the purpose of annotations not often being used as a map key. The {@link V} generic + * represents the actual map value of what is expected. The function transformation done + * is used for transforming the key, however the value is not expected to be transformed. + * + *

+ * Keep in mind that this annotation transforming does NOT need to be done, and can be + * replaced with a simple Function.identity() if not desired. + * + *

+ * See {@link BlockEntityRegistryLoader} and {@link SoundTranslatorRegistryLoader} as a + * good example of these registry loaders in use. + * + * @param the final result as transformed by the function + * @param the raw annotation itself can be transformed + * @param the value + */ +public class AnnotatedRegistryLoader implements RegistryLoader> { + private final Class annotation; + private final Function mapper; + + public AnnotatedRegistryLoader(Class annotation, Function mapper) { + this.annotation = annotation; + this.mapper = mapper; + } + + @Override + public Map load(String input) { + Map entries = new Object2ObjectOpenHashMap<>(); + for (Class clazz : FileUtils.getGeneratedClassesForAnnotation(input)) { + try { + entries.put(this.mapper.apply(clazz.getAnnotation(this.annotation)), (V) clazz.newInstance()); + } catch (InstantiationException | IllegalAccessException ex) { + ex.printStackTrace(); + } + } + return entries; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/BiomeIdentifierRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/BiomeIdentifierRegistryLoader.java new file mode 100644 index 000000000..e685c8760 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/BiomeIdentifierRegistryLoader.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.loader; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.type.TypeReference; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import org.geysermc.geyser.GeyserImpl; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +public class BiomeIdentifierRegistryLoader implements RegistryLoader> { + + @Override + public Object2IntMap load(String input) { + // As of Bedrock Edition 1.17.10 with the experimental toggle, any unmapped biome identifier sent to the client + // crashes the client. Therefore, we need to have a list of all valid Bedrock biome IDs with which we can use from. + // The server sends the corresponding Java network IDs, so we don't need to worry about that now. + + // Reference variable for Jackson to read off of + TypeReference> biomeEntriesType = new TypeReference<>() { }; + Map biomeEntries; + + try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource("mappings/biomes.json")) { + biomeEntries = GeyserImpl.JSON_MAPPER.readValue(stream, biomeEntriesType); + } catch (IOException e) { + throw new AssertionError("Unable to load Bedrock runtime biomes", e); + } + + Object2IntMap biomes = new Object2IntOpenHashMap<>(); + for (Map.Entry biome : biomeEntries.entrySet()) { + // Java Edition identifier -> Bedrock integer ID + biomes.put(biome.getKey(), biome.getValue().bedrockId); + } + + return biomes; + } + + private static class BiomeEntry { + /** + * The Bedrock network ID for this biome. + */ + @JsonProperty("bedrock_id") + private int bedrockId; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/BlockEntityRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/BlockEntityRegistryLoader.java new file mode 100644 index 000000000..b18d6959d --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/BlockEntityRegistryLoader.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.loader; + +import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import org.geysermc.geyser.translator.level.block.entity.BlockEntity; +import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator; +import org.geysermc.geyser.translator.level.block.entity.EmptyBlockEntityTranslator; +import org.geysermc.geyser.util.FileUtils; + +import java.lang.reflect.InvocationTargetException; +import java.util.Map; + +/** + * Loads block entities from the given classpath. + */ +public class BlockEntityRegistryLoader implements RegistryLoader> { + + @Override + public Map load(String input) { + // Overridden so one translator can be applied to multiple block entity types + Object2ObjectMap entries = new Object2ObjectOpenHashMap<>(); + entries.defaultReturnValue(new EmptyBlockEntityTranslator()); + for (Class clazz : FileUtils.getGeneratedClassesForAnnotation(input)) { + try { + BlockEntity annotation = clazz.getAnnotation(BlockEntity.class); + BlockEntityTranslator translator = (BlockEntityTranslator) clazz.getConstructor().newInstance(); + for (BlockEntityType type : annotation.type()) { + entries.put(type, translator); + } + } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException ex) { + ex.printStackTrace(); + } + } + return entries; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/CollisionRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/CollisionRegistryLoader.java new file mode 100644 index 000000000..949c3ee96 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/CollisionRegistryLoader.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.loader; + +import com.fasterxml.jackson.databind.node.ArrayNode; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import lombok.AllArgsConstructor; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.level.physics.BoundingBox; +import org.geysermc.geyser.translator.collision.CollisionRemapper; +import org.geysermc.geyser.translator.collision.BlockCollision; +import org.geysermc.geyser.translator.collision.OtherCollision; +import org.geysermc.geyser.translator.collision.SolidCollision; +import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.registry.type.BlockMapping; +import org.geysermc.geyser.util.FileUtils; + +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.util.*; +import java.util.regex.Pattern; + +/** + * Loads collision data from the given resource path. + */ +public class CollisionRegistryLoader extends MultiResourceRegistryLoader> { + + @Override + public Map load(Pair input) { + Int2ObjectMap collisions = new Int2ObjectOpenHashMap<>(); + + Map, CollisionInfo> annotationMap = new IdentityHashMap<>(); + for (Class clazz : FileUtils.getGeneratedClassesForAnnotation(CollisionRemapper.class.getName())) { + GeyserImpl.getInstance().getLogger().debug("Found annotated collision translator: " + clazz.getCanonicalName()); + + CollisionRemapper collisionRemapper = clazz.getAnnotation(CollisionRemapper.class); + annotationMap.put(clazz, new CollisionInfo(collisionRemapper, Pattern.compile(collisionRemapper.regex()), Pattern.compile(collisionRemapper.paramRegex()))); + } + + // Load collision mappings file + List collisionList; + try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource(input.value())) { + ArrayNode collisionNode = (ArrayNode) GeyserImpl.JSON_MAPPER.readTree(stream); + collisionList = loadBoundingBoxes(collisionNode); + } catch (Exception e) { + throw new AssertionError("Unable to load collision data", e); + } + + BlockMapping[] blockMappings = BlockRegistries.JAVA_BLOCKS.get(); + + // Map of unique collisions to its instance + Map collisionInstances = new Object2ObjectOpenHashMap<>(); + for (int i = 0; i < blockMappings.length; i++) { + BlockMapping blockMapping = blockMappings[i]; + BlockCollision newCollision = instantiateCollision(blockMapping, annotationMap, collisionList); + + if (newCollision != null) { + // If there's an existing instance equal to this one, use that instead + BlockCollision existingInstance = collisionInstances.get(newCollision); + if (existingInstance != null) { + newCollision = existingInstance; + } else { + collisionInstances.put(newCollision, newCollision); + } + } + + collisions.put(i, newCollision); + } + return collisions; + } + + private BlockCollision instantiateCollision(BlockMapping mapping, Map, CollisionInfo> annotationMap, List collisionList) { + String[] blockIdParts = mapping.getJavaIdentifier().split("\\["); + String blockName = blockIdParts[0].replace("minecraft:", ""); + String params = ""; + if (blockIdParts.length == 2) { + params = "[" + blockIdParts[1]; + } + int collisionIndex = mapping.getCollisionIndex(); + + for (Map.Entry, CollisionInfo> collisionRemappers : annotationMap.entrySet()) { + Class type = collisionRemappers.getKey(); + CollisionInfo collisionInfo = collisionRemappers.getValue(); + CollisionRemapper annotation = collisionInfo.collisionRemapper; + + if (collisionInfo.pattern.matcher(blockName).find() && collisionInfo.paramsPattern.matcher(params).find()) { + try { + if (annotation.passDefaultBoxes()) { + // Create an OtherCollision instance and get the bounding boxes + BoundingBox[] defaultBoxes = collisionList.get(collisionIndex); + return (BlockCollision) type.getDeclaredConstructor(String.class, BoundingBox[].class).newInstance(params, defaultBoxes); + } else { + return (BlockCollision) type.getDeclaredConstructor(String.class).newInstance(params); + } + } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + } + + // Unless some of the low IDs are changed, which is unlikely, the first item should always be empty collision + if (collisionIndex == 0) { + return null; + } + + // Unless some of the low IDs are changed, which is unlikely, the second item should always be full collision + if (collisionIndex == 1) { + return new SolidCollision(params); + } + return new OtherCollision(collisionList.get(collisionIndex)); + } + + private List loadBoundingBoxes(ArrayNode collisionNode) { + List collisions = new ObjectArrayList<>(); + for (int collisionIndex = 0; collisionIndex < collisionNode.size(); collisionIndex++) { + ArrayNode boundingBoxArray = (ArrayNode) collisionNode.get(collisionIndex); + + BoundingBox[] boundingBoxes = new BoundingBox[boundingBoxArray.size()]; + for (int i = 0; i < boundingBoxArray.size(); i++) { + ArrayNode boxProperties = (ArrayNode) boundingBoxArray.get(i); + boundingBoxes[i] = new BoundingBox(boxProperties.get(0).asDouble(), + boxProperties.get(1).asDouble(), + boxProperties.get(2).asDouble(), + boxProperties.get(3).asDouble(), + boxProperties.get(4).asDouble(), + boxProperties.get(5).asDouble()); + } + + // Sorting by lowest Y first fixes some bugs + Arrays.sort(boundingBoxes, Comparator.comparingDouble(BoundingBox::getMiddleY)); + collisions.add(boundingBoxes); + } + return collisions; + } + + /** + * Used to prevent patterns from being compiled more than needed + */ + @AllArgsConstructor + public static class CollisionInfo { + private final CollisionRemapper collisionRemapper; + private final Pattern pattern; + private final Pattern paramsPattern; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/EffectRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/EffectRegistryLoader.java new file mode 100644 index 000000000..8d46d2639 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/EffectRegistryLoader.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.loader; + +import com.fasterxml.jackson.databind.JsonNode; +import org.geysermc.geyser.GeyserImpl; + +import java.io.InputStream; +import java.util.Map; +import java.util.WeakHashMap; + +/** + * An abstract registry loader for loading effects from a resource path. + * + * @param the value + */ +public abstract class EffectRegistryLoader implements RegistryLoader { + private static final Map loadedFiles = new WeakHashMap<>(); + + public void loadFile(String input) { + if (!loadedFiles.containsKey(input)) { + JsonNode effects; + try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource(input)) { + effects = GeyserImpl.JSON_MAPPER.readTree(stream); + } catch (Exception e) { + throw new AssertionError("Unable to load registrations for " + input, e); + } + loadedFiles.put(input, effects); + } + } + + public JsonNode get(String input) { + return loadedFiles.get(input); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/EnchantmentRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/EnchantmentRegistryLoader.java new file mode 100644 index 000000000..59fb5029b --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/EnchantmentRegistryLoader.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.loader; + +import com.fasterxml.jackson.databind.JsonNode; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment; +import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.registry.type.EnchantmentData; +import org.geysermc.geyser.registry.type.ItemMapping; + +import java.io.InputStream; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.Map; + +public class EnchantmentRegistryLoader implements RegistryLoader> { + @Override + public Map load(String input) { + JsonNode enchantmentsNode; + try (InputStream enchantmentsStream = GeyserImpl.getInstance().getBootstrap().getResource(input)) { + enchantmentsNode = GeyserImpl.JSON_MAPPER.readTree(enchantmentsStream); + } catch (Exception e) { + throw new AssertionError("Unable to load enchantment data", e); + } + + Map enchantments = new EnumMap<>(JavaEnchantment.class); + Iterator> it = enchantmentsNode.fields(); + while (it.hasNext()) { + Map.Entry entry = it.next(); + JavaEnchantment key = JavaEnchantment.getByJavaIdentifier(entry.getKey()); + JsonNode node = entry.getValue(); + int rarityMultiplier = switch (node.get("rarity").textValue()) { + case "common" -> 1; + case "uncommon" -> 2; + case "rare" -> 4; + case "very_rare" -> 8; + default -> throw new IllegalStateException("Unexpected value: " + node.get("rarity").textValue()); + }; + int maxLevel = node.get("max_level").asInt(); + + EnumSet incompatibleEnchantments = EnumSet.noneOf(JavaEnchantment.class); + JsonNode incompatibleEnchantmentsNode = node.get("incompatible_enchantments"); + if (incompatibleEnchantmentsNode != null) { + for (JsonNode incompatibleNode : incompatibleEnchantmentsNode) { + incompatibleEnchantments.add(JavaEnchantment.getByJavaIdentifier(incompatibleNode.textValue())); + } + } + + IntSet validItems = new IntOpenHashSet(); + for (JsonNode itemNode : node.get("valid_items")) { + String javaIdentifier = itemNode.textValue(); + ItemMapping itemMapping = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getMapping(javaIdentifier); + if (itemMapping != null) { + validItems.add(itemMapping.getJavaId()); + } else { + throw new NullPointerException("No item entry exists for java identifier: " + javaIdentifier); + } + } + + EnchantmentData enchantmentData = new EnchantmentData(rarityMultiplier, maxLevel, incompatibleEnchantments, validItems); + enchantments.put(key, enchantmentData); + } + return enchantments; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/MultiResourceRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/MultiResourceRegistryLoader.java new file mode 100644 index 000000000..fd2fb317a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/MultiResourceRegistryLoader.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.loader; + +import it.unimi.dsi.fastutil.Pair; + +/** + * A RegistryLoader that loads data from two different locations, yet with the same input type. + * + * @param the input type + * @param the value + */ +public abstract class MultiResourceRegistryLoader implements RegistryLoader, V> { +} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java b/core/src/main/java/org/geysermc/geyser/registry/loader/NbtRegistryLoader.java similarity index 63% rename from connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java rename to core/src/main/java/org/geysermc/geyser/registry/loader/NbtRegistryLoader.java index f1000242e..e4006c0f2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/NbtRegistryLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,38 +23,25 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators; +package org.geysermc.geyser.registry.loader; import com.nukkitx.nbt.NBTInputStream; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtUtils; -import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.LanguageUtils; - -import java.io.InputStream; +import org.geysermc.geyser.GeyserImpl; /** - * Registry for entity identifiers. + * Loads NBT data from the given resource path. */ -public class EntityIdentifierRegistry { +public class NbtRegistryLoader implements RegistryLoader { - public static NbtMap ENTITY_IDENTIFIERS; - - private EntityIdentifierRegistry() { - } - - public static void init() { - // no-op - } - - static { - /* Load entity identifiers */ - InputStream stream = FileUtils.getResource("bedrock/entity_identifiers.dat"); - - try (NBTInputStream nbtInputStream = NbtUtils.createNetworkReader(stream)) { - ENTITY_IDENTIFIERS = (NbtMap) nbtInputStream.readTag(); + @Override + public NbtMap load(String input) { + try (NBTInputStream nbtInputStream = NbtUtils.createNetworkReader(GeyserImpl.getInstance().getBootstrap().getResource(input), + true, true)) { + return (NbtMap) nbtInputStream.readTag(); } catch (Exception e) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.entity"), e); + throw new AssertionError("Failed to load registrations for " + input, e); } } -} +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ParticleTypesRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ParticleTypesRegistryLoader.java new file mode 100644 index 000000000..0ea76c9bb --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ParticleTypesRegistryLoader.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.loader; + +import com.fasterxml.jackson.databind.JsonNode; +import com.github.steveice10.mc.protocol.data.game.level.particle.ParticleType; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.registry.type.ParticleMapping; + +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; + +/** + * Loads particle types from the given resource path. + */ +public class ParticleTypesRegistryLoader extends EffectRegistryLoader> { + + @Override + public Map load(String input) { + this.loadFile(input); + + Iterator> particlesIterator = this.get(input).fields(); + Map particles = new Object2ObjectOpenHashMap<>(); + try { + while (particlesIterator.hasNext()) { + Map.Entry entry = particlesIterator.next(); + String key = entry.getKey().toUpperCase(Locale.ROOT); + JsonNode bedrockId = entry.getValue().get("bedrockId"); + JsonNode eventType = entry.getValue().get("eventType"); + if (eventType == null && bedrockId == null) { + GeyserImpl.getInstance().getLogger().debug("Skipping particle mapping " + key + " because no Bedrock equivalent exists."); + continue; + } + particles.put(ParticleType.valueOf(key), new ParticleMapping( + eventType == null ? null : LevelEventType.valueOf(eventType.asText().toUpperCase(Locale.ROOT)), + bedrockId == null ? null : bedrockId.asText()) + ); + } + } catch (Exception e) { + e.printStackTrace(); + } + return particles; + } +} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/PotionMixRegistry.java b/core/src/main/java/org/geysermc/geyser/registry/loader/PotionMixRegistryLoader.java similarity index 70% rename from connector/src/main/java/org/geysermc/connector/network/translators/item/PotionMixRegistry.java rename to core/src/main/java/org/geysermc/geyser/registry/loader/PotionMixRegistryLoader.java index a17fbd5f8..1280ce297 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/PotionMixRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/PotionMixRegistryLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,14 +23,22 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.item; +package org.geysermc.geyser.registry.loader; import com.nukkitx.protocol.bedrock.data.inventory.PotionMixData; +import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.inventory.item.Potion; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +//TODO this needs to be versioned, but the runtime item states between 1.17 and 1.17.10 are identical except for new blocks so this works for both /** - * Generates a {@link Collection} of {@link PotionMixData} that enables the + * Generates a collection of {@link PotionMixData} that enables the * Bedrock client to place brewing items into the brewing stand. * (Does not contain actual potion mixes.) * @@ -38,18 +46,11 @@ import java.util.*; * (Ex: Bedrock cannot normally place glass bottles or fully upgraded * potions into the brewing stand, but Java can.) */ -public class PotionMixRegistry { - public static final Collection POTION_MIXES; +public class PotionMixRegistryLoader implements RegistryLoader> { - private PotionMixRegistry() { - } - - public static void init() { - // no-op - } - - static { - List ingredients = new ArrayList<>(); + @Override + public Set load(Object input) { + List ingredients = new ArrayList<>(); ingredients.add(getNonNull("minecraft:nether_wart")); ingredients.add(getNonNull("minecraft:redstone")); ingredients.add(getNonNull("minecraft:glowstone_dust")); @@ -68,21 +69,21 @@ public class PotionMixRegistry { ingredients.add(getNonNull("minecraft:turtle_helmet")); ingredients.add(getNonNull("minecraft:phantom_membrane")); - List inputs = new ArrayList<>(); + List inputs = new ArrayList<>(); inputs.add(getNonNull("minecraft:potion")); inputs.add(getNonNull("minecraft:splash_potion")); inputs.add(getNonNull("minecraft:lingering_potion")); - ItemEntry glassBottle = getNonNull("minecraft:glass_bottle"); + ItemMapping glassBottle = getNonNull("minecraft:glass_bottle"); Set potionMixes = new HashSet<>(); // Add all types of potions as inputs - ItemEntry fillerIngredient = ingredients.get(0); - for (ItemEntry input : inputs) { + ItemMapping fillerIngredient = ingredients.get(0); + for (ItemMapping entryInput : inputs) { for (Potion potion : Potion.values()) { potionMixes.add(new PotionMixData( - input.getBedrockId(), potion.getBedrockId(), + entryInput.getBedrockId(), potion.getBedrockId(), fillerIngredient.getBedrockId(), fillerIngredient.getBedrockData(), glassBottle.getBedrockId(), glassBottle.getBedrockData()) ); @@ -91,22 +92,21 @@ public class PotionMixRegistry { // Add all brewing ingredients // Also adds glass bottle as input - for (ItemEntry ingredient : ingredients) { + for (ItemMapping ingredient : ingredients) { potionMixes.add(new PotionMixData( glassBottle.getBedrockId(), glassBottle.getBedrockData(), ingredient.getBedrockId(), ingredient.getBedrockData(), glassBottle.getBedrockId(), glassBottle.getBedrockData()) ); } - - POTION_MIXES = potionMixes; + return potionMixes; } - private static ItemEntry getNonNull(String javaIdentifier) { - ItemEntry itemEntry = ItemRegistry.getItemEntry(javaIdentifier); - if (itemEntry == null) + private static ItemMapping getNonNull(String javaIdentifier) { + ItemMapping itemMapping = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getMapping(javaIdentifier); + if (itemMapping == null) throw new NullPointerException("No item entry exists for java identifier: " + javaIdentifier); - return itemEntry; + return itemMapping; } -} +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/RegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/RegistryLoader.java new file mode 100644 index 000000000..bb43ba9ba --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/RegistryLoader.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.loader; + +/** + * Represents a registry loader. {@link I} is the input value, which can be anything, + * but is commonly a file path or something similar. {@link O} represents the output + * type returned by this, which can also be anything. See {@link NbtRegistryLoader} + * as a good and simple example of how this system works. + * + * @param the input to load the registry from + * @param the output of the registry + */ +@FunctionalInterface +public interface RegistryLoader { + + /** + * Loads an output from the given input. + * + * @param input the input + * @return the output + */ + O load(I input); +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/RegistryLoaders.java b/core/src/main/java/org/geysermc/geyser/registry/loader/RegistryLoaders.java new file mode 100644 index 000000000..35bf384c7 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/RegistryLoaders.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.loader; + +import java.util.function.Supplier; + +/** + * Holds common {@link RegistryLoader}s or utility methods surrounding them. + */ +public class RegistryLoaders { + /** + * The {@link RegistryLoader} responsible for loading NBT. + */ + public static NbtRegistryLoader NBT = new NbtRegistryLoader(); + + /** + * Wraps the surrounding {@link Supplier} in a {@link RegistryLoader} which does + * not take in any input value. + * + * @param supplier the supplier + * @param the value + * @return a RegistryLoader wrapping the given Supplier + */ + public static RegistryLoader empty(Supplier supplier) { + return input -> supplier.get(); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/SoundEventsRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/SoundEventsRegistryLoader.java new file mode 100644 index 000000000..1b0f93c6a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/SoundEventsRegistryLoader.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.loader; + +import com.fasterxml.jackson.databind.JsonNode; +import com.github.steveice10.mc.protocol.data.game.level.event.SoundEvent; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.translator.level.event.LevelEventTranslator; +import org.geysermc.geyser.translator.level.event.PlaySoundEventTranslator; +import org.geysermc.geyser.translator.level.event.SoundEventEventTranslator; +import org.geysermc.geyser.translator.level.event.SoundLevelEventTranslator; + +import java.util.Iterator; +import java.util.Map; + +/** + * Loads sound effects from the given resource path. + */ +public class SoundEventsRegistryLoader extends EffectRegistryLoader> { + + @Override + public Map load(String input) { + this.loadFile(input); + + Iterator> effectsIterator = this.get(input).fields(); + Map soundEffects = new Object2ObjectOpenHashMap<>(); + while (effectsIterator.hasNext()) { + Map.Entry entry = effectsIterator.next(); + JsonNode node = entry.getValue(); + try { + String type = node.get("type").asText(); + SoundEvent javaEffect = null; + LevelEventTranslator transformer = null; + switch (type) { + case "soundLevel" -> { + javaEffect = SoundEvent.valueOf(entry.getKey()); + LevelEventType levelEventType = LevelEventType.valueOf(node.get("name").asText()); + int data = node.has("data") ? node.get("data").intValue() : 0; + transformer = new SoundLevelEventTranslator(levelEventType, data); + } + case "soundEvent" -> { + javaEffect = SoundEvent.valueOf(entry.getKey()); + com.nukkitx.protocol.bedrock.data.SoundEvent soundEvent = com.nukkitx.protocol.bedrock.data.SoundEvent.valueOf(node.get("name").asText()); + String identifier = node.has("identifier") ? node.get("identifier").asText() : ""; + int extraData = node.has("extraData") ? node.get("extraData").intValue() : -1; + transformer = new SoundEventEventTranslator(soundEvent, identifier, extraData); + } + case "playSound" -> { + javaEffect = SoundEvent.valueOf(entry.getKey()); + String name = node.get("name").asText(); + float volume = node.has("volume") ? node.get("volume").floatValue() : 1.0f; + boolean pitchSub = node.has("pitch_sub") && node.get("pitch_sub").booleanValue(); + float pitchMul = node.has("pitch_mul") ? node.get("pitch_mul").floatValue() : 1.0f; + float pitchAdd = node.has("pitch_add") ? node.get("pitch_add").floatValue() : 0.0f; + boolean relative = !node.has("relative") || node.get("relative").booleanValue(); + transformer = new PlaySoundEventTranslator(name, volume, pitchSub, pitchMul, pitchAdd, relative); + } + } + if (javaEffect != null) { + soundEffects.put(javaEffect, transformer); + } + } catch (Exception e) { + GeyserImpl.getInstance().getLogger().warning("Failed to map sound effect " + entry.getKey() + " : " + e.toString()); + } + } + return soundEffects; + } +} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundRegistry.java b/core/src/main/java/org/geysermc/geyser/registry/loader/SoundRegistryLoader.java similarity index 51% rename from connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundRegistry.java rename to core/src/main/java/org/geysermc/geyser/registry/loader/SoundRegistryLoader.java index 1c91498ba..0cdb4ea5d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/SoundRegistryLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,37 +23,28 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.sound; +package org.geysermc.geyser.registry.loader; import com.fasterxml.jackson.databind.JsonNode; -import com.nukkitx.protocol.bedrock.data.SoundEvent; -import lombok.Data; -import lombok.ToString; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.utils.FileUtils; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.registry.type.SoundMapping; + import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Iterator; import java.util.Map; -public class SoundRegistry { +/** + * Loads sounds from the given input. + */ +public class SoundRegistryLoader implements RegistryLoader> { - private static final Map SOUNDS; - - private SoundRegistry() { - } - - public static void init() { - // no-op - } - - static { - /* Load sound mappings */ - InputStream stream = FileUtils.getResource("mappings/sounds.json"); + @Override + public Map load(String input) { JsonNode soundsTree; - try { - soundsTree = GeyserConnector.JSON_MAPPER.readTree(stream); + try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource(input)) { + soundsTree = GeyserImpl.JSON_MAPPER.readTree(stream); } catch (IOException e) { throw new AssertionError("Unable to load sound mappings", e); } @@ -63,61 +54,16 @@ public class SoundRegistry { while(soundsIterator.hasNext()) { Map.Entry next = soundsIterator.next(); JsonNode brMap = next.getValue(); - soundMappings.put(next.getKey(), new SoundMapping( next.getKey(), brMap.has("bedrock_mapping") && brMap.get("bedrock_mapping").isTextual() ? brMap.get("bedrock_mapping").asText() : null, brMap.has("playsound_mapping") && brMap.get("playsound_mapping").isTextual() ? brMap.get("playsound_mapping").asText() : null, brMap.has("extra_data") && brMap.get("extra_data").isInt() ? brMap.get("extra_data").asInt() : -1, brMap.has("identifier") && brMap.get("identifier").isTextual() ? brMap.get("identifier").asText() : null, - brMap.has("level_event") && brMap.get("level_event").isBoolean() ? brMap.get("level_event").asBoolean() : false + brMap.has("level_event") && brMap.get("level_event").isBoolean() && brMap.get("level_event").asBoolean() ) ); } - SOUNDS = soundMappings; + return soundMappings; } - - /** - * Get's the sound mapping for a Java edition sound identifier - * @param java Java edition sound identifier - * @return SoundMapping object with information for bedrock, nukkit, java, etc. null if not found - */ - public static SoundMapping fromJava(String java) { - return SOUNDS.get(java); - } - - /** - * Maps a sound name to a sound event, null if one - * does not exist. - * - * @param sound the sound name - * @return a sound event from the given sound - */ - public static SoundEvent toSoundEvent(String sound) { - try { - return SoundEvent.valueOf(sound.toUpperCase().replaceAll("\\.", "_")); - } catch (Exception ex) { - return null; - } - } - - @Data - @ToString - public static class SoundMapping { - private final String java; - private final String bedrock; - private final String playsound; - private final int extraData; - private String identifier; - private boolean levelEvent; - - public SoundMapping(String java, String bedrock, String playsound, int extraData, String identifier, boolean levelEvent) { - this.java = java; - this.bedrock = bedrock == null || bedrock.equalsIgnoreCase("") ? null : bedrock; - this.playsound = playsound == null || playsound.equalsIgnoreCase("") ? null : playsound; - this.extraData = extraData; - this.identifier = identifier == null || identifier.equalsIgnoreCase("") ? ":" : identifier; - this.levelEvent = levelEvent; - } - } -} +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/SoundTranslatorRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/SoundTranslatorRegistryLoader.java new file mode 100644 index 000000000..457a32f56 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/SoundTranslatorRegistryLoader.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.loader; + +import org.geysermc.geyser.translator.sound.SoundTranslator; +import org.geysermc.geyser.translator.sound.SoundInteractionTranslator; + +import java.util.function.Function; + +/** + * Loads sound handlers from the given classpath. + */ +public class SoundTranslatorRegistryLoader extends AnnotatedRegistryLoader> { + public SoundTranslatorRegistryLoader() { + super(SoundTranslator.class, Function.identity()); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java new file mode 100644 index 000000000..fef5b32aa --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java @@ -0,0 +1,394 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.populator; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.ImmutableMap; +import com.nukkitx.nbt.*; +import com.nukkitx.protocol.bedrock.v465.Bedrock_v465; +import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectIntPair; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.level.physics.PistonBehavior; +import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.registry.type.BlockMapping; +import org.geysermc.geyser.registry.type.BlockMappings; +import org.geysermc.geyser.util.BlockUtils; + +import java.io.DataInputStream; +import java.io.InputStream; +import java.util.*; +import java.util.function.BiFunction; +import java.util.zip.GZIPInputStream; + +/** + * Populates the block registries. + */ +public class BlockRegistryPopulator { + private static final ImmutableMap, BiFunction> BLOCK_MAPPERS; + private static final BiFunction EMPTY_MAPPER = (bedrockIdentifier, statesBuilder) -> null; + + static { + ImmutableMap.Builder, BiFunction> stateMapperBuilder = ImmutableMap., BiFunction>builder() + .put(ObjectIntPair.of("1_17_30", Bedrock_v465.V465_CODEC.getProtocolVersion()), EMPTY_MAPPER) + .put(ObjectIntPair.of("1_17_40", Bedrock_v471.V471_CODEC.getProtocolVersion()), EMPTY_MAPPER); + + BLOCK_MAPPERS = stateMapperBuilder.build(); + } + + /** + * Stores the raw blocks JSON until it is no longer needed. + */ + private static JsonNode BLOCKS_JSON; + + public static void populate() { + registerJavaBlocks(); + registerBedrockBlocks(); + + BLOCKS_JSON = null; + } + + private static void registerBedrockBlocks() { + for (Map.Entry, BiFunction> palette : BLOCK_MAPPERS.entrySet()) { + NbtList blocksTag; + try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource(String.format("bedrock/block_palette.%s.nbt", palette.getKey().key())); + NBTInputStream nbtInputStream = new NBTInputStream(new DataInputStream(new GZIPInputStream(stream)), true, true)) { + NbtMap blockPalette = (NbtMap) nbtInputStream.readTag(); + blocksTag = (NbtList) blockPalette.getList("blocks", NbtType.COMPOUND); + } catch (Exception e) { + throw new AssertionError("Unable to get blocks from runtime block states", e); + } + Map javaIdentifierToBedrockTag = new Object2ObjectOpenHashMap<>(blocksTag.size()); + // New since 1.16.100 - find the block runtime ID by the order given to us in the block palette, + // as we no longer send a block palette + Object2IntMap blockStateOrderedMap = new Object2IntOpenHashMap<>(blocksTag.size()); + + int stateVersion = -1; + for (int i = 0; i < blocksTag.size(); i++) { + NbtMap tag = blocksTag.get(i); + if (blockStateOrderedMap.containsKey(tag)) { + throw new AssertionError("Duplicate block states in Bedrock palette: " + tag); + } + blockStateOrderedMap.put(tag, i); + if (stateVersion == -1) { + stateVersion = tag.getInt("version"); + } + } + int airRuntimeId = -1; + int commandBlockRuntimeId = -1; + int javaRuntimeId = -1; + int waterRuntimeId = -1; + int movingBlockRuntimeId = -1; + Iterator> blocksIterator = BLOCKS_JSON.fields(); + + BiFunction stateMapper = BLOCK_MAPPERS.getOrDefault(palette.getKey(), EMPTY_MAPPER); + + int[] javaToBedrockBlocks = new int[BLOCKS_JSON.size()]; + + Map flowerPotBlocks = new Object2ObjectOpenHashMap<>(); + Object2IntMap itemFrames = new Object2IntOpenHashMap<>(); + + IntSet jigsawStateIds = new IntOpenHashSet(); + + BlockMappings.BlockMappingsBuilder builder = BlockMappings.builder(); + while (blocksIterator.hasNext()) { + javaRuntimeId++; + Map.Entry entry = blocksIterator.next(); + String javaId = entry.getKey(); + + int bedrockRuntimeId = blockStateOrderedMap.getOrDefault(buildBedrockState(entry.getValue(), stateVersion, stateMapper), -1); + if (bedrockRuntimeId == -1) { + throw new RuntimeException("Unable to find " + javaId + " Bedrock runtime ID! Built NBT tag: \n" + + buildBedrockState(entry.getValue(), stateVersion, stateMapper)); + } + + switch (javaId) { + case "minecraft:air" -> airRuntimeId = bedrockRuntimeId; + case "minecraft:water[level=0]" -> waterRuntimeId = bedrockRuntimeId; + case "minecraft:command_block[conditional=false,facing=north]" -> commandBlockRuntimeId = bedrockRuntimeId; + case "minecraft:moving_piston[facing=north,type=normal]" -> movingBlockRuntimeId = bedrockRuntimeId; + } + + if (javaId.contains("jigsaw")) { + jigsawStateIds.add(bedrockRuntimeId); + } + + boolean waterlogged = entry.getKey().contains("waterlogged=true") + || javaId.contains("minecraft:bubble_column") || javaId.contains("minecraft:kelp") || javaId.contains("seagrass"); + + if (waterlogged) { + int finalJavaRuntimeId = javaRuntimeId; + BlockRegistries.WATERLOGGED.register(set -> set.add(finalJavaRuntimeId)); + } + + String cleanJavaIdentifier = BlockUtils.getCleanIdentifier(entry.getKey()); + + // Get the tag needed for non-empty flower pots + if (entry.getValue().get("pottable") != null) { + flowerPotBlocks.put(cleanJavaIdentifier.intern(), blocksTag.get(bedrockRuntimeId)); + } + + if (!cleanJavaIdentifier.equals(entry.getValue().get("bedrock_identifier").asText())) { + javaIdentifierToBedrockTag.put(cleanJavaIdentifier.intern(), blocksTag.get(bedrockRuntimeId)); + } + + javaToBedrockBlocks[javaRuntimeId] = bedrockRuntimeId; + } + + if (commandBlockRuntimeId == -1) { + throw new AssertionError("Unable to find command block in palette"); + } + builder.commandBlockRuntimeId(commandBlockRuntimeId); + + if (waterRuntimeId == -1) { + throw new AssertionError("Unable to find water in palette"); + } + builder.bedrockWaterId(waterRuntimeId); + + if (airRuntimeId == -1) { + throw new AssertionError("Unable to find air in palette"); + } + builder.bedrockAirId(airRuntimeId); + + if (movingBlockRuntimeId == -1) { + throw new AssertionError("Unable to find moving block in palette"); + } + builder.bedrockMovingBlockId(movingBlockRuntimeId); + + // Loop around again to find all item frame runtime IDs + for (Object2IntMap.Entry entry : blockStateOrderedMap.object2IntEntrySet()) { + String name = entry.getKey().getString("name"); + if (name.equals("minecraft:frame") || name.equals("minecraft:glow_frame")) { + itemFrames.put(entry.getKey(), entry.getIntValue()); + } + } + builder.bedrockBlockStates(blocksTag); + + BlockRegistries.BLOCKS.register(palette.getKey().valueInt(), builder.blockStateVersion(stateVersion) + .javaToBedrockBlocks(javaToBedrockBlocks) + .javaIdentifierToBedrockTag(javaIdentifierToBedrockTag) + .itemFrames(itemFrames) + .flowerPotBlocks(flowerPotBlocks) + .jigsawStateIds(jigsawStateIds) + .build()); + } + } + + private static void registerJavaBlocks() { + JsonNode blocksJson; + try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource("mappings/blocks.json")) { + blocksJson = GeyserImpl.JSON_MAPPER.readTree(stream); + } catch (Exception e) { + throw new AssertionError("Unable to load Java block mappings", e); + } + + BlockRegistries.JAVA_BLOCKS.set(new BlockMapping[blocksJson.size()]); // Set array size to number of blockstates + + Deque cleanIdentifiers = new ArrayDeque<>(); + + int javaRuntimeId = -1; + int bellBlockId = -1; + int cobwebBlockId = -1; + int furnaceRuntimeId = -1; + int furnaceLitRuntimeId = -1; + int honeyBlockRuntimeId = -1; + int slimeBlockRuntimeId = -1; + int spawnerRuntimeId = -1; + int uniqueJavaId = -1; + int waterRuntimeId = -1; + Iterator> blocksIterator = blocksJson.fields(); + while (blocksIterator.hasNext()) { + javaRuntimeId++; + Map.Entry entry = blocksIterator.next(); + String javaId = entry.getKey(); + + // TODO fix this, (no block should have a null hardness) + BlockMapping.BlockMappingBuilder builder = BlockMapping.builder(); + JsonNode hardnessNode = entry.getValue().get("block_hardness"); + if (hardnessNode != null) { + builder.hardness(hardnessNode.doubleValue()); + } + + JsonNode canBreakWithHandNode = entry.getValue().get("can_break_with_hand"); + if (canBreakWithHandNode != null) { + builder.canBreakWithHand(canBreakWithHandNode.booleanValue()); + } else { + builder.canBreakWithHand(false); + } + + JsonNode collisionIndexNode = entry.getValue().get("collision_index"); + if (hardnessNode != null) { + builder.collisionIndex(collisionIndexNode.intValue()); + } + + JsonNode pickItemNode = entry.getValue().get("pick_item"); + if (pickItemNode != null) { + builder.pickItem(pickItemNode.textValue().intern()); + } + + if (javaId.equals("minecraft:obsidian") || javaId.equals("minecraft:crying_obsidian") || javaId.startsWith("minecraft:respawn_anchor")) { + builder.pistonBehavior(PistonBehavior.BLOCK); + } else { + JsonNode pistonBehaviorNode = entry.getValue().get("piston_behavior"); + if (pistonBehaviorNode != null) { + builder.pistonBehavior(PistonBehavior.getByName(pistonBehaviorNode.textValue())); + } else { + builder.pistonBehavior(PistonBehavior.NORMAL); + } + } + + JsonNode hasBlockEntityNode = entry.getValue().get("has_block_entity"); + if (hasBlockEntityNode != null) { + builder.isBlockEntity(hasBlockEntityNode.booleanValue()); + } else { + builder.isBlockEntity(false); + } + + BlockStateValues.storeBlockStateValues(entry.getKey(), javaRuntimeId, entry.getValue()); + + String cleanJavaIdentifier = BlockUtils.getCleanIdentifier(entry.getKey()); + String bedrockIdentifier = entry.getValue().get("bedrock_identifier").asText(); + + if (!cleanJavaIdentifier.equals(cleanIdentifiers.peekLast())) { + uniqueJavaId++; + cleanIdentifiers.add(cleanJavaIdentifier.intern()); + } + + builder.javaIdentifier(javaId); + builder.javaBlockId(uniqueJavaId); + + BlockRegistries.JAVA_IDENTIFIERS.register(javaId, javaRuntimeId); + BlockRegistries.JAVA_BLOCKS.register(javaRuntimeId, builder.build()); + + // Keeping this here since this is currently unchanged between versions + // It's possible to only have this store differences in names, but the key set of all Java names is used in sending command suggestions + BlockRegistries.JAVA_TO_BEDROCK_IDENTIFIERS.register(cleanJavaIdentifier.intern(), bedrockIdentifier.intern()); + + if (javaId.startsWith("minecraft:bell[")) { + bellBlockId = uniqueJavaId; + + } else if (javaId.contains("cobweb")) { + cobwebBlockId = uniqueJavaId; + + } else if (javaId.startsWith("minecraft:furnace[facing=north")) { + if (javaId.contains("lit=true")) { + furnaceLitRuntimeId = javaRuntimeId; + } else { + furnaceRuntimeId = javaRuntimeId; + } + + } else if (javaId.startsWith("minecraft:spawner")) { + spawnerRuntimeId = javaRuntimeId; + + } else if ("minecraft:water[level=0]".equals(javaId)) { + waterRuntimeId = javaRuntimeId; + } else if (javaId.equals("minecraft:honey_block")) { + honeyBlockRuntimeId = javaRuntimeId; + } else if (javaId.equals("minecraft:slime_block")) { + slimeBlockRuntimeId = javaRuntimeId; + } + } + if (bellBlockId == -1) { + throw new AssertionError("Unable to find bell in palette"); + } + BlockStateValues.JAVA_BELL_ID = bellBlockId; + + if (cobwebBlockId == -1) { + throw new AssertionError("Unable to find cobwebs in palette"); + } + BlockStateValues.JAVA_COBWEB_ID = cobwebBlockId; + + if (furnaceRuntimeId == -1) { + throw new AssertionError("Unable to find furnace in palette"); + } + BlockStateValues.JAVA_FURNACE_ID = furnaceRuntimeId; + + if (furnaceLitRuntimeId == -1) { + throw new AssertionError("Unable to find lit furnace in palette"); + } + BlockStateValues.JAVA_FURNACE_LIT_ID = furnaceLitRuntimeId; + + if (honeyBlockRuntimeId == -1) { + throw new AssertionError("Unable to find honey block in palette"); + } + BlockStateValues.JAVA_HONEY_BLOCK_ID = honeyBlockRuntimeId; + + if (slimeBlockRuntimeId == -1) { + throw new AssertionError("Unable to find slime block in palette"); + } + BlockStateValues.JAVA_SLIME_BLOCK_ID = slimeBlockRuntimeId; + + if (spawnerRuntimeId == -1) { + throw new AssertionError("Unable to find spawner in palette"); + } + BlockStateValues.JAVA_SPAWNER_ID = spawnerRuntimeId; + + if (waterRuntimeId == -1) { + throw new AssertionError("Unable to find Java water in palette"); + } + BlockStateValues.JAVA_WATER_ID = waterRuntimeId; + + BlockRegistries.CLEAN_JAVA_IDENTIFIERS.set(cleanIdentifiers.toArray(new String[0])); + + BLOCKS_JSON = blocksJson; + } + + private static NbtMap buildBedrockState(JsonNode node, int blockStateVersion, BiFunction statesMapper) { + NbtMapBuilder tagBuilder = NbtMap.builder(); + String bedrockIdentifier = node.get("bedrock_identifier").textValue(); + tagBuilder.putString("name", bedrockIdentifier) + .putInt("version", blockStateVersion); + + NbtMapBuilder statesBuilder = NbtMap.builder(); + + // check for states + if (node.has("bedrock_states")) { + Iterator> statesIterator = node.get("bedrock_states").fields(); + + while (statesIterator.hasNext()) { + Map.Entry stateEntry = statesIterator.next(); + JsonNode stateValue = stateEntry.getValue(); + switch (stateValue.getNodeType()) { + case BOOLEAN -> statesBuilder.putBoolean(stateEntry.getKey(), stateValue.booleanValue()); + case STRING -> statesBuilder.putString(stateEntry.getKey(), stateValue.textValue()); + case NUMBER -> statesBuilder.putInt(stateEntry.getKey(), stateValue.intValue()); + } + } + } + String newIdentifier = statesMapper.apply(bedrockIdentifier, statesBuilder); + if (newIdentifier != null) { + tagBuilder.putString("name", newIdentifier); + } + tagBuilder.put("states", statesBuilder.build()); + return tagBuilder.build(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java new file mode 100644 index 000000000..71036b792 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java @@ -0,0 +1,511 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.populator; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.nbt.NbtType; +import com.nukkitx.nbt.NbtUtils; +import com.nukkitx.protocol.bedrock.data.SoundEvent; +import com.nukkitx.protocol.bedrock.data.inventory.ComponentItemData; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import com.nukkitx.protocol.bedrock.packet.StartGamePacket; +import com.nukkitx.protocol.bedrock.v465.Bedrock_v465; +import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; +import com.nukkitx.protocol.bedrock.v475.Bedrock_v475; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.objects.*; +import org.geysermc.geyser.GeyserBootstrap; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.inventory.item.StoredItemMappings; +import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.registry.type.*; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; + +/** + * Populates the item registries. + */ +public class ItemRegistryPopulator { + private static final Map PALETTE_VERSIONS; + + static { + PALETTE_VERSIONS = new Object2ObjectOpenHashMap<>(); + PALETTE_VERSIONS.put("1_17_30", new PaletteVersion(Bedrock_v465.V465_CODEC.getProtocolVersion(), Collections.emptyMap())); + PALETTE_VERSIONS.put("1_17_40", new PaletteVersion(Bedrock_v471.V471_CODEC.getProtocolVersion(), Collections.emptyMap())); + PALETTE_VERSIONS.put("1_18_0", new PaletteVersion(Bedrock_v475.V475_CODEC.getProtocolVersion(), Collections.emptyMap())); + } + + private record PaletteVersion(int protocolVersion, Map additionalTranslatedItems) { + } + + public static void populate() { + GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap(); + + TypeReference> mappingItemsType = new TypeReference<>() { }; + + Map items; + try (InputStream stream = bootstrap.getResource("mappings/items.json")) { + // Load item mappings from Java Edition to Bedrock Edition + items = GeyserImpl.JSON_MAPPER.readValue(stream, mappingItemsType); + } catch (Exception e) { + throw new AssertionError("Unable to load Java runtime item IDs", e); + } + + /* Load item palette */ + for (Map.Entry palette : PALETTE_VERSIONS.entrySet()) { + TypeReference> paletteEntriesType = new TypeReference<>() {}; + + // Used to get the Bedrock namespaced ID (in instances where there are small differences) + Object2IntMap bedrockIdentifierToId = new Object2IntOpenHashMap<>(); + bedrockIdentifierToId.defaultReturnValue(Short.MIN_VALUE); + + List itemNames = new ArrayList<>(); + + List itemEntries; + try (InputStream stream = bootstrap.getResource(String.format("bedrock/runtime_item_states.%s.json", palette.getKey()))) { + itemEntries = GeyserImpl.JSON_MAPPER.readValue(stream, paletteEntriesType); + } catch (Exception e) { + throw new AssertionError("Unable to load Bedrock runtime item IDs", e); + } + + Map entries = new Object2ObjectOpenHashMap<>(); + + for (PaletteItem entry : itemEntries) { + entries.put(entry.getName(), new StartGamePacket.ItemEntry(entry.getName(), (short) entry.getId())); + bedrockIdentifierToId.put(entry.getName(), entry.getId()); + } + + Object2IntMap bedrockBlockIdOverrides = new Object2IntOpenHashMap<>(); + Object2IntMap blacklistedIdentifiers = new Object2IntOpenHashMap<>(); + + // Load creative items + // We load this before item mappings to get overridden block runtime ID mappings + JsonNode creativeItemEntries; + try (InputStream stream = bootstrap.getResource(String.format("bedrock/creative_items.%s.json", palette.getKey()))) { + creativeItemEntries = GeyserImpl.JSON_MAPPER.readTree(stream).get("items"); + } catch (Exception e) { + throw new AssertionError("Unable to load creative items", e); + } + + IntList boats = new IntArrayList(); + IntList buckets = new IntArrayList(); + IntList spawnEggs = new IntArrayList(); + List carpets = new ObjectArrayList<>(); + + Int2ObjectMap mappings = new Int2ObjectOpenHashMap<>(); + // Temporary mapping to create stored items + Map identifierToMapping = new Object2ObjectOpenHashMap<>(); + + int netId = 1; + List creativeItems = new ArrayList<>(); + for (JsonNode itemNode : creativeItemEntries) { + int count = 1; + int damage = 0; + int blockRuntimeId = 0; + NbtMap tag = null; + JsonNode damageNode = itemNode.get("damage"); + if (damageNode != null) { + damage = damageNode.asInt(); + } + JsonNode countNode = itemNode.get("count"); + if (countNode != null) { + count = countNode.asInt(); + } + JsonNode blockRuntimeIdNode = itemNode.get("blockRuntimeId"); + if (blockRuntimeIdNode != null) { + blockRuntimeId = blockRuntimeIdNode.asInt(); + } + JsonNode nbtNode = itemNode.get("nbt_b64"); + if (nbtNode != null) { + byte[] bytes = Base64.getDecoder().decode(nbtNode.asText()); + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + try { + tag = (NbtMap) NbtUtils.createReaderLE(bais).readTag(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + String identifier = itemNode.get("id").textValue(); + if (identifier.equals("minecraft:debug_stick")) { + // Just shows an empty texture; either way it doesn't exist in the creative menu on Java + continue; + } + StartGamePacket.ItemEntry entry = entries.get(identifier); + int id = -1; + if (entry != null) { + id = entry.getId(); + } + + if (id == -1) { + throw new RuntimeException("Unable to find matching Bedrock item for " + identifier); + } + + creativeItems.add(ItemData.builder() + .id(id) + .damage(damage) + .count(count) + .blockRuntimeId(blockRuntimeId) + .tag(tag) + .netId(netId++) + .build()); + + if (blockRuntimeId != 0) { + // Add override for item mapping, unless it already exists... then we know multiple states can exist + if (!blacklistedIdentifiers.containsKey(identifier)) { + if (bedrockBlockIdOverrides.containsKey(identifier)) { + bedrockBlockIdOverrides.removeInt(identifier); + // Save this as a blacklist, but also as knowledge of what the block state name should be + blacklistedIdentifiers.put(identifier, blockRuntimeId); + } else { + // Unless there's multiple possibilities for this one state, let this be + bedrockBlockIdOverrides.put(identifier, blockRuntimeId); + } + } + } + } + + BlockMappings blockMappings = BlockRegistries.BLOCKS.forVersion(palette.getValue().protocolVersion()); + + int itemIndex = 0; + int javaFurnaceMinecartId = 0; + boolean usingFurnaceMinecart = GeyserImpl.getInstance().getConfig().isAddNonBedrockItems(); + + Set javaOnlyItems = new ObjectOpenHashSet<>(); + Collections.addAll(javaOnlyItems, "minecraft:spectral_arrow", "minecraft:debug_stick", + "minecraft:knowledge_book", "minecraft:tipped_arrow", "minecraft:trader_llama_spawn_egg", + "minecraft:bundle"); + if (!usingFurnaceMinecart) { + javaOnlyItems.add("minecraft:furnace_minecart"); + } + // Java-only items for this version + javaOnlyItems.addAll(palette.getValue().additionalTranslatedItems().keySet()); + + for (Map.Entry entry : items.entrySet()) { + String javaIdentifier = entry.getKey().intern(); + GeyserMappingItem mappingItem; + String replacementItem = palette.getValue().additionalTranslatedItems().get(javaIdentifier); + if (replacementItem != null) { + mappingItem = items.get(replacementItem); + } else { + // This items has a mapping specifically for this version of the game + mappingItem = entry.getValue(); + } + if (javaIdentifier.equals("minecraft:music_disc_otherside") && palette.getValue().protocolVersion() <= Bedrock_v471.V471_CODEC.getProtocolVersion()) { + mappingItem.setBedrockIdentifier("minecraft:music_disc_pigstep"); + } + + if (usingFurnaceMinecart && javaIdentifier.equals("minecraft:furnace_minecart")) { + javaFurnaceMinecartId = itemIndex; + itemIndex++; + continue; + } + String bedrockIdentifier = mappingItem.getBedrockIdentifier().intern(); + int bedrockId = bedrockIdentifierToId.getInt(bedrockIdentifier); + if (bedrockId == Short.MIN_VALUE) { + throw new RuntimeException("Missing Bedrock ID in mappings: " + bedrockIdentifier); + } + int stackSize = mappingItem.getStackSize(); + + int bedrockBlockId = -1; + Integer firstBlockRuntimeId = entry.getValue().getFirstBlockRuntimeId(); + if (firstBlockRuntimeId != null) { + int blockIdOverride = bedrockBlockIdOverrides.getOrDefault(bedrockIdentifier, -1); + if (blockIdOverride != -1) { + // Straight from BDS is our best chance of getting an item that doesn't run into issues + bedrockBlockId = blockIdOverride; + } else { + // Try to get an example block runtime ID from the creative contents packet, for Bedrock identifier obtaining + int aValidBedrockBlockId = blacklistedIdentifiers.getOrDefault(bedrockIdentifier, -1); + if (aValidBedrockBlockId == -1) { + // Fallback + bedrockBlockId = blockMappings.getBedrockBlockId(firstBlockRuntimeId); + } else { + // As of 1.16.220, every item requires a block runtime ID attached to it. + // This is mostly for identifying different blocks with the same item ID - wool, slabs, some walls. + // However, in order for some visuals and crafting to work, we need to send the first matching block state + // as indexed by Bedrock's block palette + // There are exceptions! But, ideally, the block ID override should take care of those. + NbtMapBuilder requiredBlockStatesBuilder = NbtMap.builder(); + String correctBedrockIdentifier = blockMappings.getBedrockBlockStates().get(aValidBedrockBlockId).getString("name"); + boolean firstPass = true; + // Block states are all grouped together. In the mappings, we store the first block runtime ID in order, + // and the last, if relevant. We then iterate over all those values and get their Bedrock equivalents + Integer lastBlockRuntimeId = entry.getValue().getLastBlockRuntimeId() == null ? firstBlockRuntimeId : entry.getValue().getLastBlockRuntimeId(); + for (int i = firstBlockRuntimeId; i <= lastBlockRuntimeId; i++) { + int bedrockBlockRuntimeId = blockMappings.getBedrockBlockId(i); + NbtMap blockTag = blockMappings.getBedrockBlockStates().get(bedrockBlockRuntimeId); + String bedrockName = blockTag.getString("name"); + if (!bedrockName.equals(correctBedrockIdentifier)) { + continue; + } + NbtMap states = blockTag.getCompound("states"); + + if (firstPass) { + firstPass = false; + if (states.size() == 0) { + // No need to iterate and find all block states - this is the one, as there can't be any others + bedrockBlockId = bedrockBlockRuntimeId; + break; + } + requiredBlockStatesBuilder.putAll(states); + continue; + } + for (Map.Entry nbtEntry : states.entrySet()) { + Object value = requiredBlockStatesBuilder.get(nbtEntry.getKey()); + if (value != null && !nbtEntry.getValue().equals(value)) { // Null means this value has already been removed/deemed as unneeded + // This state can change between different block states, and therefore is not required + // to build a successful block state of this + requiredBlockStatesBuilder.remove(nbtEntry.getKey()); + } + } + if (requiredBlockStatesBuilder.size() == 0) { + // There are no required block states + // E.G. there was only a direction property that is no longer in play + // (States that are important include color for glass) + break; + } + } + + NbtMap requiredBlockStates = requiredBlockStatesBuilder.build(); + if (bedrockBlockId == -1) { + int i = -1; + // We need to loop around again (we can't cache the block tags above) because Bedrock can include states that we don't have a pairing for + // in it's "preferred" block state - I.E. the first matching block state in the list + for (NbtMap blockTag : blockMappings.getBedrockBlockStates()) { + i++; + if (blockTag.getString("name").equals(correctBedrockIdentifier)) { + NbtMap states = blockTag.getCompound("states"); + boolean valid = true; + for (Map.Entry nbtEntry : requiredBlockStates.entrySet()) { + if (!states.get(nbtEntry.getKey()).equals(nbtEntry.getValue())) { + // A required block state doesn't match - this one is not valid + valid = false; + break; + } + } + if (valid) { + bedrockBlockId = i; + break; + } + } + } + if (bedrockBlockId == -1) { + throw new RuntimeException("Could not find a block match for " + entry.getKey()); + } + } + + // Because we have replaced the Bedrock block ID, we also need to replace the creative contents block runtime ID + // That way, creative items work correctly for these blocks + for (int j = 0; j < creativeItems.size(); j++) { + ItemData itemData = creativeItems.get(j); + if (itemData.getId() == bedrockId) { + if (itemData.getDamage() != 0) { + break; + } + NbtMap states = blockMappings.getBedrockBlockStates().get(itemData.getBlockRuntimeId()).getCompound("states"); + boolean valid = true; + for (Map.Entry nbtEntry : requiredBlockStates.entrySet()) { + if (!states.get(nbtEntry.getKey()).equals(nbtEntry.getValue())) { + // A required block state doesn't match - this one is not valid + valid = false; + break; + } + } + if (valid) { + creativeItems.set(j, itemData.toBuilder().blockRuntimeId(bedrockBlockId).build()); + break; + } + } + } + } + } + } + + ItemMapping.ItemMappingBuilder mappingBuilder = ItemMapping.builder() + .javaIdentifier(javaIdentifier) + .javaId(itemIndex) + .bedrockIdentifier(bedrockIdentifier) + .bedrockId(bedrockId) + .bedrockData(mappingItem.getBedrockData()) + .bedrockBlockId(bedrockBlockId) + .stackSize(stackSize) + .maxDamage(mappingItem.getMaxDamage()); + + if (mappingItem.getRepairMaterials() != null) { + mappingBuilder = mappingBuilder.repairMaterials(new ObjectOpenHashSet<>(mappingItem.getRepairMaterials())); + } + + if (mappingItem.getToolType() != null) { + if (mappingItem.getToolTier() != null) { + mappingBuilder = mappingBuilder.toolType(mappingItem.getToolType().intern()) + .toolTier(mappingItem.getToolTier().intern()); + } else { + mappingBuilder = mappingBuilder.toolType(mappingItem.getToolType().intern()) + .toolTier(""); + } + } + if (javaOnlyItems.contains(javaIdentifier)) { + // These items don't exist on Bedrock, so set up a variable that indicates they should have custom names + mappingBuilder = mappingBuilder.translationString((bedrockBlockId != -1 ? "block." : "item.") + entry.getKey().replace(":", ".")); + GeyserImpl.getInstance().getLogger().debug("Adding " + entry.getKey() + " as an item that needs to be translated."); + } + + ItemMapping mapping = mappingBuilder.build(); + + if (javaIdentifier.contains("boat")) { + boats.add(bedrockId); + } else if (javaIdentifier.contains("bucket") && !javaIdentifier.contains("milk")) { + buckets.add(bedrockId); + } else if (javaIdentifier.contains("_carpet") && !javaIdentifier.contains("moss")) { + // This should be the numerical order Java sends as an integer value for llamas + carpets.add(ItemData.builder() + .id(mapping.getBedrockId()) + .damage(mapping.getBedrockData()) + .count(1) + .blockRuntimeId(mapping.getBedrockBlockId()) + .build()); + } else if (javaIdentifier.startsWith("minecraft:music_disc_")) { + // The Java record level event uses the item ID as the "key" to play the record + Registries.RECORDS.register(itemIndex, SoundEvent.valueOf("RECORD_" + + javaIdentifier.replace("minecraft:music_disc_", "").toUpperCase(Locale.ENGLISH))); + } else if (javaIdentifier.endsWith("_spawn_egg")) { + spawnEggs.add(mapping.getBedrockId()); + } + + mappings.put(itemIndex, mapping); + identifierToMapping.put(javaIdentifier, mapping); + + itemNames.add(javaIdentifier); + + itemIndex++; + } + + itemNames.add("minecraft:furnace_minecart"); + + int lodestoneCompassId = entries.get("minecraft:lodestone_compass").getId(); + if (lodestoneCompassId == 0) { + throw new RuntimeException("Lodestone compass not found in item palette!"); + } + + // Add the lodestone compass since it doesn't exist on java but we need it for item conversion + ItemMapping lodestoneEntry = ItemMapping.builder() + .javaIdentifier("minecraft:lodestone_compass") + .bedrockIdentifier("minecraft:lodestone_compass") + .javaId(itemIndex) + .bedrockId(lodestoneCompassId) + .bedrockData(0) + .bedrockBlockId(-1) + .stackSize(1) + .build(); + mappings.put(itemIndex, lodestoneEntry); + identifierToMapping.put(lodestoneEntry.getJavaIdentifier(), lodestoneEntry); + + ComponentItemData furnaceMinecartData = null; + if (usingFurnaceMinecart) { + // Add the furnace minecart as a custom item + int furnaceMinecartId = mappings.size() + 1; + + entries.put("geysermc:furnace_minecart", new StartGamePacket.ItemEntry("geysermc:furnace_minecart", (short) furnaceMinecartId, true)); + + mappings.put(javaFurnaceMinecartId, ItemMapping.builder() + .javaIdentifier("minecraft:furnace_minecart") + .bedrockIdentifier("geysermc:furnace_minecart") + .javaId(javaFurnaceMinecartId) + .bedrockId(furnaceMinecartId) + .bedrockData(0) + .bedrockBlockId(-1) + .stackSize(1) + .build()); + + creativeItems.add(ItemData.builder() + .netId(netId) + .id(furnaceMinecartId) + .count(1).build()); + + NbtMapBuilder builder = NbtMap.builder(); + builder.putString("name", "geysermc:furnace_minecart") + .putInt("id", furnaceMinecartId); + + NbtMapBuilder itemProperties = NbtMap.builder(); + + NbtMapBuilder componentBuilder = NbtMap.builder(); + // Conveniently, as of 1.16.200, the furnace minecart has a texture AND translation string already. + itemProperties.putCompound("minecraft:icon", NbtMap.builder() + .putString("texture", "minecart_furnace") + .putString("frame", "0.000000") + .putInt("frame_version", 1) + .putString("legacy_id", "").build()); + componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", "item.minecartFurnace.name").build()); + + // Indicate that the arm animation should play on rails + List useOnTag = Collections.singletonList(NbtMap.builder().putString("tags", "q.any_tag('rail')").build()); + componentBuilder.putCompound("minecraft:entity_placer", NbtMap.builder() + .putList("dispense_on", NbtType.COMPOUND, useOnTag) + .putString("entity", "minecraft:minecart") + .putList("use_on", NbtType.COMPOUND, useOnTag) + .build()); + + // We always want to allow offhand usage when we can - matches Java Edition + itemProperties.putBoolean("allow_off_hand", true); + itemProperties.putBoolean("hand_equipped", false); + itemProperties.putInt("max_stack_size", 1); + itemProperties.putString("creative_group", "itemGroup.name.minecart"); + itemProperties.putInt("creative_category", 4); // 4 - "Items" + + componentBuilder.putCompound("item_properties", itemProperties.build()); + builder.putCompound("components", componentBuilder.build()); + furnaceMinecartData = new ComponentItemData("geysermc:furnace_minecart", builder.build()); + } + + ItemMappings itemMappings = ItemMappings.builder() + .items(mappings) + .creativeItems(creativeItems.toArray(new ItemData[0])) + .itemEntries(new ArrayList<>(entries.values())) + .itemNames(itemNames.toArray(new String[0])) + .storedItems(new StoredItemMappings(identifierToMapping)) + .javaOnlyItems(javaOnlyItems) + .bucketIds(buckets) + .boatIds(boats) + .spawnEggIds(spawnEggs) + .carpets(carpets) + .furnaceMinecartData(furnaceMinecartData) + .build(); + + Registries.ITEMS.register(palette.getValue().protocolVersion(), itemMappings); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/PacketRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/PacketRegistryPopulator.java new file mode 100644 index 000000000..5024d26e7 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/PacketRegistryPopulator.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.populator; + +import com.github.steveice10.packetlib.packet.Packet; +import com.nukkitx.protocol.bedrock.BedrockPacket; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.util.FileUtils; + +public class PacketRegistryPopulator { + + public static void populate() { + for (Class clazz : FileUtils.getGeneratedClassesForAnnotation(Translator.class)) { + Class packet = clazz.getAnnotation(Translator.class).packet(); + + GeyserImpl.getInstance().getLogger().debug("Found annotated translator: " + clazz.getCanonicalName() + " : " + packet.getSimpleName()); + + try { + if (Packet.class.isAssignableFrom(packet)) { + Class targetPacket = (Class) packet; + PacketTranslator translator = (PacketTranslator) clazz.newInstance(); + + Registries.JAVA_PACKET_TRANSLATORS.register(targetPacket, translator); + } else if (BedrockPacket.class.isAssignableFrom(packet)) { + Class targetPacket = (Class) packet; + PacketTranslator translator = (PacketTranslator) clazz.newInstance(); + + Registries.BEDROCK_PACKET_TRANSLATORS.register(targetPacket, translator); + } else { + GeyserImpl.getInstance().getLogger().error("Class " + clazz.getCanonicalName() + " is annotated as a translator but has an invalid target packet."); + } + } catch (InstantiationException | IllegalAccessException e) { + GeyserImpl.getInstance().getLogger().error("Could not instantiate annotated translator " + clazz.getCanonicalName()); + } + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/RecipeRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/RecipeRegistryPopulator.java new file mode 100644 index 000000000..7af45f74d --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/RecipeRegistryPopulator.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.populator; + +import com.fasterxml.jackson.databind.JsonNode; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; +import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; +import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; +import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; +import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtUtils; +import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.registry.type.ItemMappings; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.translator.inventory.item.ItemTranslator; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; + +import static org.geysermc.geyser.util.InventoryUtils.LAST_RECIPE_NET_ID; + +/** + * Populates the recipe registry. + */ +public class RecipeRegistryPopulator { + + public static void populate() { + JsonNode items; + try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource("mappings/recipes.json")) { + items = GeyserImpl.JSON_MAPPER.readTree(stream); + } catch (Exception e) { + throw new AssertionError(GeyserLocale.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e); + } + + int currentRecipeId = LAST_RECIPE_NET_ID; + for (Int2ObjectMap.Entry version : Registries.ITEMS.get().int2ObjectEntrySet()) { + // Make a bit of an assumption here that the last recipe net ID will be equivalent between all versions + LAST_RECIPE_NET_ID = currentRecipeId; + Map> craftingData = new EnumMap<>(RecipeType.class); + Int2ObjectMap recipes = new Int2ObjectOpenHashMap<>(); + + craftingData.put(RecipeType.CRAFTING_SPECIAL_BOOKCLONING, + Collections.singletonList(CraftingData.fromMulti(UUID.fromString("d1ca6b84-338e-4f2f-9c6b-76cc8b4bd98d"), ++LAST_RECIPE_NET_ID))); + craftingData.put(RecipeType.CRAFTING_SPECIAL_REPAIRITEM, + Collections.singletonList(CraftingData.fromMulti(UUID.fromString("00000000-0000-0000-0000-000000000001"), ++LAST_RECIPE_NET_ID))); + craftingData.put(RecipeType.CRAFTING_SPECIAL_MAPEXTENDING, + Collections.singletonList(CraftingData.fromMulti(UUID.fromString("d392b075-4ba1-40ae-8789-af868d56f6ce"), ++LAST_RECIPE_NET_ID))); + craftingData.put(RecipeType.CRAFTING_SPECIAL_MAPCLONING, + Collections.singletonList(CraftingData.fromMulti(UUID.fromString("85939755-ba10-4d9d-a4cc-efb7a8e943c4"), ++LAST_RECIPE_NET_ID))); + craftingData.put(RecipeType.CRAFTING_SPECIAL_BANNERADDPATTERN, + Collections.singletonList(CraftingData.fromMulti(UUID.fromString("b5c5d105-75a2-4076-af2b-923ea2bf4bf0"), ++LAST_RECIPE_NET_ID))); + + // https://github.com/pmmp/PocketMine-MP/blob/stable/src/pocketmine/inventory/MultiRecipe.php + + for (JsonNode entry : items.get("leather_armor")) { + // This won't be perfect, as we can't possibly send every leather input for every kind of color + // But it does display the correct output from a base leather armor, and besides visuals everything works fine + craftingData.computeIfAbsent(RecipeType.CRAFTING_SPECIAL_ARMORDYE, + c -> new ObjectArrayList<>()).add(getCraftingDataFromJsonNode(entry, recipes, version.getValue())); + } + for (JsonNode entry : items.get("firework_rockets")) { + craftingData.computeIfAbsent(RecipeType.CRAFTING_SPECIAL_FIREWORK_ROCKET, + c -> new ObjectArrayList<>()).add(getCraftingDataFromJsonNode(entry, recipes, version.getValue())); + } + for (JsonNode entry : items.get("firework_stars")) { + craftingData.computeIfAbsent(RecipeType.CRAFTING_SPECIAL_FIREWORK_STAR, + c -> new ObjectArrayList<>()).add(getCraftingDataFromJsonNode(entry, recipes, version.getValue())); + } + for (JsonNode entry : items.get("shulker_boxes")) { + craftingData.computeIfAbsent(RecipeType.CRAFTING_SPECIAL_SHULKERBOXCOLORING, + c -> new ObjectArrayList<>()).add(getCraftingDataFromJsonNode(entry, recipes, version.getValue())); + } + for (JsonNode entry : items.get("suspicious_stew")) { + craftingData.computeIfAbsent(RecipeType.CRAFTING_SPECIAL_SUSPICIOUSSTEW, + c -> new ObjectArrayList<>()).add(getCraftingDataFromJsonNode(entry, recipes, version.getValue())); + } + for (JsonNode entry : items.get("tipped_arrows")) { + craftingData.computeIfAbsent(RecipeType.CRAFTING_SPECIAL_TIPPEDARROW, + c -> new ObjectArrayList<>()).add(getCraftingDataFromJsonNode(entry, recipes, version.getValue())); + } + + Registries.CRAFTING_DATA.register(version.getIntKey(), craftingData); + Registries.RECIPES.register(version.getIntKey(), recipes); + } + } + + /** + * Computes a Bedrock crafting recipe from the given JSON data. + * @param node the JSON data to compute + * @param recipes a list of all the recipes + * @return the {@link CraftingData} to send to the Bedrock client. + */ + private static CraftingData getCraftingDataFromJsonNode(JsonNode node, Int2ObjectMap recipes, ItemMappings mappings) { + int netId = ++LAST_RECIPE_NET_ID; + int type = node.get("bedrockRecipeType").asInt(); + JsonNode outputNode = node.get("output"); + ItemMapping outputEntry = mappings.getMapping(outputNode.get("identifier").asText()); + ItemData output = getBedrockItemFromIdentifierJson(outputEntry, outputNode); + UUID uuid = UUID.randomUUID(); + if (type == 1) { + // Shaped recipe + List shape = new ArrayList<>(); + // Get the shape of the recipe + for (JsonNode chars : node.get("shape")) { + shape.add(chars.asText()); + } + + // In recipes.json each recipe is mapped by a letter + Map letterToRecipe = new HashMap<>(); + Iterator> iterator = node.get("inputs").fields(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + JsonNode inputNode = entry.getValue(); + ItemMapping inputEntry = mappings.getMapping(inputNode.get("identifier").asText()); + letterToRecipe.put(entry.getKey(), getBedrockItemFromIdentifierJson(inputEntry, inputNode)); + } + + List inputs = new ArrayList<>(shape.size() * shape.get(0).length()); + int i = 0; + // Create a linear array of items from the "cube" of the shape + for (int j = 0; i < shape.size() * shape.get(0).length(); j++) { + for (char c : shape.get(j).toCharArray()) { + ItemData data = letterToRecipe.getOrDefault(String.valueOf(c), ItemData.AIR); + inputs.add(data); + i++; + } + } + + /* Convert into a Java recipe class for autocrafting */ + List ingredients = new ArrayList<>(); + for (ItemData input : inputs) { + ingredients.add(new Ingredient(new ItemStack[]{ItemTranslator.translateToJava(input, mappings)})); + } + ShapedRecipeData data = new ShapedRecipeData(shape.get(0).length(), shape.size(), "crafting_table", + ingredients.toArray(new Ingredient[0]), ItemTranslator.translateToJava(output, mappings)); + Recipe recipe = new Recipe(RecipeType.CRAFTING_SHAPED, "", data); + recipes.put(netId, recipe); + /* Convert end */ + + return CraftingData.fromShaped(uuid.toString(), shape.get(0).length(), shape.size(), + inputs, Collections.singletonList(output), uuid, "crafting_table", 0, netId); + } + List inputs = new ObjectArrayList<>(); + for (JsonNode entry : node.get("inputs")) { + ItemMapping inputEntry = mappings.getMapping(entry.get("identifier").asText()); + inputs.add(getBedrockItemFromIdentifierJson(inputEntry, entry)); + } + + /* Convert into a Java Recipe class for autocrafting */ + List ingredients = new ArrayList<>(); + for (ItemData input : inputs) { + ingredients.add(new Ingredient(new ItemStack[]{ItemTranslator.translateToJava(input, mappings)})); + } + ShapelessRecipeData data = new ShapelessRecipeData("crafting_table", + ingredients.toArray(new Ingredient[0]), ItemTranslator.translateToJava(output, mappings)); + Recipe recipe = new Recipe(RecipeType.CRAFTING_SHAPELESS, "", data); + recipes.put(netId, recipe); + /* Convert end */ + + if (type == 5) { + // Shulker box + return CraftingData.fromShulkerBox(uuid.toString(), + inputs, Collections.singletonList(output), uuid, "crafting_table", 0, netId); + } + return CraftingData.fromShapeless(uuid.toString(), + inputs, Collections.singletonList(output), uuid, "crafting_table", 0, netId); + } + + private static ItemData getBedrockItemFromIdentifierJson(ItemMapping mapping, JsonNode itemNode) { + int count = 1; + short damage = 0; + NbtMap tag = null; + JsonNode damageNode = itemNode.get("bedrockDamage"); + if (damageNode != null) { + damage = damageNode.numberValue().shortValue(); + } + JsonNode countNode = itemNode.get("count"); + if (countNode != null) { + count = countNode.asInt(); + } + JsonNode nbtNode = itemNode.get("bedrockNbt"); + if (nbtNode != null) { + byte[] bytes = Base64.getDecoder().decode(nbtNode.asText()); + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + try { + tag = (NbtMap) NbtUtils.createReaderLE(bais).readTag(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return ItemData.builder() + .id(mapping.getBedrockId()) + .damage(damage) + .count(count) + .blockRuntimeId(mapping.isBlock() ? mapping.getBedrockBlockId() : 0) + .tag(tag) + .build(); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/BlockMapping.java b/core/src/main/java/org/geysermc/geyser/registry/type/BlockMapping.java new file mode 100644 index 000000000..e65ebcfff --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/type/BlockMapping.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.type; + +import lombok.Builder; +import lombok.Value; +import org.geysermc.geyser.util.BlockUtils; +import org.geysermc.geyser.level.physics.PistonBehavior; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +@Builder +@Value +public class BlockMapping { + public static BlockMapping AIR; + + String javaIdentifier; + /** + * The block ID shared between all different block states of this block. + * NOT the runtime ID! + */ + int javaBlockId; + + double hardness; + boolean canBreakWithHand; + /** + * The index of this collision in collision.json + */ + int collisionIndex; + @Nullable String pickItem; + + @Nonnull + PistonBehavior pistonBehavior; + boolean isBlockEntity; + + /** + * @return the identifier without the additional block states + */ + public String getCleanJavaIdentifier() { + return BlockUtils.getCleanIdentifier(javaIdentifier); + } + + /** + * @return the corresponding Java identifier for this item + */ + public String getItemIdentifier() { + if (pickItem != null && !pickItem.equals("minecraft:air")) { + // Spawners can have air as their pick item which we are not interested in. + return pickItem; + } + + return getCleanJavaIdentifier(); + } + + /** + * Get the item a Java client would receive when pressing + * the Pick Block key on a specific Java block state. + * + * @return The Java identifier of the item + */ + public String getPickItem() { + if (pickItem != null) { + return pickItem; + } + + return getCleanJavaIdentifier(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java b/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java new file mode 100644 index 000000000..0d85b80e0 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.type; + +import com.nukkitx.nbt.NbtList; +import com.nukkitx.nbt.NbtMap; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import lombok.Builder; +import lombok.Value; + +import java.util.Map; + +@Builder +@Value +public class BlockMappings { + int bedrockAirId; + int bedrockWaterId; + int bedrockMovingBlockId; + + int blockStateVersion; + + int[] javaToBedrockBlocks; + + NbtList bedrockBlockStates; + + /** + * Contains a map of Java blocks to their respective Bedrock block tag, if the Java identifier is different from Bedrock. + * Required to fix villager trades with these blocks. + */ + Map javaIdentifierToBedrockTag; + + int commandBlockRuntimeId; + + Object2IntMap itemFrames; + Map flowerPotBlocks; + + IntSet jigsawStateIds; + + public int getBedrockBlockId(int state) { + if (state >= this.javaToBedrockBlocks.length) { + return bedrockAirId; + } + return this.javaToBedrockBlocks[state]; + } + + public int getItemFrame(NbtMap tag) { + return this.itemFrames.getOrDefault(tag, -1); + } + + public boolean isItemFrame(int bedrockBlockRuntimeId) { + return this.itemFrames.values().contains(bedrockBlockRuntimeId); + } + + /** + * @param cleanJavaIdentifier the clean Java identifier of the block to look up + * + * @return the block tag of the block name mapped from Java to Bedrock. + */ + public NbtMap getBedrockBlockNbt(String cleanJavaIdentifier) { + return this.javaIdentifierToBedrockTag.get(cleanJavaIdentifier); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/EnchantmentData.java b/core/src/main/java/org/geysermc/geyser/registry/type/EnchantmentData.java new file mode 100644 index 000000000..ce8b6abc5 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/type/EnchantmentData.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.type; + +import it.unimi.dsi.fastutil.ints.IntSet; +import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment; + +import java.util.Set; + +public record EnchantmentData(int rarityMultiplier, int maxLevel, Set incompatibleEnchantments, + IntSet validItems) { +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/GeyserMappingItem.java b/core/src/main/java/org/geysermc/geyser/registry/type/GeyserMappingItem.java new file mode 100644 index 000000000..56d7825cc --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/type/GeyserMappingItem.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.type; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * Represents Geyser's own serialized item information before being processed per-version + */ +@Data +public class GeyserMappingItem { + @JsonProperty("bedrock_identifier") String bedrockIdentifier; + @JsonProperty("bedrock_data") int bedrockData; + Integer firstBlockRuntimeId; + Integer lastBlockRuntimeId; + @JsonProperty("stack_size") int stackSize = 64; + @JsonProperty("tool_type") String toolType; + @JsonProperty("tool_tier") String toolTier; + @JsonProperty("max_damage") int maxDamage = 0; + @JsonProperty("repair_materials") List repairMaterials; +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java new file mode 100644 index 000000000..178ebd607 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMapping.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.type; + +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.registry.BlockRegistries; + +import java.util.Set; + +@Value +@Builder +@EqualsAndHashCode +public class ItemMapping { + public static final ItemMapping AIR = new ItemMapping("minecraft:air", "minecraft:air", 0, 0, 0, + BlockRegistries.BLOCKS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getBedrockAirId(), + 64, null, null, null, 0, null); + + String javaIdentifier; + String bedrockIdentifier; + int javaId; + int bedrockId; + int bedrockData; + + /** + * The Bedrock block runtime ID to render this item with. The specific state *does* matter in how this item is rendered and used as a crafting ingredient. + * Required since 1.16.220. + */ + int bedrockBlockId; + int stackSize; + + String toolType; + String toolTier; + + String translationString; + + int maxDamage; + + Set repairMaterials; + + /** + * Gets if this item is a block. + * + * @return if this item is a block + */ + public boolean isBlock() { + return this.bedrockBlockId != -1; + } + + /** + * Gets if this item has a translation string present. + * + * @return if this item has a translation string present + */ + public boolean hasTranslation() { + return this.translationString != null; + } + + /** + * Gets if this item is a tool. + * + * @return if this item is a tool + */ + public boolean isTool() { + return this.toolType != null; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/ItemMappings.java b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMappings.java new file mode 100644 index 000000000..a246cdcf3 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/type/ItemMappings.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.type; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.nukkitx.protocol.bedrock.data.inventory.ComponentItemData; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import com.nukkitx.protocol.bedrock.packet.StartGamePacket; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.IntList; +import lombok.Builder; +import lombok.Value; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.inventory.item.StoredItemMappings; + +import javax.annotation.Nullable; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; + +@Builder +@Value +public class ItemMappings { + + Map cachedJavaMappings = new WeakHashMap<>(); + + Int2ObjectMap items; + + ItemData[] creativeItems; + List itemEntries; + + StoredItemMappings storedItems; + String[] itemNames; + Set javaOnlyItems; + + IntList bucketIds; + IntList boatIds; + IntList spawnEggIds; + List carpets; + + @Nullable ComponentItemData furnaceMinecartData; + + /** + * Gets an {@link ItemMapping} from the given {@link ItemStack}. + * + * @param itemStack the itemstack + * @return an item entry from the given java edition identifier + */ + public ItemMapping getMapping(ItemStack itemStack) { + return this.getMapping(itemStack.getId()); + } + + /** + * Gets an {@link ItemMapping} from the given Minecraft: Java + * Edition id. + * + * @param javaId the id + * @return an item entry from the given java edition identifier + */ + public ItemMapping getMapping(int javaId) { + return this.items.get(javaId); + } + + /** + * Gets an {@link ItemMapping} from the given Minecraft: Java Edition + * block state identifier. + * + * @param javaIdentifier the block state identifier + * @return an item entry from the given java edition identifier + */ + public ItemMapping getMapping(String javaIdentifier) { + return this.cachedJavaMappings.computeIfAbsent(javaIdentifier, key -> { + for (ItemMapping mapping : this.items.values()) { + if (mapping.getJavaIdentifier().equals(key)) { + return mapping; + } + } + return null; + }); + } + + /** + * Gets an {@link ItemMapping} from the given {@link ItemData}. + * + * @param data the item data + * @return an item entry from the given item data + */ + public ItemMapping getMapping(ItemData data) { + boolean isBlock = data.getBlockRuntimeId() != 0; + boolean hasDamage = data.getDamage() != 0; + + for (ItemMapping mapping : this.items.values()) { + if (mapping.getBedrockId() == data.getId()) { + if (isBlock && !hasDamage) { // Pre-1.16.220 will not use block runtime IDs at all, so we shouldn't check either + if (data.getBlockRuntimeId() != mapping.getBedrockBlockId()) { + continue; + } + } else { + if (!(mapping.getBedrockData() == data.getDamage() || + // Make exceptions for potions, tipped arrows, and firework stars, whose damage values can vary + (mapping.getJavaIdentifier().endsWith("potion") || mapping.getJavaIdentifier().equals("minecraft:arrow") + || mapping.getJavaIdentifier().equals("minecraft:firework_star")))) { + continue; + } + } + if (!this.javaOnlyItems.contains(mapping.getJavaIdentifier())) { + // From a Bedrock item data, we aren't getting one of these items + return mapping; + } + } + } + + // This will hide the message when the player clicks with an empty hand + if (data.getId() != 0 && data.getDamage() != 0) { + GeyserImpl.getInstance().getLogger().debug("Missing mapping for bedrock item " + data.getId() + ":" + data.getDamage()); + } + return ItemMapping.AIR; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/PaletteItem.java b/core/src/main/java/org/geysermc/geyser/registry/type/PaletteItem.java new file mode 100644 index 000000000..c7815c055 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/type/PaletteItem.java @@ -0,0 +1,35 @@ + +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.type; + +import lombok.Data; + +@Data +public class PaletteItem { + String name; + int id; +} \ No newline at end of file diff --git a/common/src/main/java/org/geysermc/common/window/component/InputComponent.java b/core/src/main/java/org/geysermc/geyser/registry/type/ParticleMapping.java similarity index 64% rename from common/src/main/java/org/geysermc/common/window/component/InputComponent.java rename to core/src/main/java/org/geysermc/geyser/registry/type/ParticleMapping.java index fad6a0fed..cd5ad17ce 100644 --- a/common/src/main/java/org/geysermc/common/window/component/InputComponent.java +++ b/core/src/main/java/org/geysermc/geyser/registry/type/ParticleMapping.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,30 +23,21 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.component; +package org.geysermc.geyser.registry.type; -import lombok.Getter; -import lombok.Setter; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import org.geysermc.geyser.session.GeyserSession; -public class InputComponent extends FormComponent { +import javax.annotation.ParametersAreNullableByDefault; - @Getter - @Setter - private String text; +@ParametersAreNullableByDefault +public record ParticleMapping(LevelEventType levelEventType, String identifier) { - @Getter - @Setter - private String placeholder; + public int getParticleId(GeyserSession session) { + if (this.levelEventType == null) { + return -1; + } - @Getter - @Setter - private String defaultText; - - public InputComponent(String text, String placeholder, String defaultText) { - super("input"); - - this.text = text; - this.placeholder = placeholder; - this.defaultText = defaultText; + return session.getUpstream().getSession().getPacketCodec().getHelper().getLevelEventId(this.levelEventType) & ~0x4000; } -} +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/SoundMapping.java b/core/src/main/java/org/geysermc/geyser/registry/type/SoundMapping.java new file mode 100644 index 000000000..d6aaf9bd0 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/type/SoundMapping.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.registry.type; + +import lombok.Value; + +@Value +public class SoundMapping { + String java; + String bedrock; + String playsound; + int extraData; + String identifier; + boolean levelEvent; + + public SoundMapping(String java, String bedrock, String playsound, int extraData, String identifier, boolean levelEvent) { + this.java = java; + this.bedrock = bedrock == null || bedrock.equalsIgnoreCase("") ? null : bedrock; + this.playsound = playsound == null || playsound.equalsIgnoreCase("") ? null : playsound; + this.extraData = extraData; + this.identifier = identifier == null || identifier.equalsIgnoreCase("") ? ":" : identifier; + this.levelEvent = levelEvent; + } +} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Objective.java similarity index 72% rename from connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java rename to core/src/main/java/org/geysermc/geyser/scoreboard/Objective.java index 419e99fd0..31407b77d 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/Objective.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,9 +23,10 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.scoreboard; +package org.geysermc.geyser.scoreboard; import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition; +import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; import lombok.Getter; import lombok.Setter; @@ -69,32 +70,18 @@ public final class Objective { public Objective(Scoreboard scoreboard, String objectiveName, ScoreboardPosition displaySlot, String displayName, int type) { this(scoreboard); this.objectiveName = objectiveName; - this.displaySlot = correctDisplaySlot(displaySlot); + this.displaySlot = displaySlot; this.displaySlotName = translateDisplaySlot(displaySlot); this.displayName = displayName; this.type = type; } private static String translateDisplaySlot(ScoreboardPosition displaySlot) { - switch (displaySlot) { - case BELOW_NAME: - return "belowname"; - case PLAYER_LIST: - return "list"; - default: - return "sidebar"; - } - } - - private static ScoreboardPosition correctDisplaySlot(ScoreboardPosition displaySlot) { - switch (displaySlot) { - case BELOW_NAME: - return ScoreboardPosition.BELOW_NAME; - case PLAYER_LIST: - return ScoreboardPosition.PLAYER_LIST; - default: - return ScoreboardPosition.SIDEBAR; - } + return switch (displaySlot) { + case BELOW_NAME -> "belowname"; + case PLAYER_LIST -> "list"; + default -> "sidebar"; + }; } public void registerScore(String id, int score) { @@ -151,12 +138,43 @@ public final class Objective { public void setActive(ScoreboardPosition displaySlot) { if (!active) { active = true; - this.displaySlot = correctDisplaySlot(displaySlot); + this.displaySlot = displaySlot; displaySlotName = translateDisplaySlot(displaySlot); } } + /** + * The objective will be removed on the next update + */ + public void pendingRemove() { + updateType = UpdateType.REMOVE; + } + + public TeamColor getTeamColor() { + return switch (displaySlot) { + case SIDEBAR_TEAM_RED -> TeamColor.RED; + case SIDEBAR_TEAM_AQUA -> TeamColor.AQUA; + case SIDEBAR_TEAM_BLUE -> TeamColor.BLUE; + case SIDEBAR_TEAM_GOLD -> TeamColor.GOLD; + case SIDEBAR_TEAM_GRAY -> TeamColor.GRAY; + case SIDEBAR_TEAM_BLACK -> TeamColor.BLACK; + case SIDEBAR_TEAM_GREEN -> TeamColor.GREEN; + case SIDEBAR_TEAM_WHITE -> TeamColor.WHITE; + case SIDEBAR_TEAM_YELLOW -> TeamColor.YELLOW; + case SIDEBAR_TEAM_DARK_RED -> TeamColor.DARK_RED; + case SIDEBAR_TEAM_DARK_AQUA -> TeamColor.DARK_AQUA; + case SIDEBAR_TEAM_DARK_BLUE -> TeamColor.DARK_BLUE; + case SIDEBAR_TEAM_DARK_GRAY -> TeamColor.DARK_GRAY; + case SIDEBAR_TEAM_DARK_GREEN -> TeamColor.DARK_GREEN; + case SIDEBAR_TEAM_DARK_PURPLE -> TeamColor.DARK_PURPLE; + case SIDEBAR_TEAM_LIGHT_PURPLE -> TeamColor.LIGHT_PURPLE; + default -> null; + }; + } + public void removed() { + active = false; + updateType = UpdateType.REMOVE; scores = null; } } diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Score.java similarity index 82% rename from connector/src/main/java/org/geysermc/connector/scoreboard/Score.java rename to core/src/main/java/org/geysermc/geyser/scoreboard/Score.java index e5f97b459..fe8674d1a 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/Score.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.scoreboard; +package org.geysermc.geyser.scoreboard; import com.nukkitx.protocol.bedrock.data.ScoreInfo; import lombok.Getter; @@ -36,8 +36,14 @@ public final class Score { private final String name; private ScoreInfo cachedInfo; - private ScoreData currentData; - private ScoreData cachedData; + /** + * Changes that have been made since the last cached data. + */ + private final Score.ScoreData currentData; + /** + * The data that is currently displayed to the Bedrock client. + */ + private Score.ScoreData cachedData; public Score(long id, String name) { this.id = id; @@ -53,6 +59,10 @@ public final class Score { return name; } + public int getScore() { + return currentData.getScore(); + } + public Score setScore(int score) { currentData.score = score; return this; @@ -66,32 +76,32 @@ public final class Score { if (currentData.team != null && team != null) { if (!currentData.team.equals(team)) { currentData.team = team; - currentData.updateType = UpdateType.UPDATE; + setUpdateType(UpdateType.UPDATE); } return this; } // simplified from (this.team != null && team == null) || (this.team == null && team != null) if (currentData.team != null || team != null) { currentData.team = team; - currentData.updateType = UpdateType.UPDATE; + setUpdateType(UpdateType.UPDATE); } return this; } public UpdateType getUpdateType() { - return cachedData != null ? cachedData.updateType : currentData.updateType; + return currentData.updateType; } public Score setUpdateType(UpdateType updateType) { if (updateType != UpdateType.NOTHING) { - currentData.updateTime = System.currentTimeMillis(); + currentData.changed = true; } currentData.updateType = updateType; return this; } public boolean shouldUpdate() { - return cachedData == null || currentData.updateTime > cachedData.updateTime || + return cachedData == null || currentData.changed || (currentData.team != null && currentData.team.shouldUpdate()); } @@ -106,7 +116,7 @@ public final class Score { cachedData.updateType = currentData.updateType; } - cachedData.updateTime = currentData.updateTime; + currentData.changed = false; cachedData.team = currentData.team; cachedData.score = currentData.score; @@ -120,13 +130,13 @@ public final class Score { @Getter public static final class ScoreData { - protected UpdateType updateType; - protected long updateTime; + private UpdateType updateType; + private boolean changed; private Team team; private int score; - protected ScoreData() { + private ScoreData() { updateType = UpdateType.ADD; } } diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java new file mode 100644 index 000000000..2d7f2373f --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java @@ -0,0 +1,391 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.scoreboard; + +import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition; +import com.nukkitx.protocol.bedrock.data.ScoreInfo; +import com.nukkitx.protocol.bedrock.packet.RemoveObjectivePacket; +import com.nukkitx.protocol.bedrock.packet.SetDisplayObjectivePacket; +import com.nukkitx.protocol.bedrock.packet.SetScorePacket; +import lombok.Getter; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.GeyserLogger; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.GeyserLocale; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +import static org.geysermc.geyser.scoreboard.UpdateType.*; + +public final class Scoreboard { + private final GeyserSession session; + private final GeyserLogger logger; + @Getter + private final AtomicLong nextId = new AtomicLong(0); + + private final Map objectives = new ConcurrentHashMap<>(); + @Getter + private final Map objectiveSlots = new EnumMap<>(ScoreboardPosition.class); + private final Map teams = new ConcurrentHashMap<>(); // updated on multiple threads + + private int lastAddScoreCount = 0; + private int lastRemoveScoreCount = 0; + + public Scoreboard(GeyserSession session) { + this.session = session; + this.logger = GeyserImpl.getInstance().getLogger(); + } + + public void removeScoreboard() { + Iterator iterator = objectives.values().iterator(); + while (iterator.hasNext()) { + Objective objective = iterator.next(); + iterator.remove(); + + deleteObjective(objective, false); + } + } + + public Objective registerNewObjective(String objectiveId) { + Objective objective = objectives.get(objectiveId); + if (objective != null) { + // we have no other choice, or we have to make a new map? + // if the objective hasn't been deleted, we have to force it + if (objective.getUpdateType() != REMOVE) { + return null; + } + deleteObjective(objective, true); + } + + objective = new Objective(this, objectiveId); + objectives.put(objectiveId, objective); + return objective; + } + + public void displayObjective(String objectiveId, ScoreboardPosition displaySlot) { + Objective objective = objectives.get(objectiveId); + if (objective == null) { + return; + } + + if (!objective.isActive()) { + objective.setActive(displaySlot); + // for reactivated objectives + objective.setUpdateType(ADD); + } + + Objective storedObjective = objectiveSlots.get(displaySlot); + if (storedObjective != null && storedObjective != objective) { + storedObjective.pendingRemove(); + } + objectiveSlots.put(displaySlot, objective); + + if (displaySlot == ScoreboardPosition.BELOW_NAME) { + // Display the below name score option to all players + // Of note: unlike Bedrock, if there is an objective in the below name slot, everyone has a display + for (PlayerEntity entity : session.getEntityCache().getAllPlayerEntities()) { + if (!entity.isValid()) { + // Player hasn't spawned yet - don't bother, it'll be done then + continue; + } + + entity.setBelowNameText(objective); + } + } + } + + public Team registerNewTeam(String teamName, String[] players) { + Team team = teams.get(teamName); + if (team != null) { + logger.info(GeyserLocale.getLocaleStringLog("geyser.network.translator.team.failed_overrides", teamName)); + return team; + } + + team = new Team(this, teamName); + team.addEntities(players); + teams.put(teamName, team); + return team; + } + + public void onUpdate() { + List addScores = new ArrayList<>(lastAddScoreCount); + List removeScores = new ArrayList<>(lastRemoveScoreCount); + List removedObjectives = new ArrayList<>(); + + Team playerTeam = getTeamFor(session.getPlayerEntity().getUsername()); + Objective correctSidebar = null; + + for (Objective objective : objectives.values()) { + // objective has been deleted + if (objective.getUpdateType() == REMOVE) { + removedObjectives.add(objective); + continue; + } + + // there's nothing we can do with inactive objectives + // after checking if the objective has been deleted, + // except waiting for the objective to become activated (: + if (!objective.isActive()) { + continue; + } + + if (playerTeam != null && playerTeam.getColor() == objective.getTeamColor()) { + correctSidebar = objective; + } + } + + if (correctSidebar == null) { + correctSidebar = objectiveSlots.get(ScoreboardPosition.SIDEBAR); + } + + for (Objective objective : removedObjectives) { + // Deletion must be handled before the active objectives are handled - otherwise if a scoreboard display is changed before the current + // scoreboard is removed, the client can crash + deleteObjective(objective, true); + } + + handleObjective(objectiveSlots.get(ScoreboardPosition.PLAYER_LIST), addScores, removeScores); + handleObjective(correctSidebar, addScores, removeScores); + handleObjective(objectiveSlots.get(ScoreboardPosition.BELOW_NAME), addScores, removeScores); + + Iterator teamIterator = teams.values().iterator(); + while (teamIterator.hasNext()) { + Team current = teamIterator.next(); + + switch (current.getCachedUpdateType()) { + case ADD, UPDATE -> current.markUpdated(); + case REMOVE -> teamIterator.remove(); + } + } + + if (!removeScores.isEmpty()) { + SetScorePacket setScorePacket = new SetScorePacket(); + setScorePacket.setAction(SetScorePacket.Action.REMOVE); + setScorePacket.setInfos(removeScores); + session.sendUpstreamPacket(setScorePacket); + } + + if (!addScores.isEmpty()) { + SetScorePacket setScorePacket = new SetScorePacket(); + setScorePacket.setAction(SetScorePacket.Action.SET); + setScorePacket.setInfos(addScores); + session.sendUpstreamPacket(setScorePacket); + } + + lastAddScoreCount = addScores.size(); + lastRemoveScoreCount = removeScores.size(); + } + + private void handleObjective(Objective objective, List addScores, List removeScores) { + if (objective == null || objective.getUpdateType() == REMOVE) { + return; + } + + // hearts can't hold teams, so we treat them differently + if (objective.getType() == 1) { + for (Score score : objective.getScores().values()) { + boolean update = score.shouldUpdate(); + + if (update) { + score.update(objective.getObjectiveName()); + } + + if (score.getUpdateType() != REMOVE && update) { + addScores.add(score.getCachedInfo()); + } + if (score.getUpdateType() != ADD && update) { + removeScores.add(score.getCachedInfo()); + } + } + return; + } + + boolean objectiveAdd = objective.getUpdateType() == ADD; + boolean objectiveUpdate = objective.getUpdateType() == UPDATE; + + for (Score score : objective.getScores().values()) { + if (score.getUpdateType() == REMOVE) { + removeScores.add(score.getCachedInfo()); + // score is pending to be removed, so we can remove it from the objective + objective.removeScore0(score.getName()); + break; + } + + Team team = score.getTeam(); + + boolean add = objectiveAdd || objectiveUpdate; + + if (team != null) { + if (team.getUpdateType() == REMOVE || !team.hasEntity(score.getName())) { + score.setTeam(null); + add = true; + } + } + + if (score.shouldUpdate()) { + score.update(objective.getObjectiveName()); + add = true; + } + + if (add) { + addScores.add(score.getCachedInfo()); + } + + // we need this as long as MCPE-143063 hasn't been fixed. + // the checks after 'add' are there to prevent removing scores that + // are going to be removed anyway / don't need to be removed + if (add && score.getUpdateType() != ADD && !(objectiveUpdate || objectiveAdd)) { + removeScores.add(score.getCachedInfo()); + } + + score.setUpdateType(NOTHING); + } + + if (objectiveUpdate) { + RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); + removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); + session.sendUpstreamPacket(removeObjectivePacket); + } + + if (objectiveAdd || objectiveUpdate) { + SetDisplayObjectivePacket displayObjectivePacket = new SetDisplayObjectivePacket(); + displayObjectivePacket.setObjectiveId(objective.getObjectiveName()); + displayObjectivePacket.setDisplayName(objective.getDisplayName()); + displayObjectivePacket.setCriteria("dummy"); + displayObjectivePacket.setDisplaySlot(objective.getDisplaySlotName()); + displayObjectivePacket.setSortOrder(1); // 0 = ascending, 1 = descending + session.sendUpstreamPacket(displayObjectivePacket); + } + + objective.setUpdateType(NOTHING); + } + + /** + * @param remove if we should remove the objective from the objectives map. + */ + public void deleteObjective(Objective objective, boolean remove) { + if (remove) { + objectives.remove(objective.getObjectiveName()); + } + objectiveSlots.remove(objective.getDisplaySlot(), objective); + + objective.removed(); + + RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); + removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); + session.sendUpstreamPacket(removeObjectivePacket); + } + + public Objective getObjective(String objectiveName) { + return objectives.get(objectiveName); + } + + public Collection getObjectives() { + return objectives.values(); + } + + public void unregisterObjective(String objectiveName) { + Objective objective = getObjective(objectiveName); + if (objective != null) { + objective.pendingRemove(); + } + } + + public Objective getSlot(ScoreboardPosition slot) { + return objectiveSlots.get(slot); + } + + public Team getTeam(String teamName) { + return teams.get(teamName); + } + + public Team getTeamFor(String entity) { + for (Team team : teams.values()) { + if (team.hasEntity(entity)) { + return team; + } + } + return null; + } + + public void removeTeam(String teamName) { + Team remove = teams.remove(teamName); + if (remove != null) { + remove.setUpdateType(REMOVE); + // We need to use the direct entities list here, so #refreshSessionPlayerDisplays also updates accordingly + // With the player's lack of a team in visibility checks + updateEntityNames(remove, remove.getEntities(), true); + } + } + + /** + * Updates the display names of all entities in a given team. + * @param teamChange the players have either joined or left the team. Used for optimizations when just the display name updated. + */ + public void updateEntityNames(Team team, boolean teamChange) { + Set names = new HashSet<>(team.getEntities()); + updateEntityNames(team, names, teamChange); + } + + /** + * Updates the display name of a set of entities within a given team. The team may also be null if the set is being removed + * from a team. + */ + public void updateEntityNames(@Nullable Team team, Set names, boolean teamChange) { + if (names.remove(session.getPlayerEntity().getUsername()) && teamChange) { + // If the player's team changed, then other entities' teams may modify their visibility based on team status + refreshSessionPlayerDisplays(); + } + if (!names.isEmpty()) { + for (Entity entity : session.getEntityCache().getEntities().values()) { + // This more complex logic is for the future to iterate over all entities, not just players + if (entity instanceof PlayerEntity player && names.remove(player.getUsername())) { + player.updateDisplayName(team, true); + if (names.isEmpty()) { + break; + } + } + } + } + } + + /** + * If the team's player was refreshed, then we need to go through every entity and check... + */ + private void refreshSessionPlayerDisplays() { + for (Entity entity : session.getEntityCache().getEntities().values()) { + if (entity instanceof PlayerEntity player) { + Team playerTeam = session.getWorldCache().getScoreboard().getTeamFor(player.getUsername()); + player.updateDisplayName(playerTeam, true); + } + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/ScoreboardUpdater.java b/core/src/main/java/org/geysermc/geyser/scoreboard/ScoreboardUpdater.java new file mode 100644 index 000000000..80a4491ba --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/ScoreboardUpdater.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.scoreboard; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.WorldCache; +import org.geysermc.geyser.text.GeyserLocale; + +import java.util.Collection; +import java.util.concurrent.atomic.AtomicInteger; + +public final class ScoreboardUpdater extends Thread { + public static final int FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD; + public static final int SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD = 250; + + private static final int FIRST_MILLIS_BETWEEN_UPDATES = 250; // 4 updates per second + private static final int SECOND_MILLIS_BETWEEN_UPDATES = 1000; // 1 update per second + + private static final boolean DEBUG_ENABLED; + + static { + GeyserConfiguration config = GeyserImpl.getInstance().getConfig(); + FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD = Math.min(config.getScoreboardPacketThreshold(), SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD); + DEBUG_ENABLED = config.isDebugMode(); + } + + private final GeyserImpl geyser = GeyserImpl.getInstance(); + + private long lastUpdate = System.currentTimeMillis(); + private long lastPacketsPerSecondUpdate = System.currentTimeMillis(); + + public static void init() { + new ScoreboardUpdater().start(); + } + + @Override + public void run() { + while (!geyser.isShuttingDown()) { + try { + long timeTillAction = getTimeTillNextAction(); + if (timeTillAction > 0) { + sleepFor(timeTillAction); + continue; + } + + long currentTime = System.currentTimeMillis(); + + // reset score-packets per second every second + Collection sessions = geyser.getSessionManager().getSessions().values(); + if (currentTime - lastPacketsPerSecondUpdate >= 1000) { + lastPacketsPerSecondUpdate = currentTime; + for (GeyserSession session : sessions) { + ScoreboardSession scoreboardSession = session.getWorldCache().getScoreboardSession(); + + int oldPps = scoreboardSession.getPacketsPerSecond(); + int newPps = scoreboardSession.getPendingPacketsPerSecond().get(); + + scoreboardSession.packetsPerSecond = newPps; + scoreboardSession.pendingPacketsPerSecond.set(0); + + // just making sure that all updates are pushed before giving up control + if (oldPps >= FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD && + newPps < FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) { + session.getWorldCache().getScoreboard().onUpdate(); + } + } + } + + if (currentTime - lastUpdate >= FIRST_MILLIS_BETWEEN_UPDATES) { + lastUpdate = currentTime; + + for (GeyserSession session : sessions) { + WorldCache worldCache = session.getWorldCache(); + ScoreboardSession scoreboardSession = worldCache.getScoreboardSession(); + + int pps = scoreboardSession.getPacketsPerSecond(); + if (pps >= FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) { + boolean reachedSecondThreshold = pps >= SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD; + + int millisBetweenUpdates = reachedSecondThreshold ? + SECOND_MILLIS_BETWEEN_UPDATES : + FIRST_MILLIS_BETWEEN_UPDATES; + + if (currentTime - scoreboardSession.lastUpdate >= millisBetweenUpdates) { + worldCache.getScoreboard().onUpdate(); + scoreboardSession.lastUpdate = currentTime; + + if (DEBUG_ENABLED && (currentTime - scoreboardSession.lastLog >= 60000)) { // one minute + int threshold = reachedSecondThreshold ? + SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD : + FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD; + + geyser.getLogger().info( + GeyserLocale.getLocaleStringLog("geyser.scoreboard.updater.threshold_reached.log", session.name(), threshold, pps) + + GeyserLocale.getLocaleStringLog("geyser.scoreboard.updater.threshold_reached", (millisBetweenUpdates / 1000.0)) + ); + + scoreboardSession.lastLog = currentTime; + } + } + } + } + } + + if (DEBUG_ENABLED) { + long timeSpent = System.currentTimeMillis() - currentTime; + if (timeSpent > 0) { + geyser.getLogger().info(String.format( + "Scoreboard updater: took %s ms. Updated %s players", + timeSpent, sessions.size() + )); + } + } + + long timeTillNextAction = getTimeTillNextAction(); + sleepFor(timeTillNextAction); + } catch (Throwable e) { + geyser.getLogger().error("Error while translating scoreboard information!", e); + // Wait so we don't try to run the scoreboard immediately after this + sleepFor(FIRST_MILLIS_BETWEEN_UPDATES); + } + } + } + + private long getTimeTillNextAction() { + long currentTime = System.currentTimeMillis(); + + long timeUntilNextUpdate = FIRST_MILLIS_BETWEEN_UPDATES - (currentTime - lastUpdate); + long timeUntilPacketReset = 1000 - (currentTime - lastPacketsPerSecondUpdate); + + return Math.min(timeUntilNextUpdate, timeUntilPacketReset); + } + + private void sleepFor(long millis) { + if (millis <= 0) { + return; + } + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + @RequiredArgsConstructor + @Getter + public static final class ScoreboardSession { + private final GeyserSession session; + private final AtomicInteger pendingPacketsPerSecond = new AtomicInteger(0); + private int packetsPerSecond; + private long lastUpdate; + private long lastLog; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java similarity index 75% rename from connector/src/main/java/org/geysermc/connector/scoreboard/Team.java rename to core/src/main/java/org/geysermc/geyser/scoreboard/Team.java index 377a15f1e..b2fb44d34 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.scoreboard; +package org.geysermc.geyser.scoreboard; import com.github.steveice10.mc.protocol.data.game.scoreboard.NameTagVisibility; import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; @@ -33,8 +33,7 @@ import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; import java.util.Set; @Getter @@ -43,12 +42,12 @@ public final class Team { private final Scoreboard scoreboard; private final String id; - @Getter(AccessLevel.NONE) + @Getter(AccessLevel.PACKAGE) private final Set entities; @Setter private NameTagVisibility nameTagVisibility; @Setter private TeamColor color; - private TeamData currentData; + private final TeamData currentData; private TeamData cachedData; private boolean updating; @@ -60,13 +59,20 @@ public final class Team { entities = new ObjectOpenHashSet<>(); } - private void checkAddedEntities(List added) { - if (added.size() == 0) { - return; + public Set addEntities(String... names) { + Set added = new HashSet<>(); + for (String name : names) { + if (entities.add(name)) { + added.add(name); + } + } + + if (added.isEmpty()) { + return added; } // we don't have to change the updateType, // because the scores itself need updating, not the team - for (Objective objective : scoreboard.getObjectives().values()) { + for (Objective objective : scoreboard.getObjectives()) { for (String addedEntity : added) { Score score = objective.getScores().get(addedEntity); if (score != null) { @@ -74,34 +80,21 @@ public final class Team { } } } + + return added; } - public Team addEntities(String... names) { - List added = new ArrayList<>(); + /** + * @return all removed entities from this team + */ + public Set removeEntities(String... names) { + Set removed = new HashSet<>(); for (String name : names) { - if (entities.add(name)) { - added.add(name); + if (entities.remove(name)) { + removed.add(name); } } - checkAddedEntities(added); - return this; - } - - public Team addEntities(Set names) { - List added = new ArrayList<>(); - for (String name : names) { - if (entities.add(name)) { - added.add(name); - } - } - checkAddedEntities(added); - return this; - } - - public void removeEntities(String... names) { - for (String name : names) { - entities.remove(name); - } + return removed; } public boolean hasEntity(String name) { @@ -146,7 +139,7 @@ public final class Team { } public boolean shouldUpdate() { - return updating || cachedData == null || currentData.updateTime > cachedData.updateTime; + return updating || cachedData == null || currentData.changed; } public void prepareUpdate() { @@ -162,36 +155,39 @@ public final class Team { cachedData.updateType = currentData.updateType; } - cachedData.updateTime = currentData.updateTime; + currentData.changed = false; cachedData.name = currentData.name; cachedData.prefix = currentData.prefix; cachedData.suffix = currentData.suffix; } public UpdateType getUpdateType() { + return currentData.updateType; + } + + public UpdateType getCachedUpdateType() { return cachedData != null ? cachedData.updateType : currentData.updateType; } public Team setUpdateType(UpdateType updateType) { if (updateType != UpdateType.NOTHING) { - currentData.updateTime = System.currentTimeMillis(); + currentData.changed = true; } currentData.updateType = updateType; return this; } public boolean isVisibleFor(String entity) { - switch (nameTagVisibility) { - case HIDE_FOR_OTHER_TEAMS: - return hasEntity(entity); - case HIDE_FOR_OWN_TEAM: - return !hasEntity(entity); - case ALWAYS: - return true; - case NEVER: - return false; - } - return true; + return switch (nameTagVisibility) { + case HIDE_FOR_OTHER_TEAMS -> { + // Player must be in a team in order for HIDE_FOR_OTHER_TEAMS to be triggered + Team team = scoreboard.getTeamFor(entity); + yield team == null || team == this; + } + case HIDE_FOR_OWN_TEAM -> !hasEntity(entity); + case ALWAYS -> true; + case NEVER -> false; + }; } @Override @@ -201,14 +197,14 @@ public final class Team { @Getter public static final class TeamData { - protected UpdateType updateType; - protected long updateTime; + private UpdateType updateType; + private boolean changed; - protected String name; - protected String prefix; - protected String suffix; + private String name; + private String prefix; + private String suffix; - protected TeamData() { + private TeamData() { updateType = UpdateType.ADD; } diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/UpdateType.java b/core/src/main/java/org/geysermc/geyser/scoreboard/UpdateType.java similarity index 93% rename from connector/src/main/java/org/geysermc/connector/scoreboard/UpdateType.java rename to core/src/main/java/org/geysermc/geyser/scoreboard/UpdateType.java index c7481df0b..3366b08f8 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/UpdateType.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/UpdateType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.scoreboard; +package org.geysermc.geyser.scoreboard; public enum UpdateType { REMOVE, diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java new file mode 100644 index 000000000..2e3368356 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -0,0 +1,1527 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.session; + +import com.github.steveice10.mc.auth.data.GameProfile; +import com.github.steveice10.mc.auth.exception.request.AuthPendingException; +import com.github.steveice10.mc.auth.exception.request.InvalidCredentialsException; +import com.github.steveice10.mc.auth.exception.request.RequestException; +import com.github.steveice10.mc.auth.service.AuthenticationService; +import com.github.steveice10.mc.auth.service.MojangAuthenticationService; +import com.github.steveice10.mc.auth.service.MsaAuthenticationService; +import com.github.steveice10.mc.protocol.MinecraftConstants; +import com.github.steveice10.mc.protocol.MinecraftProtocol; +import com.github.steveice10.mc.protocol.data.ProtocolState; +import com.github.steveice10.mc.protocol.data.UnexpectedEncryptionException; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; +import com.github.steveice10.mc.protocol.data.game.statistic.CustomStatistic; +import com.github.steveice10.mc.protocol.data.game.statistic.Statistic; +import com.github.steveice10.mc.protocol.packet.handshake.serverbound.ClientIntentionPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.level.ServerboundAcceptTeleportationPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosRotPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerAbilitiesPacket; +import com.github.steveice10.mc.protocol.packet.login.serverbound.ServerboundCustomQueryPacket; +import com.github.steveice10.packetlib.BuiltinFlags; +import com.github.steveice10.packetlib.Session; +import com.github.steveice10.packetlib.event.session.*; +import com.github.steveice10.packetlib.packet.Packet; +import com.github.steveice10.packetlib.tcp.TcpClientSession; +import com.github.steveice10.packetlib.tcp.TcpSession; +import com.nukkitx.math.GenericMath; +import com.nukkitx.math.vector.*; +import com.nukkitx.protocol.bedrock.BedrockPacket; +import com.nukkitx.protocol.bedrock.BedrockServerSession; +import com.nukkitx.protocol.bedrock.data.*; +import com.nukkitx.protocol.bedrock.data.command.CommandPermission; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.*; +import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; +import io.netty.channel.Channel; +import io.netty.channel.EventLoop; +import it.unimi.dsi.fastutil.ints.*; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import org.geysermc.common.PlatformType; +import org.geysermc.cumulus.Form; +import org.geysermc.cumulus.util.FormBuilder; +import org.geysermc.floodgate.crypto.FloodgateCipher; +import org.geysermc.floodgate.util.BedrockData; +import org.geysermc.geyser.Constants; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.connection.GeyserConnection; +import org.geysermc.geyser.command.CommandSender; +import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption; +import org.geysermc.geyser.entity.InteractiveTagManager; +import org.geysermc.geyser.entity.attribute.GeyserAttributeType; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.type.ItemFrameEntity; +import org.geysermc.geyser.entity.type.Tickable; +import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; +import org.geysermc.geyser.entity.type.player.SkullPlayerEntity; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.inventory.PlayerInventory; +import org.geysermc.geyser.level.WorldManager; +import org.geysermc.geyser.level.physics.CollisionManager; +import org.geysermc.geyser.network.netty.LocalSession; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.registry.type.BlockMappings; +import org.geysermc.geyser.registry.type.ItemMappings; +import org.geysermc.geyser.session.auth.AuthData; +import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.session.auth.BedrockClientData; +import org.geysermc.geyser.session.cache.*; +import org.geysermc.geyser.skin.FloodgateSkinUploader; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.text.MinecraftLocale; +import org.geysermc.geyser.translator.inventory.InventoryTranslator; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.geyser.util.ChunkUtils; +import org.geysermc.geyser.util.DimensionUtils; +import org.geysermc.geyser.util.LoginEncryptionUtils; +import org.geysermc.geyser.util.MathUtils; + +import java.net.ConnectException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +@Getter +public class GeyserSession implements GeyserConnection, CommandSender { + + private final GeyserImpl geyser; + private final UpstreamSession upstream; + /** + * The loop where all packets and ticking is processed to prevent concurrency issues. + * If this is manually called, ensure that any exceptions are properly handled. + */ + private final EventLoop eventLoop; + private TcpSession downstream; + @Setter + private AuthData authData; + @Setter + private BedrockClientData clientData; + + /* Setter for GeyserConnect */ + @Setter + private String remoteAddress; + @Setter + private int remotePort; + @Setter + private AuthType remoteAuthType; + /* Setter for GeyserConnect */ + + @Deprecated + @Setter + private boolean microsoftAccount; + + private final SessionPlayerEntity playerEntity; + + private final AdvancementsCache advancementsCache; + private final BookEditCache bookEditCache; + private final ChunkCache chunkCache; + private final EntityCache entityCache; + private final EntityEffectCache effectCache; + private final FormCache formCache; + private final LodestoneCache lodestoneCache; + private final PistonCache pistonCache; + private final PreferencesCache preferencesCache; + private final TagCache tagCache; + private final WorldCache worldCache; + + private final Int2ObjectMap teleportMap = new Int2ObjectOpenHashMap<>(); + + private final WorldBorder worldBorder; + /** + * Whether simulated fog has been sent to the client or not. + */ + private boolean isInWorldBorderWarningArea = false; + + private final PlayerInventory playerInventory; + @Setter + private Inventory openInventory; + @Setter + private boolean closingInventory; + + @Setter + private InventoryTranslator inventoryTranslator = InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR; + + /** + * Use {@link #getNextItemNetId()} instead for consistency + */ + @Getter(AccessLevel.NONE) + private final AtomicInteger itemNetId = new AtomicInteger(2); + + @Setter + private ScheduledFuture craftingGridFuture; + + /** + * Stores session collision + */ + private final CollisionManager collisionManager; + + /** + * Stores the block mappings for this specific version. + */ + @Setter + private BlockMappings blockMappings; + + /** + * Stores the item translations for this specific version. + */ + @Setter + private ItemMappings itemMappings; + + private final Map skullCache = new Object2ObjectOpenHashMap<>(); + private final Long2ObjectMap storedMaps = new Long2ObjectOpenHashMap<>(); + + /** + * Required to decode biomes correctly. + */ + @Setter + private int biomeGlobalPalette; + /** + * Stores the map between Java and Bedrock biome network IDs. + */ + private final Int2IntMap biomeTranslations = new Int2IntOpenHashMap(); + + /** + * A map of Vector3i positions to Java entities. + * Used for translating Bedrock block actions to Java entity actions. + */ + private final Map itemFrameCache = new Object2ObjectOpenHashMap<>(); + + /** + * Stores a list of all lectern locations and their block entity tags. + * See {@link WorldManager#getLecternDataAt(GeyserSession, int, int, int, boolean)} + * for more information. + */ + private final Set lecternCache; + + /** + * A list of all players that have a player head on with a custom texture. + * Our workaround for these players is to give them a custom skin and geometry to emulate wearing a custom skull. + */ + private final Set playerWithCustomHeads = new ObjectOpenHashSet<>(); + + @Setter + private boolean droppingLecternBook; + + @Setter + private Vector2i lastChunkPosition = null; + private int renderDistance; + + // Exposed for GeyserConnect usage + protected boolean sentSpawnPacket; + + private boolean loggedIn; + private boolean loggingIn; + + @Setter + private boolean spawned; + /** + * Accessed on the initial Java and Bedrock packet processing threads + */ + private volatile boolean closed; + + @Setter + private GameMode gameMode = GameMode.SURVIVAL; + + /** + * Keeps track of the world name for respawning. + */ + @Setter + private String worldName = null; + + private boolean sneaking; + + /** + * Stores the Java pose that the server and/or Geyser believes the player currently has. + */ + @Setter + private Pose pose = Pose.STANDING; + + @Setter + private boolean sprinting; + + /** + * Whether the player is swimming in water. + * Used to update speed when crawling. + */ + @Setter + private boolean swimmingInWater; + + /** + * Tracks the original speed attribute. + * + * We need to do this in order to emulate speeds when sneaking under 1.5-blocks-tall areas if the player isn't sneaking, + * and when crawling. + */ + @Setter + private float originalSpeedAttribute; + + /** + * The dimension of the player. + * As all entities are in the same world, this can be safely applied to all other entities. + */ + @Setter + private String dimension = DimensionUtils.OVERWORLD; + + @Setter + private int breakingBlock; + + @Setter + private Vector3i lastBlockPlacePosition; + + @Setter + private String lastBlockPlacedId; + + @Setter + private boolean interacting; + + /** + * Stores the last position of the block the player interacted with. This can either be a block that the client + * placed or an existing block the player interacted with (for example, a chest).
+ * Initialized as (0, 0, 0) so it is always not-null. + */ + @Setter + private Vector3i lastInteractionBlockPosition = Vector3i.ZERO; + + /** + * Stores the position of the player the last time they interacted. + * Used to verify that the player did not move since their last interaction.
+ * Initialized as (0, 0, 0) so it is always not-null. + */ + @Setter + private Vector3f lastInteractionPlayerPosition = Vector3f.ZERO; + + @Setter + private Entity ridingVehicleEntity; + + /** + * The entity that the client is currently looking at. + */ + @Setter + private Entity mouseoverEntity; + + @Setter + private Int2ObjectMap craftingRecipes; + private final Set unlockedRecipes; + private final AtomicInteger lastRecipeNetId; + + /** + * Saves a list of all stonecutter recipes, for use in a stonecutter inventory. + * The key is the Java ID of the item; the values are all the possible outputs' Java IDs sorted by their string identifier + */ + @Setter + private Int2ObjectMap stonecutterRecipes; + + /** + * The current attack speed of the player. Used for sending proper cooldown timings. + * Setting a default fixes cooldowns not showing up on a fresh world. + */ + @Setter + private double attackSpeed = 4.0d; + /** + * The time of the last hit. Used to gauge how long the cooldown is taking. + * This is a session variable in order to prevent more scheduled threads than necessary. + */ + @Setter + private long lastHitTime; + + /** + * Saves if the client is steering left on a boat. + */ + @Setter + private boolean steeringLeft; + /** + * Saves if the client is steering right on a boat. + */ + @Setter + private boolean steeringRight; + + /** + * Store the last time the player interacted. Used to fix a right-click spam bug. + * See https://github.com/GeyserMC/Geyser/issues/503 for context. + */ + @Setter + private long lastInteractionTime; + + /** + * Stores a future interaction to place a bucket. Will be cancelled if the client instead intended to + * interact with a block. + */ + @Setter + private ScheduledFuture bucketScheduledFuture; + + /** + * Used to send a movement packet every three seconds if the player hasn't moved. Prevents timeouts when AFK in certain instances. + */ + @Setter + private long lastMovementTimestamp = System.currentTimeMillis(); + + /** + * Used to send a ServerboundMoveVehiclePacket for every PlayerInputPacket after idling on a boat/horse for more than 100ms + */ + @Setter + private long lastVehicleMoveTimestamp = System.currentTimeMillis(); + + /** + * Controls whether the daylight cycle gamerule has been sent to the client, so the sun/moon remain motionless. + */ + private boolean daylightCycle = true; + + private boolean reducedDebugInfo = false; + + /** + * The op permission level set by the server + */ + @Setter + private int opPermissionLevel = 0; + + /** + * If the current player can fly + */ + @Setter + private boolean canFly = false; + + /** + * If the current player is flying + */ + private boolean flying = false; + + /** + * Caches current rain status. + */ + @Setter + private boolean raining = false; + + /** + * Caches current thunder status. + */ + @Setter + private boolean thunder = false; + + /** + * Stores the last text inputted into a sign. + *

+ * Bedrock sends packets every time you update the sign, Java only wants the final packet. + * Until we determine that the user has finished editing, we save the sign's current status. + */ + @Setter + private String lastSignMessage; + + /** + * Stores a map of all statistics sent from the server. + * The server only sends new statistics back to us, so in order to show all statistics we need to cache existing ones. + */ + private final Map statistics = new HashMap<>(); + + /** + * Whether we're expecting statistics to be sent back to us. + */ + @Setter + private boolean waitingForStatistics = false; + + private final Set fogNameSpaces = new HashSet<>(); + + private final Set emotes; + + /** + * Whether advanced tooltips will be added to the player's items. + */ + @Setter + private boolean advancedTooltips = false; + + /** + * The thread that will run every 50 milliseconds - one Minecraft tick. + */ + private ScheduledFuture tickThread = null; + + private MinecraftProtocol protocol; + + public GeyserSession(GeyserImpl geyser, BedrockServerSession bedrockServerSession, EventLoop eventLoop) { + this.geyser = geyser; + this.upstream = new UpstreamSession(bedrockServerSession); + this.eventLoop = eventLoop; + + this.advancementsCache = new AdvancementsCache(this); + this.bookEditCache = new BookEditCache(this); + this.chunkCache = new ChunkCache(this); + this.entityCache = new EntityCache(this); + this.effectCache = new EntityEffectCache(); + this.formCache = new FormCache(this); + this.lodestoneCache = new LodestoneCache(); + this.pistonCache = new PistonCache(this); + this.preferencesCache = new PreferencesCache(this); + this.tagCache = new TagCache(); + this.worldCache = new WorldCache(this); + + this.worldBorder = new WorldBorder(this); + + this.collisionManager = new CollisionManager(this); + + this.playerEntity = new SessionPlayerEntity(this); + collisionManager.updatePlayerBoundingBox(this.playerEntity.getPosition()); + + this.playerInventory = new PlayerInventory(); + this.openInventory = null; + this.craftingRecipes = new Int2ObjectOpenHashMap<>(); + this.unlockedRecipes = new ObjectOpenHashSet<>(); + this.lastRecipeNetId = new AtomicInteger(1); + + this.spawned = false; + this.loggedIn = false; + + if (geyser.getWorldManager().shouldExpectLecternHandled()) { + // Unneeded on these platforms + this.lecternCache = null; + } else { + this.lecternCache = new ObjectOpenHashSet<>(); + } + + if (geyser.getConfig().getEmoteOffhandWorkaround() != EmoteOffhandWorkaroundOption.NO_EMOTES) { + this.emotes = new HashSet<>(); + geyser.getSessionManager().getSessions().values().forEach(player -> this.emotes.addAll(player.getEmotes())); + } else { + this.emotes = null; + } + + bedrockServerSession.addDisconnectHandler(disconnectReason -> { + InetAddress address = bedrockServerSession.getRealAddress().getAddress(); + geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.disconnect", address, disconnectReason)); + + disconnect(disconnectReason.name()); + geyser.getSessionManager().removeSession(this); + }); + + this.remoteAddress = geyser.getConfig().getRemote().getAddress(); + this.remotePort = geyser.getConfig().getRemote().getPort(); + this.remoteAuthType = geyser.getConfig().getRemote().getAuthType(); + } + + /** + * Send all necessary packets to load Bedrock into the server + */ + public void connect() { + startGame(); + sentSpawnPacket = true; + + // Set the hardcoded shield ID to the ID we just defined in StartGamePacket + upstream.getSession().getHardcodedBlockingId().set(this.itemMappings.getStoredItems().shield().getBedrockId()); + + if (this.itemMappings.getFurnaceMinecartData() != null) { + ItemComponentPacket componentPacket = new ItemComponentPacket(); + componentPacket.getItems().add(this.itemMappings.getFurnaceMinecartData()); + upstream.sendPacket(componentPacket); + } + + ChunkUtils.sendEmptyChunks(this, playerEntity.getPosition().toInt(), 0, false); + + BiomeDefinitionListPacket biomeDefinitionListPacket = new BiomeDefinitionListPacket(); + biomeDefinitionListPacket.setDefinitions(Registries.BIOMES_NBT.get()); + upstream.sendPacket(biomeDefinitionListPacket); + + AvailableEntityIdentifiersPacket entityPacket = new AvailableEntityIdentifiersPacket(); + entityPacket.setIdentifiers(Registries.BEDROCK_ENTITY_IDENTIFIERS.get()); + upstream.sendPacket(entityPacket); + + CreativeContentPacket creativePacket = new CreativeContentPacket(); + creativePacket.setContents(this.itemMappings.getCreativeItems()); + upstream.sendPacket(creativePacket); + + PlayStatusPacket playStatusPacket = new PlayStatusPacket(); + playStatusPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN); + upstream.sendPacket(playStatusPacket); + + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); + attributesPacket.setRuntimeEntityId(getPlayerEntity().getGeyserId()); + // Default move speed + // Bedrock clients move very fast by default until they get an attribute packet correcting the speed + attributesPacket.setAttributes(Collections.singletonList( + new AttributeData("minecraft:movement", 0.0f, 1024f, 0.1f, 0.1f))); + upstream.sendPacket(attributesPacket); + + GameRulesChangedPacket gamerulePacket = new GameRulesChangedPacket(); + // Only allow the server to send health information + // Setting this to false allows natural regeneration to work false but doesn't break it being true + gamerulePacket.getGameRules().add(new GameRuleData<>("naturalregeneration", false)); + // Don't let the client modify the inventory on death + // Setting this to true allows keep inventory to work if enabled but doesn't break functionality being false + gamerulePacket.getGameRules().add(new GameRuleData<>("keepinventory", true)); + // Ensure client doesn't try and do anything funky; the server handles this for us + gamerulePacket.getGameRules().add(new GameRuleData<>("spawnradius", 0)); + upstream.sendPacket(gamerulePacket); + } + + public void login() { + if (this.remoteAuthType != AuthType.ONLINE) { + if (this.remoteAuthType == AuthType.OFFLINE) { + geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.login.offline")); + } else { + geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.login.floodgate")); + } + authenticate(authData.name()); + } + } + + public void authenticate(String username) { + authenticate(username, ""); + } + + public void authenticate(String username, String password) { + if (loggedIn) { + geyser.getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.auth.already_loggedin", username)); + return; + } + + loggingIn = true; + + // Use a future to prevent timeouts as all the authentication is handled sync + // This will be changed with the new protocol library. + CompletableFuture.supplyAsync(() -> { + try { + if (password != null && !password.isEmpty()) { + AuthenticationService authenticationService; + if (microsoftAccount) { + authenticationService = new MsaAuthenticationService(GeyserImpl.OAUTH_CLIENT_ID); + } else { + authenticationService = new MojangAuthenticationService(); + } + authenticationService.setUsername(username); + authenticationService.setPassword(password); + authenticationService.login(); + + GameProfile profile = authenticationService.getSelectedProfile(); + if (profile == null) { + // Java account is offline + disconnect(GeyserLocale.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode())); + return null; + } + + protocol = new MinecraftProtocol(profile, authenticationService.getAccessToken()); + } else { + // always replace spaces when using Floodgate, + // as usernames with spaces cause issues with Bungeecord's login cycle. + // However, this doesn't affect the final username as Floodgate is still in charge of that. + // So if you have (for example) replace spaces enabled on Floodgate the spaces will re-appear. + String validUsername = username; + if (remoteAuthType == AuthType.FLOODGATE) { + validUsername = username.replace(' ', '_'); + } + + protocol = new MinecraftProtocol(validUsername); + } + } catch (InvalidCredentialsException | IllegalArgumentException e) { + geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.login.invalid", username)); + disconnect(GeyserLocale.getPlayerLocaleString("geyser.auth.login.invalid.kick", getClientData().getLanguageCode())); + } catch (RequestException ex) { + disconnect(ex.getMessage()); + } + return null; + }).whenComplete((aVoid, ex) -> { + if (ex != null) { + disconnect(ex.toString()); + } + if (this.closed) { + if (ex != null) { + geyser.getLogger().error("", ex); + } + // Client disconnected during the authentication attempt + return; + } + + connectDownstream(); + }); + } + + /** + * Present a form window to the user asking to log in with another web browser + */ + public void authenticateWithMicrosoftCode() { + if (loggedIn) { + geyser.getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.auth.already_loggedin", getAuthData().name())); + return; + } + + loggingIn = true; + + // This just looks cool + SetTimePacket packet = new SetTimePacket(); + packet.setTime(16000); + sendUpstreamPacket(packet); + + // new thread so clients don't timeout + MsaAuthenticationService msaAuthenticationService = new MsaAuthenticationService(GeyserImpl.OAUTH_CLIENT_ID); + + // Use a future to prevent timeouts as all the authentication is handled sync + // This will be changed with the new protocol library. + CompletableFuture.supplyAsync(() -> { + try { + return msaAuthenticationService.getAuthCode(); + } catch (RequestException e) { + throw new CompletionException(e); + } + }).whenComplete((response, ex) -> { + if (ex != null) { + ex.printStackTrace(); + disconnect(ex.toString()); + return; + } + LoginEncryptionUtils.buildAndShowMicrosoftCodeWindow(this, response); + attemptCodeAuthentication(msaAuthenticationService); + }); + } + + /** + * Poll every second to see if the user has successfully signed in + */ + private void attemptCodeAuthentication(MsaAuthenticationService msaAuthenticationService) { + if (loggedIn || closed) { + return; + } + CompletableFuture.supplyAsync(() -> { + try { + msaAuthenticationService.login(); + GameProfile profile = msaAuthenticationService.getSelectedProfile(); + if (profile == null) { + // Java account is offline + disconnect(GeyserLocale.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode())); + return null; + } + + return new MinecraftProtocol(profile, msaAuthenticationService.getAccessToken()); + } catch (RequestException e) { + throw new CompletionException(e); + } + }).whenComplete((response, ex) -> { + if (ex != null) { + if (!(ex instanceof CompletionException completionException) || !(completionException.getCause() instanceof AuthPendingException)) { + geyser.getLogger().error("Failed to log in with Microsoft code!", ex); + disconnect(ex.toString()); + } else { + // Wait one second before trying again + geyser.getScheduledThread().schedule(() -> attemptCodeAuthentication(msaAuthenticationService), 1, TimeUnit.SECONDS); + } + return; + } + if (!closed) { + this.protocol = response; + connectDownstream(); + } + }); + } + + /** + * After getting whatever credentials needed, we attempt to join the Java server. + */ + private void connectDownstream() { + boolean floodgate = this.remoteAuthType == AuthType.FLOODGATE; + + // Start ticking + tickThread = eventLoop.scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS); + + if (geyser.getBootstrap().getSocketAddress() != null) { + // We're going to connect through the JVM and not through TCP + downstream = new LocalSession(this.remoteAddress, this.remotePort, + geyser.getBootstrap().getSocketAddress(), upstream.getAddress().getAddress().getHostAddress(), this.protocol); + } else { + downstream = new TcpClientSession(this.remoteAddress, this.remotePort, this.protocol); + disableSrvResolving(); + } + + if (geyser.getConfig().getRemote().isUseProxyProtocol()) { + downstream.setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true); + downstream.setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress()); + } + if (geyser.getConfig().isForwardPlayerPing()) { + // Let Geyser handle sending the keep alive + downstream.setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false); + } + downstream.addListener(new SessionAdapter() { + @Override + public void packetSending(PacketSendingEvent event) { + //todo move this somewhere else + if (event.getPacket() instanceof ClientIntentionPacket) { + String addressSuffix; + if (floodgate) { + byte[] encryptedData; + + try { + FloodgateSkinUploader skinUploader = geyser.getSkinUploader(); + FloodgateCipher cipher = geyser.getCipher(); + + encryptedData = cipher.encryptFromString(BedrockData.of( + clientData.getGameVersion(), + authData.name(), + authData.xuid(), + clientData.getDeviceOs().ordinal(), + clientData.getLanguageCode(), + clientData.getUiProfile().ordinal(), + clientData.getCurrentInputMode().ordinal(), + upstream.getAddress().getAddress().getHostAddress(), + skinUploader.getId(), + skinUploader.getVerifyCode() + ).toString()); + } catch (Exception e) { + geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e); + disconnect(GeyserLocale.getPlayerLocaleString("geyser.auth.floodgate.encryption_fail", getClientData().getLanguageCode())); + return; + } + + addressSuffix = '\0' + new String(encryptedData, StandardCharsets.UTF_8); + } else { + addressSuffix = ""; + } + + ClientIntentionPacket intentionPacket = event.getPacket(); + + String address; + if (geyser.getConfig().getRemote().isForwardHost()) { + address = clientData.getServerAddress().split(":")[0]; + } else { + address = intentionPacket.getHostname(); + } + + event.setPacket(intentionPacket.withHostname(address + addressSuffix)); + } + } + + @Override + public void connected(ConnectedEvent event) { + loggingIn = false; + loggedIn = true; + + if (downstream instanceof LocalSession) { + // Connected directly to the server + geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.remote.connect_internal", + authData.name(), protocol.getProfile().getName())); + } else { + // Connected to an IP address + geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.remote.connect", + authData.name(), protocol.getProfile().getName(), remoteAddress)); + } + + UUID uuid = protocol.getProfile().getId(); + if (uuid == null) { + // Set what our UUID *probably* is going to be + if (remoteAuthType == AuthType.FLOODGATE) { + uuid = new UUID(0, Long.parseLong(authData.xuid())); + } else { + uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + protocol.getProfile().getName()).getBytes(StandardCharsets.UTF_8)); + } + } + playerEntity.setUuid(uuid); + playerEntity.setUsername(protocol.getProfile().getName()); + + String locale = clientData.getLanguageCode(); + + // Let the user know there locale may take some time to download + // as it has to be extracted from a JAR + if (locale.equalsIgnoreCase("en_us") && !MinecraftLocale.LOCALE_MAPPINGS.containsKey("en_us")) { + // This should probably be left hardcoded as it will only show for en_us clients + sendMessage("Loading your locale (en_us); if this isn't already downloaded, this may take some time"); + } + + // Download and load the language for the player + MinecraftLocale.downloadAndLoadLocale(locale); + } + + @Override + public void disconnected(DisconnectedEvent event) { + loggingIn = false; + loggedIn = false; + + String disconnectMessage; + Throwable cause = event.getCause(); + if (cause instanceof UnexpectedEncryptionException) { + if (remoteAuthType != AuthType.FLOODGATE) { + // Server expects online mode + disconnectMessage = GeyserLocale.getPlayerLocaleString("geyser.network.remote.authentication_type_mismatch", getLocale()); + // Explain that they may be looking for Floodgate. + geyser.getLogger().warning(GeyserLocale.getLocaleStringLog( + geyser.getPlatformType() == PlatformType.STANDALONE ? + "geyser.network.remote.floodgate_explanation_standalone" + : "geyser.network.remote.floodgate_explanation_plugin", + Constants.FLOODGATE_DOWNLOAD_LOCATION + )); + } else { + // Likely that Floodgate is not configured correctly. + disconnectMessage = GeyserLocale.getPlayerLocaleString("geyser.network.remote.floodgate_login_error", getLocale()); + if (geyser.getPlatformType() == PlatformType.STANDALONE) { + geyser.getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.network.remote.floodgate_login_error_standalone")); + } + } + } else if (cause instanceof ConnectException) { + // Server is offline, probably + disconnectMessage = GeyserLocale.getPlayerLocaleString("geyser.network.remote.server_offline", getLocale()); + } else { + disconnectMessage = MessageTranslator.convertMessageLenient(event.getReason()); + } + + if (downstream instanceof LocalSession) { + geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.remote.disconnect_internal", authData.name(), disconnectMessage)); + } else { + geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.remote.disconnect", authData.name(), remoteAddress, disconnectMessage)); + } + if (cause != null) { + cause.printStackTrace(); + } + + upstream.disconnect(disconnectMessage); + } + + @Override + public void packetReceived(Session session, Packet packet) { + Registries.JAVA_PACKET_TRANSLATORS.translate(packet.getClass(), packet, GeyserSession.this); + } + + @Override + public void packetError(PacketErrorEvent event) { + geyser.getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.network.downstream_error", event.getCause().getMessage())); + if (geyser.getConfig().isDebugMode()) + event.getCause().printStackTrace(); + event.setSuppress(true); + } + }); + + if (!daylightCycle) { + setDaylightCycle(true); + } + + downstream.connect(); + } + + public void disconnect(String reason) { + if (!closed) { + loggedIn = false; + if (downstream != null) { + downstream.disconnect(reason); + } + if (upstream != null && !upstream.isClosed()) { + geyser.getSessionManager().removeSession(this); + upstream.disconnect(reason); + } + } + + if (tickThread != null) { + tickThread.cancel(false); + } + + closed = true; + } + + public void close() { + disconnect(GeyserLocale.getPlayerLocaleString("geyser.network.close", getClientData().getLanguageCode())); + } + + /** + * Executes a task and prints a stack trace if an error occurs. + */ + public void executeInEventLoop(Runnable runnable) { + eventLoop.execute(() -> { + try { + runnable.run(); + } catch (Throwable e) { + geyser.getLogger().error("Error thrown in " + this.name() + "'s event loop!", e); + } + }); + } + + /** + * Schedules a task and prints a stack trace if an error occurs. + */ + public ScheduledFuture scheduleInEventLoop(Runnable runnable, long duration, TimeUnit timeUnit) { + return eventLoop.schedule(() -> { + try { + runnable.run(); + } catch (Throwable e) { + geyser.getLogger().error("Error thrown in " + this.name() + "'s event loop!", e); + } + }, duration, timeUnit); + } + + /** + * Called every 50 milliseconds - one Minecraft tick. + */ + protected void tick() { + try { + pistonCache.tick(); + // Check to see if the player's position needs updating - a position update should be sent once every 3 seconds + if (spawned && (System.currentTimeMillis() - lastMovementTimestamp) > 3000) { + // Recalculate in case something else changed position + Vector3d position = collisionManager.adjustBedrockPosition(playerEntity.getPosition(), playerEntity.isOnGround(), false); + // A null return value cancels the packet + if (position != null) { + ServerboundMovePlayerPosPacket packet = new ServerboundMovePlayerPosPacket(playerEntity.isOnGround(), + position.getX(), position.getY(), position.getZ()); + sendDownstreamPacket(packet); + } + lastMovementTimestamp = System.currentTimeMillis(); + } + + if (worldBorder.isResizing()) { + worldBorder.resize(); + } + + if (!worldBorder.isWithinWarningBoundaries()) { + // Show particles representing where the world border is + worldBorder.drawWall(); + // Set the mood + if (!isInWorldBorderWarningArea) { + isInWorldBorderWarningArea = true; + sendFog("minecraft:fog_crimson_forest"); + } + } else if (isInWorldBorderWarningArea) { + // Clear fog as we are outside the world border now + removeFog("minecraft:fog_crimson_forest"); + isInWorldBorderWarningArea = false; + } + + + for (Tickable entity : entityCache.getTickableEntities()) { + entity.tick(); + } + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + } + + public void setAuthenticationData(AuthData authData) { + this.authData = authData; + } + + public void setSneaking(boolean sneaking) { + this.sneaking = sneaking; + + // Update pose and bounding box on our end + AttributeData speedAttribute; + if (!sneaking && (speedAttribute = adjustSpeed()) != null) { + // Update attributes since we're still "sneaking" under a 1.5-block-tall area + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); + attributesPacket.setRuntimeEntityId(playerEntity.getGeyserId()); + attributesPacket.setAttributes(Collections.singletonList(speedAttribute)); + sendUpstreamPacket(attributesPacket); + // the server *should* update our pose once it has returned to normal + } else { + if (!flying) { + // The pose and bounding box should not be updated if the player is flying + setSneakingPose(sneaking); + } + collisionManager.updateScaffoldingFlags(false); + } + + playerEntity.updateBedrockMetadata(); + + if (mouseoverEntity != null) { + // Horses, etc can change their property depending on if you're sneaking + InteractiveTagManager.updateTag(this, mouseoverEntity); + } + } + + private void setSneakingPose(boolean sneaking) { + this.pose = sneaking ? Pose.SNEAKING : Pose.STANDING; + playerEntity.setBoundingBoxHeight(sneaking ? 1.5f : playerEntity.getDefinition().height()); + playerEntity.setFlag(EntityFlag.SNEAKING, sneaking); + + collisionManager.updatePlayerBoundingBox(); + } + + public void setSwimming(boolean swimming) { + this.pose = swimming ? Pose.SWIMMING : Pose.STANDING; + playerEntity.setBoundingBoxHeight(swimming ? 0.6f : playerEntity.getDefinition().height()); + playerEntity.setFlag(EntityFlag.SWIMMING, swimming); + playerEntity.updateBedrockMetadata(); + } + + public void setFlying(boolean flying) { + this.flying = flying; + + if (sneaking) { + // update bounding box as it is not reduced when flying + setSneakingPose(!flying); + playerEntity.updateBedrockMetadata(); + } + } + + /** + * Adjusts speed if the player is crawling. + * + * @return not null if attributes should be updated. + */ + public AttributeData adjustSpeed() { + AttributeData currentPlayerSpeed = playerEntity.getAttributes().get(GeyserAttributeType.MOVEMENT_SPEED); + if (currentPlayerSpeed != null) { + if ((pose.equals(Pose.SNEAKING) && !sneaking && collisionManager.isUnderSlab()) || + (!swimmingInWater && playerEntity.getFlag(EntityFlag.SWIMMING) && !collisionManager.isPlayerInWater())) { + // Either of those conditions means that Bedrock goes zoom when they shouldn't be + AttributeData speedAttribute = GeyserAttributeType.MOVEMENT_SPEED.getAttribute(originalSpeedAttribute / 3.32f); + playerEntity.getAttributes().put(GeyserAttributeType.MOVEMENT_SPEED, speedAttribute); + return speedAttribute; + } else if (originalSpeedAttribute != currentPlayerSpeed.getValue()) { + // Speed has reset to normal + AttributeData speedAttribute = GeyserAttributeType.MOVEMENT_SPEED.getAttribute(originalSpeedAttribute); + playerEntity.getAttributes().put(GeyserAttributeType.MOVEMENT_SPEED, speedAttribute); + return speedAttribute; + } + } + return null; + } + + /** + * Will be overwritten for GeyserConnect. + */ + protected void disableSrvResolving() { + this.downstream.setFlag(BuiltinFlags.ATTEMPT_SRV_RESOLVE, false); + } + + @Override + public String name() { + return authData.name(); + } + + @Override + public UUID uuid() { + return authData.uuid(); + } + + @Override + public String xuid() { + return authData.xuid(); + } + + @Override + public void sendMessage(String message) { + TextPacket textPacket = new TextPacket(); + textPacket.setPlatformChatId(""); + textPacket.setSourceName(""); + textPacket.setXuid(""); + textPacket.setType(TextPacket.Type.CHAT); + textPacket.setNeedsTranslation(false); + textPacket.setMessage(message); + + upstream.sendPacket(textPacket); + } + + @Override + public boolean isConsole() { + return false; + } + + @Override + public String getLocale() { + return clientData.getLanguageCode(); + } + + public void setRenderDistance(int renderDistance) { + renderDistance = GenericMath.ceil(++renderDistance * MathUtils.SQRT_OF_TWO); //square to circle + this.renderDistance = renderDistance; + + ChunkRadiusUpdatedPacket chunkRadiusUpdatedPacket = new ChunkRadiusUpdatedPacket(); + chunkRadiusUpdatedPacket.setRadius(renderDistance); + upstream.sendPacket(chunkRadiusUpdatedPacket); + } + + public InetSocketAddress getSocketAddress() { + return this.upstream.getAddress(); + } + + public void sendForm(Form form) { + formCache.showForm(form); + } + + public void sendForm(FormBuilder formBuilder) { + formCache.showForm(formBuilder.build()); + } + + private void startGame() { + StartGamePacket startGamePacket = new StartGamePacket(); + startGamePacket.setUniqueEntityId(playerEntity.getGeyserId()); + startGamePacket.setRuntimeEntityId(playerEntity.getGeyserId()); + startGamePacket.setPlayerGameType(switch (gameMode) { + case CREATIVE -> GameType.CREATIVE; + case ADVENTURE -> GameType.ADVENTURE; + default -> GameType.SURVIVAL; + }); + startGamePacket.setPlayerPosition(Vector3f.from(0, 69, 0)); + startGamePacket.setRotation(Vector2f.from(1, 1)); + + startGamePacket.setSeed(-1); + startGamePacket.setDimensionId(DimensionUtils.javaToBedrock(dimension)); + startGamePacket.setGeneratorId(1); + startGamePacket.setLevelGameType(GameType.SURVIVAL); + startGamePacket.setDifficulty(1); + startGamePacket.setDefaultSpawn(Vector3i.ZERO); + startGamePacket.setAchievementsDisabled(!geyser.getConfig().isXboxAchievementsEnabled()); + startGamePacket.setCurrentTick(-1); + startGamePacket.setEduEditionOffers(0); + startGamePacket.setEduFeaturesEnabled(false); + startGamePacket.setRainLevel(0); + startGamePacket.setLightningLevel(0); + startGamePacket.setMultiplayerGame(true); + startGamePacket.setBroadcastingToLan(true); + startGamePacket.setPlatformBroadcastMode(GamePublishSetting.PUBLIC); + startGamePacket.setXblBroadcastMode(GamePublishSetting.PUBLIC); + startGamePacket.setCommandsEnabled(!geyser.getConfig().isXboxAchievementsEnabled()); + startGamePacket.setTexturePacksRequired(false); + startGamePacket.setBonusChestEnabled(false); + startGamePacket.setStartingWithMap(false); + startGamePacket.setTrustingPlayers(true); + startGamePacket.setDefaultPlayerPermission(PlayerPermission.MEMBER); + startGamePacket.setServerChunkTickRange(4); + startGamePacket.setBehaviorPackLocked(false); + startGamePacket.setResourcePackLocked(false); + startGamePacket.setFromLockedWorldTemplate(false); + startGamePacket.setUsingMsaGamertagsOnly(false); + startGamePacket.setFromWorldTemplate(false); + startGamePacket.setWorldTemplateOptionLocked(false); + + String serverName = geyser.getConfig().getBedrock().getServerName(); + startGamePacket.setLevelId(serverName); + startGamePacket.setLevelName(serverName); + + startGamePacket.setPremiumWorldTemplateId("00000000-0000-0000-0000-000000000000"); + // startGamePacket.setCurrentTick(0); + startGamePacket.setEnchantmentSeed(0); + startGamePacket.setMultiplayerCorrelationId(""); + startGamePacket.setItemEntries(this.itemMappings.getItemEntries()); + startGamePacket.setVanillaVersion("*"); + startGamePacket.setInventoriesServerAuthoritative(true); + startGamePacket.setServerEngine(""); // Do we want to fill this in? + + SyncedPlayerMovementSettings settings = new SyncedPlayerMovementSettings(); + settings.setMovementMode(AuthoritativeMovementMode.CLIENT); + settings.setRewindHistorySize(0); + settings.setServerAuthoritativeBlockBreaking(false); + startGamePacket.setPlayerMovementSettings(settings); + + if (upstream.getProtocolVersion() <= Bedrock_v471.V471_CODEC.getProtocolVersion()) { + startGamePacket.getExperiments().add(new ExperimentData("caves_and_cliffs", true)); + } + + upstream.sendPacket(startGamePacket); + } + + /** + * @return the next Bedrock item network ID to use for a new item + */ + public int getNextItemNetId() { + return itemNetId.getAndIncrement(); + } + + public void addTeleport(TeleportCache teleportCache) { + teleportMap.put(teleportCache.getTeleportConfirmId(), teleportCache); + + ObjectIterator> it = teleportMap.int2ObjectEntrySet().iterator(); + + // Remove any teleports with a higher number - maybe this is a world change that reset the ID to 0? + while (it.hasNext()) { + Int2ObjectMap.Entry entry = it.next(); + int nextID = entry.getValue().getTeleportConfirmId(); + if (nextID > teleportCache.getTeleportConfirmId()) { + it.remove(); + } + } + } + + public void confirmTeleport(Vector3d position) { + if (teleportMap.size() == 0) { + return; + } + int teleportID = -1; + + for (Int2ObjectMap.Entry entry : teleportMap.int2ObjectEntrySet()) { + if (entry.getValue().canConfirm(position)) { + if (entry.getValue().getTeleportConfirmId() > teleportID) { + teleportID = entry.getValue().getTeleportConfirmId(); + } + } + } + + if (teleportID != -1) { + ObjectIterator> it = teleportMap.int2ObjectEntrySet().iterator(); + + // Confirm the current teleport and any earlier ones + while (it.hasNext()) { + TeleportCache entry = it.next().getValue(); + int nextID = entry.getTeleportConfirmId(); + if (nextID <= teleportID) { + ServerboundAcceptTeleportationPacket teleportConfirmPacket = new ServerboundAcceptTeleportationPacket(nextID); + sendDownstreamPacket(teleportConfirmPacket); + // Servers (especially ones like Hypixel) expect exact coordinates given back to them. + ServerboundMovePlayerPosRotPacket positionPacket = new ServerboundMovePlayerPosRotPacket(playerEntity.isOnGround(), + entry.getX(), entry.getY(), entry.getZ(), entry.getYaw(), entry.getPitch()); + sendDownstreamPacket(positionPacket); + it.remove(); + geyser.getLogger().debug("Confirmed teleport " + nextID); + } + } + } + + if (teleportMap.size() > 0) { + int resendID = -1; + for (Int2ObjectMap.Entry entry : teleportMap.int2ObjectEntrySet()) { + TeleportCache teleport = entry.getValue(); + teleport.incrementUnconfirmedFor(); + if (teleport.shouldResend()) { + if (teleport.getTeleportConfirmId() >= resendID) { + resendID = teleport.getTeleportConfirmId(); + } + } + } + + if (resendID != -1) { + geyser.getLogger().debug("Resending teleport " + resendID); + TeleportCache teleport = teleportMap.get(resendID); + getPlayerEntity().moveAbsolute(Vector3f.from(teleport.getX(), teleport.getY(), teleport.getZ()), + teleport.getYaw(), teleport.getPitch(), playerEntity.isOnGround(), true); + } + } + } + + /** + * Queue a packet to be sent to player. + * + * @param packet the bedrock packet from the NukkitX protocol lib + */ + public void sendUpstreamPacket(BedrockPacket packet) { + upstream.sendPacket(packet); + } + + /** + * Send a packet immediately to the player. + * + * @param packet the bedrock packet from the NukkitX protocol lib + */ + public void sendUpstreamPacketImmediately(BedrockPacket packet) { + upstream.sendPacketImmediately(packet); + } + + /** + * Send a packet to the remote server. + * + * @param packet the java edition packet from MCProtocolLib + */ + public void sendDownstreamPacket(Packet packet) { + if (!closed && this.downstream != null) { + Channel channel = this.downstream.getChannel(); + if (channel == null) { + // Channel is only null before the connection has initialized + geyser.getLogger().warning("Tried to send a packet to the Java server too early!"); + if (geyser.getConfig().isDebugMode()) { + Thread.dumpStack(); + } + return; + } + + EventLoop eventLoop = channel.eventLoop(); + if (eventLoop.inEventLoop()) { + sendDownstreamPacket0(packet); + } else { + eventLoop.execute(() -> sendDownstreamPacket0(packet)); + } + } + } + + private void sendDownstreamPacket0(Packet packet) { + if (protocol.getState().equals(ProtocolState.GAME) || packet.getClass() == ServerboundCustomQueryPacket.class) { + downstream.send(packet); + } else { + geyser.getLogger().debug("Tried to send downstream packet " + packet.getClass().getSimpleName() + " before connected to the server"); + } + } + + /** + * Update the cached value for the reduced debug info gamerule. + * If enabled, also hides the player's coordinates. + * + * @param value The new value for reducedDebugInfo + */ + public void setReducedDebugInfo(boolean value) { + reducedDebugInfo = value; + // Set the showCoordinates data. This is done because updateShowCoordinates() uses this gamerule as a variable. + preferencesCache.updateShowCoordinates(); + } + + /** + * Changes the daylight cycle gamerule on the client + * This is used in the login screen along-side normal usage + * + * @param doCycle If the cycle should continue + */ + public void setDaylightCycle(boolean doCycle) { + sendGameRule("dodaylightcycle", doCycle); + // Save the value so we don't have to constantly send a daylight cycle gamerule update + this.daylightCycle = doCycle; + } + + /** + * Send a gamerule value to the client + * + * @param gameRule The gamerule to send + * @param value The value of the gamerule + */ + public void sendGameRule(String gameRule, Object value) { + GameRulesChangedPacket gameRulesChangedPacket = new GameRulesChangedPacket(); + gameRulesChangedPacket.getGameRules().add(new GameRuleData<>(gameRule, value)); + upstream.sendPacket(gameRulesChangedPacket); + } + + /** + * Checks if the given session's player has a permission + * + * @param permission The permission node to check + * @return true if the player has the requested permission, false if not + */ + @Override + public boolean hasPermission(String permission) { + return geyser.getWorldManager().hasPermission(this, permission); + } + + /** + * Send an AdventureSettingsPacket to the client with the latest flags + */ + public void sendAdventureSettings() { + AdventureSettingsPacket adventureSettingsPacket = new AdventureSettingsPacket(); + adventureSettingsPacket.setUniqueEntityId(playerEntity.getGeyserId()); + // Set command permission if OP permission level is high enough + // This allows mobile players access to a GUI for doing commands. The commands there do not change above OPERATOR + // and all commands there are accessible with OP permission level 2 + adventureSettingsPacket.setCommandPermission(opPermissionLevel >= 2 ? CommandPermission.OPERATOR : CommandPermission.NORMAL); + // Required to make command blocks destroyable + adventureSettingsPacket.setPlayerPermission(opPermissionLevel >= 2 ? PlayerPermission.OPERATOR : PlayerPermission.MEMBER); + + // Update the noClip and worldImmutable values based on the current gamemode + boolean spectator = gameMode == GameMode.SPECTATOR; + boolean worldImmutable = gameMode == GameMode.ADVENTURE || spectator; + + Set flags = adventureSettingsPacket.getSettings(); + if (canFly || spectator) { + flags.add(AdventureSetting.MAY_FLY); + } + + if (flying || spectator) { + if (spectator && !flying) { + // We're "flying locked" in this gamemode + flying = true; + ServerboundPlayerAbilitiesPacket abilitiesPacket = new ServerboundPlayerAbilitiesPacket(true); + sendDownstreamPacket(abilitiesPacket); + } + flags.add(AdventureSetting.FLYING); + } + + if (worldImmutable) { + flags.add(AdventureSetting.WORLD_IMMUTABLE); + } + + if (spectator) { + flags.add(AdventureSetting.NO_CLIP); + } + + flags.add(AdventureSetting.AUTO_JUMP); + + sendUpstreamPacket(adventureSettingsPacket); + } + + /** + * Used for updating statistic values since we only get changes from the server + * + * @param statistics Updated statistics values + */ + public void updateStatistics(@NonNull Map statistics) { + if (this.statistics.isEmpty()) { + // Initialize custom statistics to 0, so that they appear in the form + for (CustomStatistic customStatistic : CustomStatistic.values()) { + this.statistics.put(customStatistic, 0); + } + } + this.statistics.putAll(statistics); + } + + public void refreshEmotes(List emotes) { + this.emotes.addAll(emotes); + for (GeyserSession player : geyser.getSessionManager().getSessions().values()) { + List pieces = new ArrayList<>(); + for (UUID piece : emotes) { + if (!player.getEmotes().contains(piece)) { + pieces.add(piece); + } + player.getEmotes().add(piece); + } + EmoteListPacket emoteList = new EmoteListPacket(); + emoteList.setRuntimeEntityId(player.getPlayerEntity().getGeyserId()); + emoteList.getPieceIds().addAll(pieces); + player.sendUpstreamPacket(emoteList); + } + } + + /** + * Send the following fog IDs, as well as the cached ones, to the client. + * + * Fog IDs can be found here: + * https://wiki.bedrock.dev/documentation/fog-ids.html + * + * @param fogNameSpaces the fog ids to add + */ + public void sendFog(String... fogNameSpaces) { + this.fogNameSpaces.addAll(Arrays.asList(fogNameSpaces)); + + PlayerFogPacket packet = new PlayerFogPacket(); + packet.getFogStack().addAll(this.fogNameSpaces); + sendUpstreamPacket(packet); + } + + /** + * Removes the following fog IDs from the client and the cache. + * + * @param fogNameSpaces the fog ids to remove + */ + public void removeFog(String... fogNameSpaces) { + if (fogNameSpaces.length == 0) { + this.fogNameSpaces.clear(); + } else { + this.fogNameSpaces.removeAll(Arrays.asList(fogNameSpaces)); + } + PlayerFogPacket packet = new PlayerFogPacket(); + packet.getFogStack().addAll(this.fogNameSpaces); + sendUpstreamPacket(packet); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/session/SessionManager.java b/core/src/main/java/org/geysermc/geyser/session/SessionManager.java new file mode 100644 index 000000000..5aecdf175 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/SessionManager.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.session; + +import com.google.common.collect.ImmutableList; +import lombok.AccessLevel; +import lombok.Getter; +import org.geysermc.geyser.text.GeyserLocale; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public final class SessionManager { + /** + * A list of all players who don't currently have a permanent UUID attached yet. + */ + @Getter(AccessLevel.PACKAGE) + private final Set pendingSessions = ConcurrentHashMap.newKeySet(); + /** + * A list of all players who are currently in-game. + */ + @Getter + private final Map sessions = new ConcurrentHashMap<>(); + + /** + * Called once the player has successfully authenticated to the Geyser server. + */ + public void addPendingSession(GeyserSession session) { + pendingSessions.add(session); + } + + /** + * Called once a player has successfully logged into their Java server. + */ + public void addSession(UUID uuid, GeyserSession session) { + pendingSessions.remove(session); + sessions.put(uuid, session); + } + + public void removeSession(GeyserSession session) { + if (sessions.remove(session.getPlayerEntity().getUuid()) == null) { + // Connection was likely pending + pendingSessions.remove(session); + } + } + + /** + * Creates a new, immutable list containing all pending and active sessions. + */ + public List getAllSessions() { + return ImmutableList.builder() // builderWithExpectedSize is probably not a good idea yet as older Spigot builds probably won't have it. + .addAll(pendingSessions) + .addAll(sessions.values()) + .build(); + } + + public void disconnectAll(String message) { + Collection sessions = getAllSessions(); + for (GeyserSession session : sessions) { + session.disconnect(GeyserLocale.getPlayerLocaleString(message, session.getLocale())); + } + } + + /** + * @return the total amount of sessions, including those pending. + */ + public int size() { + return pendingSessions.size() + sessions.size(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java b/core/src/main/java/org/geysermc/geyser/session/UpstreamSession.java similarity index 85% rename from connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java rename to core/src/main/java/org/geysermc/geyser/session/UpstreamSession.java index 393ebfa82..88dc360bf 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/UpstreamSession.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.session; +package org.geysermc.geyser.session; import com.nukkitx.protocol.bedrock.BedrockPacket; import com.nukkitx.protocol.bedrock.BedrockServerSession; @@ -61,6 +61,15 @@ public class UpstreamSession { } public InetSocketAddress getAddress() { - return session.getAddress(); + return session.getRealAddress(); + } + + /** + * Gets the session's protocol version. + * + * @return the session's protocol version. + */ + public int getProtocolVersion() { + return this.session.getPacketCodec().getProtocolVersion(); } } diff --git a/common/src/main/java/org/geysermc/common/window/component/DropdownComponent.java b/core/src/main/java/org/geysermc/geyser/session/auth/AuthData.java similarity index 62% rename from common/src/main/java/org/geysermc/common/window/component/DropdownComponent.java rename to core/src/main/java/org/geysermc/geyser/session/auth/AuthData.java index 4dac6b043..d48cf3889 100644 --- a/common/src/main/java/org/geysermc/common/window/component/DropdownComponent.java +++ b/core/src/main/java/org/geysermc/geyser/session/auth/AuthData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,34 +23,20 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.component; +package org.geysermc.geyser.session.auth; -import lombok.Getter; -import lombok.Setter; +import com.fasterxml.jackson.databind.JsonNode; +import org.geysermc.geyser.GeyserImpl; -import java.util.List; +import java.util.UUID; -public class DropdownComponent extends FormComponent { +public record AuthData(String name, UUID uuid, String xuid, + JsonNode certChainData, String clientData) { - @Getter - @Setter - private String text; - - @Getter - @Setter - private List options; - - @Getter - @Setter - private int defaultOptionIndex; - - public DropdownComponent() { - super("dropdown"); - } - - public void addOption(String option, boolean isDefault) { - options.add(option); - if (isDefault) - defaultOptionIndex = options.size() - 1; + public void upload(GeyserImpl geyser) { + // we can't upload the skin in LoginEncryptionUtil since the global server would return + // the skin too fast, that's why we upload it after we know for sure that the target server + // is ready to handle the result of the global server + geyser.getSkinUploader().uploadSkin(certChainData, clientData); } } diff --git a/connector/src/main/java/org/geysermc/connector/common/AuthType.java b/core/src/main/java/org/geysermc/geyser/session/auth/AuthType.java similarity index 72% rename from connector/src/main/java/org/geysermc/connector/common/AuthType.java rename to core/src/main/java/org/geysermc/geyser/session/auth/AuthType.java index cf0c88b20..00427e7e4 100644 --- a/connector/src/main/java/org/geysermc/connector/common/AuthType.java +++ b/core/src/main/java/org/geysermc/geyser/session/auth/AuthType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,10 +23,15 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.common; +package org.geysermc.geyser.session.auth; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; import lombok.Getter; +import java.io.IOException; + @Getter public enum AuthType { OFFLINE, @@ -40,7 +45,7 @@ public enum AuthType { } /** - * Convert the AuthType string (from config) to the enum, OFFLINE on fail + * Convert the AuthType string (from config) to the enum, ONLINE on fail * * @param name AuthType string * @@ -53,6 +58,13 @@ public enum AuthType { return type; } } - return OFFLINE; + return ONLINE; + } + + public static class Deserializer extends JsonDeserializer { + @Override + public AuthType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + return getByName(p.getValueAsString()); + } } } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java b/core/src/main/java/org/geysermc/geyser/session/auth/BedrockClientData.java similarity index 80% rename from connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java rename to core/src/main/java/org/geysermc/geyser/session/auth/BedrockClientData.java index fb9948ea1..b0c0b16b0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java +++ b/core/src/main/java/org/geysermc/geyser/session/auth/BedrockClientData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,19 +23,20 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.session.auth; +package org.geysermc.geyser.session.auth; -import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; -import org.geysermc.floodgate.util.DeviceOS; +import org.geysermc.floodgate.util.DeviceOs; +import org.geysermc.floodgate.util.InputMode; +import org.geysermc.floodgate.util.UiProfile; import java.util.UUID; @JsonIgnoreProperties(ignoreUnknown = true) @Getter -public class BedrockClientData { +public final class BedrockClientData { @JsonProperty(value = "GameVersion") private String gameVersion; @JsonProperty(value = "ServerAddress") @@ -77,9 +78,9 @@ public class BedrockClientData { @JsonProperty(value = "DeviceModel") private String deviceModel; @JsonProperty(value = "DeviceOS") - private DeviceOS deviceOS; + private DeviceOs deviceOs; @JsonProperty(value = "UIProfile") - private UIProfile uiProfile; + private UiProfile uiProfile; @JsonProperty(value = "GuiScale") private int guiScale; @JsonProperty(value = "CurrentInputMode") @@ -103,19 +104,22 @@ public class BedrockClientData { private String skinColor; @JsonProperty(value = "ThirdPartyNameOnly") private boolean thirdPartyNameOnly; + @JsonProperty(value = "PlayFabId") + private String playFabId; - public enum UIProfile { - @JsonEnumDefaultValue - CLASSIC, - POCKET + public DeviceOs getDeviceOs() { + return deviceOs != null ? deviceOs : DeviceOs.UNKNOWN; } - public enum InputMode { - @JsonEnumDefaultValue - UNKNOWN, - KEYBOARD_MOUSE, - TOUCH, // I guess Touch? - CONTROLLER, - VR + public InputMode getCurrentInputMode() { + return currentInputMode != null ? currentInputMode : InputMode.UNKNOWN; + } + + public InputMode getDefaultInputMode() { + return defaultInputMode != null ? defaultInputMode : InputMode.UNKNOWN; + } + + public UiProfile getUiProfile() { + return uiProfile != null ? uiProfile : UiProfile.CLASSIC; } } diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/AdvancementsCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/AdvancementsCache.java new file mode 100644 index 000000000..b6dde975c --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/cache/AdvancementsCache.java @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.session.cache; + +import com.github.steveice10.mc.protocol.data.game.advancement.Advancement; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSeenAdvancementsPacket; +import lombok.Getter; +import lombok.Setter; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.geyser.level.GeyserAdvancement; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.text.MinecraftLocale; +import org.geysermc.cumulus.SimpleForm; +import org.geysermc.cumulus.response.SimpleFormResponse; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class AdvancementsCache { + /** + * Stores the player's advancement progress + */ + @Getter + private final Map> storedAdvancementProgress = new HashMap<>(); + + /** + * Stores advancements for the player. + */ + @Getter + private final Map storedAdvancements = new HashMap<>(); + + /** + * Stores player's chosen advancement's ID and title for use in form creators. + */ + @Setter + private String currentAdvancementCategoryId = null; + + private final GeyserSession session; + + public AdvancementsCache(GeyserSession session) { + this.session = session; + } + + /** + * Build and send a form with all advancement categories + */ + public void buildAndShowMenuForm() { + SimpleForm.Builder builder = + SimpleForm.builder() + .translator(MinecraftLocale::getLocaleString, session.getLocale()) + .title("gui.advancements"); + + boolean hasAdvancements = false; + for (Map.Entry advancement : storedAdvancements.entrySet()) { + if (advancement.getValue().getParentId() == null) { // No parent means this is a root advancement + hasAdvancements = true; + builder.button(MessageTranslator.convertMessage(advancement.getValue().getDisplayData().getTitle(), session.getLocale())); + } + } + + if (!hasAdvancements) { + builder.content("advancements.empty"); + } + + builder.responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData); + if (!response.isCorrect()) { + return; + } + + String id = ""; + + int advancementIndex = 0; + for (Map.Entry advancement : storedAdvancements.entrySet()) { + if (advancement.getValue().getParentId() == null) { // Root advancement + if (advancementIndex == response.getClickedButtonId()) { + id = advancement.getKey(); + break; + } else { + advancementIndex++; + } + } + } + + if (!id.equals("")) { + if (id.equals(currentAdvancementCategoryId)) { + // The server thinks we are already on this tab + buildAndShowListForm(); + } else { + // Send a packet indicating that we intend to open this particular advancement window + ServerboundSeenAdvancementsPacket packet = new ServerboundSeenAdvancementsPacket(id); + session.sendDownstreamPacket(packet); + // Wait for a response there + } + } + }); + + session.sendForm(builder); + } + + /** + * Build and send the list of advancements + */ + public void buildAndShowListForm() { + GeyserAdvancement categoryAdvancement = storedAdvancements.get(currentAdvancementCategoryId); + String language = session.getLocale(); + + SimpleForm.Builder builder = + SimpleForm.builder() + .title(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getTitle(), language)) + .content(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getDescription(), language)); + + if (currentAdvancementCategoryId != null) { + for (GeyserAdvancement advancement : storedAdvancements.values()) { + if (advancement != null) { + if (advancement.getParentId() != null && currentAdvancementCategoryId.equals(advancement.getRootId(this))) { + boolean color = isEarned(advancement) || !advancement.getDisplayData().isShowToast(); + builder.button((color ? "§6" : "") + MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()) + '\n'); + } + } + } + } + + builder.button(GeyserLocale.getPlayerLocaleString("gui.back", language)); + + builder.responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData); + if (!response.isCorrect()) { + // Indicate that we have closed the current advancement tab + session.sendDownstreamPacket(new ServerboundSeenAdvancementsPacket()); + return; + } + + GeyserAdvancement advancement = null; + int advancementIndex = 0; + // Loop around to find the advancement that the client pressed + for (GeyserAdvancement advancementEntry : storedAdvancements.values()) { + if (advancementEntry.getParentId() != null && + currentAdvancementCategoryId.equals(advancementEntry.getRootId(this))) { + if (advancementIndex == response.getClickedButtonId()) { + advancement = advancementEntry; + break; + } else { + advancementIndex++; + } + } + } + + if (advancement != null) { + buildAndShowInfoForm(advancement); + } else { + buildAndShowMenuForm(); + // Indicate that we have closed the current advancement tab + session.sendDownstreamPacket(new ServerboundSeenAdvancementsPacket()); + } + }); + + session.sendForm(builder); + } + + /** + * Builds the advancement display info based on the chosen category + * + * @param advancement The advancement used to create the info display + */ + public void buildAndShowInfoForm(GeyserAdvancement advancement) { + // Cache language for easier access + String language = session.getLocale(); + + String earned = isEarned(advancement) ? "yes" : "no"; + + String description = getColorFromAdvancementFrameType(advancement) + MessageTranslator.convertMessage(advancement.getDisplayData().getDescription(), language); + String earnedString = GeyserLocale.getPlayerLocaleString("geyser.advancements.earned", language, MinecraftLocale.getLocaleString("gui." + earned, language)); + + /* + Layout will look like: + + (Form title) Stone Age + + (Description) Mine stone with your new pickaxe + + Earned: Yes + Parent Advancement: Minecraft // If relevant + */ + + String content = description + "\n\n§f" + earnedString + "\n"; + if (!currentAdvancementCategoryId.equals(advancement.getParentId())) { + // Only display the parent if it is not the category + content += GeyserLocale.getPlayerLocaleString("geyser.advancements.parentid", language, MessageTranslator.convertMessage(storedAdvancements.get(advancement.getParentId()).getDisplayData().getTitle(), language)); + } + + session.sendForm( + SimpleForm.builder() + .title(MessageTranslator.convertMessage(advancement.getDisplayData().getTitle())) + .content(content) + .button(GeyserLocale.getPlayerLocaleString("gui.back", language)) + .responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData); + if (response.isCorrect()) { + buildAndShowListForm(); + } + }) + ); + } + + /** + * Determine if this advancement has been earned. + * + * @param advancement the advancement to determine + * @return true if the advancement has been earned. + */ + public boolean isEarned(GeyserAdvancement advancement) { + boolean earned = false; + if (advancement.getRequirements().size() == 0) { + // Minecraft handles this case, so we better as well + return false; + } + Map progress = storedAdvancementProgress.get(advancement.getId()); + if (progress != null) { + // Each advancement's requirement must be fulfilled + // For example, [[zombie, blaze, skeleton]] means that one of those three categories must be achieved + // But [[zombie], [blaze], [skeleton]] means that all three requirements must be completed + for (List requirements : advancement.getRequirements()) { + boolean requirementsDone = false; + for (String requirement : requirements) { + Long obtained = progress.get(requirement); + // -1 means that this particular component required for completing the advancement + // has yet to be fulfilled + if (obtained != null && !obtained.equals(-1L)) { + requirementsDone = true; + break; + } + } + if (!requirementsDone) { + return false; + } + } + earned = true; + } + return earned; + } + + public String getColorFromAdvancementFrameType(GeyserAdvancement advancement) { + String base = "\u00a7"; + if (advancement.getDisplayData().getFrameType() == Advancement.DisplayData.FrameType.CHALLENGE) { + return base + "5"; + } + return base + "a"; // Used for types TASK and GOAL + } +} diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/BookEditCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/BookEditCache.java new file mode 100644 index 000000000..faaeafdb9 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/cache/BookEditCache.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.session.cache; + +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundEditBookPacket; +import lombok.Setter; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.session.GeyserSession; + +/** + * Manages updating the current writable book. + * + * Java sends book updates less frequently than Bedrock, and this can cause issues with servers that rate limit + * book packets. Because of this, we need to ensure packets are only send every second or so at maximum. + */ +public class BookEditCache { + private final GeyserSession session; + @Setter + private ServerboundEditBookPacket packet; + /** + * Stores the last time a book update packet was sent to the server. + */ + private long lastBookUpdate; + + public BookEditCache(GeyserSession session) { + this.session = session; + } + + /** + * Check to see if there is a book edit update to send, and if so, send it. + */ + public void checkForSend() { + if (packet == null) { + // No new packet has to be sent + return; + } + // Prevent kicks due to rate limiting - specifically on Spigot servers + if ((System.currentTimeMillis() - lastBookUpdate) < 1000) { + return; + } + // Don't send the update if the player isn't not holding a book, shouldn't happen if we catch all interactions + GeyserItemStack itemStack = session.getPlayerInventory().getItemInHand(); + if (itemStack == null || itemStack.getJavaId() != this.session.getItemMappings().getStoredItems().writableBook().getJavaId()) { + packet = null; + return; + } + session.sendDownstreamPacket(packet); + packet = null; + lastBookUpdate = System.currentTimeMillis(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java b/core/src/main/java/org/geysermc/geyser/session/cache/BossBar.java similarity index 82% rename from connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java rename to core/src/main/java/org/geysermc/geyser/session/cache/BossBar.java index 7eadb7942..3d1e11db9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/BossBar.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,29 +23,28 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.session.cache; +package org.geysermc.geyser.session.cache; -import com.github.steveice10.mc.protocol.data.message.Message; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; import com.nukkitx.protocol.bedrock.packet.BossEventPacket; import com.nukkitx.protocol.bedrock.packet.RemoveEntityPacket; import lombok.AllArgsConstructor; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.chat.MessageTranslator; +import net.kyori.adventure.text.Component; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.text.MessageTranslator; @AllArgsConstructor public class BossBar { + private final GeyserSession session; - private GeyserSession session; - - private long entityId; - private Message title; + private final long entityId; + private Component title; private float health; private int color; - private int overlay; - private int darkenSky; + private final int overlay; + private final int darkenSky; public void addBossBar() { addBossEntity(); @@ -58,21 +57,21 @@ public class BossBar { BossEventPacket bossEventPacket = new BossEventPacket(); bossEventPacket.setBossUniqueEntityId(entityId); bossEventPacket.setAction(BossEventPacket.Action.CREATE); - bossEventPacket.setTitle(MessageTranslator.convertMessage(title.toString(), session.getLocale())); + bossEventPacket.setTitle(MessageTranslator.convertMessage(title, session.getLocale())); bossEventPacket.setHealthPercentage(health); - bossEventPacket.setColor(color); //ignored by client + bossEventPacket.setColor(color); bossEventPacket.setOverlay(overlay); bossEventPacket.setDarkenSky(darkenSky); session.sendUpstreamPacket(bossEventPacket); } - public void updateTitle(Message title) { + public void updateTitle(Component title) { this.title = title; BossEventPacket bossEventPacket = new BossEventPacket(); bossEventPacket.setBossUniqueEntityId(entityId); bossEventPacket.setAction(BossEventPacket.Action.UPDATE_NAME); - bossEventPacket.setTitle(MessageTranslator.convertMessage(title.toString(), session.getLocale())); + bossEventPacket.setTitle(MessageTranslator.convertMessage(title, session.getLocale())); session.sendUpstreamPacket(bossEventPacket); } @@ -87,6 +86,16 @@ public class BossBar { session.sendUpstreamPacket(bossEventPacket); } + public void updateColor(int color) { + this.color = color; + BossEventPacket bossEventPacket = new BossEventPacket(); + bossEventPacket.setBossUniqueEntityId(entityId); + bossEventPacket.setAction(BossEventPacket.Action.UPDATE_STYLE); + bossEventPacket.setColor(color); + + session.sendUpstreamPacket(bossEventPacket); + } + public void removeBossBar() { BossEventPacket bossEventPacket = new BossEventPacket(); bossEventPacket.setBossUniqueEntityId(entityId); diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java new file mode 100644 index 000000000..522c00d2a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.session.cache; + +import com.github.steveice10.mc.protocol.data.game.chunk.DataPalette; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import lombok.Getter; +import lombok.Setter; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.level.chunk.GeyserChunk; +import org.geysermc.geyser.util.MathUtils; + +public class ChunkCache { + private final boolean cache; + private final Long2ObjectMap chunks; + + @Setter + private int minY; + @Setter + private int heightY; + + /** + * Whether the Bedrock client believes they are in a world with a minimum of -64 and maximum of 320 + */ + @Getter + @Setter + private boolean isExtendedHeight = false; + + public ChunkCache(GeyserSession session) { + this.cache = !session.getGeyser().getWorldManager().hasOwnChunkCache(); // To prevent Spigot from initializing + chunks = cache ? new Long2ObjectOpenHashMap<>() : null; + } + + public void addToCache(int x, int z, DataPalette[] chunks) { + if (!cache) { + return; + } + + long chunkPosition = MathUtils.chunkPositionToLong(x, z); + GeyserChunk geyserChunk = GeyserChunk.from(chunks); + this.chunks.put(chunkPosition, geyserChunk); + } + + /** + * Doesn't check for cache enabled, so don't use this without checking that first! + */ + private GeyserChunk getChunk(int chunkX, int chunkZ) { + long chunkPosition = MathUtils.chunkPositionToLong(chunkX, chunkZ); + return chunks.getOrDefault(chunkPosition, null); + } + + public void updateBlock(int x, int y, int z, int block) { + if (!cache) { + return; + } + + GeyserChunk chunk = this.getChunk(x >> 4, z >> 4); + if (chunk == null) { + return; + } + + if (y < minY || ((y - minY) >> 4) > chunk.sections().length - 1) { + // Y likely goes above or below the height limit of this world + return; + } + + DataPalette palette = chunk.sections()[(y - minY) >> 4]; + if (palette == null) { + if (block != BlockStateValues.JAVA_AIR_ID) { + // A previously empty chunk, which is no longer empty as a block has been added to it + palette = DataPalette.createForChunk(); + // Fixes the chunk assuming that all blocks is the `block` variable we are updating. /shrug + palette.getPalette().stateToId(BlockStateValues.JAVA_AIR_ID); + chunk.sections()[(y - minY) >> 4] = palette; + } else { + // Nothing to update + return; + } + } + + palette.set(x & 0xF, y & 0xF, z & 0xF, block); + } + + public int getBlockAt(int x, int y, int z) { + if (!cache) { + return BlockStateValues.JAVA_AIR_ID; + } + + GeyserChunk column = this.getChunk(x >> 4, z >> 4); + if (column == null) { + return BlockStateValues.JAVA_AIR_ID; + } + + if (y < minY || ((y - minY) >> 4) > column.sections().length - 1) { + // Y likely goes above or below the height limit of this world + return BlockStateValues.JAVA_AIR_ID; + } + + DataPalette chunk = column.sections()[(y - minY) >> 4]; + if (chunk != null) { + return chunk.get(x & 0xF, y & 0xF, z & 0xF); + } + + return BlockStateValues.JAVA_AIR_ID; + } + + public void removeChunk(int chunkX, int chunkZ) { + if (!cache) { + return; + } + + long chunkPosition = MathUtils.chunkPositionToLong(chunkX, chunkZ); + chunks.remove(chunkPosition); + } + + /** + * Manually clears all entries in the chunk cache. + * The server is responsible for clearing chunk entries if out of render distance (for example) or switching dimensions, + * but it is the client that must clear sections in the event of proxy switches. + */ + public void clear() { + if (!cache) { + return; + } + + chunks.clear(); + } + + public int getChunkMinY() { + return minY >> 4; + } + + public int getChunkHeightY() { + return heightY >> 4; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/EntityCache.java similarity index 60% rename from connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java rename to core/src/main/java/org/geysermc/geyser/session/cache/EntityCache.java index 4b54c9434..5d99ba0e3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/EntityCache.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,14 +23,19 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.session.cache; +package org.geysermc.geyser.session.cache; -import it.unimi.dsi.fastutil.longs.*; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import it.unimi.dsi.fastutil.longs.Long2LongMap; +import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import lombok.Getter; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.PlayerEntity; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.type.Tickable; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.session.GeyserSession; import java.util.*; import java.util.concurrent.atomic.AtomicLong; @@ -40,25 +45,36 @@ import java.util.concurrent.atomic.AtomicLong; * for that player (e.g. seeing vanished players from /vanish) */ public class EntityCache { - private GeyserSession session; + private final GeyserSession session; @Getter - private Long2ObjectMap entities = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); - private Long2LongMap entityIdTranslations = Long2LongMaps.synchronize(new Long2LongOpenHashMap()); - private Map playerEntities = Collections.synchronizedMap(new HashMap<>()); - private Map bossBars = Collections.synchronizedMap(new HashMap<>()); - private Long2LongMap cachedPlayerEntityLinks = Long2LongMaps.synchronize(new Long2LongOpenHashMap()); + private final Long2ObjectMap entities = new Long2ObjectOpenHashMap<>(); + /** + * A list of all entities that must be ticked. + */ + private final List tickableEntities = new ObjectArrayList<>(); + private final Long2LongMap entityIdTranslations = new Long2LongOpenHashMap(); + private final Map playerEntities = new Object2ObjectOpenHashMap<>(); + private final Map bossBars = new Object2ObjectOpenHashMap<>(); + private final Long2LongMap cachedPlayerEntityLinks = new Long2LongOpenHashMap(); @Getter - private AtomicLong nextEntityId = new AtomicLong(2L); + private final AtomicLong nextEntityId = new AtomicLong(2L); public EntityCache(GeyserSession session) { this.session = session; + + cachedPlayerEntityLinks.defaultReturnValue(-1L); } public void spawnEntity(Entity entity) { if (cacheEntity(entity)) { - entity.spawnEntity(session); + entity.spawnEntity(); + + if (entity instanceof Tickable) { + // Start ticking it + tickableEntities.add((Tickable) entity); + } } } @@ -73,19 +89,31 @@ public class EntityCache { } public boolean removeEntity(Entity entity, boolean force) { - if (entity != null && entity.isValid() && (force || entity.despawnEntity(session))) { + if (entity instanceof PlayerEntity player) { + session.getPlayerWithCustomHeads().remove(player.getUuid()); + } + + if (entity != null && entity.isValid() && (force || entity.despawnEntity())) { long geyserId = entityIdTranslations.remove(entity.getEntityId()); entities.remove(geyserId); + + if (entity instanceof Tickable) { + tickableEntities.remove(entity); + } return true; } return false; } public void removeAllEntities() { - List entities = new ArrayList<>(session.getEntityCache().getEntities().values()); + List entities = new ArrayList<>(this.entities.values()); for (Entity entity : entities) { - session.getEntityCache().removeEntity(entity, false); + removeEntity(entity, false); } + + session.getPlayerWithCustomHeads().clear(); + // As a precaution + cachedPlayerEntityLinks.clear(); } public Entity getEntityByGeyserId(long geyserId) { @@ -96,16 +124,6 @@ public class EntityCache { return entities.get(entityIdTranslations.get(javaId)); } - public Set getEntitiesByType(Class entityType) { - Set entitiesOfType = new ObjectOpenHashSet<>(); - for (Entity entity : (entityType == PlayerEntity.class ? playerEntities : entities).values()) { - if (entity.is(entityType)) { - entitiesOfType.add(entity.as(entityType)); - } - } - return entitiesOfType; - } - public void addPlayerEntity(PlayerEntity entity) { playerEntities.put(entity.getUuid(), entity); } @@ -114,8 +132,12 @@ public class EntityCache { return playerEntities.get(uuid); } - public void removePlayerEntity(UUID uuid) { - playerEntities.remove(uuid); + public PlayerEntity removePlayerEntity(UUID uuid) { + return playerEntities.remove(uuid); + } + + public Collection getAllPlayerEntities() { + return playerEntities.values(); } public void addBossBar(UUID uuid, BossBar bossBar) { @@ -138,18 +160,15 @@ public class EntityCache { bossBars.values().forEach(BossBar::updateBossBar); } - public void clear() { - entities = null; - entityIdTranslations = null; - playerEntities = null; - bossBars = null; - } - public long getCachedPlayerEntityLink(long playerId) { - return cachedPlayerEntityLinks.getOrDefault(playerId, -1); + return cachedPlayerEntityLinks.remove(playerId); } public void addCachedPlayerEntityLink(long playerId, long linkedEntityId) { cachedPlayerEntityLinks.put(playerId, linkedEntityId); } + + public List getTickableEntities() { + return tickableEntities; + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityEffectCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/EntityEffectCache.java similarity index 58% rename from connector/src/main/java/org/geysermc/connector/network/session/cache/EntityEffectCache.java rename to core/src/main/java/org/geysermc/geyser/session/cache/EntityEffectCache.java index a16ef6902..95c059475 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityEffectCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/EntityEffectCache.java @@ -1,54 +1,66 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.session.cache; - -import com.github.steveice10.mc.protocol.data.game.entity.Effect; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import lombok.Getter; - -public class EntityEffectCache { - - @Getter - private final Object2IntMap entityEffects = new Object2IntOpenHashMap<>(); - - public void addEffect(Effect effect, int effectAmplifier) { - if (effect != null) { - entityEffects.putIfAbsent(effect, effectAmplifier + 1); - } - } - - public void removeEffect(Effect effect) { - if (entityEffects.containsKey(effect)) { - int effectLevel = entityEffects.getInt(effect); - entityEffects.remove(effect, effectLevel); - } - } - - public int getEffectLevel(Effect effect) { - return entityEffects.getOrDefault(effect, 0); - } -} +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.session.cache; + +import com.github.steveice10.mc.protocol.data.game.entity.Effect; +import lombok.Getter; + +import java.util.EnumSet; +import java.util.Set; + +public class EntityEffectCache { + /** + * Used to clear effects on dimension switch. + */ + @Getter + private final Set entityEffects = EnumSet.noneOf(Effect.class); + + /* Used to track mining speed */ + @Getter + private int conduitPower; + @Getter + private int haste; + @Getter + private int miningFatigue; + + public void setEffect(Effect effect, int effectAmplifier) { + switch (effect) { + case CONDUIT_POWER -> conduitPower = effectAmplifier + 1; + case HASTE -> haste = effectAmplifier + 1; + case MINING_FATIGUE -> miningFatigue = effectAmplifier + 1; + } + entityEffects.add(effect); + } + + public void removeEffect(Effect effect) { + switch (effect) { + case CONDUIT_POWER -> conduitPower = 0; + case HASTE -> haste = 0; + case MINING_FATIGUE -> miningFatigue = 0; + } + entityEffects.remove(effect); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/FormCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/FormCache.java new file mode 100644 index 000000000..6f7d180de --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/cache/FormCache.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.session.cache; + +import com.nukkitx.protocol.bedrock.packet.ModalFormRequestPacket; +import com.nukkitx.protocol.bedrock.packet.ModalFormResponsePacket; +import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import lombok.RequiredArgsConstructor; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.cumulus.Form; +import org.geysermc.cumulus.SimpleForm; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +@RequiredArgsConstructor +public class FormCache { + private final AtomicInteger formId = new AtomicInteger(0); + private final Int2ObjectMap

forms = new Int2ObjectOpenHashMap<>(); + private final GeyserSession session; + + public int addForm(Form form) { + int windowId = formId.getAndIncrement(); + forms.put(windowId, form); + return windowId; + } + + public int showForm(Form form) { + int windowId = addForm(form); + + ModalFormRequestPacket formRequestPacket = new ModalFormRequestPacket(); + formRequestPacket.setFormId(windowId); + formRequestPacket.setFormData(form.getJsonData()); + session.sendUpstreamPacket(formRequestPacket); + + // Hack to fix the (url) image loading bug + if (form instanceof SimpleForm) { + NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket(); + latencyPacket.setFromServer(true); + latencyPacket.setTimestamp(-System.currentTimeMillis()); + session.scheduleInEventLoop(() -> session.sendUpstreamPacket(latencyPacket), + 500, TimeUnit.MILLISECONDS); + } + + return windowId; + } + + public void handleResponse(ModalFormResponsePacket response) { + Form form = forms.remove(response.getFormId()); + if (form == null) { + return; + } + + Consumer responseConsumer = form.getResponseHandler(); + if (responseConsumer != null) { + try { + responseConsumer.accept(response.getFormData()); + } catch (Exception e) { + GeyserImpl.getInstance().getLogger().error("Error while processing form response!", e); + } + } + } + + public boolean removeWindow(int id) { + return forms.remove(id) != null; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/LodestoneCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/LodestoneCache.java new file mode 100644 index 000000000..bd3c73574 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/cache/LodestoneCache.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.session.cache; + +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.IntTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.geysermc.geyser.inventory.GeyserItemStack; + +import javax.annotation.Nullable; +import java.util.Map; +import java.util.WeakHashMap; + +/** + * A temporary cache for lodestone information. + * Bedrock requests the lodestone position information separately from the item. + */ +public class LodestoneCache { + /** + * A list of any GeyserItemStacks that are lodestones. Used mainly to minimize Bedrock's "pop-in" effect + * when a new item has been created; instead we can re-use already existing IDs + */ + private final Map activeLodestones = new WeakHashMap<>(); + private final Int2ObjectMap lodestones = new Int2ObjectOpenHashMap<>(); + /** + * An ID to increment for each lodestone + */ + private int id = 1; + + public void cacheInventoryItem(GeyserItemStack itemStack) { + CompoundTag tag = itemStack.getNbt(); + CompoundTag lodestonePos = tag.get("LodestonePos"); + if (lodestonePos == null) { + // invalid + return; + } + + // Get all info needed for tracking + int x = ((IntTag) lodestonePos.get("X")).getValue(); + int y = ((IntTag) lodestonePos.get("Y")).getValue(); + int z = ((IntTag) lodestonePos.get("Z")).getValue(); + String dim = ((StringTag) tag.get("LodestoneDimension")).getValue(); + + for (LodestonePos pos : this.activeLodestones.values()) { + if (pos.equals(x, y, z, dim)) { + this.activeLodestones.put(itemStack, pos); + return; + } + } + + for (Int2ObjectMap.Entry entry : this.lodestones.int2ObjectEntrySet()) { + LodestonePos pos = entry.getValue(); + if (pos.equals(x, y, z, dim)) { + // Use this existing position instead + this.activeLodestones.put(itemStack, pos); + return; + } + } + + this.activeLodestones.put(itemStack, new LodestonePos(id++, x, y, z, dim)); + } + + public int store(CompoundTag tag) { + CompoundTag lodestonePos = tag.get("LodestonePos"); + if (lodestonePos == null) { + // invalid + return 0; + } + + // Get all info needed for tracking + int x = ((IntTag) lodestonePos.get("X")).getValue(); + int y = ((IntTag) lodestonePos.get("Y")).getValue(); + int z = ((IntTag) lodestonePos.get("Z")).getValue(); + String dim = ((StringTag) tag.get("LodestoneDimension")).getValue(); + + for (LodestonePos pos : this.activeLodestones.values()) { + if (pos.equals(x, y, z, dim)) { + // No need to add this into the lodestones map as it should not be re-requested + return pos.id; + } + } + + for (Int2ObjectMap.Entry entry : this.lodestones.int2ObjectEntrySet()) { + if (entry.getValue().equals(x, y, z, dim)) { + // Use this existing position instead + return entry.getIntKey(); + } + } + + // Start at 1 as 0 does not work + this.lodestones.put(id, new LodestonePos(id, x, y, z, dim)); + return id++; + } + + public @Nullable LodestonePos getPos(int id) { + // We should not need to check the activeLodestones map as Bedrock should already be aware of this ID + return this.lodestones.remove(id); + } + + public void clear() { + // Just in case... + this.activeLodestones.clear(); + this.lodestones.clear(); + } + + @Getter + @AllArgsConstructor + @EqualsAndHashCode + public static class LodestonePos { + private final int id; + private final int x; + private final int y; + private final int z; + private final String dimension; + + boolean equals(int x, int y, int z, String dimension) { + return this.x == x && this.y == y && this.z == z && this.dimension.equals(dimension); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/PistonCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/PistonCache.java new file mode 100644 index 000000000..05e59a5bb --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/cache/PistonCache.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.session.cache; + +import com.nukkitx.math.vector.Vector3d; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.physics.BoundingBox; +import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity; +import org.geysermc.geyser.level.physics.Axis; + +import java.util.Map; + +@Getter +public class PistonCache { + @Getter(AccessLevel.PRIVATE) + private final GeyserSession session; + + /** + * Maps the position of a piston to its block entity + */ + private final Map pistons = new Object2ObjectOpenHashMap<>(); + + /** + * Maps the position of a moving block to the piston moving it + * Positions in this map represent the starting position of the block + */ + private final Map movingBlocksMap = new Object2ObjectOpenHashMap<>(); + + private Vector3d playerDisplacement = Vector3d.ZERO; + + @Setter + private Vector3f playerMotion = Vector3f.ZERO; + + /** + * Stores whether a player has/will collide with any moving blocks. + */ + @Setter + private boolean playerCollided = false; + + /** + * Stores whether a player has/will collide with any slime blocks. + * This is used to prevent movement from being corrected when players + * are about to hit a slime block. + */ + @Setter + private boolean playerSlimeCollision = false; + + /** + * Stores whether a player is standing on a honey block. + * This is used to ignore movement from Bedrock to prevent them from + * falling off. + */ + @Setter + private boolean playerAttachedToHoney = false; + + public PistonCache(GeyserSession session) { + this.session = session; + } + + public void tick() { + resetPlayerMovement(); + if (!pistons.isEmpty()) { + pistons.values().forEach(PistonBlockEntity::updateMovement); + sendPlayerMovement(); + sendPlayerMotion(); + // Update blocks after movement, so that players don't get stuck inside blocks + pistons.values().forEach(PistonBlockEntity::updateBlocks); + + pistons.entrySet().removeIf((entry) -> entry.getValue().canBeRemoved()); + + if (pistons.isEmpty() && !movingBlocksMap.isEmpty()) { + session.getGeyser().getLogger().error("The moving block map has de-synced!"); + for (Map.Entry entry : movingBlocksMap.entrySet()) { + session.getGeyser().getLogger().error("Moving Block at " + entry.getKey() + " was previously owned by the piston at " + entry.getValue().getPosition()); + } + } + } + } + + private void resetPlayerMovement() { + playerDisplacement = Vector3d.ZERO; + playerMotion = Vector3f.ZERO; + playerCollided = false; + playerSlimeCollision = false; + playerAttachedToHoney = false; + } + + private void sendPlayerMovement() { + if (!playerDisplacement.equals(Vector3d.ZERO) && playerMotion.equals(Vector3f.ZERO)) { + SessionPlayerEntity playerEntity = session.getPlayerEntity(); + boolean isOnGround = playerDisplacement.getY() > 0 || playerEntity.isOnGround(); + Vector3d position = session.getCollisionManager().getPlayerBoundingBox().getBottomCenter(); + playerEntity.moveAbsolute(position.toFloat(), playerEntity.getYaw(), playerEntity.getPitch(), playerEntity.getHeadYaw(), isOnGround, true); + } + } + + private void sendPlayerMotion() { + if (!playerMotion.equals(Vector3f.ZERO)) { + SessionPlayerEntity playerEntity = session.getPlayerEntity(); + playerEntity.setMotion(playerMotion); + + SetEntityMotionPacket setEntityMotionPacket = new SetEntityMotionPacket(); + setEntityMotionPacket.setRuntimeEntityId(playerEntity.getGeyserId()); + setEntityMotionPacket.setMotion(playerMotion); + session.sendUpstreamPacket(setEntityMotionPacket); + } + } + + /** + * Add to the player's displacement and move the player's bounding box + * The total displacement is capped to a range of -0.51 to 0.51 per tick + * + * @param displacement The displacement to apply to the player's bounding box + */ + public void displacePlayer(Vector3d displacement) { + Vector3d totalDisplacement = playerDisplacement.add(displacement); + // Clamp to range -0.51 to 0.51 + totalDisplacement = totalDisplacement.max(-0.51d, -0.51d, -0.51d).min(0.51d, 0.51d, 0.51d); + + Vector3d delta = totalDisplacement.sub(playerDisplacement); + // Check if the piston is pushing a player into collision + delta = session.getCollisionManager().correctPlayerMovement(delta, true, false); + + session.getCollisionManager().getPlayerBoundingBox().translate(delta.getX(), delta.getY(), delta.getZ()); + + playerDisplacement = totalDisplacement; + } + + /** + * @param blockPos The block position to test + * @param boundingBox The bounding box that moves + * @param axis The axis to apply the offset + * @param offset The current maximum distance the bounding box can travel + * @return The new maximum distance the bounding box can travel without colliding with the tested moving block + */ + public double computeCollisionOffset(Vector3i blockPos, BoundingBox boundingBox, Axis axis, double offset) { + PistonBlockEntity piston = movingBlocksMap.get(blockPos); + if (piston != null) { + return piston.computeCollisionOffset(blockPos, boundingBox, axis, offset); + } + return offset; + } + + public boolean checkCollision(Vector3i blockPos, BoundingBox boundingBox) { + PistonBlockEntity piston = movingBlocksMap.get(blockPos); + if (piston != null) { + return piston.checkCollision(blockPos, boundingBox); + } + return false; + } + + public void clear() { + pistons.clear(); + movingBlocksMap.clear(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/PreferencesCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/PreferencesCache.java new file mode 100644 index 000000000..534045f81 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/cache/PreferencesCache.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.session.cache; + +import lombok.Getter; +import lombok.Setter; +import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.CooldownUtils; + +@Getter +public class PreferencesCache { + private final GeyserSession session; + + /** + * True if the client prefers being shown their coordinates, regardless if they're being shown or not. + * This will be true everytime the client joins the server because neither the client nor server store the preference permanently. + */ + @Setter + private boolean prefersShowCoordinates = true; + + /** + * If the client's preference will be ignored, this will return false. + */ + private boolean allowShowCoordinates; + + /** + * If the session wants custom skulls to be shown. + */ + @Setter + private boolean prefersCustomSkulls; + + /** + * Which CooldownType the client prefers. Initially set to {@link CooldownUtils#getDefaultShowCooldown()}. + */ + @Setter + private CooldownUtils.CooldownType cooldownPreference = CooldownUtils.getDefaultShowCooldown(); + + public PreferencesCache(GeyserSession session) { + this.session = session; + + prefersCustomSkulls = session.getGeyser().getConfig().isAllowCustomSkulls(); + } + + /** + * Tell the client to hide or show the coordinates. + * + * If {@link #prefersShowCoordinates} is true, coordinates will be shown, unless either of the following conditions apply:
+ *
+ * {@link GeyserSession#reducedDebugInfo} is enabled + * {@link GeyserConfiguration#isShowCoordinates()} is disabled + */ + public void updateShowCoordinates() { + allowShowCoordinates = !session.isReducedDebugInfo() && session.getGeyser().getConfig().isShowCoordinates(); + session.sendGameRule("showcoordinates", allowShowCoordinates && prefersShowCoordinates); + } + + /** + * @return true if the session prefers custom skulls, and the config allows them. + */ + public boolean showCustomSkulls() { + return prefersCustomSkulls && session.getGeyser().getConfig().isAllowCustomSkulls(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java new file mode 100644 index 000000000..32827cfec --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.session.cache; + +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundUpdateTagsPacket; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.IntLists; +import org.geysermc.geyser.registry.type.BlockMapping; +import org.geysermc.geyser.registry.type.ItemMapping; + +import java.util.Map; + +/** + * Manages information sent from the {@link ClientboundUpdateTagsPacket}. If that packet is not sent, all lists here + * will remain empty, matching Java Edition behavior. + */ +public class TagCache { + /* Blocks */ + private IntList leaves; + private IntList wool; + + private IntList axeEffective; + private IntList hoeEffective; + private IntList pickaxeEffective; + private IntList shovelEffective; + + private IntList requiresStoneTool; + private IntList requiresIronTool; + private IntList requiresDiamondTool; + + /* Items */ + private IntList flowers; + private IntList foxFood; + private IntList piglinLoved; + + public TagCache() { + // Ensure all lists are non-null + clear(); + } + + public void loadPacket(ClientboundUpdateTagsPacket packet) { + Map blockTags = packet.getTags().get("minecraft:block"); + this.leaves = IntList.of(blockTags.get("minecraft:leaves")); + this.wool = IntList.of(blockTags.get("minecraft:wool")); + + this.axeEffective = IntList.of(blockTags.get("minecraft:mineable/axe")); + this.hoeEffective = IntList.of(blockTags.get("minecraft:mineable/hoe")); + this.pickaxeEffective = IntList.of(blockTags.get("minecraft:mineable/pickaxe")); + this.shovelEffective = IntList.of(blockTags.get("minecraft:mineable/shovel")); + + this.requiresStoneTool = IntList.of(blockTags.get("minecraft:needs_stone_tool")); + this.requiresIronTool = IntList.of(blockTags.get("minecraft:needs_iron_tool")); + this.requiresDiamondTool = IntList.of(blockTags.get("minecraft:needs_diamond_tool")); + + Map itemTags = packet.getTags().get("minecraft:item"); + this.flowers = IntList.of(itemTags.get("minecraft:flowers")); + this.foxFood = IntList.of(itemTags.get("minecraft:fox_food")); + this.piglinLoved = IntList.of(itemTags.get("minecraft:piglin_loved")); + } + + public void clear() { + this.leaves = IntLists.emptyList(); + this.wool = IntLists.emptyList(); + + this.axeEffective = IntLists.emptyList(); + this.hoeEffective = IntLists.emptyList(); + this.pickaxeEffective = IntLists.emptyList(); + this.shovelEffective = IntLists.emptyList(); + + this.requiresStoneTool = IntLists.emptyList(); + this.requiresIronTool = IntLists.emptyList(); + this.requiresDiamondTool = IntLists.emptyList(); + + this.flowers = IntLists.emptyList(); + this.foxFood = IntLists.emptyList(); + this.piglinLoved = IntLists.emptyList(); + } + + public boolean isFlower(ItemMapping mapping) { + return flowers.contains(mapping.getJavaId()); + } + + public boolean isFoxFood(ItemMapping mapping) { + return foxFood.contains(mapping.getJavaId()); + } + + public boolean shouldPiglinAdmire(ItemMapping mapping) { + return piglinLoved.contains(mapping.getJavaId()); + } + + public boolean isAxeEffective(BlockMapping blockMapping) { + return axeEffective.contains(blockMapping.getJavaBlockId()); + } + + public boolean isHoeEffective(BlockMapping blockMapping) { + return hoeEffective.contains(blockMapping.getJavaBlockId()); + } + + public boolean isPickaxeEffective(BlockMapping blockMapping) { + return pickaxeEffective.contains(blockMapping.getJavaBlockId()); + } + + public boolean isShovelEffective(BlockMapping blockMapping) { + return shovelEffective.contains(blockMapping.getJavaBlockId()); + } + + public boolean isShearsEffective(BlockMapping blockMapping) { + int javaBlockId = blockMapping.getJavaBlockId(); + return leaves.contains(javaBlockId) || wool.contains(javaBlockId); + } + + public boolean requiresStoneTool(BlockMapping blockMapping) { + return requiresStoneTool.contains(blockMapping.getJavaBlockId()); + } + + public boolean requiresIronTool(BlockMapping blockMapping) { + return requiresIronTool.contains(blockMapping.getJavaBlockId()); + } + + public boolean requiresDiamondTool(BlockMapping blockMapping) { + return requiresDiamondTool.contains(blockMapping.getJavaBlockId()); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/TeleportCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/TeleportCache.java new file mode 100644 index 000000000..cc9a7c09f --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/cache/TeleportCache.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.session.cache; + +import com.nukkitx.math.vector.Vector3d; +import lombok.Data; +import lombok.RequiredArgsConstructor; + +/** + * Represents a teleport ID and corresponding coordinates that need to be confirmed.
+ * + * The vanilla Java client, after getting a + * {@link com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.player.ClientboundPlayerPositionPacket}, + * adjusts the player's positions and immediately sends a teleport back. However, we want to acknowledge that the + * Bedrock player actually moves close to that point, so we store the teleport until we get a movement packet from + * Bedrock that the teleport was successful. + */ +@RequiredArgsConstructor +@Data +public class TeleportCache { + + private static final double ERROR_X_AND_Z = 0.1; + private static final double ERROR_Y = 0.1; + + /** + * How many move packets the teleport can be unconfirmed for before it gets resent to the client + */ + private static final int RESEND_THRESHOLD = 5; + + private final double x, y, z; + private final float pitch, yaw; + private final int teleportConfirmId; + + private int unconfirmedFor = 0; + + public boolean canConfirm(Vector3d position) { + return (Math.abs(this.x - position.getX()) < ERROR_X_AND_Z && + Math.abs(this.y - position.getY()) < ERROR_Y && + Math.abs(this.z - position.getZ()) < ERROR_X_AND_Z); + } + + public void incrementUnconfirmedFor() { + unconfirmedFor++; + } + + public boolean shouldResend() { + return unconfirmedFor >= RESEND_THRESHOLD; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/WorldBorder.java b/core/src/main/java/org/geysermc/geyser/session/cache/WorldBorder.java new file mode 100644 index 000000000..01c5949c7 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/cache/WorldBorder.java @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.session.cache; + +import com.nukkitx.math.GenericMath; +import com.nukkitx.math.vector.Vector2d; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; +import com.nukkitx.protocol.bedrock.packet.PlayerFogPacket; +import lombok.Getter; +import lombok.Setter; +import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.session.GeyserSession; + +import javax.annotation.Nonnull; +import java.util.Collections; + +public class WorldBorder { + private static final double DEFAULT_WORLD_BORDER_SIZE = 5.9999968E7D; + + @Setter + private @Nonnull Vector2d center = Vector2d.ZERO; + /** + * The diameter in blocks of the world border before it got changed or similar to newDiameter if not changed. + */ + @Setter + private double oldDiameter = DEFAULT_WORLD_BORDER_SIZE; + /** + * The diameter in blocks of the new world border. + */ + @Setter + private double newDiameter = DEFAULT_WORLD_BORDER_SIZE; + /** + * The speed to apply an expansion/shrinking of the world border. + * When a client joins they get the actual border oldDiameter and the time left to reach the newDiameter. + */ + @Setter + private long speed = 0; + /** + * The time in seconds before a shrinking world border would hit a not moving player. + * Creates the same visual warning effect as warningBlocks. + */ + @Setter + private int warningDelay = 15; + /** + * Block length before you reach the border to show warning particles. + */ + @Setter + private int warningBlocks = 5; + /** + * The world border cannot go beyond this number, positive or negative, in world coordinates + */ + @Setter + private int absoluteMaxSize = 29999984; + + /** + * The world coordinate scale as sent in the dimension registry. Used to scale the center X and Z. + */ + private double worldCoordinateScale = 1.0D; + + @Getter + private boolean resizing; + private double currentDiameter; + + /* + * Boundaries of the actual world border. + * (The will get updated on expanding or shrinking) + */ + private double minX = 0.0D; + private double minZ = 0.0D; + private double maxX = 0.0D; + private double maxZ = 0.0D; + + /* + * The boundaries for the for the warning visuals. + */ + private double warningMaxX = 0.0D; + private double warningMaxZ = 0.0D; + private double warningMinX = 0.0D; + private double warningMinZ = 0.0D; + + /** + * To track when to send wall particle packets. + */ + private int currentWallTick; + + /** + * If the world border is resizing, this variable saves how many ticks have progressed in the resizing + */ + private long lastUpdatedWorldBorderTime = 0; + + private final GeyserSession session; + + public WorldBorder(GeyserSession session) { + this.session = session; + // Initialize all min/max/warning variables + update(); + } + + public void setWorldCoordinateScale(double worldCoordinateScale) { + boolean needsUpdate = worldCoordinateScale != this.worldCoordinateScale; + this.worldCoordinateScale = worldCoordinateScale; + if (needsUpdate) { + this.update(); + } + } + + /** + * @return true as long the entity is within the world limits. + */ + public boolean isInsideBorderBoundaries() { + Vector3f entityPosition = session.getPlayerEntity().getPosition(); + return entityPosition.getX() > minX && entityPosition.getX() < maxX && entityPosition.getZ() > minZ && entityPosition.getZ() < maxZ; + } + + /** + * Confirms that the entity is within world border boundaries when they move. + * Otherwise, if {@code adjustPosition} is true, this function will push the player back. + * + * @return if this player was indeed against the world border. Will return false if no world border was defined for us. + */ + public boolean isPassingIntoBorderBoundaries(Vector3f newPosition, boolean adjustPosition) { + boolean isInWorldBorder = isPassingIntoBorderBoundaries(newPosition); + if (isInWorldBorder && adjustPosition) { + PlayerEntity playerEntity = session.getPlayerEntity(); + // Move the player back, but allow gravity to take place + // Teleported = true makes going back better, but disconnects the player from their mounted entity + playerEntity.moveAbsolute(Vector3f.from(playerEntity.getPosition().getX(), (newPosition.getY() - EntityDefinitions.PLAYER.offset()), playerEntity.getPosition().getZ()), + playerEntity.getYaw(), playerEntity.getPitch(), playerEntity.getHeadYaw(), playerEntity.isOnGround(), session.getRidingVehicleEntity() == null); + } + return isInWorldBorder; + } + + public boolean isPassingIntoBorderBoundaries(Vector3f newEntityPosition) { + int entityX = GenericMath.floor(newEntityPosition.getX()); + int entityZ = GenericMath.floor(newEntityPosition.getZ()); + Vector3f currentEntityPosition = session.getPlayerEntity().getPosition(); + // Make sure we can't move out of the world border, but if we're out of the world border, we can move in + return (entityX == (int) minX && currentEntityPosition.getX() > newEntityPosition.getX()) || + (entityX == (int) maxX && currentEntityPosition.getX() < newEntityPosition.getX()) || + (entityZ == (int) minZ && currentEntityPosition.getZ() > newEntityPosition.getZ()) || + (entityZ == (int) maxZ && currentEntityPosition.getZ() < newEntityPosition.getZ()); + } + + /** + * Same as {@link #isInsideBorderBoundaries()} but using the warning boundaries. + * + * @return true as long the entity is within the world limits and not in the warning zone at the edge to the border. + */ + public boolean isWithinWarningBoundaries() { + Vector3f entityPosition = session.getPlayerEntity().getPosition(); + return entityPosition.getX() > warningMinX && entityPosition.getX() < warningMaxX && entityPosition.getZ() > warningMinZ && entityPosition.getZ() < warningMaxZ; + } + + /** + * Updates the world border's minimum and maximum properties + */ + public void update() { + /* + * Setting the correct boundary of our world border's square. + */ + double radius; + if (resizing) { + radius = this.currentDiameter / 2.0D; + } else { + radius = this.newDiameter / 2.0D; + } + + double absoluteMinSize = -this.absoluteMaxSize; + // Used in the Nether by default + double centerX = this.center.getX() / this.worldCoordinateScale; + double centerZ = this.center.getY() / this.worldCoordinateScale; // Mapping 2D vector to 3D coordinates >> Y becomes Z + + this.minX = GenericMath.clamp(centerX - radius, absoluteMinSize, this.absoluteMaxSize); + this.minZ = GenericMath.clamp(centerZ - radius, absoluteMinSize, this.absoluteMaxSize); + this.maxX = GenericMath.clamp(centerX + radius, absoluteMinSize, this.absoluteMaxSize); + this.maxZ = GenericMath.clamp(centerZ + radius, absoluteMinSize, this.absoluteMaxSize); + + /* + * Caching the warning boundaries. + */ + this.warningMinX = this.minX + this.warningBlocks; + this.warningMinZ = this.minZ + this.warningBlocks; + this.warningMaxX = this.maxX - this.warningBlocks; + this.warningMaxZ = this.maxZ - this.warningBlocks; + } + + public void resize() { + if (this.lastUpdatedWorldBorderTime >= this.speed) { + // Diameter has now updated to the new diameter + this.resizing = false; + this.lastUpdatedWorldBorderTime = 0; + } else if (resizing) { + this.currentDiameter = this.oldDiameter + ((double) this.lastUpdatedWorldBorderTime / (double) this.speed) * (this.newDiameter - this.oldDiameter); + this.lastUpdatedWorldBorderTime += 50; + } + update(); + } + + public void setResizing(boolean resizing) { + this.resizing = resizing; + if (!resizing) { + this.lastUpdatedWorldBorderTime = 0; + } + } + + private static final LevelEventType WORLD_BORDER_PARTICLE = LevelEventType.PARTICLE_DENY_BLOCK; + + /** + * Draws a wall of particles where the world border resides + */ + public void drawWall() { + if (currentWallTick++ != 20) { + // Only draw a wall once every second + return; + } + currentWallTick = 0; + Vector3f entityPosition = session.getPlayerEntity().getPosition(); + float particlePosX = entityPosition.getX(); + float particlePosY = entityPosition.getY(); + float particlePosZ = entityPosition.getZ(); + + if (entityPosition.getX() > warningMaxX) { + drawWall(Vector3f.from(maxX, particlePosY, particlePosZ), true); + } + if (entityPosition.getX() < warningMinX) { + drawWall(Vector3f.from(minX, particlePosY, particlePosZ), true); + } + if (entityPosition.getZ() > warningMaxZ) { + drawWall(Vector3f.from(particlePosX, particlePosY, maxZ), false); + } + if (entityPosition.getZ() < warningMinZ) { + drawWall(Vector3f.from(particlePosX, particlePosY, minZ), false); + } + } + + private void drawWall(Vector3f position, boolean drawWallX) { + int initialY = (int) (position.getY() - EntityDefinitions.PLAYER.offset() - 1); + for (int y = initialY; y < (initialY + 5); y++) { + if (drawWallX) { + float x = position.getX(); + for (int z = (int) position.getZ() - 3; z < ((int) position.getZ() + 3); z++) { + if (z < minZ) { + continue; + } + if (z > maxZ) { + break; + } + + sendWorldBorderParticle(x, y, z); + } + } else { + float z = position.getZ(); + for (int x = (int) position.getX() - 3; x < ((int) position.getX() + 3); x++) { + if (x < minX) { + continue; + } + if (x > maxX) { + break; + } + + sendWorldBorderParticle(x, y, z); + } + } + } + } + + private void sendWorldBorderParticle(float x, float y, float z) { + LevelEventPacket effectPacket = new LevelEventPacket(); + effectPacket.setPosition(Vector3f.from(x, y, z)); + effectPacket.setType(WORLD_BORDER_PARTICLE); + session.getUpstream().sendPacket(effectPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java similarity index 62% rename from connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java rename to core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java index ce49d2a09..a9564b157 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,55 +23,39 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.session.cache; +package org.geysermc.geyser.session.cache; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; import lombok.Getter; import lombok.Setter; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.scoreboard.Objective; -import org.geysermc.connector.scoreboard.Scoreboard; -import org.geysermc.connector.scoreboard.ScoreboardUpdater; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.scoreboard.Scoreboard; +import org.geysermc.geyser.scoreboard.ScoreboardUpdater.ScoreboardSession; @Getter public class WorldCache { private final GeyserSession session; + private final ScoreboardSession scoreboardSession; + private Scoreboard scoreboard; @Setter private Difficulty difficulty = Difficulty.EASY; - private boolean showCoordinates = true; - - private Scoreboard scoreboard; - private final ScoreboardUpdater scoreboardUpdater; public WorldCache(GeyserSession session) { this.session = session; this.scoreboard = new Scoreboard(session); - scoreboardUpdater = new ScoreboardUpdater(this); - scoreboardUpdater.start(); + scoreboardSession = new ScoreboardSession(session); } public void removeScoreboard() { if (scoreboard != null) { - for (Objective objective : scoreboard.getObjectives().values()) { - scoreboard.despawnObjective(objective); - } + scoreboard.removeScoreboard(); scoreboard = new Scoreboard(session); } } public int increaseAndGetScoreboardPacketsPerSecond() { - int pendingPps = scoreboardUpdater.incrementAndGetPacketsPerSecond(); - int pps = scoreboardUpdater.getPacketsPerSecond(); + int pendingPps = scoreboardSession.getPendingPacketsPerSecond().incrementAndGet(); + int pps = scoreboardSession.getPacketsPerSecond(); return Math.max(pps, pendingPps); } - - /** - * Tell the client to hide or show the coordinates - * - * @param value True to show, false to hide - */ - public void setShowCoordinates(boolean value) { - showCoordinates = value; - session.sendGameRule("showcoordinates", value); - } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java new file mode 100644 index 000000000..90f19eb24 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.skin; + +import com.github.steveice10.mc.auth.data.GameProfile; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.nukkitx.protocol.bedrock.data.skin.ImageData; +import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin; +import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; +import com.nukkitx.protocol.bedrock.packet.PlayerSkinPacket; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.entity.type.LivingEntity; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.GeyserLocale; + +import javax.annotation.Nonnull; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.Collections; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +/** + * Responsible for modifying a player's skin when wearing a player head + */ +public class FakeHeadProvider { + private static final LoadingCache MERGED_SKINS_LOADING_CACHE = CacheBuilder.newBuilder() + .expireAfterAccess(1, TimeUnit.HOURS) + .maximumSize(10000) + .build(new CacheLoader<>() { + @Override + public SkinProvider.SkinData load(@Nonnull FakeHeadEntry fakeHeadEntry) throws Exception { + SkinProvider.SkinData skinData = SkinProvider.getOrDefault(SkinProvider.requestSkinData(fakeHeadEntry.getEntity()), null, 5); + + if (skinData == null) { + throw new Exception("Couldn't load player's original skin"); + } + + SkinProvider.Skin skin = skinData.skin(); + SkinProvider.Cape cape = skinData.cape(); + SkinProvider.SkinGeometry geometry = skinData.geometry().getGeometryName().equals("{\"geometry\" :{\"default\" :\"geometry.humanoid.customSlim\"}}") + ? SkinProvider.WEARING_CUSTOM_SKULL_SLIM : SkinProvider.WEARING_CUSTOM_SKULL; + + SkinProvider.Skin headSkin = SkinProvider.getOrDefault( + SkinProvider.requestSkin(fakeHeadEntry.getEntity().getUuid(), fakeHeadEntry.getFakeHeadSkinUrl(), false), SkinProvider.EMPTY_SKIN, 5); + BufferedImage originalSkinImage = SkinProvider.imageDataToBufferedImage(skin.getSkinData(), 64, skin.getSkinData().length / 4 / 64); + BufferedImage headSkinImage = SkinProvider.imageDataToBufferedImage(headSkin.getSkinData(), 64, headSkin.getSkinData().length / 4 / 64); + + Graphics2D graphics2D = originalSkinImage.createGraphics(); + graphics2D.setComposite(AlphaComposite.Clear); + graphics2D.fillRect(0, 0, 64, 16); + graphics2D.setComposite(AlphaComposite.SrcOver); + graphics2D.drawImage(headSkinImage, 0, 0, 64, 16, 0, 0, 64, 16, null); + graphics2D.dispose(); + + // Make the skin key a combination of the current skin data and the new skin data + // Don't tie it to a player - that player *can* change skins in-game + String skinKey = "customPlayerHead_" + fakeHeadEntry.getFakeHeadSkinUrl() + "_" + skin.getTextureUrl(); + byte[] targetSkinData = SkinProvider.bufferedImageToImageData(originalSkinImage); + SkinProvider.Skin mergedSkin = new SkinProvider.Skin(fakeHeadEntry.getEntity().getUuid(), skinKey, targetSkinData, System.currentTimeMillis(), false, false); + + // Avoiding memory leak + fakeHeadEntry.setEntity(null); + + return new SkinProvider.SkinData(mergedSkin, cape, geometry); + } + }); + + public static void setHead(GeyserSession session, PlayerEntity entity, CompoundTag profileTag) { + SkinManager.GameProfileData gameProfileData = SkinManager.GameProfileData.from(profileTag); + if (gameProfileData == null) { + return; + } + String fakeHeadSkinUrl = gameProfileData.skinUrl(); + + session.getPlayerWithCustomHeads().add(entity.getUuid()); + + GameProfile.Property texturesProperty = entity.getProfile().getProperty("textures"); + + SkinProvider.EXECUTOR_SERVICE.execute(() -> { + try { + SkinProvider.SkinData mergedSkinData = MERGED_SKINS_LOADING_CACHE.get(new FakeHeadEntry(texturesProperty, fakeHeadSkinUrl, entity)); + + sendSkinPacket(session, entity, mergedSkinData); + } catch (ExecutionException e) { + GeyserImpl.getInstance().getLogger().error("Couldn't merge skin of " + entity.getUsername() + " with head skin url " + fakeHeadSkinUrl, e); + } + }); + } + + public static void restoreOriginalSkin(GeyserSession session, LivingEntity livingEntity) { + if (!(livingEntity instanceof PlayerEntity entity)) { + return; + } + + if (!session.getPlayerWithCustomHeads().remove(entity.getUuid())) { + return; + } + + SkinProvider.requestSkinData(entity).whenCompleteAsync((skinData, throwable) -> { + if (throwable != null) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), throwable); + return; + } + + sendSkinPacket(session, entity, skinData); + }); + } + + private static void sendSkinPacket(GeyserSession session, PlayerEntity entity, SkinProvider.SkinData skinData) { + SkinProvider.Skin skin = skinData.skin(); + SkinProvider.Cape cape = skinData.cape(); + SkinProvider.SkinGeometry geometry = skinData.geometry(); + + if (entity.getUuid().equals(session.getPlayerEntity().getUuid())) { + PlayerListPacket.Entry updatedEntry = SkinManager.buildEntryManually( + session, + entity.getUuid(), + entity.getUsername(), + entity.getGeyserId(), + skin.getTextureUrl(), + skin.getSkinData(), + cape.getCapeId(), + cape.getCapeData(), + geometry + ); + + PlayerListPacket playerAddPacket = new PlayerListPacket(); + playerAddPacket.setAction(PlayerListPacket.Action.ADD); + playerAddPacket.getEntries().add(updatedEntry); + session.sendUpstreamPacket(playerAddPacket); + } else { + PlayerSkinPacket packet = new PlayerSkinPacket(); + packet.setUuid(entity.getUuid()); + packet.setOldSkinName(""); + packet.setNewSkinName(skin.getTextureUrl()); + packet.setSkin(getSkin(skin.getTextureUrl(), skin, cape, geometry)); + packet.setTrustedSkin(true); + session.sendUpstreamPacket(packet); + } + } + + private static SerializedSkin getSkin(String skinId, SkinProvider.Skin skin, SkinProvider.Cape cape, SkinProvider.SkinGeometry geometry) { + return SerializedSkin.of(skinId, "", geometry.getGeometryName(), + ImageData.of(skin.getSkinData()), Collections.emptyList(), + ImageData.of(cape.getCapeData()), geometry.getGeometryData(), + "", true, false, false, cape.getCapeId(), skinId); + } + + @AllArgsConstructor + @Getter + @Setter + private static class FakeHeadEntry { + private final GameProfile.Property texturesProperty; + private final String fakeHeadSkinUrl; + private PlayerEntity entity; + + @Override + public boolean equals(Object o) { + // We don't care about the equality of the entity as that is not used for caching purposes + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FakeHeadEntry that = (FakeHeadEntry) o; + return equals(texturesProperty, that.texturesProperty) && Objects.equals(fakeHeadSkinUrl, that.fakeHeadSkinUrl); + } + + private boolean equals(GameProfile.Property a, GameProfile.Property b) { + //TODO actually fix this in MCAuthLib + if (a == b) { + return true; + } + if (a == null || b == null) { + return false; + } + return Objects.equals(a.getName(), b.getName()) && Objects.equals(a.getValue(), b.getValue()) && Objects.equals(a.getSignature(), b.getSignature()); + } + + @Override + public int hashCode() { + return Objects.hash(texturesProperty, fakeHeadSkinUrl); + } + } + +} diff --git a/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java b/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java new file mode 100644 index 000000000..5beeed9b1 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.skin; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Getter; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.GeyserLogger; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.Constants; +import org.geysermc.geyser.util.PluginMessageUtils; +import org.geysermc.floodgate.util.WebsocketEventType; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ServerHandshake; + +import javax.net.ssl.SSLException; +import java.net.ConnectException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; + +import static org.geysermc.geyser.util.PluginMessageUtils.getSkinChannel; + +public final class FloodgateSkinUploader { + private final ObjectMapper JACKSON = new ObjectMapper(); + private final List skinQueue = new ArrayList<>(); + + private final GeyserLogger logger; + private final WebSocketClient client; + private volatile boolean closed; + + @Getter private int id; + @Getter private String verifyCode; + @Getter private int subscribersCount; + + public FloodgateSkinUploader(GeyserImpl geyser) { + this.logger = geyser.getLogger(); + this.client = new WebSocketClient(Constants.GLOBAL_API_WS_URI) { + @Override + public void onOpen(ServerHandshake handshake) { + setConnectionLostTimeout(11); + + Iterator queueIterator = skinQueue.iterator(); + while (isOpen() && queueIterator.hasNext()) { + send(queueIterator.next()); + queueIterator.remove(); + } + } + + @Override + public void onMessage(String message) { + // The reason why I don't like Jackson + try { + JsonNode node = JACKSON.readTree(message); + if (node.has("error")) { + logger.error("Got an error: " + node.get("error").asText()); + return; + } + + int typeId = node.get("event_id").asInt(); + WebsocketEventType type = WebsocketEventType.fromId(typeId); + if (type == null) { + logger.warning(String.format( + "Got (unknown) type %s. Ensure that Geyser is on the latest version and report this issue!", + typeId)); + return; + } + + switch (type) { + case SUBSCRIBER_CREATED: + id = node.get("id").asInt(); + verifyCode = node.get("verify_code").asText(); + break; + case SUBSCRIBER_COUNT: + subscribersCount = node.get("subscribers_count").asInt(); + break; + case SKIN_UPLOADED: + // if Geyser is the only subscriber we have send it to the server manually + // otherwise it's handled by the Floodgate plugin subscribers + if (subscribersCount != 1) { + break; + } + + String xuid = node.get("xuid").asText(); + GeyserSession session = geyser.connectionByXuid(xuid); + + if (session != null) { + if (!node.get("success").asBoolean()) { + logger.info("Failed to upload skin for " + session.name()); + return; + } + + JsonNode data = node.get("data"); + + String value = data.get("value").asText(); + String signature = data.get("signature").asText(); + + byte[] bytes = (value + '\0' + signature) + .getBytes(StandardCharsets.UTF_8); + PluginMessageUtils.sendMessage(session, getSkinChannel(), bytes); + } + break; + case LOG_MESSAGE: + String logMessage = node.get("message").asText(); + switch (node.get("priority").asInt()) { + case -1 -> logger.debug("Got a message from skin uploader: " + logMessage); + case 0 -> logger.info("Got a message from skin uploader: " + logMessage); + case 1 -> logger.error("Got a message from skin uploader: " + logMessage); + default -> logger.info(logMessage); + } + break; + case NEWS_ADDED: + //todo + } + } catch (Exception e) { + logger.error("Error while receiving a message", e); + } + } + + @Override + public void onClose(int code, String reason, boolean remote) { + if (reason != null && !reason.isEmpty()) { + // The reason why I don't like Jackson + try { + JsonNode node = JACKSON.readTree(reason); + // info means that the uploader itself did nothing wrong + if (node.has("info")) { + String info = node.get("info").asText(); + logger.debug("Got disconnected from the skin uploader: " + info); + } + // error means that the uploader did something wrong + if (node.has("error")) { + String error = node.get("error").asText(); + logger.info("Got disconnected from the skin uploader: " + error); + } + } catch (JsonProcessingException ignored) { + // ignore invalid json + } catch (Exception e) { + logger.error("Error while handling onClose", e); + } + } + // try to reconnect (which will make a new id and verify token) after a few seconds + reconnectLater(geyser); + } + + @Override + public void onError(Exception ex) { + if (ex instanceof ConnectException || ex instanceof SSLException) { + if (logger.isDebug()) { + logger.error("[debug] Got an error", ex); + } + return; + } + logger.error("Got an error", ex); + } + }; + } + + public void uploadSkin(JsonNode chainData, String clientData) { + if (chainData == null || !chainData.isArray() || clientData == null) { + return; + } + + ObjectNode node = JACKSON.createObjectNode(); + node.set("chain_data", chainData); + node.put("client_data", clientData); + + // The reason why I don't like Jackson + String jsonString; + try { + jsonString = JACKSON.writeValueAsString(node); + } catch (Exception e) { + logger.error("Failed to upload skin", e); + return; + } + + if (client.isOpen()) { + client.send(jsonString); + return; + } + skinQueue.add(jsonString); + } + + private void reconnectLater(GeyserImpl geyser) { + // we ca only reconnect when the thread pool is open + if (geyser.getScheduledThread().isShutdown() || closed) { + logger.info("The skin uploader has been closed"); + return; + } + + long additionalTime = ThreadLocalRandom.current().nextInt(7); + // we don't have to check the result. onClose will handle that for us + geyser.getScheduledThread() + .schedule(client::reconnect, 8 + additionalTime, TimeUnit.SECONDS); + } + + public FloodgateSkinUploader start() { + client.connect(); + return this; + } + + public void close() { + if (!closed) { + closed = true; + client.close(); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/ProvidedSkin.java b/core/src/main/java/org/geysermc/geyser/skin/ProvidedSkin.java similarity index 64% rename from connector/src/main/java/org/geysermc/connector/utils/ProvidedSkin.java rename to core/src/main/java/org/geysermc/geyser/skin/ProvidedSkin.java index 2c0165d38..e51882036 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ProvidedSkin.java +++ b/core/src/main/java/org/geysermc/geyser/skin/ProvidedSkin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,40 +23,39 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.utils; +package org.geysermc.geyser.skin; import lombok.Getter; +import org.geysermc.geyser.GeyserImpl; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; public class ProvidedSkin { @Getter private byte[] skin; public ProvidedSkin(String internalUrl) { try { - BufferedImage image = ImageIO.read(ProvidedSkin.class.getClassLoader().getResource(internalUrl)); + BufferedImage image; + try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource(internalUrl)) { + image = ImageIO.read(stream); + } ByteArrayOutputStream outputStream = new ByteArrayOutputStream(image.getWidth() * 4 + image.getHeight() * 4); - try { - for (int y = 0; y < image.getHeight(); y++) { - for (int x = 0; x < image.getWidth(); x++) { - int rgba = image.getRGB(x, y); - outputStream.write((rgba >> 16) & 0xFF); // Red - outputStream.write((rgba >> 8) & 0xFF); // Green - outputStream.write(rgba & 0xFF); // Blue - outputStream.write((rgba >> 24) & 0xFF); // Alpha - } + for (int y = 0; y < image.getHeight(); y++) { + for (int x = 0; x < image.getWidth(); x++) { + int rgba = image.getRGB(x, y); + outputStream.write((rgba >> 16) & 0xFF); // Red + outputStream.write((rgba >> 8) & 0xFF); // Green + outputStream.write(rgba & 0xFF); // Blue + outputStream.write((rgba >> 24) & 0xFF); // Alpha } - image.flush(); - skin = outputStream.toByteArray(); - } finally { - try { - outputStream.close(); - } catch (IOException ignored) {} } + image.flush(); + skin = outputStream.toByteArray(); } catch (IOException e) { e.printStackTrace(); } diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java new file mode 100644 index 000000000..16833d634 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java @@ -0,0 +1,300 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.skin; + +import com.fasterxml.jackson.databind.JsonNode; +import com.github.steveice10.mc.auth.data.GameProfile; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.nukkitx.protocol.bedrock.data.skin.ImageData; +import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin; +import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.auth.BedrockClientData; +import org.geysermc.geyser.text.GeyserLocale; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Collections; +import java.util.UUID; +import java.util.function.Consumer; + +public class SkinManager { + + /** + * Builds a Bedrock player list entry from our existing, cached Bedrock skin information + */ + public static PlayerListPacket.Entry buildCachedEntry(GeyserSession session, PlayerEntity playerEntity) { + GameProfileData data = GameProfileData.from(playerEntity.getProfile()); + SkinProvider.Cape cape = SkinProvider.getCachedCape(data.capeUrl()); + SkinProvider.SkinGeometry geometry = SkinProvider.SkinGeometry.getLegacy(data.isAlex()); + + SkinProvider.Skin skin = SkinProvider.getCachedSkin(data.skinUrl()); + if (skin == null) { + skin = SkinProvider.EMPTY_SKIN; + } + + return buildEntryManually( + session, + playerEntity.getProfile().getId(), + playerEntity.getProfile().getName(), + playerEntity.getGeyserId(), + skin.getTextureUrl(), + skin.getSkinData(), + cape.getCapeId(), + cape.getCapeData(), + geometry + ); + } + + /** + * With all the information needed, build a Bedrock player entry with translated skin information. + */ + public static PlayerListPacket.Entry buildEntryManually(GeyserSession session, UUID uuid, String username, long geyserId, + String skinId, byte[] skinData, + String capeId, byte[] capeData, + SkinProvider.SkinGeometry geometry) { + SerializedSkin serializedSkin = SerializedSkin.of( + skinId, "", geometry.getGeometryName(), ImageData.of(skinData), Collections.emptyList(), + ImageData.of(capeData), geometry.getGeometryData(), "", true, false, + !capeId.equals(SkinProvider.EMPTY_CAPE.getCapeId()), capeId, skinId + ); + + // This attempts to find the XUID of the player so profile images show up for Xbox accounts + String xuid = ""; + GeyserSession playerSession = GeyserImpl.getInstance().connectionByUuid(uuid); + + if (playerSession != null) { + xuid = playerSession.getAuthData().xuid(); + } + + PlayerListPacket.Entry entry; + + // If we are building a PlayerListEntry for our own session we use our AuthData UUID instead of the Java UUID + // as Bedrock expects to get back its own provided UUID + if (session.getPlayerEntity().getUuid().equals(uuid)) { + entry = new PlayerListPacket.Entry(session.getAuthData().uuid()); + } else { + entry = new PlayerListPacket.Entry(uuid); + } + + entry.setName(username); + entry.setEntityId(geyserId); + entry.setSkin(serializedSkin); + entry.setXuid(xuid); + entry.setPlatformChatId(""); + entry.setTeacher(false); + entry.setTrustedSkin(true); + return entry; + } + + public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSession session, + Consumer skinAndCapeConsumer) { + SkinProvider.requestSkinData(entity).whenCompleteAsync((skinData, throwable) -> { + if (skinData == null) { + if (skinAndCapeConsumer != null) { + skinAndCapeConsumer.accept(null); + } + + return; + } + + if (skinData.geometry() != null) { + SkinProvider.Skin skin = skinData.skin(); + SkinProvider.Cape cape = skinData.cape(); + SkinProvider.SkinGeometry geometry = skinData.geometry(); + + PlayerListPacket.Entry updatedEntry = buildEntryManually( + session, + entity.getUuid(), + entity.getUsername(), + entity.getGeyserId(), + skin.getTextureUrl(), + skin.getSkinData(), + cape.getCapeId(), + cape.getCapeData(), + geometry + ); + + + PlayerListPacket playerAddPacket = new PlayerListPacket(); + playerAddPacket.setAction(PlayerListPacket.Action.ADD); + playerAddPacket.getEntries().add(updatedEntry); + session.sendUpstreamPacket(playerAddPacket); + + if (!entity.isPlayerList()) { + PlayerListPacket playerRemovePacket = new PlayerListPacket(); + playerRemovePacket.setAction(PlayerListPacket.Action.REMOVE); + playerRemovePacket.getEntries().add(updatedEntry); + session.sendUpstreamPacket(playerRemovePacket); + } + } + + if (skinAndCapeConsumer != null) { + skinAndCapeConsumer.accept(new SkinProvider.SkinAndCape(skinData.skin(), skinData.cape())); + } + }); + } + + public static void handleBedrockSkin(PlayerEntity playerEntity, BedrockClientData clientData) { + GeyserImpl geyser = GeyserImpl.getInstance(); + if (geyser.getConfig().isDebugMode()) { + geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.skin.bedrock.register", playerEntity.getUsername(), playerEntity.getUuid())); + } + + try { + byte[] skinBytes = Base64.getDecoder().decode(clientData.getSkinData().getBytes(StandardCharsets.UTF_8)); + byte[] capeBytes = clientData.getCapeData(); + + byte[] geometryNameBytes = Base64.getDecoder().decode(clientData.getGeometryName().getBytes(StandardCharsets.UTF_8)); + byte[] geometryBytes = Base64.getDecoder().decode(clientData.getGeometryData().getBytes(StandardCharsets.UTF_8)); + + if (skinBytes.length <= (128 * 128 * 4) && !clientData.isPersonaSkin()) { + SkinProvider.storeBedrockSkin(playerEntity.getUuid(), clientData.getSkinId(), skinBytes); + SkinProvider.storeBedrockGeometry(playerEntity.getUuid(), geometryNameBytes, geometryBytes); + } else if (geyser.getConfig().isDebugMode()) { + geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.skin.bedrock.fail", playerEntity.getUsername())); + geyser.getLogger().debug("The size of '" + playerEntity.getUsername() + "' skin is: " + clientData.getSkinImageWidth() + "x" + clientData.getSkinImageHeight()); + } + + if (!clientData.getCapeId().equals("")) { + SkinProvider.storeBedrockCape(playerEntity.getUuid(), capeBytes); + } + } catch (Exception e) { + throw new AssertionError("Failed to cache skin for bedrock user (" + playerEntity.getUsername() + "): ", e); + } + } + + public record GameProfileData(String skinUrl, String capeUrl, boolean isAlex) { + /** + * Generate the GameProfileData from the given CompoundTag representing a GameProfile + * + * @param tag tag to build the GameProfileData from + * @return The built GameProfileData, or null if this wasn't a valid tag + */ + public static @Nullable GameProfileData from(CompoundTag tag) { + if (!(tag.get("Properties") instanceof CompoundTag propertiesTag)) { + return null; + } + if (!(propertiesTag.get("textures") instanceof ListTag texturesTag) || texturesTag.size() == 0) { + return null; + } + if (!(texturesTag.get(0) instanceof CompoundTag texturesData)) { + return null; + } + if (!(texturesData.get("Value") instanceof StringTag skinDataValue)) { + return null; + } + + try { + return loadFromJson(skinDataValue.getValue()); + } catch (IOException e) { + GeyserImpl.getInstance().getLogger().debug("Something went wrong while processing skin for tag " + tag); + if (GeyserImpl.getInstance().getConfig().isDebugMode()) { + e.printStackTrace(); + } + return null; + } + } + + /** + * Generate the GameProfileData from the given GameProfile + * + * @param profile GameProfile to build the GameProfileData from + * @return The built GameProfileData + */ + public static GameProfileData from(GameProfile profile) { + try { + GameProfile.Property skinProperty = profile.getProperty("textures"); + + if (skinProperty == null) { + // Likely offline mode + return loadBedrockOrOfflineSkin(profile); + } + GameProfileData data = loadFromJson(skinProperty.getValue()); + if (data != null) { + return data; + } else { + return loadBedrockOrOfflineSkin(profile); + } + } catch (IOException exception) { + GeyserImpl.getInstance().getLogger().debug("Something went wrong while processing skin for " + profile.getName()); + if (GeyserImpl.getInstance().getConfig().isDebugMode()) { + exception.printStackTrace(); + } + return loadBedrockOrOfflineSkin(profile); + } + } + + private static GameProfileData loadFromJson(String encodedJson) throws IOException { + JsonNode skinObject = GeyserImpl.JSON_MAPPER.readTree(new String(Base64.getDecoder().decode(encodedJson), StandardCharsets.UTF_8)); + JsonNode textures = skinObject.get("textures"); + + if (textures != null) { + JsonNode skinTexture = textures.get("SKIN"); + String skinUrl = skinTexture.get("url").asText().replace("http://", "https://"); + + boolean isAlex = skinTexture.has("metadata"); + + String capeUrl = null; + JsonNode capeTexture = textures.get("CAPE"); + if (capeTexture != null) { + capeUrl = capeTexture.get("url").asText().replace("http://", "https://"); + } + + return new GameProfileData(skinUrl, capeUrl, isAlex); + } + return null; + } + + /** + * @return default skin with default cape when texture data is invalid, or the Bedrock player's skin if this + * is a Bedrock player. + */ + private static GameProfileData loadBedrockOrOfflineSkin(GameProfile profile) { + // Fallback to the offline mode of working it out + boolean isAlex = (Math.abs(profile.getId().hashCode() % 2) == 1); + + String skinUrl = isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl(); + String capeUrl = SkinProvider.EMPTY_CAPE.getTextureUrl(); + if (("steve".equals(skinUrl) || "alex".equals(skinUrl)) && GeyserImpl.getInstance().getConfig().getRemote().getAuthType() != AuthType.ONLINE) { + GeyserSession session = GeyserImpl.getInstance().connectionByUuid(profile.getId()); + + if (session != null) { + skinUrl = session.getClientData().getSkinId(); + capeUrl = session.getClientData().getCapeId(); + } + } + return new GameProfileData(skinUrl, capeUrl, isAlex); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java similarity index 61% rename from connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java rename to core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index d848d95ed..91c555f3d 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,38 +23,49 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.utils; +package org.geysermc.geyser.skin; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.steveice10.mc.auth.data.GameProfile; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.IntArrayTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.util.FileUtils; +import org.geysermc.geyser.util.WebUtils; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; -import java.io.*; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.List; import java.util.*; import java.util.concurrent.*; public class SkinProvider { - public static final boolean ALLOW_THIRD_PARTY_CAPES = GeyserConnector.getInstance().getConfig().isAllowThirdPartyCapes(); - private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(ALLOW_THIRD_PARTY_CAPES ? 21 : 14); + public static final boolean ALLOW_THIRD_PARTY_CAPES = GeyserImpl.getInstance().getConfig().isAllowThirdPartyCapes(); + static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(ALLOW_THIRD_PARTY_CAPES ? 21 : 14); public static final byte[] STEVE_SKIN = new ProvidedSkin("bedrock/skin/skin_steve.png").getSkin(); public static final Skin EMPTY_SKIN = new Skin(-1, "steve", STEVE_SKIN); public static final byte[] ALEX_SKIN = new ProvidedSkin("bedrock/skin/skin_alex.png").getSkin(); public static final Skin EMPTY_SKIN_ALEX = new Skin(-1, "alex", ALEX_SKIN); - private static final Map permanentSkins = new HashMap() {{ + private static final Map permanentSkins = new HashMap<>() {{ put("steve", EMPTY_SKIN); put("alex", EMPTY_SKIN_ALEX); }}; @@ -70,57 +81,44 @@ public class SkinProvider { .build(); private static final Map> requestedCapes = new ConcurrentHashMap<>(); - public static final SkinGeometry EMPTY_GEOMETRY = SkinProvider.SkinGeometry.getLegacy(false); private static final Map cachedGeometry = new ConcurrentHashMap<>(); - public static final boolean ALLOW_THIRD_PARTY_EARS = GeyserConnector.getInstance().getConfig().isAllowThirdPartyEars(); - public static String EARS_GEOMETRY; - public static String EARS_GEOMETRY_SLIM; + public static final boolean ALLOW_THIRD_PARTY_EARS = GeyserImpl.getInstance().getConfig().isAllowThirdPartyEars(); + public static final String EARS_GEOMETRY; + public static final String EARS_GEOMETRY_SLIM; + public static final SkinGeometry SKULL_GEOMETRY; + public static final SkinGeometry WEARING_CUSTOM_SKULL; + public static final SkinGeometry WEARING_CUSTOM_SKULL_SLIM; - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); static { /* Load in the normal ears geometry */ - InputStream earsStream = FileUtils.getResource("bedrock/skin/geometry.humanoid.ears.json"); - - StringBuilder earsDataBuilder = new StringBuilder(); - try (Reader reader = new BufferedReader(new InputStreamReader(earsStream, Charset.forName(StandardCharsets.UTF_8.name())))) { - int c = 0; - while ((c = reader.read()) != -1) { - earsDataBuilder.append((char) c); - } - } catch (IOException e) { - throw new AssertionError("Unable to load ears geometry", e); - } - - EARS_GEOMETRY = earsDataBuilder.toString(); - + EARS_GEOMETRY = new String(FileUtils.readAllBytes("bedrock/skin/geometry.humanoid.ears.json"), StandardCharsets.UTF_8); /* Load in the slim ears geometry */ - earsStream = FileUtils.getResource("bedrock/skin/geometry.humanoid.earsSlim.json"); + EARS_GEOMETRY_SLIM = new String(FileUtils.readAllBytes("bedrock/skin/geometry.humanoid.earsSlim.json"), StandardCharsets.UTF_8); - earsDataBuilder = new StringBuilder(); - try (Reader reader = new BufferedReader(new InputStreamReader(earsStream, Charset.forName(StandardCharsets.UTF_8.name())))) { - int c = 0; - while ((c = reader.read()) != -1) { - earsDataBuilder.append((char) c); - } - } catch (IOException e) { - throw new AssertionError("Unable to load ears geometry", e); - } + /* Load in the custom skull geometry */ + String skullData = new String(FileUtils.readAllBytes("bedrock/skin/geometry.humanoid.customskull.json"), StandardCharsets.UTF_8); + SKULL_GEOMETRY = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.customskull\"}}", skullData, false); - EARS_GEOMETRY_SLIM = earsDataBuilder.toString(); + /* Load in the player head skull geometry */ + String wearingCustomSkull = new String(FileUtils.readAllBytes("bedrock/skin/geometry.humanoid.wearingCustomSkull.json"), StandardCharsets.UTF_8); + WEARING_CUSTOM_SKULL = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.wearingCustomSkull\"}}", wearingCustomSkull, false); + String wearingCustomSkullSlim = new String(FileUtils.readAllBytes("bedrock/skin/geometry.humanoid.wearingCustomSkullSlim.json"), StandardCharsets.UTF_8); + WEARING_CUSTOM_SKULL_SLIM = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.wearingCustomSkullSlim\"}}", wearingCustomSkullSlim, false); // Schedule Daily Image Expiry if we are caching them - if (GeyserConnector.getInstance().getConfig().getCacheImages() > 0) { - GeyserConnector.getInstance().getGeneralThreadPool().scheduleAtFixedRate(() -> { - File cacheFolder = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("images").toFile(); + if (GeyserImpl.getInstance().getConfig().getCacheImages() > 0) { + GeyserImpl.getInstance().getScheduledThread().scheduleAtFixedRate(() -> { + File cacheFolder = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("images").toFile(); if (!cacheFolder.exists()) { return; } int count = 0; - final long expireTime = ((long)GeyserConnector.getInstance().getConfig().getCacheImages()) * ((long)1000 * 60 * 60 * 24); + final long expireTime = ((long) GeyserImpl.getInstance().getConfig().getCacheImages()) * ((long)1000 * 60 * 60 * 24); for (File imageFile : Objects.requireNonNull(cacheFolder.listFiles())) { if (imageFile.lastModified() < System.currentTimeMillis() - expireTime) { //noinspection ResultOfMethodCallIgnored @@ -130,7 +128,7 @@ public class SkinProvider { } if (count > 0) { - GeyserConnector.getInstance().getLogger().debug(String.format("Removed %d cached image files as they have expired", count)); + GeyserImpl.getInstance().getLogger().debug(String.format("Removed %d cached image files as they have expired", count)); } }, 10, 1440, TimeUnit.MINUTES); } @@ -149,18 +147,79 @@ public class SkinProvider { return cape != null ? cape : EMPTY_CAPE; } + public static CompletableFuture requestSkinData(PlayerEntity entity) { + SkinManager.GameProfileData data = SkinManager.GameProfileData.from(entity.getProfile()); + + return requestSkinAndCape(entity.getUuid(), data.skinUrl(), data.capeUrl()) + .thenApplyAsync(skinAndCape -> { + try { + Skin skin = skinAndCape.getSkin(); + Cape cape = skinAndCape.getCape(); + SkinGeometry geometry = SkinGeometry.getLegacy(data.isAlex()); + + if (cape.isFailed()) { + cape = getOrDefault(requestBedrockCape(entity.getUuid()), + EMPTY_CAPE, 3); + } + + if (cape.isFailed() && ALLOW_THIRD_PARTY_CAPES) { + cape = getOrDefault(requestUnofficialCape( + cape, entity.getUuid(), + entity.getUsername(), false + ), EMPTY_CAPE, CapeProvider.VALUES.length * 3); + } + + geometry = getOrDefault(requestBedrockGeometry( + geometry, entity.getUuid() + ), geometry, 3); + + boolean isDeadmau5 = "deadmau5".equals(entity.getUsername()); + // Not a bedrock player check for ears + if (geometry.isFailed() && (ALLOW_THIRD_PARTY_EARS || isDeadmau5)) { + boolean isEars; + + // Its deadmau5, gotta support his skin :) + if (isDeadmau5) { + isEars = true; + } else { + // Get the ears texture for the player + skin = getOrDefault(requestUnofficialEars( + skin, entity.getUuid(), entity.getUsername(), false + ), skin, 3); + + isEars = skin.isEars(); + } + + // Does the skin have an ears texture + if (isEars) { + // Get the new geometry + geometry = SkinGeometry.getEars(data.isAlex()); + + // Store the skin and geometry for the ears + storeEarSkin(skin); + storeEarGeometry(entity.getUuid(), data.isAlex()); + } + } + + return new SkinData(skin, cape, geometry); + } catch (Exception e) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e); + } + + return new SkinData(skinAndCape.getSkin(), skinAndCape.getCape(), null); + }); + } + public static CompletableFuture requestSkinAndCape(UUID playerId, String skinUrl, String capeUrl) { return CompletableFuture.supplyAsync(() -> { long time = System.currentTimeMillis(); String newSkinUrl = skinUrl; if ("steve".equals(skinUrl) || "alex".equals(skinUrl)) { - // TODO: Don't have a for loop for this? Have a proper map? - for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) { - if (session.getPlayerEntity().getUuid().equals(playerId)) { - newSkinUrl = session.getClientData().getSkinId(); - break; - } + GeyserSession session = GeyserImpl.getInstance().connectionByUuid(playerId); + + if (session != null) { + newSkinUrl = session.getClientData().getSkinId(); } } @@ -170,14 +229,18 @@ public class SkinProvider { getOrDefault(requestCape(capeUrl, provider, false), EMPTY_CAPE, 5) ); - GeyserConnector.getInstance().getLogger().debug("Took " + (System.currentTimeMillis() - time) + "ms for " + playerId); + GeyserImpl.getInstance().getLogger().debug("Took " + (System.currentTimeMillis() - time) + "ms for " + playerId); return skinAndCape; }, EXECUTOR_SERVICE); } public static CompletableFuture requestSkin(UUID playerId, String textureUrl, boolean newThread) { if (textureUrl == null || textureUrl.isEmpty()) return CompletableFuture.completedFuture(EMPTY_SKIN); - if (requestedSkins.containsKey(textureUrl)) return requestedSkins.get(textureUrl); // already requested + CompletableFuture requestedSkin = requestedSkins.get(textureUrl); + if (requestedSkin != null) { + // already requested + return requestedSkin; + } Skin cachedSkin = getCachedSkin(textureUrl); if (cachedSkin != null) { @@ -205,7 +268,6 @@ public class SkinProvider { if (capeUrl == null || capeUrl.isEmpty()) return CompletableFuture.completedFuture(EMPTY_CAPE); if (requestedCapes.containsKey(capeUrl)) return requestedCapes.get(capeUrl); // already requested - boolean officialCape = provider == CapeProvider.MINECRAFT; Cape cachedCape = cachedCapes.getIfPresent(capeUrl); if (cachedCape != null) { return CompletableFuture.completedFuture(cachedCape); @@ -243,15 +305,15 @@ public class SkinProvider { return CompletableFuture.completedFuture(officialCape); } - public static CompletableFuture requestEars(String earsUrl, EarsProvider provider, boolean newThread, Skin skin) { + public static CompletableFuture requestEars(String earsUrl, boolean newThread, Skin skin) { if (earsUrl == null || earsUrl.isEmpty()) return CompletableFuture.completedFuture(skin); CompletableFuture future; if (newThread) { - future = CompletableFuture.supplyAsync(() -> supplyEars(skin, earsUrl, provider), EXECUTOR_SERVICE) + future = CompletableFuture.supplyAsync(() -> supplyEars(skin, earsUrl), EXECUTOR_SERVICE) .whenCompleteAsync((outSkin, throwable) -> { }); } else { - Skin ears = supplyEars(skin, earsUrl, provider); // blocking + Skin ears = supplyEars(skin, earsUrl); // blocking future = CompletableFuture.completedFuture(ears); } return future; @@ -269,7 +331,7 @@ public class SkinProvider { public static CompletableFuture requestUnofficialEars(Skin officialSkin, UUID playerId, String username, boolean newThread) { for (EarsProvider provider : EarsProvider.VALUES) { Skin skin1 = getOrDefault( - requestEars(provider.getUrlFor(playerId, username), provider, newThread, officialSkin), + requestEars(provider.getUrlFor(playerId, username), newThread, officialSkin), officialSkin, 4 ); if (skin1.isEars()) { @@ -280,7 +342,7 @@ public class SkinProvider { return CompletableFuture.completedFuture(officialSkin); } - public static CompletableFuture requestBedrockCape(UUID playerID, boolean newThread) { + public static CompletableFuture requestBedrockCape(UUID playerID) { Cape bedrockCape = cachedCapes.getIfPresent(playerID.toString() + ".Bedrock"); if (bedrockCape == null) { bedrockCape = EMPTY_CAPE; @@ -288,7 +350,7 @@ public class SkinProvider { return CompletableFuture.completedFuture(bedrockCape); } - public static CompletableFuture requestBedrockGeometry(SkinGeometry currentGeometry, UUID playerID, boolean newThread) { + public static CompletableFuture requestBedrockGeometry(SkinGeometry currentGeometry, UUID playerID) { SkinGeometry bedrockGeometry = cachedGeometry.getOrDefault(playerID, currentGeometry); return CompletableFuture.completedFuture(bedrockGeometry); } @@ -309,12 +371,11 @@ public class SkinProvider { } /** - * Stores the ajusted skin with the ear texture to the cache + * Stores the adjusted skin with the ear texture to the cache * - * @param playerID The UUID to cache it against * @param skin The skin to cache */ - public static void storeEarSkin(UUID playerID, Skin skin) { + public static void storeEarSkin(Skin skin) { cachedSkins.put(skin.getTextureUrl(), skin); } @@ -338,17 +399,18 @@ public class SkinProvider { } private static Cape supplyCape(String capeUrl, CapeProvider provider) { - byte[] cape = new byte[0]; + byte[] cape = EMPTY_CAPE.getCapeData(); try { cape = requestImage(capeUrl, provider); - } catch (Exception ignored) {} // just ignore I guess + } catch (Exception ignored) { + } // just ignore I guess String[] urlSection = capeUrl.split("/"); // A real url is expected at this stage return new Cape( capeUrl, urlSection[urlSection.length - 1], // get the texture id and use it as cape id - cape.length > 0 ? cape : EMPTY_CAPE.getCapeData(), + cape, System.currentTimeMillis(), cape.length == 0 ); @@ -359,10 +421,9 @@ public class SkinProvider { * * @param existingSkin The players current skin * @param earsUrl The URL to get the ears texture from - * @param provider The ears texture provider * @return The updated skin with ears */ - private static Skin supplyEars(Skin existingSkin, String earsUrl, EarsProvider provider) { + private static Skin supplyEars(Skin existingSkin, String earsUrl) { try { // Get the ears texture BufferedImage ears = ImageIO.read(new URL(earsUrl)); @@ -401,10 +462,10 @@ public class SkinProvider { BufferedImage image = null; // First see if we have a cached file. We also update the modification stamp so we know when the file was last used - File imageFile = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("images").resolve(UUID.nameUUIDFromBytes(imageUrl.getBytes()).toString() + ".png").toFile(); + File imageFile = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("images").resolve(UUID.nameUUIDFromBytes(imageUrl.getBytes()).toString() + ".png").toFile(); if (imageFile.exists()) { try { - GeyserConnector.getInstance().getLogger().debug("Reading cached image from file " + imageFile.getPath() + " for " + imageUrl); + GeyserImpl.getInstance().getLogger().debug("Reading cached image from file " + imageFile.getPath() + " for " + imageUrl); imageFile.setLastModified(System.currentTimeMillis()); image = ImageIO.read(imageFile); } catch (IOException ignored) {} @@ -413,30 +474,48 @@ public class SkinProvider { // If no image we download it if (image == null) { image = downloadImage(imageUrl, provider); - GeyserConnector.getInstance().getLogger().debug("Downloaded " + imageUrl); + GeyserImpl.getInstance().getLogger().debug("Downloaded " + imageUrl); // Write to cache if we are allowed - if (GeyserConnector.getInstance().getConfig().getCacheImages() > 0) { + if (GeyserImpl.getInstance().getConfig().getCacheImages() > 0) { imageFile.getParentFile().mkdirs(); try { ImageIO.write(image, "png", imageFile); - GeyserConnector.getInstance().getLogger().debug("Writing cached skin to file " + imageFile.getPath() + " for " + imageUrl); + GeyserImpl.getInstance().getLogger().debug("Writing cached skin to file " + imageFile.getPath() + " for " + imageUrl); } catch (IOException e) { - GeyserConnector.getInstance().getLogger().error("Failed to write cached skin to file " + imageFile.getPath() + " for " + imageUrl); + GeyserImpl.getInstance().getLogger().error("Failed to write cached skin to file " + imageFile.getPath() + " for " + imageUrl); } } } // if the requested image is a cape if (provider != null) { - while(image.getWidth() > 64) { - image = scale(image); + if (image.getWidth() > 64 || image.getHeight() > 32) { + // Prevent weirdly-scaled capes from being cut off + BufferedImage newImage = new BufferedImage(128, 64, BufferedImage.TYPE_INT_ARGB); + Graphics g = newImage.createGraphics(); + g.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null); + g.dispose(); + image.flush(); + image = scale(newImage, 64, 32); + } else if (image.getWidth() < 64 || image.getHeight() < 32) { + // Bedrock doesn't like smaller-sized capes, either. + BufferedImage newImage = new BufferedImage(64, 32, BufferedImage.TYPE_INT_ARGB); + Graphics g = newImage.createGraphics(); + g.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null); + g.dispose(); + image.flush(); + image = newImage; } - BufferedImage newImage = new BufferedImage(64, 32, BufferedImage.TYPE_INT_ARGB); - Graphics g = newImage.createGraphics(); - g.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null); - g.dispose(); - image = newImage; + } else { + // Very rarely, skins can be larger than Minecraft's default. + // Bedrock will not render anything above a width of 128. + if (image.getWidth() > 128) { + // On Height: Scale by the amount we divided width by, or simply cut down to 128 + image = scale(image, 128, image.getHeight() >= 256 ? (image.getHeight() / (image.getWidth() / 128)) : 128); + } + + // TODO remove alpha channel } byte[] data = bufferedImageToImageData(image); @@ -444,12 +523,67 @@ public class SkinProvider { return data; } + /** + * If a skull has a username but no textures, request them. + * + * @param skullOwner the CompoundTag of the skull with no textures + * @return a completable GameProfile with textures included + */ + public static CompletableFuture requestTexturesFromUsername(CompoundTag skullOwner) { + return CompletableFuture.supplyAsync(() -> { + Tag uuidTag = skullOwner.get("Id"); + String uuidToString = ""; + JsonNode node; + GameProfile gameProfile = new GameProfile(UUID.randomUUID(), ""); + boolean retrieveUuidFromInternet = !(uuidTag instanceof IntArrayTag); // also covers null check + + if (!retrieveUuidFromInternet) { + int[] uuidAsArray = ((IntArrayTag) uuidTag).getValue(); + // thank u viaversion + UUID uuid = new UUID((long) uuidAsArray[0] << 32 | ((long) uuidAsArray[1] & 0xFFFFFFFFL), + (long) uuidAsArray[2] << 32 | ((long) uuidAsArray[3] & 0xFFFFFFFFL)); + retrieveUuidFromInternet = uuid.version() != 4; + uuidToString = uuid.toString().replace("-", ""); + } + + try { + if (retrieveUuidFromInternet) { + // Offline skin, or no present UUID + node = WebUtils.getJson("https://api.mojang.com/users/profiles/minecraft/" + skullOwner.get("Name").getValue()); + JsonNode id = node.get("id"); + if (id == null) { + GeyserImpl.getInstance().getLogger().debug("No UUID found in Mojang response for " + skullOwner.get("Name").getValue()); + return null; + } + uuidToString = id.asText(); + } + + // Get textures from UUID + node = WebUtils.getJson("https://sessionserver.mojang.com/session/minecraft/profile/" + uuidToString); + List profileProperties = new ArrayList<>(); + JsonNode properties = node.get("properties"); + if (properties == null) { + GeyserImpl.getInstance().getLogger().debug("No properties found in Mojang response for " + uuidToString); + return null; + } + profileProperties.add(new GameProfile.Property("textures", node.get("properties").get(0).get("value").asText())); + gameProfile.setProperties(profileProperties); + return gameProfile; + } catch (Exception e) { + if (GeyserImpl.getInstance().getConfig().isDebugMode()) { + e.printStackTrace(); + } + return null; + } + }, EXECUTOR_SERVICE); + } + private static BufferedImage downloadImage(String imageUrl, CapeProvider provider) throws IOException { if (provider == CapeProvider.FIVEZIG) return readFiveZigCape(imageUrl); HttpURLConnection con = (HttpURLConnection) new URL(imageUrl).openConnection(); - con.setRequestProperty("User-Agent", "Geyser-" + GeyserConnector.getInstance().getPlatformType().toString() + "/" + GeyserConnector.VERSION); + con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().toString() + "/" + GeyserImpl.VERSION); BufferedImage image = ImageIO.read(con.getInputStream()); if (image == null) throw new NullPointerException(); @@ -466,12 +600,13 @@ public class SkinProvider { return null; } - private static BufferedImage scale(BufferedImage bufferedImage) { - BufferedImage resized = new BufferedImage(bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2, BufferedImage.TYPE_INT_ARGB); + public static BufferedImage scale(BufferedImage bufferedImage, int newWidth, int newHeight) { + BufferedImage resized = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = resized.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); - g2.drawImage(bufferedImage, 0, 0, bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2, null); + g2.drawImage(bufferedImage, 0, 0, newWidth, newHeight, null); g2.dispose(); + bufferedImage.flush(); return resized; } @@ -525,7 +660,6 @@ public class SkinProvider { outputStream.write((rgba >> 24) & 0xFF); } } - return outputStream.toByteArray(); } @@ -539,17 +673,20 @@ public class SkinProvider { @AllArgsConstructor @Getter public static class SkinAndCape { - private Skin skin; - private Cape cape; + private final Skin skin; + private final Cape cape; + } + + public record SkinData(Skin skin, Cape cape, SkinGeometry geometry) { } @AllArgsConstructor @Getter public static class Skin { private UUID skinOwner; - private String textureUrl; - private byte[] skinData; - private long requestedOn; + private final String textureUrl; + private final byte[] skinData; + private final long requestedOn; private boolean updated; private boolean ears; @@ -563,19 +700,19 @@ public class SkinProvider { @AllArgsConstructor @Getter public static class Cape { - private String textureUrl; - private String capeId; - private byte[] capeData; - private long requestedOn; - private boolean failed; + private final String textureUrl; + private final String capeId; + private final byte[] capeData; + private final long requestedOn; + private final boolean failed; } @AllArgsConstructor @Getter public static class SkinGeometry { - private String geometryName; - private String geometryData; - private boolean failed; + private final String geometryName; + private final String geometryData; + private final boolean failed; /** * Generate generic geometry @@ -624,11 +761,11 @@ public class SkinProvider { } public static String toRequestedType(CapeUrlType type, UUID uuid, String username) { - switch (type) { - case UUID: return uuid.toString().replace("-", ""); - case UUID_DASHED: return uuid.toString(); - default: return username; - } + return switch (type) { + case UUID -> uuid.toString().replace("-", ""); + case UUID_DASHED -> uuid.toString(); + default -> username; + }; } } @@ -660,11 +797,11 @@ public class SkinProvider { } public static String toRequestedType(CapeUrlType type, UUID uuid, String username) { - switch (type) { - case UUID: return uuid.toString().replace("-", ""); - case UUID_DASHED: return uuid.toString(); - default: return username; - } + return switch (type) { + case UUID -> uuid.toString().replace("-", ""); + case UUID_DASHED -> uuid.toString(); + default -> username; + }; } } } diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java b/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java new file mode 100644 index 000000000..100f06caf --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.skin; + +import com.nukkitx.protocol.bedrock.data.skin.ImageData; +import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin; +import com.nukkitx.protocol.bedrock.packet.PlayerSkinPacket; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.GeyserLocale; + +import java.util.Collections; +import java.util.function.Consumer; + +public class SkullSkinManager extends SkinManager { + + public static SerializedSkin buildSkullEntryManually(String skinId, byte[] skinData) { + // Prevents https://cdn.discordapp.com/attachments/613194828359925800/779458146191147008/unknown.png + skinId = skinId + "_skull"; + return SerializedSkin.of( + skinId, "", SkinProvider.SKULL_GEOMETRY.getGeometryName(), ImageData.of(skinData), Collections.emptyList(), + ImageData.of(SkinProvider.EMPTY_CAPE.getCapeData()), SkinProvider.SKULL_GEOMETRY.getGeometryData(), + "", true, false, false, SkinProvider.EMPTY_CAPE.getCapeId(), skinId + ); + } + + public static void requestAndHandleSkin(PlayerEntity entity, GeyserSession session, + Consumer skinConsumer) { + GameProfileData data = GameProfileData.from(entity.getProfile()); + + SkinProvider.requestSkin(entity.getUuid(), data.skinUrl(), true) + .whenCompleteAsync((skin, throwable) -> { + try { + PlayerSkinPacket packet = new PlayerSkinPacket(); + packet.setUuid(entity.getUuid()); + packet.setOldSkinName(""); + packet.setNewSkinName(skin.getTextureUrl()); + packet.setSkin(buildSkullEntryManually(skin.getTextureUrl(), skin.getSkinData())); + packet.setTrustedSkin(true); + session.sendUpstreamPacket(packet); + } catch (Exception e) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e); + } + + if (skinConsumer != null) { + skinConsumer.accept(skin); + } + }); + } + +} diff --git a/connector/src/main/java/org/geysermc/connector/common/serializer/AsteriskSerializer.java b/core/src/main/java/org/geysermc/geyser/text/AsteriskSerializer.java similarity index 75% rename from connector/src/main/java/org/geysermc/connector/common/serializer/AsteriskSerializer.java rename to core/src/main/java/org/geysermc/geyser/text/AsteriskSerializer.java index d91034bd6..9ff55d61b 100644 --- a/connector/src/main/java/org/geysermc/connector/common/serializer/AsteriskSerializer.java +++ b/core/src/main/java/org/geysermc/geyser/text/AsteriskSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.common.serializer; +package org.geysermc.geyser.text; import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import com.fasterxml.jackson.core.JsonGenerator; @@ -51,20 +51,25 @@ public class AsteriskSerializer extends StdSerializer implements Context @JsonSerialize(using = AsteriskSerializer.class) public @interface Asterisk { String value() default "***"; - boolean sensitive() default false; + /** + * If true, this value will be shown if {@link #showSensitive} is true, or if the IP is determined to not be a public IP + * + * @return true if this should be analyzed and treated as an IP + */ + boolean isIp() default false; } String asterisk; - boolean sensitive; + boolean isIp; public AsteriskSerializer() { super(Object.class); } - public AsteriskSerializer(String asterisk, boolean sensitive) { + public AsteriskSerializer(String asterisk, boolean isIp) { super(Object.class); this.asterisk = asterisk; - this.sensitive = sensitive; + this.isIp = isIp; } @Override @@ -72,16 +77,25 @@ public class AsteriskSerializer extends StdSerializer implements Context Optional anno = Optional.ofNullable(property) .map(prop -> prop.getAnnotation(Asterisk.class)); - return new AsteriskSerializer(anno.map(Asterisk::value).orElse(null), anno.map(Asterisk::sensitive).orElse(null)); + return new AsteriskSerializer(anno.map(Asterisk::value).orElse(null), anno.map(Asterisk::isIp).orElse(null)); } @Override public void serialize(Object obj, JsonGenerator gen, SerializerProvider prov) throws IOException { - if (sensitive && showSensitive) { + if (isIp && (showSensitive || !isSensitiveIp((String) obj))) { gen.writeObject(obj); return; } gen.writeString(asterisk); } + + private boolean isSensitiveIp(String ip) { + if (ip.equalsIgnoreCase("localhost") || ip.equalsIgnoreCase("auto")) { + // `auto` should not be shown unless there is an obscure issue with setting the localhost address + return false; + } + + return !ip.isEmpty() && !ip.equals("0.0.0.0") && !ip.equals("127.0.0.1"); + } } diff --git a/connector/src/main/java/org/geysermc/connector/common/ChatColor.java b/core/src/main/java/org/geysermc/geyser/text/ChatColor.java similarity index 97% rename from connector/src/main/java/org/geysermc/connector/common/ChatColor.java rename to core/src/main/java/org/geysermc/geyser/text/ChatColor.java index 0728ecc7c..29901d857 100644 --- a/connector/src/main/java/org/geysermc/connector/common/ChatColor.java +++ b/core/src/main/java/org/geysermc/geyser/text/ChatColor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.common; +package org.geysermc.geyser.text; public class ChatColor { diff --git a/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java b/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java new file mode 100644 index 000000000..8fc98402a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.text; + +import org.geysermc.geyser.GeyserBootstrap; +import org.geysermc.geyser.GeyserImpl; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; + +public class GeyserLocale { + + /** + * If we determine the default locale that the user wishes to use, use that locale + */ + private static String DEFAULT_LOCALE; + /** + * Whether the system locale cannot be loaded by Geyser. + */ + private static boolean SYSTEM_LOCALE_INVALID; + + private static final Map LOCALE_MAPPINGS = new HashMap<>(); + + /** + * Loads the initial locale(s) with the help of the bootstrap. + */ + public static void init(GeyserBootstrap bootstrap) { + String defaultLocale = formatLocale(Locale.getDefault().getLanguage() + "_" + Locale.getDefault().getCountry()); + String loadedLocale = loadGeyserLocale(defaultLocale, bootstrap); + if (loadedLocale != null) { + DEFAULT_LOCALE = loadedLocale; + // Load English as a backup in case something goes really wrong + if (!"en_US".equals(loadedLocale)) { + loadGeyserLocale("en_US", bootstrap); + } + SYSTEM_LOCALE_INVALID = false; + } else { + DEFAULT_LOCALE = loadGeyserLocale("en_US", bootstrap); + if (DEFAULT_LOCALE == null) { + // en_US can't be loaded? + throw new IllegalStateException("English locale not found in Geyser. Did you clone the submodules? (git submodule update --init)"); + } + SYSTEM_LOCALE_INVALID = true; + } + } + + /** + * Finalize the default locale, now that we know what the default locale should be. + */ + public static void finalizeDefaultLocale(GeyserImpl geyser) { + String newDefaultLocale = geyser.getConfig().getDefaultLocale(); + if (newDefaultLocale == null) { + // We want to use the system locale which is already loaded + return; + } + String loadedNewLocale = loadGeyserLocale(newDefaultLocale, geyser.getBootstrap()); + if (loadedNewLocale != null) { + // The config's locale is valid + DEFAULT_LOCALE = loadedNewLocale; + } else if (SYSTEM_LOCALE_INVALID) { + geyser.getLogger().warning(Locale.getDefault().toString() + " is not a valid Bedrock language."); + } + } + + public static String getDefaultLocale() { + return DEFAULT_LOCALE; + } + + /** + * Loads a Geyser locale from resources, if the file doesn't exist it just logs a warning + * + * @param locale Locale to load + */ + public static void loadGeyserLocale(String locale) { + GeyserImpl geyser = GeyserImpl.getInstance(); + if (geyser == null) { + throw new IllegalStateException("Geyser instance cannot be null when loading a locale!"); + } + loadGeyserLocale(locale, geyser.getBootstrap()); + } + + private static String loadGeyserLocale(String locale, GeyserBootstrap bootstrap) { + locale = formatLocale(locale); + // Don't load the locale if it's already loaded. + if (LOCALE_MAPPINGS.containsKey(locale)) { + return locale; + } + + InputStream localeStream = bootstrap.getResourceOrNull("languages/texts/" + locale + ".properties"); + + // Load the locale + if (localeStream != null) { + try { + Properties localeProp = new Properties(); + try (InputStreamReader reader = new InputStreamReader(localeStream, StandardCharsets.UTF_8)) { + localeProp.load(reader); + } catch (Exception e) { + throw new AssertionError(getLocaleStringLog("geyser.language.load_failed", locale), e); + } + + // Insert the locale into the mappings + LOCALE_MAPPINGS.put(locale, localeProp); + return locale; + } finally { + try { + localeStream.close(); + } catch (IOException ignored) {} + } + } else { + if (GeyserImpl.getInstance() != null) { + GeyserImpl.getInstance().getLogger().warning("Missing locale: " + locale); + } + return null; + } + } + + /** + * Get a formatted language string with the default locale for Geyser + * + * @param key Language string to translate + * @param values Values to put into the string + * @return Translated string or the original message if it was not found in the given locale + */ + public static String getLocaleStringLog(String key, Object... values) { + return getPlayerLocaleString(key, getDefaultLocale(), values); + } + + /** + * Get a formatted language string with the given locale for Geyser + * + * @param key Language string to translate + * @param locale Locale to translate to + * @param values Values to put into the string + * @return Translated string or the original message if it was not found in the given locale + */ + public static String getPlayerLocaleString(String key, String locale, Object... values) { + locale = formatLocale(locale); + + Properties properties = LOCALE_MAPPINGS.get(locale); + String formatString = null; + + if (properties != null) { + formatString = properties.getProperty(key); + } + + // Try and get the key from the default locale + if (formatString == null) { + properties = LOCALE_MAPPINGS.get(getDefaultLocale()); + formatString = properties.getProperty(key); + + // Try and get the key from en_US (this should only ever happen in development) + if (formatString == null) { + properties = LOCALE_MAPPINGS.get("en_US"); + formatString = properties.getProperty(key); + + // Final fallback + if (formatString == null) { + return key; + } + } + } + + String message = formatString.replace("&", "\u00a7"); + if (values == null || values.length == 0) { + // Nothing to replace + return message; + } + + return MessageFormat.format(message.replace("'", "''"), values); + } + + /** + * Cleans up and formats a locale string + * + * @param locale The locale to format + * @return The formatted locale + */ + public static String formatLocale(String locale) { + // Currently, all valid Geyser locales follow the same pattern of ll_CC, where ll is the language and + // CC is the country + if (locale.length() != 5 || locale.indexOf('_') != 2) { + // Invalid locale + return locale; + } + String language = locale.substring(0, 2); + String country = locale.substring(3); + return language.toLowerCase(Locale.ENGLISH) + "_" + country.toUpperCase(Locale.ENGLISH); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/text/GsonComponentSerializerWrapper.java b/core/src/main/java/org/geysermc/geyser/text/GsonComponentSerializerWrapper.java new file mode 100644 index 000000000..930d4a62c --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/text/GsonComponentSerializerWrapper.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.text; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.function.UnaryOperator; + +/** + * A wrapper around a normal GsonComponentSerializer to accept null components. + */ +public record GsonComponentSerializerWrapper(GsonComponentSerializer source) implements GsonComponentSerializer { + + @Override + public @NotNull Gson serializer() { + return this.source.serializer(); + } + + @Override + public @NotNull UnaryOperator populator() { + return this.source.populator(); + } + + @Override + public @NotNull Component deserializeFromTree(@NotNull JsonElement input) { + // This has yet to be an issue, so it won't be overridden unless we have to + return this.source.deserializeFromTree(input); + } + + @Override + public @NotNull JsonElement serializeToTree(@NotNull Component component) { + return this.source.serializeToTree(component); + } + + @Override + public @Nullable Component deserialize(@NotNull String input) { + // See https://github.com/KyoriPowered/adventure/issues/447 + return this.serializer().fromJson(input, Component.class); + } + + @Override + public @NotNull String serialize(@NotNull Component component) { + return this.source.serialize(component); + } + + @Override + public @NotNull Builder toBuilder() { + return this.source.toBuilder(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/text/MinecraftLocale.java b/core/src/main/java/org/geysermc/geyser/text/MinecraftLocale.java new file mode 100644 index 000000000..4f8680c73 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/text/MinecraftLocale.java @@ -0,0 +1,436 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.text; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Getter; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.util.FileUtils; +import org.geysermc.geyser.util.WebUtils; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.zip.ZipFile; + +public class MinecraftLocale { + + public static final Map> LOCALE_MAPPINGS = new HashMap<>(); + + private static final Map ASSET_MAP = new HashMap<>(); + + private static VersionDownload clientJarInfo; + + static { + // Create the locales folder + File localesFolder = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("locales").toFile(); + //noinspection ResultOfMethodCallIgnored + localesFolder.mkdir(); + + // Download the latest asset list and cache it + generateAssetCache().whenComplete((aVoid, ex) -> downloadAndLoadLocale(GeyserLocale.getDefaultLocale())); + } + + /** + * Fetch the latest versions asset cache from Mojang so we can grab the locale files later + */ + private static CompletableFuture generateAssetCache() { + return CompletableFuture.supplyAsync(() -> { + try { + // Get the version manifest from Mojang + VersionManifest versionManifest = GeyserImpl.JSON_MAPPER.readValue(WebUtils.getBody("https://launchermeta.mojang.com/mc/game/version_manifest.json"), VersionManifest.class); + + // Get the url for the latest version of the games manifest + String latestInfoURL = ""; + for (Version version : versionManifest.getVersions()) { + if (version.getId().equals(MinecraftProtocol.getJavaCodec().getMinecraftVersion())) { + latestInfoURL = version.getUrl(); + break; + } + } + + // Make sure we definitely got a version + if (latestInfoURL.isEmpty()) { + throw new Exception(GeyserLocale.getLocaleStringLog("geyser.locale.fail.latest_version")); + } + + // Get the individual version manifest + VersionInfo versionInfo = GeyserImpl.JSON_MAPPER.readValue(WebUtils.getBody(latestInfoURL), VersionInfo.class); + + // Get the client jar for use when downloading the en_us locale + GeyserImpl.getInstance().getLogger().debug(GeyserImpl.JSON_MAPPER.writeValueAsString(versionInfo.getDownloads())); + clientJarInfo = versionInfo.getDownloads().get("client"); + GeyserImpl.getInstance().getLogger().debug(GeyserImpl.JSON_MAPPER.writeValueAsString(clientJarInfo)); + + // Get the assets list + JsonNode assets = GeyserImpl.JSON_MAPPER.readTree(WebUtils.getBody(versionInfo.getAssetIndex().getUrl())).get("objects"); + + // Put each asset into an array for use later + Iterator> assetIterator = assets.fields(); + while (assetIterator.hasNext()) { + Map.Entry entry = assetIterator.next(); + if (!entry.getKey().startsWith("minecraft/lang/")) { + // No need to cache non-language assets as we don't use them + continue; + } + + Asset asset = GeyserImpl.JSON_MAPPER.treeToValue(entry.getValue(), Asset.class); + ASSET_MAP.put(entry.getKey(), asset); + } + } catch (Exception e) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.locale.fail.asset_cache", (!e.getMessage().isEmpty() ? e.getMessage() : e.getStackTrace()))); + } + return null; + }); + } + + /** + * Downloads a locale from Mojang if its not already loaded + * + * @param locale Locale to download and load + */ + public static void downloadAndLoadLocale(String locale) { + locale = locale.toLowerCase(Locale.ROOT); + if (locale.equals("nb_no")) { + // Different locale code - https://minecraft.fandom.com/wiki/Language + locale = "no_no"; + } + + // Check the locale isn't already loaded + if (!ASSET_MAP.containsKey("minecraft/lang/" + locale + ".json") && !locale.equals("en_us")) { + GeyserImpl.getInstance().getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.locale.fail.invalid", locale)); + return; + } + + GeyserImpl.getInstance().getLogger().debug("Downloading and loading locale: " + locale); + + downloadLocale(locale); + loadLocale(locale); + } + + /** + * Downloads the specified locale if its not already downloaded + * + * @param locale Locale to download + */ + private static void downloadLocale(String locale) { + File localeFile = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("locales/" + locale + ".json").toFile(); + + // Check if we have already downloaded the locale file + if (localeFile.exists()) { + String curHash = ""; + String targetHash; + + if (locale.equals("en_us")) { + try { + File hashFile = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("locales/en_us.hash").toFile(); + if (hashFile.exists()) { + try (BufferedReader br = new BufferedReader(new FileReader(hashFile))) { + curHash = br.readLine().trim(); + } + } + } catch (IOException ignored) { } + + if (clientJarInfo == null) { + // Likely failed to download + GeyserImpl.getInstance().getLogger().debug("Skipping en_US hash check as client jar is null."); + return; + } + targetHash = clientJarInfo.getSha1(); + } else { + curHash = byteArrayToHexString(FileUtils.calculateSHA1(localeFile)); + targetHash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash(); + } + + if (!curHash.equals(targetHash)) { + GeyserImpl.getInstance().getLogger().debug("Locale out of date; re-downloading: " + locale); + } else { + GeyserImpl.getInstance().getLogger().debug("Locale already downloaded and up-to date: " + locale); + return; + } + } + + // Create the en_us locale + if (locale.equals("en_us")) { + downloadEN_US(localeFile); + + return; + } + + try { + // Get the hash and download the locale + String hash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash(); + WebUtils.downloadFile("https://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, localeFile.toString()); + } catch (Exception e) { + GeyserImpl.getInstance().getLogger().error("Unable to download locale file hash", e); + } + } + + /** + * Loads a locale already downloaded, if the file doesn't exist it just logs a warning + * + * @param locale Locale to load + */ + private static void loadLocale(String locale) { + File localeFile = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("locales/" + locale + ".json").toFile(); + + // Load the locale + if (localeFile.exists()) { + // Read the localefile + InputStream localeStream; + try { + localeStream = new FileInputStream(localeFile); + } catch (FileNotFoundException e) { + throw new AssertionError(GeyserLocale.getLocaleStringLog("geyser.locale.fail.file", locale, e.getMessage())); + } + + // Parse the file as json + JsonNode localeObj; + try { + localeObj = GeyserImpl.JSON_MAPPER.readTree(localeStream); + } catch (Exception e) { + throw new AssertionError(GeyserLocale.getLocaleStringLog("geyser.locale.fail.json", locale), e); + } + + // Parse all the locale fields + Iterator> localeIterator = localeObj.fields(); + Map langMap = new HashMap<>(); + while (localeIterator.hasNext()) { + Map.Entry entry = localeIterator.next(); + langMap.put(entry.getKey(), entry.getValue().asText()); + } + + String bedrockLocale = locale.toLowerCase(Locale.ROOT); + if (bedrockLocale.equals("no_no")) { + // Store this locale under the Bedrock locale so we don't need to do this check over and over + bedrockLocale = "nb_no"; + } + + // Insert the locale into the mappings + LOCALE_MAPPINGS.put(bedrockLocale, langMap); + + try { + localeStream.close(); + } catch (IOException e) { + throw new AssertionError(GeyserLocale.getLocaleStringLog("geyser.locale.fail.file", locale, e.getMessage())); + } + } else { + GeyserImpl.getInstance().getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.locale.fail.missing", locale)); + } + } + + /** + * Download then en_us locale by downloading the server jar and extracting it from there. + * + * @param localeFile File to save the locale to + */ + private static void downloadEN_US(File localeFile) { + try { + // Let the user know we are downloading the JAR + GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.locale.download.en_us")); + GeyserImpl.getInstance().getLogger().debug("Download URL: " + clientJarInfo.getUrl()); + + // Download the smallest JAR (client or server) + Path tmpFilePath = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("tmp_locale.jar"); + WebUtils.downloadFile(clientJarInfo.getUrl(), tmpFilePath.toString()); + + // Load in the JAR as a zip and extract the file + try (ZipFile localeJar = new ZipFile(tmpFilePath.toString())) { + try (InputStream fileStream = localeJar.getInputStream(localeJar.getEntry("assets/minecraft/lang/en_us.json"))) { + try (FileOutputStream outStream = new FileOutputStream(localeFile)) { + + // Write the file to the locale dir + byte[] buf = new byte[fileStream.available()]; + int length; + while ((length = fileStream.read(buf)) != -1) { + outStream.write(buf, 0, length); + } + + // Flush all changes to disk and cleanup + outStream.flush(); + } + } + } + + // Store the latest jar hash + FileUtils.writeFile(GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("locales/en_us.hash").toString(), clientJarInfo.getSha1().toCharArray()); + + // Delete the nolonger needed client/server jar + Files.delete(tmpFilePath); + + GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.locale.download.en_us.done")); + } catch (Exception e) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.locale.fail.en_us"), e); + } + } + + /** + * Translate the given language string into the given locale, or falls back to the default locale + * + * @param messageText Language string to translate + * @param locale Locale to translate to + * @return Translated string or the original message if it was not found in the given locale + */ + public static String getLocaleString(String messageText, String locale) { + Map localeStrings = MinecraftLocale.LOCALE_MAPPINGS.get(locale.toLowerCase()); + if (localeStrings == null) { + localeStrings = MinecraftLocale.LOCALE_MAPPINGS.get(GeyserLocale.getDefaultLocale()); + if (localeStrings == null) { + // Don't cause a NPE if the locale is STILL missing + GeyserImpl.getInstance().getLogger().debug("MISSING DEFAULT LOCALE: " + GeyserLocale.getDefaultLocale()); + return messageText; + } + } + + return localeStrings.getOrDefault(messageText, messageText); + } + + /** + * Convert a byte array into a hex string + * + * @param b Byte array to convert + * @return The hex representation of the given byte array + */ + private static String byteArrayToHexString(byte[] b) { + StringBuilder result = new StringBuilder(); + for (byte value : b) { + result.append(Integer.toString((value & 0xff) + 0x100, 16).substring(1)); + } + return result.toString(); + } + + public static void init() { + // no-op + } + + @JsonIgnoreProperties(ignoreUnknown = true) + @Getter + static class VersionManifest { + @JsonProperty("latest") + private LatestVersion latestVersion; + + @JsonProperty("versions") + private List versions; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + @Getter + static class LatestVersion { + @JsonProperty("release") + private String release; + + @JsonProperty("snapshot") + private String snapshot; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + @Getter + static class Version { + @JsonProperty("id") + private String id; + + @JsonProperty("type") + private String type; + + @JsonProperty("url") + private String url; + + @JsonProperty("time") + private String time; + + @JsonProperty("releaseTime") + private String releaseTime; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + @Getter + static class VersionInfo { + @JsonProperty("id") + private String id; + + @JsonProperty("type") + private String type; + + @JsonProperty("time") + private String time; + + @JsonProperty("releaseTime") + private String releaseTime; + + @JsonProperty("assetIndex") + private AssetIndex assetIndex; + + @JsonProperty("downloads") + private Map downloads; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + @Getter + static class VersionDownload { + @JsonProperty("sha1") + private String sha1; + + @JsonProperty("size") + private int size; + + @JsonProperty("url") + private String url; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + @Getter + static class AssetIndex { + @JsonProperty("id") + private String id; + + @JsonProperty("sha1") + private String sha1; + + @JsonProperty("size") + private int size; + + @JsonProperty("totalSize") + private int totalSize; + + @JsonProperty("url") + private String url; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + @Getter + static class Asset { + @JsonProperty("hash") + private String hash; + + @JsonProperty("size") + private int size; + } +} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java b/core/src/main/java/org/geysermc/geyser/text/MinecraftTranslationRegistry.java similarity index 64% rename from connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java rename to core/src/main/java/org/geysermc/geyser/text/MinecraftTranslationRegistry.java index 127e00603..129fb1f40 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/text/MinecraftTranslationRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,14 +23,12 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.chat; +package org.geysermc.geyser.text; -import net.kyori.adventure.key.Key; -import net.kyori.adventure.translation.TranslationRegistry; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.geysermc.connector.utils.LocaleUtils; +import net.kyori.adventure.text.renderer.TranslatableComponentRenderer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.text.MessageFormat; import java.util.Locale; import java.util.regex.Matcher; @@ -40,19 +38,17 @@ import java.util.regex.Pattern; * This class is used for mapping a translation key with the already loaded Java locale data * Used in MessageTranslator.java as part of the KyoriPowered/Adventure library */ -public class MinecraftTranslationRegistry implements TranslationRegistry { - @Override - public @NonNull Key name() { - return Key.key("", ""); - } +public class MinecraftTranslationRegistry extends TranslatableComponentRenderer { + private final Pattern stringReplacement = Pattern.compile("%s"); + private final Pattern positionalStringReplacement = Pattern.compile("%([0-9]+)\\$s"); @Override - public @Nullable MessageFormat translate(@NonNull String key, @NonNull Locale locale) { + public @Nullable MessageFormat translate(@Nonnull String key, @Nonnull String locale) { // Get the locale string - String localeString = LocaleUtils.getLocaleString(key, locale.toString()); + String localeString = MinecraftLocale.getLocaleString(key, locale); // Replace the `%s` with numbered inserts `{0}` - Pattern p = Pattern.compile("%s"); + Pattern p = stringReplacement; Matcher m = p.matcher(localeString); StringBuffer sb = new StringBuffer(); int i = 0; @@ -62,7 +58,7 @@ public class MinecraftTranslationRegistry implements TranslationRegistry { m.appendTail(sb); // Replace the `%x$s` with numbered inserts `{x}` - p = Pattern.compile("%([0-9]+)\\$s"); + p = positionalStringReplacement; m = p.matcher(sb.toString()); sb = new StringBuffer(); while (m.find()) { @@ -71,21 +67,8 @@ public class MinecraftTranslationRegistry implements TranslationRegistry { } m.appendTail(sb); - return new MessageFormat(sb.toString(), locale); - } - - @Override - public void defaultLocale(@NonNull Locale locale) { - - } - - @Override - public void register(@NonNull String key, @NonNull Locale locale, @NonNull MessageFormat format) { - - } - - @Override - public void unregister(@NonNull String key) { - + // replace single quote instances which get lost in MessageFormat otherwise + // Locale shouldn't need to be specific - dates for example will not be handled + return new MessageFormat(sb.toString().replace("'", "''"), Locale.ROOT); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/collision/BlockCollision.java b/core/src/main/java/org/geysermc/geyser/translator/collision/BlockCollision.java new file mode 100644 index 000000000..964faab0a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/collision/BlockCollision.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.collision; + +import com.nukkitx.math.vector.Vector3d; +import com.nukkitx.math.vector.Vector3i; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.physics.BoundingBox; +import org.geysermc.geyser.level.physics.CollisionManager; +import org.geysermc.geyser.level.physics.Axis; + +@EqualsAndHashCode +public class BlockCollision { + + @Getter + protected final BoundingBox[] boundingBoxes; + + /** + * This is used for the step up logic. + * Usually, the player can only step up a block if they are on the same Y level as its bottom face or higher + * For snow layers, due to its beforeCorrectPosition method the player can be slightly below (0.125 blocks) and + * still need to step up + * This used to be 0 but for now this has been set to 1 as it fixes bed collision + * I didn't just set it for beds because other collision may also be slightly raised off the ground. + * If this causes any problems, change this back to 0 and add an exception for beds. + */ + protected double pushUpTolerance = 1; + + /** + * This is used to control the maximum distance a face of a bounding box can push the player away + */ + protected double pushAwayTolerance = CollisionManager.COLLISION_TOLERANCE * 1.1; + + protected BlockCollision(BoundingBox[] boxes) { + this.boundingBoxes = boxes; + } + + /** + * Overridden in classes like SnowCollision and GrassPathCollision when correction code needs to be run before the + * main correction + */ + public void beforeCorrectPosition(int x, int y, int z, BoundingBox playerCollision) {} + + /** + * Returns false if the movement is invalid, and in this case it shouldn't be sent to the server and should be + * cancelled + * While the Java server should do this, it could result in false flags by anticheat + * This functionality is currently only used in 6 or 7 layer snow + */ + public boolean correctPosition(GeyserSession session, int x, int y, int z, BoundingBox playerCollision) { + double playerMinY = playerCollision.getMiddleY() - (playerCollision.getSizeY() / 2); + for (BoundingBox b : this.boundingBoxes) { + double boxMinY = (b.getMiddleY() + y) - (b.getSizeY() / 2); + double boxMaxY = (b.getMiddleY() + y) + (b.getSizeY() / 2); + if (b.checkIntersection(x, y, z, playerCollision) && (playerMinY + pushUpTolerance) >= boxMinY) { + // Max steppable distance in Minecraft as far as we know is 0.5625 blocks (for beds) + if (boxMaxY - playerMinY <= 0.5625) { + playerCollision.translate(0, boxMaxY - playerMinY, 0); + // Update player Y for next collision box + playerMinY = playerCollision.getMiddleY() - (playerCollision.getSizeY() / 2); + } + } + + // Make player collision slightly bigger to pick up on blocks that could cause problems with Passable + playerCollision.setSizeX(playerCollision.getSizeX() + CollisionManager.COLLISION_TOLERANCE * 2); + playerCollision.setSizeZ(playerCollision.getSizeZ() + CollisionManager.COLLISION_TOLERANCE * 2); + + // If the player still intersects the block, then push them out + // This fixes NoCheatPlus's Passable check + // This check doesn't allow players right up against the block, so they must be pushed slightly away + if (b.checkIntersection(x, y, z, playerCollision)) { + Vector3d relativePlayerPosition = Vector3d.from(playerCollision.getMiddleX() - x, + playerCollision.getMiddleY() - y, + playerCollision.getMiddleZ() - z); + + // The ULP should give an upper bound on the floating point error + double xULP = Math.ulp((float) Math.max(Math.abs(playerCollision.getMiddleX()) + playerCollision.getSizeX() / 2.0, Math.abs(x) + 1)); + double zULP = Math.ulp((float) Math.max(Math.abs(playerCollision.getMiddleZ()) + playerCollision.getSizeZ() / 2.0, Math.abs(z) + 1)); + + double xPushAwayTolerance = Math.max(pushAwayTolerance, xULP); + double zPushAwayTolerance = Math.max(pushAwayTolerance, zULP); + + double northFaceZPos = b.getMiddleZ() - (b.getSizeZ() / 2); + double translateDistance = northFaceZPos - relativePlayerPosition.getZ() - (playerCollision.getSizeZ() / 2); + if (Math.abs(translateDistance) < zPushAwayTolerance) { + playerCollision.translate(0, 0, translateDistance); + } + + double southFaceZPos = b.getMiddleZ() + (b.getSizeZ() / 2); + translateDistance = southFaceZPos - relativePlayerPosition.getZ() + (playerCollision.getSizeZ() / 2); + if (Math.abs(translateDistance) < zPushAwayTolerance) { + playerCollision.translate(0, 0, translateDistance); + } + + double eastFaceXPos = b.getMiddleX() + (b.getSizeX() / 2); + translateDistance = eastFaceXPos - relativePlayerPosition.getX() + (playerCollision.getSizeX() / 2); + if (Math.abs(translateDistance) < xPushAwayTolerance) { + playerCollision.translate(translateDistance, 0, 0); + } + + double westFaceXPos = b.getMiddleX() - (b.getSizeX() / 2); + translateDistance = westFaceXPos - relativePlayerPosition.getX() - (playerCollision.getSizeX() / 2); + if (Math.abs(translateDistance) < xPushAwayTolerance) { + playerCollision.translate(translateDistance, 0, 0); + } + + double bottomFaceYPos = b.getMiddleY() - (b.getSizeY() / 2); + translateDistance = bottomFaceYPos - relativePlayerPosition.getY() - (playerCollision.getSizeY() / 2); + if (Math.abs(translateDistance) < pushAwayTolerance) { + playerCollision.translate(0, translateDistance, 0); + } + } + + // Set the collision size back to normal + playerCollision.setSizeX(0.6); + playerCollision.setSizeZ(0.6); + } + + return true; + } + + public boolean checkIntersection(double x, double y, double z, BoundingBox playerCollision) { + for (BoundingBox b : boundingBoxes) { + if (b.checkIntersection(x, y, z, playerCollision)) { + return true; + } + } + return false; + } + + public boolean checkIntersection(Vector3i position, BoundingBox playerCollision) { + return checkIntersection(position.getX(), position.getY(), position.getZ(), playerCollision); + } + + public double computeCollisionOffset(double x, double y, double z, BoundingBox boundingBox, Axis axis, double offset) { + for (BoundingBox b : boundingBoxes) { + offset = b.getMaxOffset(x, y, z, boundingBox, axis, offset); + if (Math.abs(offset) < CollisionManager.COLLISION_TOLERANCE) { + return 0; + } + } + return offset; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/translator/collision/CollisionRemapper.java b/core/src/main/java/org/geysermc/geyser/translator/collision/CollisionRemapper.java new file mode 100644 index 000000000..2156cc670 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/collision/CollisionRemapper.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.collision; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(value = RetentionPolicy.RUNTIME) +public @interface CollisionRemapper { + + /** + * Regex of block identifiers to apply this collision to + * Matches against just the block ID name, not including the namespace or parameters + */ + String regex(); + + /** + * Regex of block state parameters to apply this collision to + * Defaults to matching any value + */ + String paramRegex() default ".*"; + + /** + * Signals if a new instance needs to created for every block state + */ + boolean usesParams() default false; + + /** + * Signals if the default bounding boxes of this block as defined in collision.json should be passed to the + * constructor + */ + boolean passDefaultBoxes() default false; +} diff --git a/common/src/main/java/org/geysermc/common/window/component/SliderComponent.java b/core/src/main/java/org/geysermc/geyser/translator/collision/DirtPathCollision.java similarity index 51% rename from common/src/main/java/org/geysermc/common/window/component/SliderComponent.java rename to core/src/main/java/org/geysermc/geyser/translator/collision/DirtPathCollision.java index a7a78362e..a7babead3 100644 --- a/common/src/main/java/org/geysermc/common/window/component/SliderComponent.java +++ b/core/src/main/java/org/geysermc/geyser/translator/collision/DirtPathCollision.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,43 +23,28 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.component; +package org.geysermc.geyser.translator.collision; -import lombok.Getter; -import lombok.Setter; +import lombok.EqualsAndHashCode; +import org.geysermc.geyser.level.physics.BoundingBox; +import org.geysermc.geyser.level.physics.CollisionManager; -public class SliderComponent extends FormComponent { +@EqualsAndHashCode(callSuper = true) +@CollisionRemapper(regex = "^dirt_path$", passDefaultBoxes = true) +public class DirtPathCollision extends BlockCollision { + public DirtPathCollision(String params, BoundingBox[] defaultBoxes) { + super(defaultBoxes); + } - @Getter - @Setter - private String text; - - @Getter - @Setter - private float min; - - @Getter - @Setter - private float max; - - @Getter - @Setter - private int step; - - @Getter - @Setter - private float defaultValue; - - public SliderComponent(String text, float min, float max, int step, float defaultValue) { - super("slider"); - - this.text = text; - this.min = Math.max(min, 0f); - this.max = max > this.min ? max : this.min; - if (step != -1f && step > 0) - this.step = step; - - if (defaultValue != -1f) - this.defaultValue = defaultValue; + // Needs to run before the main correction code or it can move the player into blocks + // This is counteracted by the main collision code pushing them out + @Override + public void beforeCorrectPosition(int x, int y, int z, BoundingBox playerCollision) { + // In Bedrock, dirt paths are solid blocks, so the player must be pushed down. + double playerMinY = playerCollision.getMiddleY() - (playerCollision.getSizeY() / 2); + double blockMaxY = y + 1; + if (Math.abs(blockMaxY - playerMinY) <= CollisionManager.COLLISION_TOLERANCE) { + playerCollision.translate(0, -0.0625, 0); + } } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/collision/DoorCollision.java b/core/src/main/java/org/geysermc/geyser/translator/collision/DoorCollision.java new file mode 100644 index 000000000..3c858dc92 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/collision/DoorCollision.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.collision; + +import lombok.EqualsAndHashCode; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.physics.BoundingBox; + +@EqualsAndHashCode(callSuper = true) +@CollisionRemapper(regex = "_door$", usesParams = true, passDefaultBoxes = true) +public class DoorCollision extends BlockCollision { + /** + * 1 = north + * 2 = east + * 3 = south + * 4 = west + */ + private int facing; + + public DoorCollision(String params, BoundingBox[] defaultBoxes) { + super(defaultBoxes); + if (params.contains("facing=north")) { + facing = 1; + } else if (params.contains("facing=east")) { + facing = 2; + } else if (params.contains("facing=south")) { + facing = 3; + } else if (params.contains("facing=west")) { + facing = 4; + } + + // If the door is open it changes direction + if (params.contains("open=true")) { + facing = facing % 2 + 1; + } + } + + @Override + public boolean correctPosition(GeyserSession session, int x, int y, int z, BoundingBox playerCollision) { + boolean result = super.correctPosition(session, x, y, z, playerCollision); + // Hack to prevent false positives + playerCollision.setSizeX(playerCollision.getSizeX() - 0.0001); + playerCollision.setSizeY(playerCollision.getSizeY() - 0.0001); + playerCollision.setSizeZ(playerCollision.getSizeZ() - 0.0001); + + // Check for door bug (doors are 0.1875 blocks thick on Java but 0.1825 blocks thick on Bedrock) + if (this.checkIntersection(x, y, z, playerCollision)) { + switch (facing) { + case 1 -> playerCollision.setMiddleZ(z + 0.5125); // North + case 2 -> playerCollision.setMiddleX(x + 0.5125); // East + case 3 -> playerCollision.setMiddleZ(z + 0.4875); // South + case 4 -> playerCollision.setMiddleX(x + 0.4875); // West + } + } + + playerCollision.setSizeX(playerCollision.getSizeX() + 0.0001); + playerCollision.setSizeY(playerCollision.getSizeY() + 0.0001); + playerCollision.setSizeZ(playerCollision.getSizeZ() + 0.0001); + return result; + } +} diff --git a/common/src/main/java/org/geysermc/common/window/response/ModalFormResponse.java b/core/src/main/java/org/geysermc/geyser/translator/collision/OtherCollision.java similarity index 75% rename from common/src/main/java/org/geysermc/common/window/response/ModalFormResponse.java rename to core/src/main/java/org/geysermc/geyser/translator/collision/OtherCollision.java index e1a14039d..2a81077ca 100644 --- a/common/src/main/java/org/geysermc/common/window/response/ModalFormResponse.java +++ b/core/src/main/java/org/geysermc/geyser/translator/collision/OtherCollision.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,16 +23,15 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.response; +package org.geysermc.geyser.translator.collision; -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.geysermc.common.window.response.FormResponse; +import lombok.EqualsAndHashCode; +import org.geysermc.geyser.level.physics.BoundingBox; -@Getter -@AllArgsConstructor -public class ModalFormResponse implements FormResponse { +@EqualsAndHashCode(callSuper = true) +public class OtherCollision extends BlockCollision { - private int clickedButtonId; - private String clickedButtonText; + public OtherCollision(BoundingBox[] boundingBoxes) { + super(boundingBoxes); + } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/collision/ScaffoldingCollision.java b/core/src/main/java/org/geysermc/geyser/translator/collision/ScaffoldingCollision.java new file mode 100644 index 000000000..fddc2e39b --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/collision/ScaffoldingCollision.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.collision; + +import lombok.EqualsAndHashCode; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.physics.BoundingBox; + +/** + * In order for scaffolding to work on Bedrock, entity flags need to be sent to the player + */ +@EqualsAndHashCode(callSuper = true) +@CollisionRemapper(regex = "^scaffolding$", usesParams = true, passDefaultBoxes = true) +public class ScaffoldingCollision extends BlockCollision { + public ScaffoldingCollision(String params, BoundingBox[] defaultBoxes) { + super(defaultBoxes); + } + + @Override + public boolean correctPosition(GeyserSession session, int x, int y, int z, BoundingBox playerCollision) { + // Hack to not check below the player + playerCollision.setSizeY(playerCollision.getSizeY() - 0.001); + playerCollision.setMiddleY(playerCollision.getMiddleY() + 0.002); + + boolean intersected = this.checkIntersection(x, y, z, playerCollision); + + playerCollision.setSizeY(playerCollision.getSizeY() + 0.001); + playerCollision.setMiddleY(playerCollision.getMiddleY() - 0.002); + + if (intersected) { + session.getCollisionManager().setTouchingScaffolding(true); + session.getCollisionManager().setOnScaffolding(true); + } else { + // Hack to check slightly below the player + playerCollision.setSizeY(playerCollision.getSizeY() + 0.001); + playerCollision.setMiddleY(playerCollision.getMiddleY() - 0.002); + + if (this.checkIntersection(x, y, z, playerCollision)) { + session.getCollisionManager().setOnScaffolding(true); + } + + playerCollision.setSizeY(playerCollision.getSizeY() - 0.001); + playerCollision.setMiddleY(playerCollision.getMiddleY() + 0.002); + } + + // Normal move correction isn't really needed for scaffolding + return true; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/collision/SnowCollision.java b/core/src/main/java/org/geysermc/geyser/translator/collision/SnowCollision.java new file mode 100644 index 000000000..912d6a9b9 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/collision/SnowCollision.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.collision; + +import lombok.EqualsAndHashCode; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.physics.BoundingBox; + +@EqualsAndHashCode(callSuper = true) +@CollisionRemapper(regex = "^snow$", passDefaultBoxes = true, usesParams = true) +public class SnowCollision extends BlockCollision { + private final int layers; + + public SnowCollision(String params, BoundingBox[] defaultBoxes) { + super(defaultBoxes); + int layerCharIndex = params.indexOf("=") + 1; + layers = Integer.parseInt(params.substring(layerCharIndex, layerCharIndex + 1)); + + pushUpTolerance = 0.125; + } + + // Needs to run before the main correction code or it can move the player into blocks + // This is counteracted by the main collision code pushing them out + @Override + public void beforeCorrectPosition(int x, int y, int z, BoundingBox playerCollision) { + // In Bedrock, snow layers round down to half blocks but you can't sink into them at all + // This means the collision each half block reaches above where it should be on Java so the player has to be + // pushed down + if (layers == 4 || layers == 8) { + double playerMinY = playerCollision.getMiddleY() - (playerCollision.getSizeY() / 2); + double boxMaxY = (boundingBoxes[0].getMiddleY() + y) + (boundingBoxes[0].getSizeY() / 2); + // If the player is in the buggy area, push them down + if (playerMinY > boxMaxY && + playerMinY <= (boxMaxY + 0.125)) { + playerCollision.translate(0, boxMaxY - playerMinY, 0); + } + } + } + + @Override + public boolean correctPosition(GeyserSession session, int x, int y, int z, BoundingBox playerCollision) { + if (layers == 1) { + // 1 layer of snow does not have collision + return true; + } + // Hack to prevent false positives + playerCollision.setSizeX(playerCollision.getSizeX() - 0.0001); + playerCollision.setSizeY(playerCollision.getSizeY() - 0.0001); + playerCollision.setSizeZ(playerCollision.getSizeZ() - 0.0001); + + if (this.checkIntersection(x, y, z, playerCollision)) { + double playerMinY = playerCollision.getMiddleY() - (playerCollision.getSizeY() / 2); + double boxMaxY = (boundingBoxes[0].getMiddleY() + y) + (boundingBoxes[0].getSizeY() / 2); + // If the player actually can't step onto it (they can step onto it from other snow layers) + if ((boxMaxY - playerMinY) > 0.5) { + // Cancel the movement + return false; + } + } + + playerCollision.setSizeX(playerCollision.getSizeX() + 0.0001); + playerCollision.setSizeY(playerCollision.getSizeY() + 0.0001); + playerCollision.setSizeZ(playerCollision.getSizeZ() + 0.0001); + return super.correctPosition(session, x, y, z, playerCollision); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/collision/SolidCollision.java b/core/src/main/java/org/geysermc/geyser/translator/collision/SolidCollision.java new file mode 100644 index 000000000..1f47d8abb --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/collision/SolidCollision.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.collision; + +import lombok.EqualsAndHashCode; +import org.geysermc.geyser.level.physics.BoundingBox; + +@EqualsAndHashCode(callSuper = true) +@CollisionRemapper(regex = "shulker_box$") // These have no collision in the mappings as it depends on the NBT data +public class SolidCollision extends BlockCollision { + public SolidCollision(String params) { + super(new BoundingBox[] { + new BoundingBox(0.5, 0.5, 0.5, 1, 1, 1) + }); + } +} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/AmbientEntity.java b/core/src/main/java/org/geysermc/geyser/translator/collision/SpawnerCollision.java similarity index 71% rename from connector/src/main/java/org/geysermc/connector/entity/living/AmbientEntity.java rename to core/src/main/java/org/geysermc/geyser/translator/collision/SpawnerCollision.java index bcaf81e64..698b8f82d 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/AmbientEntity.java +++ b/core/src/main/java/org/geysermc/geyser/translator/collision/SpawnerCollision.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,14 +23,16 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living; +package org.geysermc.geyser.translator.collision; -import com.nukkitx.math.vector.Vector3f; -import org.geysermc.connector.entity.type.EntityType; +import lombok.EqualsAndHashCode; -public class AmbientEntity extends InsentientEntity { - - public AmbientEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); +@EqualsAndHashCode(callSuper = true) +@CollisionRemapper(regex = "^spawner$") +public class SpawnerCollision extends SolidCollision { + public SpawnerCollision(String params) { + super(params); + // Increase pushAwayTolerance to work around https://bugs.mojang.com/browse/MCPE-41996 + pushAwayTolerance = 0.0002; } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/collision/TrapdoorCollision.java b/core/src/main/java/org/geysermc/geyser/translator/collision/TrapdoorCollision.java new file mode 100644 index 000000000..49b9f6c3f --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/collision/TrapdoorCollision.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.collision; + +import lombok.EqualsAndHashCode; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.physics.BoundingBox; +import org.geysermc.geyser.level.physics.CollisionManager; + +@EqualsAndHashCode(callSuper = true) +@CollisionRemapper(regex = "_trapdoor$", usesParams = true, passDefaultBoxes = true) +public class TrapdoorCollision extends BlockCollision { + /** + * 1 = north + * 2 = east + * 3 = south + * 4 = west + * 5 = up + * 6 = down + */ + private int facing; + + public TrapdoorCollision(String params, BoundingBox[] defaultBoxes) { + super(defaultBoxes); + if (params.contains("open=true")) { + if (params.contains("facing=north")) { + facing = 1; + } else if (params.contains("facing=east")) { + facing = 2; + } else if (params.contains("facing=south")) { + facing = 3; + } else if (params.contains("facing=west")) { + facing = 4; + } + } else { + if (params.contains("half=bottom")) { + // Up + facing = 5; + } else { + // Down + facing = 6; + } + } + } + + @Override + public boolean correctPosition(GeyserSession session, int x, int y, int z, BoundingBox playerCollision) { + boolean result = super.correctPosition(session, x, y, z, playerCollision); + // Check for door bug (doors are 0.1875 blocks thick on Java but 0.1825 blocks thick on Bedrock) + if (this.checkIntersection(x, y, z, playerCollision)) { + switch (facing) { + case 1: // North + playerCollision.setMiddleZ(z + 0.5125); + break; + case 2: // East + playerCollision.setMiddleX(x + 0.5125); + break; + case 3: // South + playerCollision.setMiddleZ(z + 0.4875); + break; + case 4: // West + playerCollision.setMiddleX(x + 0.4875); + break; + case 5: + // Up-facing trapdoors are handled by the step-up check + break; + case 6: // Down + // (top y of trap door) - (trap door thickness) = top y of player + playerCollision.setMiddleY(y + 1 - (3.0 / 16.0) - playerCollision.getSizeY() / 2.0 - CollisionManager.COLLISION_TOLERANCE); + break; + } + } + return result; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java b/core/src/main/java/org/geysermc/geyser/translator/entity/EntityMetadataTranslator.java similarity index 61% rename from connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java rename to core/src/main/java/org/geysermc/geyser/translator/entity/EntityMetadataTranslator.java index 225335a97..e8eba1654 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java +++ b/core/src/main/java/org/geysermc/geyser/translator/entity/EntityMetadataTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,37 +23,22 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.inventory; +package org.geysermc.geyser.translator.entity; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import lombok.Getter; -import lombok.Setter; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType; +import org.geysermc.geyser.entity.type.Entity; -public class PlayerInventory extends Inventory { +import java.util.function.BiConsumer; - /** - * Stores the held item slot, starting at index 0. - * Add 36 in order to get the network item slot. - */ - @Getter - @Setter - private int heldItemSlot; +/** + * Translates a given Java {@link EntityMetadata} into a similar/same construct for Bedrock + */ +public record EntityMetadataTranslator>>( + MetadataType acceptedType, + BiConsumer translateFunction) { - @Getter - private ItemStack cursor; - - public PlayerInventory() { - super(0, null, 46); - heldItemSlot = 0; - } - - public void setCursor(ItemStack stack) { - if (stack != null && (stack.getId() == 0 || stack.getAmount() < 1)) - stack = null; - cursor = stack; - } - - public ItemStack getItemInHand() { - return items[36 + heldItemSlot]; + public void translate(E entity, EM metadata) { + this.translateFunction.accept(entity, metadata); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/AbstractBlockInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/AbstractBlockInventoryTranslator.java new file mode 100644 index 000000000..d34f5195c --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/AbstractBlockInventoryTranslator.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.inventory.holder.BlockInventoryHolder; +import org.geysermc.geyser.inventory.holder.InventoryHolder; +import org.geysermc.geyser.inventory.updater.InventoryUpdater; + +/** + * Provided as a base for any inventory that requires a block for opening it + */ +public abstract class AbstractBlockInventoryTranslator extends BaseInventoryTranslator { + private final InventoryHolder holder; + private final InventoryUpdater updater; + + /** + * @param size the amount of slots that the inventory adds alongside the base inventory slots + * @param javaBlockIdentifier a Java block identifier that is used as a temporary block + * @param containerType the container type of this inventory + * @param updater updater + * @param additionalValidBlocks any other block identifiers that can safely use this inventory without a fake block + */ + public AbstractBlockInventoryTranslator(int size, String javaBlockIdentifier, ContainerType containerType, InventoryUpdater updater, + String... additionalValidBlocks) { + super(size); + this.holder = new BlockInventoryHolder(javaBlockIdentifier, containerType, additionalValidBlocks); + this.updater = updater; + } + + /** + * @param size the amount of slots that the inventory adds alongside the base inventory slots + * @param holder the custom block holder + * @param updater updater + */ + public AbstractBlockInventoryTranslator(int size, InventoryHolder holder, InventoryUpdater updater) { + super(size); + this.holder = holder; + this.updater = updater; + } + + @Override + public void prepareInventory(GeyserSession session, Inventory inventory) { + holder.prepareInventory(this, session, inventory); + } + + @Override + public void openInventory(GeyserSession session, Inventory inventory) { + holder.openInventory(this, session, inventory); + } + + @Override + public void closeInventory(GeyserSession session, Inventory inventory) { + holder.closeInventory(this, session, inventory); + } + + @Override + public void updateInventory(GeyserSession session, Inventory inventory) { + updater.updateInventory(this, session, inventory); + } + + @Override + public void updateSlot(GeyserSession session, Inventory inventory, int slot) { + updater.updateSlot(this, session, inventory, slot); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/AnvilInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/AnvilInventoryTranslator.java new file mode 100644 index 000000000..b7aaad7b1 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/AnvilInventoryTranslator.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory; + +import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import org.geysermc.geyser.inventory.AnvilContainer; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.inventory.PlayerInventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.inventory.BedrockContainerSlot; +import org.geysermc.geyser.inventory.updater.AnvilInventoryUpdater; + +public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator { + public AnvilInventoryTranslator() { + super(3, "minecraft:anvil[facing=north]", com.nukkitx.protocol.bedrock.data.inventory.ContainerType.ANVIL, AnvilInventoryUpdater.INSTANCE, + "minecraft:chipped_anvil", "minecraft:damaged_anvil"); + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + return switch (slotInfoData.getContainer()) { + case ANVIL_INPUT -> 0; + case ANVIL_MATERIAL -> 1; + case ANVIL_RESULT, CREATIVE_OUTPUT -> 2; + default -> super.bedrockSlotToJava(slotInfoData); + }; + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + return switch (slot) { + case 0 -> new BedrockContainerSlot(ContainerSlotType.ANVIL_INPUT, 1); + case 1 -> new BedrockContainerSlot(ContainerSlotType.ANVIL_MATERIAL, 2); + case 2 -> new BedrockContainerSlot(ContainerSlotType.ANVIL_RESULT, 50); + default -> super.javaSlotToBedrockContainer(slot); + }; + } + + @Override + public int javaSlotToBedrock(int slot) { + return switch (slot) { + case 0 -> 1; + case 1 -> 2; + case 2 -> 50; + default -> super.javaSlotToBedrock(slot); + }; + } + + @Override + public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) { + return new AnvilContainer(name, windowId, this.size, containerType, playerInventory); + } + + @Override + public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { + // The only property sent by Java is key 0 which is the level cost + if (key != 0) return; + AnvilContainer anvilContainer = (AnvilContainer) inventory; + anvilContainer.setJavaLevelCost(value); + anvilContainer.setUseJavaLevelCost(true); + updateSlot(session, anvilContainer, 1); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BaseInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/BaseInventoryTranslator.java similarity index 50% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/BaseInventoryTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/inventory/BaseInventoryTranslator.java index 6f00fc4d7..becca359b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BaseInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/BaseInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,18 +23,20 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.inventory; +package org.geysermc.geyser.translator.inventory; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; -import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.action.InventoryActionDataTranslator; +import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import org.geysermc.geyser.inventory.Container; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.inventory.PlayerInventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.inventory.BedrockContainerSlot; +import org.geysermc.geyser.inventory.SlotType; -import java.util.List; - -public abstract class BaseInventoryTranslator extends InventoryTranslator{ - BaseInventoryTranslator(int size) { +public abstract class BaseInventoryTranslator extends InventoryTranslator { + public BaseInventoryTranslator(int size) { super(size); } @@ -44,15 +46,18 @@ public abstract class BaseInventoryTranslator extends InventoryTranslator{ } @Override - public int bedrockSlotToJava(InventoryActionData action) { - int slotnum = action.getSlot(); - if (action.getSource().getContainerId() == ContainerId.INVENTORY) { - //hotbar - if (slotnum >= 9) { - return slotnum + this.size - 9; - } else { - return slotnum + this.size + 27; - } + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + int slotnum = slotInfoData.getSlot(); + switch (slotInfoData.getContainer()) { + case HOTBAR_AND_INVENTORY: + case HOTBAR: + case INVENTORY: + //hotbar + if (slotnum >= 9) { + return slotnum + this.size - 9; + } else { + return slotnum + this.size + 27; + } } return slotnum; } @@ -70,13 +75,26 @@ public abstract class BaseInventoryTranslator extends InventoryTranslator{ return slot; } + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot >= this.size) { + final int tmp = slot - this.size; + if (tmp < 27) { + return new BedrockContainerSlot(ContainerSlotType.INVENTORY, tmp + 9); + } else { + return new BedrockContainerSlot(ContainerSlotType.HOTBAR, tmp - 27); + } + } + throw new IllegalArgumentException("Unknown bedrock slot"); + } + @Override public SlotType getSlotType(int javaSlot) { return SlotType.NORMAL; } @Override - public void translateActions(GeyserSession session, Inventory inventory, List actions) { - InventoryActionDataTranslator.translate(this, session, inventory, actions); + public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) { + return new Container(name, windowId, this.size, containerType, playerInventory); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/BeaconInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/BeaconInventoryTranslator.java new file mode 100644 index 000000000..f0820d9e8 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/BeaconInventoryTranslator.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory; + +import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSetBeaconPacket; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.BeaconPaymentStackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; +import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; +import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; +import org.geysermc.geyser.inventory.BeaconContainer; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.inventory.PlayerInventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.inventory.BedrockContainerSlot; +import org.geysermc.geyser.inventory.holder.BlockInventoryHolder; +import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; +import org.geysermc.geyser.util.InventoryUtils; + +import java.util.Collections; + +public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator { + public BeaconInventoryTranslator() { + super(1, new BlockInventoryHolder("minecraft:beacon", com.nukkitx.protocol.bedrock.data.inventory.ContainerType.BEACON) { + @Override + protected boolean checkInteractionPosition(GeyserSession session) { + // Since we can't fall back to a virtual inventory, let's make opening one easier + return true; + } + + @Override + public void openInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { + if (!((BeaconContainer) inventory).isUsingRealBlock()) { + InventoryUtils.closeInventory(session, inventory.getId(), false); + return; + } + super.openInventory(translator, session, inventory); + } + }, UIInventoryUpdater.INSTANCE); + } + + @Override + public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { + //FIXME?: Beacon graphics look weird after inputting an item. This might be a Bedrock bug, since it resets to nothing + // on BDS + BeaconContainer beaconContainer = (BeaconContainer) inventory; + switch (key) { + case 0: + // Power - beacon doesn't use this, and uses the block position instead + break; + case 1: + beaconContainer.setPrimaryId(value == -1 ? 0 : value); + break; + case 2: + beaconContainer.setSecondaryId(value == -1 ? 0 : value); + break; + } + + // Send a block entity data packet update to the fake beacon inventory + Vector3i position = inventory.getHolderPosition(); + NbtMapBuilder builder = NbtMap.builder() + .putInt("x", position.getX()) + .putInt("y", position.getY()) + .putInt("z", position.getZ()) + .putString("CustomName", inventory.getTitle()) + .putString("id", "Beacon") + .putInt("primary", beaconContainer.getPrimaryId()) + .putInt("secondary", beaconContainer.getSecondaryId()); + + BlockEntityDataPacket packet = new BlockEntityDataPacket(); + packet.setBlockPosition(position); + packet.setData(builder.build()); + session.sendUpstreamPacket(packet); + } + + @Override + public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { + return action.getType() == StackRequestActionType.BEACON_PAYMENT; + } + + @Override + public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + // Input a beacon payment + BeaconPaymentStackRequestActionData beaconPayment = (BeaconPaymentStackRequestActionData) request.getActions()[0]; + ServerboundSetBeaconPacket packet = new ServerboundSetBeaconPacket(beaconPayment.getPrimaryEffect(), beaconPayment.getSecondaryEffect()); + session.sendDownstreamPacket(packet); + return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet())); + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + if (slotInfoData.getContainer() == ContainerSlotType.BEACON_PAYMENT) { + return 0; + } + return super.bedrockSlotToJava(slotInfoData); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot == 0) { + return new BedrockContainerSlot(ContainerSlotType.BEACON_PAYMENT, 27); + } + return super.javaSlotToBedrockContainer(slot); + } + + @Override + public int javaSlotToBedrock(int slot) { + if (slot == 0) { + return 27; + } + return super.javaSlotToBedrock(slot); + } + + @Override + public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) { + return new BeaconContainer(name, windowId, this.size, containerType, playerInventory); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BrewingInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/BrewingInventoryTranslator.java similarity index 61% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/BrewingInventoryTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/inventory/BrewingInventoryTranslator.java index 89cdbe8d7..43bea55ab 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BrewingInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/BrewingInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,18 +23,20 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.inventory; +package org.geysermc.geyser.translator.inventory; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; import com.nukkitx.protocol.bedrock.packet.ContainerSetDataPacket; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.inventory.BedrockContainerSlot; +import org.geysermc.geyser.inventory.updater.ContainerInventoryUpdater; -public class BrewingInventoryTranslator extends BlockInventoryTranslator { +public class BrewingInventoryTranslator extends AbstractBlockInventoryTranslator { public BrewingInventoryTranslator() { - super(5, "minecraft:brewing_stand[has_bottle_0=false,has_bottle_1=false,has_bottle_2=false]", ContainerType.BREWING_STAND, new ContainerInventoryUpdater()); + super(5, "minecraft:brewing_stand[has_bottle_0=false,has_bottle_1=false,has_bottle_2=false]", ContainerType.BREWING_STAND, ContainerInventoryUpdater.INSTANCE); } @Override @@ -66,34 +68,37 @@ public class BrewingInventoryTranslator extends BlockInventoryTranslator { } @Override - public int bedrockSlotToJava(InventoryActionData action) { - final int slot = super.bedrockSlotToJava(action); - switch (slot) { - case 0: - return 3; - case 1: - return 0; - case 2: - return 1; - case 3: - return 2; - default: - return slot; + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + if (slotInfoData.getContainer() == ContainerSlotType.BREWING_INPUT) { + // Ingredient + return 3; } + if (slotInfoData.getContainer() == ContainerSlotType.BREWING_RESULT) { + // Potions + return slotInfoData.getSlot() - 1; + } + return super.bedrockSlotToJava(slotInfoData); } @Override public int javaSlotToBedrock(int slot) { - switch (slot) { - case 0: - return 1; - case 1: - return 2; - case 2: - return 3; - case 3: - return 0; - } - return super.javaSlotToBedrock(slot); + return switch (slot) { + case 0 -> 1; + case 1 -> 2; + case 2 -> 3; + case 3 -> 0; + case 4 -> 4; + default -> super.javaSlotToBedrock(slot); + }; + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + return switch (slot) { + case 0, 1, 2 -> new BedrockContainerSlot(ContainerSlotType.BREWING_RESULT, javaSlotToBedrock(slot)); + case 3 -> new BedrockContainerSlot(ContainerSlotType.BREWING_INPUT, 0); + case 4 -> new BedrockContainerSlot(ContainerSlotType.BREWING_FUEL, 4); + default -> super.javaSlotToBedrockContainer(slot); + }; } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/CartographyInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/CartographyInventoryTranslator.java new file mode 100644 index 000000000..08caa7784 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/CartographyInventoryTranslator.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory; + +import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import org.geysermc.geyser.inventory.CartographyContainer; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.inventory.PlayerInventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.inventory.BedrockContainerSlot; +import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; + +public class CartographyInventoryTranslator extends AbstractBlockInventoryTranslator { + public CartographyInventoryTranslator() { + super(3, "minecraft:cartography_table", com.nukkitx.protocol.bedrock.data.inventory.ContainerType.CARTOGRAPHY, UIInventoryUpdater.INSTANCE); + } + + @Override + public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, ContainerSlotType bedrockSourceContainer, + int javaSourceSlot, ContainerSlotType bedrockDestinationContainer, int javaDestinationSlot) { + if (javaDestinationSlot == 0) { + // Bedrock Edition can use paper or an empty map in slot 0 + GeyserItemStack itemStack = javaSourceSlot == -1 ? session.getPlayerInventory().getCursor() : inventory.getItem(javaSourceSlot); + return itemStack.getMapping(session).getJavaIdentifier().equals("minecraft:paper") || itemStack.getMapping(session).getJavaIdentifier().equals("minecraft:map"); + } else if (javaDestinationSlot == 1) { + // Bedrock Edition can use a compass to create locator maps, or use a filled map, in the ADDITIONAL slot + GeyserItemStack itemStack = javaSourceSlot == -1 ? session.getPlayerInventory().getCursor() : inventory.getItem(javaSourceSlot); + return itemStack.getMapping(session).getJavaIdentifier().equals("minecraft:compass") || itemStack.getMapping(session).getJavaIdentifier().equals("minecraft:filled_map"); + } + return false; + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + return switch (slotInfoData.getContainer()) { + case CARTOGRAPHY_INPUT -> 0; + case CARTOGRAPHY_ADDITIONAL -> 1; + case CARTOGRAPHY_RESULT, CREATIVE_OUTPUT -> 2; + default -> super.bedrockSlotToJava(slotInfoData); + }; + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + return switch (slot) { + case 0 -> new BedrockContainerSlot(ContainerSlotType.CARTOGRAPHY_INPUT, 12); + case 1 -> new BedrockContainerSlot(ContainerSlotType.CARTOGRAPHY_ADDITIONAL, 13); + case 2 -> new BedrockContainerSlot(ContainerSlotType.CARTOGRAPHY_RESULT, 50); + default -> super.javaSlotToBedrockContainer(slot); + }; + } + + @Override + public int javaSlotToBedrock(int slot) { + return switch (slot) { + case 0 -> 12; + case 1 -> 13; + case 2 -> 50; + default -> super.javaSlotToBedrock(slot); + }; + } + + @Override + public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) { + return new CartographyContainer(name, windowId, this.size, containerType, playerInventory); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/CraftingInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/CraftingInventoryTranslator.java new file mode 100644 index 000000000..ea01dc43d --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/CraftingInventoryTranslator.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import org.geysermc.geyser.inventory.BedrockContainerSlot; +import org.geysermc.geyser.inventory.SlotType; +import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; + +public class CraftingInventoryTranslator extends AbstractBlockInventoryTranslator { + public CraftingInventoryTranslator() { + super(10, "minecraft:crafting_table", ContainerType.WORKBENCH, UIInventoryUpdater.INSTANCE); + } + + @Override + public SlotType getSlotType(int javaSlot) { + if (javaSlot == 0) { + return SlotType.OUTPUT; + } + return SlotType.NORMAL; + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot >= 1 && slot <= 9) { + return new BedrockContainerSlot(ContainerSlotType.CRAFTING_INPUT, slot + 31); + } + if (slot == 0) { + return new BedrockContainerSlot(ContainerSlotType.CRAFTING_OUTPUT, 0); + } + return super.javaSlotToBedrockContainer(slot); + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + if (slotInfoData.getContainer() == ContainerSlotType.CRAFTING_INPUT) { + // Java goes from 1 - 9, left to right then up to down + // Bedrock is the same, but it starts from 32. + return slotInfoData.getSlot() - 31; + } + if (slotInfoData.getContainer() == ContainerSlotType.CRAFTING_OUTPUT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) { + return 0; + } + return super.bedrockSlotToJava(slotInfoData); + } + + @Override + public int javaSlotToBedrock(int slot) { + if (slot < size) { + return slot == 0 ? 50 : slot + 31; + } + return super.javaSlotToBedrock(slot); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/EnchantingInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/EnchantingInventoryTranslator.java new file mode 100644 index 000000000..f18a902ff --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/EnchantingInventoryTranslator.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory; + +import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerButtonClickPacket; +import com.nukkitx.protocol.bedrock.data.inventory.*; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftRecipeStackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; +import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; +import com.nukkitx.protocol.bedrock.packet.PlayerEnchantOptionsPacket; +import org.geysermc.geyser.inventory.EnchantingContainer; +import org.geysermc.geyser.inventory.GeyserEnchantOption; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.inventory.PlayerInventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.inventory.BedrockContainerSlot; +import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; +import org.geysermc.geyser.inventory.item.Enchantment; + +import java.util.Arrays; +import java.util.Collections; + +public class EnchantingInventoryTranslator extends AbstractBlockInventoryTranslator { + public EnchantingInventoryTranslator() { + super(2, "minecraft:enchanting_table", com.nukkitx.protocol.bedrock.data.inventory.ContainerType.ENCHANTMENT, UIInventoryUpdater.INSTANCE); + } + + @Override + public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { + int slotToUpdate; + EnchantingContainer enchantingInventory = (EnchantingContainer) inventory; + boolean shouldUpdate = false; + switch (key) { + case 0: + case 1: + case 2: + // Experience required + slotToUpdate = key; + enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].setXpCost(value); + break; + case 4: + case 5: + case 6: + // Enchantment type + slotToUpdate = key - 4; + // "value" here is the Java enchant ordinal, so that does not need to be changed + // The Bedrock index might need changed, so let's look it up and see. + int bedrockIndex = value; + if (bedrockIndex != -1) { + Enchantment enchantment = Enchantment.getByJavaIdentifier("minecraft:" + Enchantment.JavaEnchantment.of(bedrockIndex).name().toLowerCase()); + if (enchantment != null) { + // Convert the Java enchantment index to Bedrock's + bedrockIndex = enchantment.ordinal(); + } else { + // There is no Bedrock enchantment equivalent + bedrockIndex = -1; + } + } + enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].setEnchantIndex(value, bedrockIndex); + break; + case 7: + case 8: + case 9: + // Enchantment level + slotToUpdate = key - 7; + enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].setEnchantLevel(value); + shouldUpdate = true; // Java sends each property as its own packet, so let's only update after all properties have been sent + break; + default: + return; + } + GeyserEnchantOption enchantOption = enchantingInventory.getGeyserEnchantOptions()[slotToUpdate]; + if (shouldUpdate && enchantOption.hasChanged()) { + enchantingInventory.getEnchantOptions()[slotToUpdate] = enchantOption.build(session); + PlayerEnchantOptionsPacket packet = new PlayerEnchantOptionsPacket(); + packet.getOptions().addAll(Arrays.asList(enchantingInventory.getEnchantOptions())); + session.sendUpstreamPacket(packet); + } + } + + @Override + public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { + return action.getType() == StackRequestActionType.CRAFT_RECIPE; + } + + @Override + public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + // Client has requested an item to be enchanted + CraftRecipeStackRequestActionData craftRecipeData = (CraftRecipeStackRequestActionData) request.getActions()[0]; + EnchantingContainer enchantingInventory = (EnchantingContainer) inventory; + int javaSlot = -1; + for (int i = 0; i < enchantingInventory.getEnchantOptions().length; i++) { + EnchantOptionData enchantData = enchantingInventory.getEnchantOptions()[i]; + if (enchantData != null) { + if (craftRecipeData.getRecipeNetworkId() == enchantData.getEnchantNetId()) { + // Enchant net ID is how we differentiate between what item Bedrock wants + javaSlot = enchantingInventory.getGeyserEnchantOptions()[i].getJavaIndex(); + break; + } + } + } + if (javaSlot == -1) { + // Slot should be determined as 0, 1, or 2 + return rejectRequest(request); + } + ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getId(), javaSlot); + session.sendDownstreamPacket(packet); + return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet())); + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + if (slotInfoData.getContainer() == ContainerSlotType.ENCHANTING_INPUT) { + return 0; + } + if (slotInfoData.getContainer() == ContainerSlotType.ENCHANTING_LAPIS) { + return 1; + } + return super.bedrockSlotToJava(slotInfoData); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot == 0) { + return new BedrockContainerSlot(ContainerSlotType.ENCHANTING_INPUT, 14); + } + if (slot == 1) { + return new BedrockContainerSlot(ContainerSlotType.ENCHANTING_LAPIS, 15); + } + return super.javaSlotToBedrockContainer(slot); + } + + @Override + public int javaSlotToBedrock(int slot) { + if (slot == 0) { + return 14; + } + if (slot == 1) { + return 15; + } + return super.javaSlotToBedrock(slot); + } + + @Override + public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) { + return new EnchantingContainer(name, windowId, this.size, containerType, playerInventory); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/Generic3X3InventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/Generic3X3InventoryTranslator.java new file mode 100644 index 000000000..4265b39e3 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/Generic3X3InventoryTranslator.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory; + +import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; +import org.geysermc.geyser.inventory.Generic3X3Container; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.inventory.PlayerInventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.inventory.BedrockContainerSlot; +import org.geysermc.geyser.inventory.updater.ContainerInventoryUpdater; + +/** + * Droppers and dispensers + */ +public class Generic3X3InventoryTranslator extends AbstractBlockInventoryTranslator { + public Generic3X3InventoryTranslator() { + super(9, "minecraft:dispenser[facing=north,triggered=false]", com.nukkitx.protocol.bedrock.data.inventory.ContainerType.DISPENSER, ContainerInventoryUpdater.INSTANCE, + "minecraft:dropper"); + } + + @Override + public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) { + return new Generic3X3Container(name, windowId, this.size, containerType, playerInventory); + } + + @Override + public void openInventory(GeyserSession session, Inventory inventory) { + ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket(); + containerOpenPacket.setId((byte) inventory.getId()); + // Required for opening the real block - otherwise, if the container type is incorrect, it refuses to open + containerOpenPacket.setType(((Generic3X3Container) inventory).isDropper() ? com.nukkitx.protocol.bedrock.data.inventory.ContainerType.DROPPER : com.nukkitx.protocol.bedrock.data.inventory.ContainerType.DISPENSER); + containerOpenPacket.setBlockPosition(inventory.getHolderPosition()); + containerOpenPacket.setUniqueEntityId(inventory.getHolderId()); + session.sendUpstreamPacket(containerOpenPacket); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot) { + if (javaSlot < this.size) { + return new BedrockContainerSlot(ContainerSlotType.CONTAINER, javaSlot); + } + return super.javaSlotToBedrockContainer(javaSlot); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/GrindstoneInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/GrindstoneInventoryTranslator.java new file mode 100644 index 000000000..fae539db8 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/GrindstoneInventoryTranslator.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import org.geysermc.geyser.inventory.BedrockContainerSlot; +import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; + +public class GrindstoneInventoryTranslator extends AbstractBlockInventoryTranslator { + public GrindstoneInventoryTranslator() { + super(3, "minecraft:grindstone[face=floor,facing=north]", ContainerType.GRINDSTONE, UIInventoryUpdater.INSTANCE); + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + return switch (slotInfoData.getContainer()) { + case GRINDSTONE_INPUT -> 0; + case GRINDSTONE_ADDITIONAL -> 1; + case GRINDSTONE_RESULT, CREATIVE_OUTPUT -> 2; + default -> super.bedrockSlotToJava(slotInfoData); + }; + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + return switch (slot) { + case 0 -> new BedrockContainerSlot(ContainerSlotType.GRINDSTONE_INPUT, 16); + case 1 -> new BedrockContainerSlot(ContainerSlotType.GRINDSTONE_ADDITIONAL, 17); + case 2 -> new BedrockContainerSlot(ContainerSlotType.GRINDSTONE_RESULT, 50); + default -> super.javaSlotToBedrockContainer(slot); + }; + } + + @Override + public int javaSlotToBedrock(int slot) { + return switch (slot) { + case 0 -> 16; + case 1 -> 17; + case 2 -> 50; + default -> super.javaSlotToBedrock(slot); + }; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/TridentEntity.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/HopperInventoryTranslator.java similarity index 55% rename from connector/src/main/java/org/geysermc/connector/entity/TridentEntity.java rename to core/src/main/java/org/geysermc/geyser/translator/inventory/HopperInventoryTranslator.java index 4a9007aba..9356dcfb6 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/TridentEntity.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/HopperInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,26 +23,26 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity; +package org.geysermc.geyser.translator.inventory; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import org.geysermc.geyser.inventory.BedrockContainerSlot; +import org.geysermc.geyser.inventory.updater.ContainerInventoryUpdater; -public class TridentEntity extends AbstractArrowEntity { - - public TridentEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); +/** + * Implemented on top of any block that does not have special properties implemented + */ +public class HopperInventoryTranslator extends AbstractBlockInventoryTranslator { + public HopperInventoryTranslator() { + super(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER, ContainerInventoryUpdater.INSTANCE); } @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 10) { - metadata.getFlags().setFlag(EntityFlag.ENCHANTED, (boolean) entityMetadata.getValue()); + public BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot) { + if (javaSlot < this.size) { + return new BedrockContainerSlot(ContainerSlotType.CONTAINER, javaSlot); } - - super.updateBedrockMetadata(entityMetadata, session); + return super.javaSlotToBedrockContainer(javaSlot); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java new file mode 100644 index 000000000..66fd6959e --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java @@ -0,0 +1,904 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; +import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; +import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; +import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; +import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import com.github.steveice10.opennbt.tag.builtin.IntTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.*; +import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; +import it.unimi.dsi.fastutil.ints.*; +import lombok.AllArgsConstructor; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.inventory.CartographyContainer; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.inventory.PlayerInventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.inventory.BedrockContainerSlot; +import org.geysermc.geyser.inventory.SlotType; +import org.geysermc.geyser.inventory.click.Click; +import org.geysermc.geyser.inventory.click.ClickPlan; +import org.geysermc.geyser.translator.inventory.chest.DoubleChestInventoryTranslator; +import org.geysermc.geyser.translator.inventory.chest.SingleChestInventoryTranslator; +import org.geysermc.geyser.translator.inventory.furnace.BlastFurnaceInventoryTranslator; +import org.geysermc.geyser.translator.inventory.furnace.FurnaceInventoryTranslator; +import org.geysermc.geyser.translator.inventory.furnace.SmokerInventoryTranslator; +import org.geysermc.geyser.util.InventoryUtils; +import org.geysermc.geyser.util.ItemUtils; + +import java.util.*; + +@AllArgsConstructor +public abstract class InventoryTranslator { + + public static final InventoryTranslator PLAYER_INVENTORY_TRANSLATOR = new PlayerInventoryTranslator(); + private static final Map INVENTORY_TRANSLATORS = new EnumMap<>(ContainerType.class) { + { + /* Chest UIs */ + put(ContainerType.GENERIC_9X1, new SingleChestInventoryTranslator(9)); + put(ContainerType.GENERIC_9X2, new SingleChestInventoryTranslator(18)); + put(ContainerType.GENERIC_9X3, new SingleChestInventoryTranslator(27)); + put(ContainerType.GENERIC_9X4, new DoubleChestInventoryTranslator(36)); + put(ContainerType.GENERIC_9X5, new DoubleChestInventoryTranslator(45)); + put(ContainerType.GENERIC_9X6, new DoubleChestInventoryTranslator(54)); + + /* Furnaces */ + put(ContainerType.FURNACE, new FurnaceInventoryTranslator()); + put(ContainerType.BLAST_FURNACE, new BlastFurnaceInventoryTranslator()); + put(ContainerType.SMOKER, new SmokerInventoryTranslator()); + + /* Specific Inventories */ + put(ContainerType.ANVIL, new AnvilInventoryTranslator()); + put(ContainerType.BEACON, new BeaconInventoryTranslator()); + put(ContainerType.BREWING_STAND, new BrewingInventoryTranslator()); + put(ContainerType.CARTOGRAPHY, new CartographyInventoryTranslator()); + put(ContainerType.CRAFTING, new CraftingInventoryTranslator()); + put(ContainerType.ENCHANTMENT, new EnchantingInventoryTranslator()); + put(ContainerType.HOPPER, new HopperInventoryTranslator()); + put(ContainerType.GENERIC_3X3, new Generic3X3InventoryTranslator()); + put(ContainerType.GRINDSTONE, new GrindstoneInventoryTranslator()); + put(ContainerType.LOOM, new LoomInventoryTranslator()); + put(ContainerType.MERCHANT, new MerchantInventoryTranslator()); + put(ContainerType.SHULKER_BOX, new ShulkerInventoryTranslator()); + put(ContainerType.SMITHING, new SmithingInventoryTranslator()); + put(ContainerType.STONECUTTER, new StonecutterInventoryTranslator()); + + /* Lectern */ + put(ContainerType.LECTERN, new LecternInventoryTranslator()); + } + }; + + public static final int PLAYER_INVENTORY_SIZE = 36; + public static final int PLAYER_INVENTORY_OFFSET = 9; + + public final int size; + + public abstract void prepareInventory(GeyserSession session, Inventory inventory); + public abstract void openInventory(GeyserSession session, Inventory inventory); + public abstract void closeInventory(GeyserSession session, Inventory inventory); + public abstract void updateProperty(GeyserSession session, Inventory inventory, int key, int value); + public abstract void updateInventory(GeyserSession session, Inventory inventory); + public abstract void updateSlot(GeyserSession session, Inventory inventory, int slot); + public abstract int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData); + public abstract int javaSlotToBedrock(int javaSlot); + public abstract BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot); + public abstract SlotType getSlotType(int javaSlot); + public abstract Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory); + + /** + * Should be overwritten in cases where specific inventories should reject an item being in a specific spot. + * For examples, looms use this to reject items that are dyes in Bedrock but not in Java. + * + * The source/destination slot will be -1 if the cursor is the slot + * + * @return true if this transfer should be rejected + */ + public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, ContainerSlotType bedrockSourceContainer, + int javaSourceSlot, ContainerSlotType bedrockDestinationContainer, int javaDestinationSlot) { + return false; + } + + /** + * Should be overrided if this request matches a certain criteria and shouldn't be treated normally. + * E.G. anvil renaming or enchanting + */ + public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { + return false; + } + + /** + * If {@link #shouldHandleRequestFirst(StackRequestActionData, Inventory)} returns true, this will be called + */ + public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + return rejectRequest(request); + } + + public void translateRequests(GeyserSession session, Inventory inventory, List requests) { + boolean refresh = false; + ItemStackResponsePacket responsePacket = new ItemStackResponsePacket(); + for (ItemStackRequest request : requests) { + ItemStackResponsePacket.Response response; + if (request.getActions().length > 0) { + StackRequestActionData firstAction = request.getActions()[0]; + if (shouldHandleRequestFirst(firstAction, inventory)) { + // Some special request that shouldn't be processed normally + response = translateSpecialRequest(session, inventory, request); + } else { + response = switch (firstAction.getType()) { + case CRAFT_RECIPE -> translateCraftingRequest(session, inventory, request); + case CRAFT_RECIPE_AUTO -> translateAutoCraftingRequest(session, inventory, request); + case CRAFT_CREATIVE -> + // This is also used for pulling items out of creative + translateCreativeRequest(session, inventory, request); + default -> translateRequest(session, inventory, request); + }; + } + } else { + response = rejectRequest(request); + } + + if (response.getResult() != ItemStackResponsePacket.ResponseStatus.OK) { + // Sync our copy of the inventory with Bedrock's to prevent desyncs + refresh = true; + } + + responsePacket.getEntries().add(response); + } + session.sendUpstreamPacket(responsePacket); + + if (refresh) { + InventoryUtils.updateCursor(session); + updateInventory(session, inventory); + } + } + + public ItemStackResponsePacket.Response translateRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + ClickPlan plan = new ClickPlan(session, this, inventory); + IntSet affectedSlots = new IntOpenHashSet(); + for (StackRequestActionData action : request.getActions()) { + GeyserItemStack cursor = session.getPlayerInventory().getCursor(); + switch (action.getType()) { + case TAKE: + case PLACE: { + TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action; + if (!(checkNetId(session, inventory, transferAction.getSource()) && checkNetId(session, inventory, transferAction.getDestination()))) { + if (session.getGameMode().equals(GameMode.CREATIVE) && transferAction.getSource().getContainer() == ContainerSlotType.CRAFTING_INPUT && + transferAction.getSource().getSlot() >= 28 && transferAction.getSource().getSlot() <= 31) { + return rejectRequest(request, false); + } + if (session.getGeyser().getConfig().isDebugMode()) { + session.getGeyser().getLogger().error("DEBUG: About to reject TAKE/PLACE request made by " + session.name()); + dumpStackRequestDetails(session, inventory, transferAction.getSource(), transferAction.getDestination()); + } + return rejectRequest(request); + } + + int sourceSlot = bedrockSlotToJava(transferAction.getSource()); + int destSlot = bedrockSlotToJava(transferAction.getDestination()); + + if (shouldRejectItemPlace(session, inventory, transferAction.getSource().getContainer(), + isCursor(transferAction.getSource()) ? -1 : sourceSlot, + transferAction.getDestination().getContainer(), isCursor(transferAction.getDestination()) ? -1 : destSlot)) { + // This item would not be here in Java + return rejectRequest(request, false); + } + + if (isCursor(transferAction.getSource()) && isCursor(transferAction.getDestination())) { //??? + return rejectRequest(request); + } else if (isCursor(transferAction.getSource())) { //releasing cursor + int sourceAmount = cursor.getAmount(); + if (transferAction.getCount() == sourceAmount) { //release all + plan.add(Click.LEFT, destSlot); + } else { //release some + for (int i = 0; i < transferAction.getCount(); i++) { + plan.add(Click.RIGHT, destSlot); + } + } + } else if (isCursor(transferAction.getDestination())) { //picking up into cursor + GeyserItemStack sourceItem = plan.getItem(sourceSlot); + int sourceAmount = sourceItem.getAmount(); + if (cursor.isEmpty()) { //picking up into empty cursor + if (transferAction.getCount() == sourceAmount) { //pickup all + plan.add(Click.LEFT, sourceSlot); + } else if (transferAction.getCount() == sourceAmount - (sourceAmount / 2)) { //larger half; simple right click + plan.add(Click.RIGHT, sourceSlot); + } else { //pickup some; not a simple right click + plan.add(Click.LEFT, sourceSlot); //first pickup all + for (int i = 0; i < sourceAmount - transferAction.getCount(); i++) { + plan.add(Click.RIGHT, sourceSlot); //release extra items back into source slot + } + } + } else { //pickup into non-empty cursor + if (!InventoryUtils.canStack(cursor, plan.getItem(sourceSlot))) { //doesn't make sense, reject + return rejectRequest(request); + } + if (transferAction.getCount() != sourceAmount) { + int tempSlot = findTempSlot(inventory, cursor, false, sourceSlot); + if (tempSlot == -1) { + return rejectRequest(request); + } + plan.add(Click.LEFT, tempSlot); //place cursor into temp slot + plan.add(Click.LEFT, sourceSlot); //pickup source items into cursor + for (int i = 0; i < transferAction.getCount(); i++) { + plan.add(Click.RIGHT, tempSlot); //partially transfer source items into temp slot (original cursor) + } + plan.add(Click.LEFT, sourceSlot); //return remaining source items + plan.add(Click.LEFT, tempSlot); //retrieve original cursor items from temp slot + } else { + if (getSlotType(sourceSlot).equals(SlotType.NORMAL)) { + plan.add(Click.LEFT, sourceSlot); //release cursor onto source slot + } + plan.add(Click.LEFT, sourceSlot); //pickup combined cursor and source + } + } + } else { //transfer from one slot to another + int tempSlot = -1; + if (!plan.getCursor().isEmpty()) { + tempSlot = findTempSlot(inventory, cursor, false, sourceSlot, destSlot); + if (tempSlot == -1) { + return rejectRequest(request); + } + plan.add(Click.LEFT, tempSlot); //place cursor into temp slot + } + + transferSlot(plan, sourceSlot, destSlot, transferAction.getCount()); + + if (tempSlot != -1) { + plan.add(Click.LEFT, tempSlot); //retrieve original cursor + } + } + break; + } + case SWAP: { + SwapStackRequestActionData swapAction = (SwapStackRequestActionData) action; + if (!(checkNetId(session, inventory, swapAction.getSource()) && checkNetId(session, inventory, swapAction.getDestination()))) { + if (session.getGeyser().getConfig().isDebugMode()) { + session.getGeyser().getLogger().error("DEBUG: About to reject SWAP request made by " + session.name()); + dumpStackRequestDetails(session, inventory, swapAction.getSource(), swapAction.getDestination()); + } + return rejectRequest(request); + } + + int sourceSlot = bedrockSlotToJava(swapAction.getSource()); + int destSlot = bedrockSlotToJava(swapAction.getDestination()); + boolean isSourceCursor = isCursor(swapAction.getSource()); + boolean isDestCursor = isCursor(swapAction.getDestination()); + + if (shouldRejectItemPlace(session, inventory, swapAction.getSource().getContainer(), + isSourceCursor ? -1 : sourceSlot, + swapAction.getDestination().getContainer(), isDestCursor ? -1 : destSlot)) { + // This item would not be here in Java + return rejectRequest(request, false); + } + + if (isSourceCursor && isDestCursor) { //??? + return rejectRequest(request); + } else if (isSourceCursor) { //swap cursor + if (InventoryUtils.canStack(cursor, plan.getItem(destSlot))) { //TODO: cannot simply swap if cursor stacks with slot (temp slot) + return rejectRequest(request); + } + plan.add(Click.LEFT, destSlot); + } else if (isDestCursor) { //swap cursor + if (InventoryUtils.canStack(cursor, plan.getItem(sourceSlot))) { //TODO + return rejectRequest(request); + } + plan.add(Click.LEFT, sourceSlot); + } else { + if (!cursor.isEmpty()) { //TODO: (temp slot) + return rejectRequest(request); + } + if (sourceSlot == destSlot) { //doesn't make sense + return rejectRequest(request); + } + if (InventoryUtils.canStack(plan.getItem(sourceSlot), plan.getItem(destSlot))) { //TODO: (temp slot) + return rejectRequest(request); + } + plan.add(Click.LEFT, sourceSlot); //pickup source into cursor + plan.add(Click.LEFT, destSlot); //swap cursor with dest slot + plan.add(Click.LEFT, sourceSlot); //release cursor onto source + } + break; + } + case DROP: { + DropStackRequestActionData dropAction = (DropStackRequestActionData) action; + if (!checkNetId(session, inventory, dropAction.getSource())) + return rejectRequest(request); + + if (isCursor(dropAction.getSource())) { //clicking outside of window + int sourceAmount = plan.getCursor().getAmount(); + if (dropAction.getCount() == sourceAmount) { //drop all + plan.add(Click.LEFT_OUTSIDE, Click.OUTSIDE_SLOT); + } else { //drop some + for (int i = 0; i < dropAction.getCount(); i++) { + plan.add(Click.RIGHT_OUTSIDE, Click.OUTSIDE_SLOT); //drop one until goal is met + } + } + } else { //dropping from inventory + int sourceSlot = bedrockSlotToJava(dropAction.getSource()); + int sourceAmount = plan.getItem(sourceSlot).getAmount(); + if (dropAction.getCount() == sourceAmount && sourceAmount > 1) { //dropping all? (prefer DROP_ONE if only one) + plan.add(Click.DROP_ALL, sourceSlot); + } else { //drop some + for (int i = 0; i < dropAction.getCount(); i++) { + plan.add(Click.DROP_ONE, sourceSlot); //drop one until goal is met + } + } + } + break; + } + case CONSUME: { // Tends to be called for UI inventories + if (inventory instanceof CartographyContainer) { + // TODO add this for more inventories? Only seems to glitch out the cartography table, though. + ConsumeStackRequestActionData consumeData = (ConsumeStackRequestActionData) action; + + int sourceSlot = bedrockSlotToJava(consumeData.getSource()); + if ((sourceSlot == 0 && inventory.getItem(1).isEmpty()) || (sourceSlot == 1 && inventory.getItem(0).isEmpty())) { + // Java doesn't allow an item to be renamed; this is why one of the slots could remain empty for Bedrock + // We check this now since setting the inventory slots here messes up shouldRejectItemPlace + return rejectRequest(request, false); + } + + if (sourceSlot == 1) { + // Decrease the item count, but only after both slots are checked. + // Otherwise, the slot 1 check will fail + GeyserItemStack item = inventory.getItem(sourceSlot); + item.setAmount(item.getAmount() - consumeData.getCount()); + if (item.isEmpty()) { + inventory.setItem(sourceSlot, GeyserItemStack.EMPTY, session); + } + + GeyserItemStack itemZero = inventory.getItem(0); + itemZero.setAmount(itemZero.getAmount() - consumeData.getCount()); + if (itemZero.isEmpty()) { + inventory.setItem(0, GeyserItemStack.EMPTY, session); + } + } + affectedSlots.add(sourceSlot); + } + break; + } + case CRAFT_RECIPE: // Called by stonecutters 1.18+ + case CRAFT_RECIPE_AUTO: // Called by villagers + case CRAFT_NON_IMPLEMENTED_DEPRECATED: // Tends to be called for UI inventories + case CRAFT_RESULTS_DEPRECATED: // Tends to be called for UI inventories + case CRAFT_RECIPE_OPTIONAL: // Anvils and cartography tables will handle this + case CRAFT_LOOM: // Looms 1.17.40+ + case CRAFT_REPAIR_AND_DISENCHANT: { // Grindstones 1.17.40+ + break; + } + default: + return rejectRequest(request); + } + } + plan.execute(false); + affectedSlots.addAll(plan.getAffectedSlots()); + return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); + } + + public ItemStackResponsePacket.Response translateCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + int resultSize = 0; + int timesCrafted; + CraftState craftState = CraftState.START; + + int leftover = 0; + ClickPlan plan = new ClickPlan(session, this, inventory); + for (StackRequestActionData action : request.getActions()) { + switch (action.getType()) { + case CRAFT_RECIPE: { + if (craftState != CraftState.START) { + return rejectRequest(request); + } + craftState = CraftState.RECIPE_ID; + break; + } + case CRAFT_RESULTS_DEPRECATED: { + CraftResultsDeprecatedStackRequestActionData deprecatedCraftAction = (CraftResultsDeprecatedStackRequestActionData) action; + if (craftState != CraftState.RECIPE_ID) { + return rejectRequest(request); + } + craftState = CraftState.DEPRECATED; + + if (deprecatedCraftAction.getResultItems().length != 1) { + return rejectRequest(request); + } + resultSize = deprecatedCraftAction.getResultItems()[0].getCount(); + timesCrafted = deprecatedCraftAction.getTimesCrafted(); + if (resultSize <= 0 || timesCrafted <= 0) { + return rejectRequest(request); + } + break; + } + case CONSUME: { + if (craftState != CraftState.DEPRECATED && craftState != CraftState.INGREDIENTS) { + return rejectRequest(request); + } + craftState = CraftState.INGREDIENTS; + break; + } + case TAKE: + case PLACE: { + TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action; + if (craftState != CraftState.INGREDIENTS && craftState != CraftState.TRANSFER) { + return rejectRequest(request); + } + craftState = CraftState.TRANSFER; + + if (transferAction.getSource().getContainer() != ContainerSlotType.CREATIVE_OUTPUT) { + return rejectRequest(request); + } + if (transferAction.getCount() <= 0) { + return rejectRequest(request); + } + + int sourceSlot = bedrockSlotToJava(transferAction.getSource()); + int destSlot = bedrockSlotToJava(transferAction.getDestination()); + + if (isCursor(transferAction.getDestination())) { + plan.add(Click.LEFT, sourceSlot); + craftState = CraftState.DONE; + } else { + if (leftover != 0) { + if (transferAction.getCount() > leftover) { + return rejectRequest(request); + } + if (transferAction.getCount() == leftover) { + plan.add(Click.LEFT, destSlot); + } else { + for (int i = 0; i < transferAction.getCount(); i++) { + plan.add(Click.RIGHT, destSlot); + } + } + leftover -= transferAction.getCount(); + break; + } + + int remainder = transferAction.getCount() % resultSize; + int timesToCraft = transferAction.getCount() / resultSize; + for (int i = 0; i < timesToCraft; i++) { + plan.add(Click.LEFT, sourceSlot); + plan.add(Click.LEFT, destSlot); + } + if (remainder > 0) { + plan.add(Click.LEFT, 0); + for (int i = 0; i < remainder; i++) { + plan.add(Click.RIGHT, destSlot); + } + leftover = resultSize - remainder; + } + } + break; + } + default: + return rejectRequest(request); + } + } + plan.execute(false); + return acceptRequest(request, makeContainerEntries(session, inventory, plan.getAffectedSlots())); + } + + public ItemStackResponsePacket.Response translateAutoCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + int gridSize; + int gridDimensions; + if (this instanceof PlayerInventoryTranslator) { + gridSize = 4; + gridDimensions = 2; + } else if (this instanceof CraftingInventoryTranslator) { + gridSize = 9; + gridDimensions = 3; + } else { + return rejectRequest(request); + } + + Recipe recipe; + Ingredient[] ingredients = new Ingredient[0]; + ItemStack output = null; + int recipeWidth = 0; + int ingRemaining = 0; + int ingredientIndex = -1; + + Int2IntMap consumedSlots = new Int2IntOpenHashMap(); + int prioritySlot = -1; + int tempSlot; + + int resultSize; + int timesCrafted = 0; + Int2ObjectMap ingredientMap = new Int2ObjectOpenHashMap<>(); + CraftState craftState = CraftState.START; + + ClickPlan plan = new ClickPlan(session, this, inventory); + requestLoop: + for (StackRequestActionData action : request.getActions()) { + switch (action.getType()) { + case CRAFT_RECIPE_AUTO: { + AutoCraftRecipeStackRequestActionData autoCraftAction = (AutoCraftRecipeStackRequestActionData) action; + // TODO autoCraftAction#getTimesCrafted 1.17.10 ??? + if (craftState != CraftState.START) { + return rejectRequest(request); + } + craftState = CraftState.RECIPE_ID; + + int recipeId = autoCraftAction.getRecipeNetworkId(); + recipe = session.getCraftingRecipes().get(recipeId); + if (recipe == null) { + return rejectRequest(request); + } + if (!plan.getCursor().isEmpty()) { + return rejectRequest(request); + } + //reject if crafting grid is not clear + for (int i = 1; i <= gridSize; i++) { + if (!inventory.getItem(i).isEmpty()) { + return rejectRequest(request); + } + } + + switch (recipe.getType()) { + case CRAFTING_SHAPED -> { + ShapedRecipeData shapedData = (ShapedRecipeData) recipe.getData(); + ingredients = shapedData.getIngredients(); + recipeWidth = shapedData.getWidth(); + output = shapedData.getResult(); + if (shapedData.getWidth() > gridDimensions || shapedData.getHeight() > gridDimensions) { + return rejectRequest(request); + } + } + case CRAFTING_SHAPELESS -> { + ShapelessRecipeData shapelessData = (ShapelessRecipeData) recipe.getData(); + ingredients = shapelessData.getIngredients(); + recipeWidth = gridDimensions; + output = shapelessData.getResult(); + if (ingredients.length > gridSize) { + return rejectRequest(request); + } + } + } + break; + } + case CRAFT_RESULTS_DEPRECATED: { + CraftResultsDeprecatedStackRequestActionData deprecatedCraftAction = (CraftResultsDeprecatedStackRequestActionData) action; + if (craftState != CraftState.RECIPE_ID) { + return rejectRequest(request); + } + craftState = CraftState.DEPRECATED; + + if (deprecatedCraftAction.getResultItems().length != 1) { + return rejectRequest(request); + } + resultSize = deprecatedCraftAction.getResultItems()[0].getCount(); + timesCrafted = deprecatedCraftAction.getTimesCrafted(); + if (resultSize <= 0 || timesCrafted <= 0) { + return rejectRequest(request); + } + break; + } + case CONSUME: { + ConsumeStackRequestActionData consumeAction = (ConsumeStackRequestActionData) action; + if (craftState != CraftState.DEPRECATED && craftState != CraftState.INGREDIENTS) { + return rejectRequest(request); + } + craftState = CraftState.INGREDIENTS; + + if (ingRemaining == 0) { + while (++ingredientIndex < ingredients.length) { + if (ingredients[ingredientIndex].getOptions().length != 0) { + ingRemaining = timesCrafted; + break; + } + } + } + + ingRemaining -= consumeAction.getCount(); + if (ingRemaining < 0) + return rejectRequest(request); + + int javaSlot = bedrockSlotToJava(consumeAction.getSource()); + consumedSlots.merge(javaSlot, consumeAction.getCount(), Integer::sum); + + int gridSlot = 1 + ingredientIndex + ((ingredientIndex / recipeWidth) * (gridDimensions - recipeWidth)); + Int2IntMap sources = ingredientMap.computeIfAbsent(gridSlot, k -> new Int2IntOpenHashMap()); + sources.put(javaSlot, consumeAction.getCount()); + break; + } + case TAKE: + case PLACE: { + TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action; + if (craftState != CraftState.INGREDIENTS && craftState != CraftState.TRANSFER) { + return rejectRequest(request); + } + craftState = CraftState.TRANSFER; + + if (transferAction.getSource().getContainer() != ContainerSlotType.CREATIVE_OUTPUT) { + return rejectRequest(request); + } + if (transferAction.getCount() <= 0) { + return rejectRequest(request); + } + + int javaSlot = bedrockSlotToJava(transferAction.getDestination()); + if (isCursor(transferAction.getDestination())) { //TODO + if (timesCrafted > 1) { + tempSlot = findTempSlot(inventory, GeyserItemStack.from(output), true); + if (tempSlot == -1) { + return rejectRequest(request); + } + } + break requestLoop; + } else if (inventory.getItem(javaSlot).getAmount() == consumedSlots.get(javaSlot)) { + prioritySlot = bedrockSlotToJava(transferAction.getDestination()); + break requestLoop; + } + break; + } + default: + return rejectRequest(request); + } + } + + final int maxLoops = Math.min(64, timesCrafted); + for (int loops = 0; loops < maxLoops; loops++) { + boolean done = true; + for (Int2ObjectMap.Entry entry : ingredientMap.int2ObjectEntrySet()) { + Int2IntMap sources = entry.getValue(); + if (sources.isEmpty()) + continue; + + done = false; + int gridSlot = entry.getIntKey(); + if (!plan.getItem(gridSlot).isEmpty()) + continue; + + int sourceSlot; + if (loops == 0 && sources.containsKey(prioritySlot)) { + sourceSlot = prioritySlot; + } else { + sourceSlot = sources.keySet().iterator().nextInt(); + } + int transferAmount = sources.remove(sourceSlot); + transferSlot(plan, sourceSlot, gridSlot, transferAmount); + } + + if (!done) { + //TODO: sometimes the server does not agree on this slot? + plan.add(Click.LEFT_SHIFT, 0, true); + } else { + break; + } + } + + inventory.setItem(0, GeyserItemStack.from(output), session); + plan.execute(true); + return acceptRequest(request, makeContainerEntries(session, inventory, plan.getAffectedSlots())); + } + + /** + * Handled in {@link PlayerInventoryTranslator} + */ + public ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + return rejectRequest(request); + } + + private void transferSlot(ClickPlan plan, int sourceSlot, int destSlot, int transferAmount) { + boolean tempSwap = !plan.getCursor().isEmpty(); + int sourceAmount = plan.getItem(sourceSlot).getAmount(); + if (transferAmount == sourceAmount) { //transfer all + plan.add(Click.LEFT, sourceSlot); //pickup source + plan.add(Click.LEFT, destSlot); //let go of all items and done + } else { //transfer some + //try to transfer items with least clicks possible + int halfSource = sourceAmount - (sourceAmount / 2); //larger half + int holding; + if (!tempSwap && transferAmount <= halfSource) { //faster to take only half. CURSOR MUST BE EMPTY + plan.add(Click.RIGHT, sourceSlot); + holding = halfSource; + } else { //need all + plan.add(Click.LEFT, sourceSlot); + holding = sourceAmount; + } + if (!tempSwap && transferAmount > holding / 2) { //faster to release extra items onto source or dest slot? + for (int i = 0; i < holding - transferAmount; i++) { + plan.add(Click.RIGHT, sourceSlot); //prepare cursor + } + plan.add(Click.LEFT, destSlot); //release cursor onto dest slot + } else { + for (int i = 0; i < transferAmount; i++) { + plan.add(Click.RIGHT, destSlot); //right click until transfer goal is met + } + plan.add(Click.LEFT, sourceSlot); //return extra items to source slot + } + } + } + + public static ItemStackResponsePacket.Response acceptRequest(ItemStackRequest request, List containerEntries) { + return new ItemStackResponsePacket.Response(ItemStackResponsePacket.ResponseStatus.OK, request.getRequestId(), containerEntries); + } + + /** + * Reject an incorrect ItemStackRequest. + */ + public static ItemStackResponsePacket.Response rejectRequest(ItemStackRequest request) { + return rejectRequest(request, true); + } + + /** + * Reject an incorrect ItemStackRequest. + * + * @param throwError whether this request was truly erroneous (true), or known as an outcome and should not be treated + * as bad (false). + */ + public static ItemStackResponsePacket.Response rejectRequest(ItemStackRequest request, boolean throwError) { + if (throwError && GeyserImpl.getInstance().getConfig().isDebugMode()) { + new Throwable("DEBUGGING: ItemStackRequest rejected " + request.toString()).printStackTrace(); + } + return new ItemStackResponsePacket.Response(ItemStackResponsePacket.ResponseStatus.ERROR, request.getRequestId(), Collections.emptyList()); + } + + /** + * Print out the contents of an ItemStackRequest, should the net ID check fail. + */ + protected void dumpStackRequestDetails(GeyserSession session, Inventory inventory, StackRequestSlotInfoData source, StackRequestSlotInfoData destination) { + session.getGeyser().getLogger().error("Source: " + source.toString() + " Result: " + checkNetId(session, inventory, source)); + session.getGeyser().getLogger().error("Destination: " + destination.toString() + " Result: " + checkNetId(session, inventory, destination)); + session.getGeyser().getLogger().error("Geyser's record of source slot: " + inventory.getItem(bedrockSlotToJava(source))); + session.getGeyser().getLogger().error("Geyser's record of destination slot: " + inventory.getItem(bedrockSlotToJava(destination))); + } + + public boolean checkNetId(GeyserSession session, Inventory inventory, StackRequestSlotInfoData slotInfoData) { + int netId = slotInfoData.getStackNetworkId(); + // "In my testing, sometimes the client thinks the netId of an item in the crafting grid is 1, even though we never said it was. + // I think it only happens when we manually set the grid but that was my quick fix" + if (netId < 0 || netId == 1) + return true; + + GeyserItemStack currentItem = isCursor(slotInfoData) ? session.getPlayerInventory().getCursor() : inventory.getItem(bedrockSlotToJava(slotInfoData)); + return currentItem.getNetId() == netId; + } + + /** + * Try to find a slot that can temporarily store the given item. + * Only looks in the main inventory and hotbar (excluding offhand). + * Only slots that are empty or contain a different type of item are valid. + * + * @return java id for the temporary slot, or -1 if no viable slot was found + */ + //TODO: compatibility for simulated inventory (ClickPlan) + private static int findTempSlot(Inventory inventory, GeyserItemStack item, boolean emptyOnly, int... slotBlacklist) { + int offset = inventory.getId() == 0 ? 1 : 0; //offhand is not a viable temp slot + HashSet itemBlacklist = new HashSet<>(slotBlacklist.length + 1); + itemBlacklist.add(item); + + IntSet potentialSlots = new IntOpenHashSet(36); + for (int i = inventory.getSize() - (36 + offset); i < inventory.getSize() - offset; i++) { + potentialSlots.add(i); + } + for (int i : slotBlacklist) { + potentialSlots.remove(i); + GeyserItemStack blacklistedItem = inventory.getItem(i); + if (!blacklistedItem.isEmpty()) { + itemBlacklist.add(blacklistedItem); + } + } + + for (int i : potentialSlots) { + GeyserItemStack testItem = inventory.getItem(i); + if ((emptyOnly && !testItem.isEmpty())) { + continue; + } + + boolean viable = true; + for (GeyserItemStack blacklistedItem : itemBlacklist) { + if (InventoryUtils.canStack(testItem, blacklistedItem)) { + viable = false; + break; + } + } + if (!viable) { + continue; + } + return i; + } + //could not find a viable temp slot + return -1; + } + + public List makeContainerEntries(GeyserSession session, Inventory inventory, Set affectedSlots) { + Map> containerMap = new HashMap<>(); + for (int slot : affectedSlots) { + BedrockContainerSlot bedrockSlot = javaSlotToBedrockContainer(slot); + List list = containerMap.computeIfAbsent(bedrockSlot.getContainer(), k -> new ArrayList<>()); + list.add(makeItemEntry(session, bedrockSlot.getSlot(), inventory.getItem(slot))); + } + + List containerEntries = new ArrayList<>(); + for (Map.Entry> entry : containerMap.entrySet()) { + containerEntries.add(new ItemStackResponsePacket.ContainerEntry(entry.getKey(), entry.getValue())); + } + + ItemStackResponsePacket.ItemEntry cursorEntry = makeItemEntry(session, 0, session.getPlayerInventory().getCursor()); + containerEntries.add(new ItemStackResponsePacket.ContainerEntry(ContainerSlotType.CURSOR, Collections.singletonList(cursorEntry))); + + return containerEntries; + } + + public static ItemStackResponsePacket.ItemEntry makeItemEntry(GeyserSession session, int bedrockSlot, GeyserItemStack itemStack) { + ItemStackResponsePacket.ItemEntry itemEntry; + if (!itemStack.isEmpty()) { + // As of 1.16.210: Bedrock needs confirmation on what the current item durability is. + // If 0 is sent, then Bedrock thinks the item is not damaged + int durability = 0; + if (itemStack.getNbt() != null) { + Tag damage = itemStack.getNbt().get("Damage"); + if (damage instanceof IntTag) { + durability = ItemUtils.getCorrectBedrockDurability(session, itemStack.getJavaId(), ((IntTag) damage).getValue()); + } + } + + itemEntry = new ItemStackResponsePacket.ItemEntry((byte) bedrockSlot, (byte) bedrockSlot, (byte) itemStack.getAmount(), itemStack.getNetId(), "", durability); + } else { + itemEntry = new ItemStackResponsePacket.ItemEntry((byte) bedrockSlot, (byte) bedrockSlot, (byte) 0, 0, "", 0); + } + return itemEntry; + } + + protected static boolean isCursor(StackRequestSlotInfoData slotInfoData) { + return slotInfoData.getContainer() == ContainerSlotType.CURSOR; + } + + /** + * Gets the {@link InventoryTranslator} for the given {@link ContainerType}. + * Returns {@link #PLAYER_INVENTORY_TRANSLATOR} if type is null. + * + * @param type the type + * @return the InventoryType for the given ContainerType. + */ + @Nullable + public static InventoryTranslator inventoryTranslator(@Nullable ContainerType type) { + if (type == null) { + return PLAYER_INVENTORY_TRANSLATOR; + } + + return INVENTORY_TRANSLATORS.get(type); + } + + protected enum CraftState { + START, + RECIPE_ID, + DEPRECATED, + INGREDIENTS, + TRANSFER, + DONE + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/LecternInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/LecternInventoryTranslator.java new file mode 100644 index 000000000..b59914a13 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/LecternInventoryTranslator.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory; + +import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerButtonClickPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClosePacket; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.nbt.NbtType; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.inventory.LecternContainer; +import org.geysermc.geyser.inventory.PlayerInventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.inventory.updater.InventoryUpdater; +import org.geysermc.geyser.util.BlockEntityUtils; +import org.geysermc.geyser.util.InventoryUtils; + +import java.util.Collections; + +public class LecternInventoryTranslator extends BaseInventoryTranslator { + private final InventoryUpdater updater; + + public LecternInventoryTranslator() { + super(1); + this.updater = new InventoryUpdater(); + } + + @Override + public void prepareInventory(GeyserSession session, Inventory inventory) { + } + + @Override + public void openInventory(GeyserSession session, Inventory inventory) { + } + + @Override + public void closeInventory(GeyserSession session, Inventory inventory) { + } + + @Override + public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { + if (key == 0) { // Lectern page update + LecternContainer lecternContainer = (LecternContainer) inventory; + lecternContainer.setCurrentBedrockPage(value / 2); + lecternContainer.setBlockEntityTag(lecternContainer.getBlockEntityTag().toBuilder().putInt("page", lecternContainer.getCurrentBedrockPage()).build()); + BlockEntityUtils.updateBlockEntity(session, lecternContainer.getBlockEntityTag(), lecternContainer.getPosition()); + } + } + + @Override + public void updateInventory(GeyserSession session, Inventory inventory) { + GeyserItemStack itemStack = inventory.getItem(0); + if (!itemStack.isEmpty()) { + updateBook(session, inventory, itemStack); + } + } + + @Override + public void updateSlot(GeyserSession session, Inventory inventory, int slot) { + this.updater.updateSlot(this, session, inventory, slot); + if (slot == 0) { + updateBook(session, inventory, inventory.getItem(0)); + } + } + + /** + * Translate the data of the book in the lectern into a block entity tag. + */ + private void updateBook(GeyserSession session, Inventory inventory, GeyserItemStack book) { + LecternContainer lecternContainer = (LecternContainer) inventory; + if (session.isDroppingLecternBook()) { + // We have to enter the inventory GUI to eject the book + ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getId(), 3); + session.sendDownstreamPacket(packet); + session.setDroppingLecternBook(false); + InventoryUtils.closeInventory(session, inventory.getId(), false); + } else if (lecternContainer.getBlockEntityTag() == null) { + CompoundTag tag = book.getNbt(); + // Position has to be the last interacted position... right? + Vector3i position = session.getLastInteractionBlockPosition(); + // If shouldExpectLecternHandled returns true, this is already handled for us + // shouldRefresh means that we should boot out the client on our side because their lectern GUI isn't updated yet + boolean shouldRefresh = !session.getGeyser().getWorldManager().shouldExpectLecternHandled() && !session.getLecternCache().contains(position); + + NbtMap blockEntityTag; + if (tag != null) { + int pagesSize = ((ListTag) tag.get("pages")).size(); + ItemData itemData = book.getItemData(session); + NbtMapBuilder lecternTag = getBaseLecternTag(position.getX(), position.getY(), position.getZ(), pagesSize); + lecternTag.putCompound("book", NbtMap.builder() + .putByte("Count", (byte) itemData.getCount()) + .putShort("Damage", (short) 0) + .putString("Name", "minecraft:written_book") + .putCompound("tag", itemData.getTag()) + .build()); + lecternTag.putInt("page", lecternContainer.getCurrentBedrockPage()); + blockEntityTag = lecternTag.build(); + } else { + // There is *a* book here, but... no NBT. + NbtMapBuilder lecternTag = getBaseLecternTag(position.getX(), position.getY(), position.getZ(), 1); + NbtMapBuilder bookTag = NbtMap.builder() + .putByte("Count", (byte) 1) + .putShort("Damage", (short) 0) + .putString("Name", "minecraft:writable_book") + .putCompound("tag", NbtMap.builder().putList("pages", NbtType.COMPOUND, Collections.singletonList( + NbtMap.builder() + .putString("photoname", "") + .putString("text", "") + .build() + )).build()); + + blockEntityTag = lecternTag.putCompound("book", bookTag.build()).build(); + } + + // Even with serverside access to lecterns, we don't easily know which lectern this is, so we need to rebuild + // the block entity tag + lecternContainer.setBlockEntityTag(blockEntityTag); + lecternContainer.setPosition(position); + if (shouldRefresh) { + // Update the lectern because it's not updated client-side + BlockEntityUtils.updateBlockEntity(session, blockEntityTag, position); + session.getLecternCache().add(position); + // Close the window - we will reopen it once the client has this data synced + ServerboundContainerClosePacket closeWindowPacket = new ServerboundContainerClosePacket(lecternContainer.getId()); + session.sendDownstreamPacket(closeWindowPacket); + InventoryUtils.closeInventory(session, inventory.getId(), false); + } + } + } + + @Override + public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) { + return new LecternContainer(name, windowId, this.size, containerType, playerInventory); + } + + public static NbtMapBuilder getBaseLecternTag(int x, int y, int z, int totalPages) { + NbtMapBuilder builder = NbtMap.builder() + .putInt("x", x) + .putInt("y", y) + .putInt("z", z) + .putString("id", "Lectern"); + if (totalPages != 0) { + builder.putByte("hasBook", (byte) 1); + builder.putInt("totalPages", totalPages); + } else { + // Not usually needed, but helps with kicking out Bedrock players from reading the UI + builder.putByte("hasBook", (byte) 0); + } + return builder; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/LoomInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/LoomInventoryTranslator.java new file mode 100644 index 000000000..d035543bc --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/LoomInventoryTranslator.java @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory; + +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerButtonClickPacket; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftLoomStackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftResultsDeprecatedStackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; +import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.inventory.BedrockContainerSlot; +import org.geysermc.geyser.inventory.SlotType; +import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; +import org.geysermc.geyser.translator.inventory.item.BannerTranslator; + +import java.util.Collections; +import java.util.List; + +public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator { + /** + * A map of Bedrock patterns to Java index. Used to request for a specific banner pattern. + */ + private static final Object2IntMap PATTERN_TO_INDEX = new Object2IntOpenHashMap<>(); + + static { + // Added from left-to-right then up-to-down in the order Java presents it + int index = 1; + PATTERN_TO_INDEX.put("bl", index++); + PATTERN_TO_INDEX.put("br", index++); + PATTERN_TO_INDEX.put("tl", index++); + PATTERN_TO_INDEX.put("tr", index++); + PATTERN_TO_INDEX.put("bs", index++); + PATTERN_TO_INDEX.put("ts", index++); + PATTERN_TO_INDEX.put("ls", index++); + PATTERN_TO_INDEX.put("rs", index++); + PATTERN_TO_INDEX.put("cs", index++); + PATTERN_TO_INDEX.put("ms", index++); + PATTERN_TO_INDEX.put("drs", index++); + PATTERN_TO_INDEX.put("dls", index++); + PATTERN_TO_INDEX.put("ss", index++); + PATTERN_TO_INDEX.put("cr", index++); + PATTERN_TO_INDEX.put("sc", index++); + PATTERN_TO_INDEX.put("bt", index++); + PATTERN_TO_INDEX.put("tt", index++); + PATTERN_TO_INDEX.put("bts", index++); + PATTERN_TO_INDEX.put("tts", index++); + PATTERN_TO_INDEX.put("ld", index++); + PATTERN_TO_INDEX.put("rd", index++); + PATTERN_TO_INDEX.put("lud", index++); + PATTERN_TO_INDEX.put("rud", index++); + PATTERN_TO_INDEX.put("mc", index++); + PATTERN_TO_INDEX.put("mr", index++); + PATTERN_TO_INDEX.put("vh", index++); + PATTERN_TO_INDEX.put("hh", index++); + PATTERN_TO_INDEX.put("vhr", index++); + PATTERN_TO_INDEX.put("hhb", index++); + PATTERN_TO_INDEX.put("bo", index++); + index++; // Bordure indented, does not appear to exist in Bedrock? + PATTERN_TO_INDEX.put("gra", index++); + PATTERN_TO_INDEX.put("gru", index); + // Bricks do not appear to be a pattern on Bedrock, either + } + + public LoomInventoryTranslator() { + super(4, "minecraft:loom[facing=north]", ContainerType.LOOM, UIInventoryUpdater.INSTANCE); + } + + @Override + public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, ContainerSlotType bedrockSourceContainer, + int javaSourceSlot, ContainerSlotType bedrockDestinationContainer, int javaDestinationSlot) { + if (javaDestinationSlot != 1) { + return false; + } + GeyserItemStack itemStack = javaSourceSlot == -1 ? session.getPlayerInventory().getCursor() : inventory.getItem(javaSourceSlot); + if (itemStack.isEmpty()) { + return false; + } + + // Reject the item if Bedrock is attempting to put in a dye that is not a dye in Java Edition + return !itemStack.getMapping(session).getJavaIdentifier().endsWith("_dye"); + } + + @Override + public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { + // If the LOOM_MATERIAL slot is not empty, we are crafting a pattern that does not come from an item + // Remove the CRAFT_NON_IMPLEMENTED_DEPRECATED when 1.17.30 is dropped + return (action.getType() == StackRequestActionType.CRAFT_NON_IMPLEMENTED_DEPRECATED || action.getType() == StackRequestActionType.CRAFT_LOOM) + && inventory.getItem(2).isEmpty(); + } + + @Override + public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + StackRequestActionData headerData = request.getActions()[0]; + StackRequestActionData data = request.getActions()[1]; + if (!(data instanceof CraftResultsDeprecatedStackRequestActionData craftData)) { + return rejectRequest(request); + } + + // Get the patterns compound tag + List newBlockEntityTag = craftData.getResultItems()[0].getTag().getList("Patterns", NbtType.COMPOUND); + // Get the pattern that the Bedrock client requests - the last pattern in the Patterns list + NbtMap pattern = newBlockEntityTag.get(newBlockEntityTag.size() - 1); + String bedrockPattern; + + if (headerData instanceof CraftLoomStackRequestActionData loomData) { + // Prioritize this if on 1.17.40 + // Remove the below if statement when 1.17.30 is dropped + bedrockPattern = loomData.getPatternId(); + } else { + bedrockPattern = pattern.getString("Pattern"); + } + + // Get the Java index of this pattern + int index = PATTERN_TO_INDEX.getOrDefault(bedrockPattern, -1); + if (index == -1) { + return rejectRequest(request); + } + // Java's formula: 4 * row + col + // And the Java loom window has a fixed row/width of four + // So... Number / 4 = row (so we don't have to bother there), and number % 4 is our column, which leads us back to our index. :) + ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getId(), index); + session.sendDownstreamPacket(packet); + + GeyserItemStack inputCopy = inventory.getItem(0).copy(1); + inputCopy.setNetId(session.getNextItemNetId()); + // Add the pattern manually, for better item synchronization + if (inputCopy.getNbt() == null) { + inputCopy.setNbt(new CompoundTag("")); + } + CompoundTag blockEntityTag = inputCopy.getNbt().get("BlockEntityTag"); + CompoundTag javaBannerPattern = BannerTranslator.getJavaBannerPattern(pattern); + + if (blockEntityTag != null) { + ListTag patternsList = blockEntityTag.get("Patterns"); + if (patternsList != null) { + patternsList.add(javaBannerPattern); + } else { + patternsList = new ListTag("Patterns", Collections.singletonList(javaBannerPattern)); + blockEntityTag.put(patternsList); + } + } else { + blockEntityTag = new CompoundTag("BlockEntityTag"); + ListTag patternsList = new ListTag("Patterns", Collections.singletonList(javaBannerPattern)); + blockEntityTag.put(patternsList); + inputCopy.getNbt().put(blockEntityTag); + } + + // Set the new item as the output + inventory.setItem(3, inputCopy, session); + + return translateRequest(session, inventory, request); + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + return switch (slotInfoData.getContainer()) { + case LOOM_INPUT -> 0; + case LOOM_DYE -> 1; + case LOOM_MATERIAL -> 2; + case LOOM_RESULT, CREATIVE_OUTPUT -> 3; + default -> super.bedrockSlotToJava(slotInfoData); + }; + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + return switch (slot) { + case 0 -> new BedrockContainerSlot(ContainerSlotType.LOOM_INPUT, 9); + case 1 -> new BedrockContainerSlot(ContainerSlotType.LOOM_DYE, 10); + case 2 -> new BedrockContainerSlot(ContainerSlotType.LOOM_MATERIAL, 11); + case 3 -> new BedrockContainerSlot(ContainerSlotType.LOOM_RESULT, 50); + default -> super.javaSlotToBedrockContainer(slot); + }; + } + + @Override + public int javaSlotToBedrock(int slot) { + return switch (slot) { + case 0 -> 9; + case 1 -> 10; + case 2 -> 11; + case 3 -> 50; + default -> super.javaSlotToBedrock(slot); + }; + } + + @Override + public SlotType getSlotType(int javaSlot) { + if (javaSlot == 3) { + return SlotType.OUTPUT; + } + return super.getSlotType(javaSlot); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/MerchantInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/MerchantInventoryTranslator.java new file mode 100644 index 000000000..cf0c475f0 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/MerchantInventoryTranslator.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory; + +import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; +import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.inventory.MerchantContainer; +import org.geysermc.geyser.inventory.PlayerInventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.inventory.BedrockContainerSlot; +import org.geysermc.geyser.inventory.SlotType; +import org.geysermc.geyser.inventory.updater.InventoryUpdater; +import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; + +public class MerchantInventoryTranslator extends BaseInventoryTranslator { + private final InventoryUpdater updater; + + public MerchantInventoryTranslator() { + super(3); + this.updater = UIInventoryUpdater.INSTANCE; + } + + @Override + public int javaSlotToBedrock(int slot) { + return switch (slot) { + case 0 -> 4; + case 1 -> 5; + case 2 -> 50; + default -> super.javaSlotToBedrock(slot); + }; + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + return switch (slot) { + case 0 -> new BedrockContainerSlot(ContainerSlotType.TRADE2_INGREDIENT1, 4); + case 1 -> new BedrockContainerSlot(ContainerSlotType.TRADE2_INGREDIENT2, 5); + case 2 -> new BedrockContainerSlot(ContainerSlotType.TRADE2_RESULT, 50); + default -> super.javaSlotToBedrockContainer(slot); + }; + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + return switch (slotInfoData.getContainer()) { + case TRADE2_INGREDIENT1 -> 0; + case TRADE2_INGREDIENT2 -> 1; + case TRADE2_RESULT, CREATIVE_OUTPUT -> 2; + default -> super.bedrockSlotToJava(slotInfoData); + }; + } + + @Override + public SlotType getSlotType(int javaSlot) { + if (javaSlot == 2) { + return SlotType.OUTPUT; + } + return SlotType.NORMAL; + } + + @Override + public void prepareInventory(GeyserSession session, Inventory inventory) { + MerchantContainer merchantInventory = (MerchantContainer) inventory; + if (merchantInventory.getVillager() == null) { + long geyserId = session.getEntityCache().getNextEntityId().incrementAndGet(); + Vector3f pos = session.getPlayerEntity().getPosition().sub(0, 3, 0); + + Entity villager = new Entity(session, 0, geyserId, null, EntityDefinitions.VILLAGER, pos, Vector3f.ZERO, 0f, 0f, 0f) { + @Override + protected void initializeMetadata() { + dirtyMetadata.put(EntityData.SCALE, 0f); + dirtyMetadata.put(EntityData.BOUNDING_BOX_WIDTH, 0f); + dirtyMetadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0f); + } + }; + villager.spawnEntity(); + + SetEntityLinkPacket linkPacket = new SetEntityLinkPacket(); + EntityLinkData.Type type = EntityLinkData.Type.PASSENGER; + linkPacket.setEntityLink(new EntityLinkData(session.getPlayerEntity().getGeyserId(), geyserId, type, true, false)); + session.sendUpstreamPacket(linkPacket); + + merchantInventory.setVillager(villager); + } + } + + @Override + public void openInventory(GeyserSession session, Inventory inventory) { + //Handled in JavaMerchantOffersTranslator + //TODO: send a blank inventory here in case the villager doesn't send a TradeList packet + } + + @Override + public void closeInventory(GeyserSession session, Inventory inventory) { + MerchantContainer merchantInventory = (MerchantContainer) inventory; + if (merchantInventory.getVillager() != null) { + merchantInventory.getVillager().despawnEntity(); + } + } + + @Override + public ItemStackResponsePacket.Response translateAutoCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + // We're not crafting here + // Called at least by consoles when pressing a trade option button + return translateRequest(session, inventory, request); + } + + @Override + public void updateInventory(GeyserSession session, Inventory inventory) { + updater.updateInventory(this, session, inventory); + } + + @Override + public void updateSlot(GeyserSession session, Inventory inventory, int slot) { + updater.updateSlot(this, session, inventory, slot); + } + + @Override + public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) { + return new MerchantContainer(name, windowId, this.size, containerType, playerInventory); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/PlayerInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/PlayerInventoryTranslator.java new file mode 100644 index 000000000..0fd9f114f --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/PlayerInventoryTranslator.java @@ -0,0 +1,498 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSetCreativeModeSlotPacket; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.protocol.bedrock.data.inventory.*; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.*; +import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; +import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; +import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import org.geysermc.geyser.inventory.*; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.skin.FakeHeadProvider; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.translator.inventory.item.ItemTranslator; +import org.geysermc.geyser.util.InventoryUtils; + +import java.util.Arrays; +import java.util.Collections; +import java.util.function.IntFunction; + +public class PlayerInventoryTranslator extends InventoryTranslator { + private static final IntFunction UNUSUABLE_CRAFTING_SPACE_BLOCK = InventoryUtils.createUnusableSpaceBlock(GeyserLocale.getLocaleStringLog("geyser.inventory.unusable_item.creative")); + + public PlayerInventoryTranslator() { + super(46); + } + + @Override + public void updateInventory(GeyserSession session, Inventory inventory) { + updateCraftingGrid(session, inventory); + + InventoryContentPacket inventoryContentPacket = new InventoryContentPacket(); + inventoryContentPacket.setContainerId(ContainerId.INVENTORY); + ItemData[] contents = new ItemData[36]; + // Inventory + for (int i = 9; i < 36; i++) { + contents[i] = inventory.getItem(i).getItemData(session); + } + // Hotbar + for (int i = 36; i < 45; i++) { + contents[i - 36] = inventory.getItem(i).getItemData(session); + } + inventoryContentPacket.setContents(Arrays.asList(contents)); + session.sendUpstreamPacket(inventoryContentPacket); + + // Armor + InventoryContentPacket armorContentPacket = new InventoryContentPacket(); + armorContentPacket.setContainerId(ContainerId.ARMOR); + contents = new ItemData[4]; + for (int i = 5; i < 9; i++) { + contents[i - 5] = inventory.getItem(i).getItemData(session); + } + armorContentPacket.setContents(Arrays.asList(contents)); + session.sendUpstreamPacket(armorContentPacket); + + // Offhand + InventoryContentPacket offhandPacket = new InventoryContentPacket(); + offhandPacket.setContainerId(ContainerId.OFFHAND); + offhandPacket.setContents(Collections.singletonList(inventory.getItem(45).getItemData(session))); + session.sendUpstreamPacket(offhandPacket); + } + + /** + * Update the crafting grid for the player to hide/show the barriers in the creative inventory + * @param session Connection of the player + * @param inventory Inventory of the player + */ + public static void updateCraftingGrid(GeyserSession session, Inventory inventory) { + // Crafting grid + for (int i = 1; i < 5; i++) { + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.UI); + slotPacket.setSlot(i + 27); + + if (session.getGameMode() == GameMode.CREATIVE) { + slotPacket.setItem(UNUSUABLE_CRAFTING_SPACE_BLOCK.apply(session.getUpstream().getProtocolVersion())); + } else { + slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(i).getItemStack())); + } + + session.sendUpstreamPacket(slotPacket); + } + } + + @Override + public void updateSlot(GeyserSession session, Inventory inventory, int slot) { + GeyserItemStack javaItem = inventory.getItem(slot); + ItemData bedrockItem = javaItem.getItemData(session); + + if (slot == 5) { + // Check for custom skull + if (javaItem.getJavaId() == session.getItemMappings().getStoredItems().playerHead().getJavaId() + && javaItem.getNbt() != null + && javaItem.getNbt().get("SkullOwner") instanceof CompoundTag profile) { + FakeHeadProvider.setHead(session, session.getPlayerEntity(), profile); + } else { + FakeHeadProvider.restoreOriginalSkin(session, session.getPlayerEntity()); + } + } + + if (slot >= 1 && slot <= 44) { + InventorySlotPacket slotPacket = new InventorySlotPacket(); + if (slot >= 9) { + slotPacket.setContainerId(ContainerId.INVENTORY); + if (slot >= 36) { + slotPacket.setSlot(slot - 36); + } else { + slotPacket.setSlot(slot); + } + } else if (slot >= 5) { + slotPacket.setContainerId(ContainerId.ARMOR); + slotPacket.setSlot(slot - 5); + } else { + slotPacket.setContainerId(ContainerId.UI); + slotPacket.setSlot(slot + 27); + } + slotPacket.setItem(bedrockItem); + session.sendUpstreamPacket(slotPacket); + } else if (slot == 45) { + InventoryContentPacket offhandPacket = new InventoryContentPacket(); + offhandPacket.setContainerId(ContainerId.OFFHAND); + offhandPacket.setContents(Collections.singletonList(bedrockItem)); + session.sendUpstreamPacket(offhandPacket); + } + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + int slotnum = slotInfoData.getSlot(); + switch (slotInfoData.getContainer()) { + case HOTBAR_AND_INVENTORY: + case HOTBAR: + case INVENTORY: + // Inventory + if (slotnum >= 9 && slotnum <= 35) { + return slotnum; + } + // Hotbar + if (slotnum >= 0 && slotnum <= 8) { + return slotnum + 36; + } + break; + case ARMOR: + if (slotnum >= 0 && slotnum <= 3) { + return slotnum + 5; + } + break; + case OFFHAND: + return 45; + case CRAFTING_INPUT: + if (slotnum >= 28 && 31 >= slotnum) { + return slotnum - 27; + } + break; + case CREATIVE_OUTPUT: + return 0; + } + return slotnum; + } + + @Override + public int javaSlotToBedrock(int slot) { + return -1; + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot >= 36 && slot <= 44) { + return new BedrockContainerSlot(ContainerSlotType.HOTBAR, slot - 36); + } else if (slot >= 9 && slot <= 35) { + return new BedrockContainerSlot(ContainerSlotType.INVENTORY, slot); + } else if (slot >= 5 && slot <= 8) { + return new BedrockContainerSlot(ContainerSlotType.ARMOR, slot - 5); + } else if (slot == 45) { + return new BedrockContainerSlot(ContainerSlotType.OFFHAND, 1); + } else if (slot >= 1 && slot <= 4) { + return new BedrockContainerSlot(ContainerSlotType.CRAFTING_INPUT, slot + 27); + } else if (slot == 0) { + return new BedrockContainerSlot(ContainerSlotType.CRAFTING_OUTPUT, 0); + } else { + throw new IllegalArgumentException("Unknown bedrock slot"); + } + } + + @Override + public SlotType getSlotType(int javaSlot) { + if (javaSlot == 0) + return SlotType.OUTPUT; + return SlotType.NORMAL; + } + + @Override + public ItemStackResponsePacket.Response translateRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + if (session.getGameMode() != GameMode.CREATIVE) { + return super.translateRequest(session, inventory, request); + } + + PlayerInventory playerInv = session.getPlayerInventory(); + IntSet affectedSlots = new IntOpenHashSet(); + for (StackRequestActionData action : request.getActions()) { + switch (action.getType()) { + case TAKE, PLACE -> { + TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action; + if (!(checkNetId(session, inventory, transferAction.getSource()) && checkNetId(session, inventory, transferAction.getDestination()))) { + return rejectRequest(request); + } + if (isCraftingGrid(transferAction.getSource()) || isCraftingGrid(transferAction.getDestination())) { + return rejectRequest(request, false); + } + + int transferAmount = transferAction.getCount(); + if (isCursor(transferAction.getDestination())) { + int sourceSlot = bedrockSlotToJava(transferAction.getSource()); + GeyserItemStack sourceItem = inventory.getItem(sourceSlot); + if (playerInv.getCursor().isEmpty()) { + playerInv.setCursor(sourceItem.copy(0), session); + } + + playerInv.getCursor().add(transferAmount); + sourceItem.sub(transferAmount); + + affectedSlots.add(sourceSlot); + } else if (isCursor(transferAction.getSource())) { + int destSlot = bedrockSlotToJava(transferAction.getDestination()); + GeyserItemStack sourceItem = playerInv.getCursor(); + if (inventory.getItem(destSlot).isEmpty()) { + inventory.setItem(destSlot, sourceItem.copy(0), session); + } + + inventory.getItem(destSlot).add(transferAmount); + sourceItem.sub(transferAmount); + + affectedSlots.add(destSlot); + } else { + int sourceSlot = bedrockSlotToJava(transferAction.getSource()); + int destSlot = bedrockSlotToJava(transferAction.getDestination()); + GeyserItemStack sourceItem = inventory.getItem(sourceSlot); + if (inventory.getItem(destSlot).isEmpty()) { + inventory.setItem(destSlot, sourceItem.copy(0), session); + } + + inventory.getItem(destSlot).add(transferAmount); + sourceItem.sub(transferAmount); + + affectedSlots.add(sourceSlot); + affectedSlots.add(destSlot); + } + } + case SWAP -> { + SwapStackRequestActionData swapAction = (SwapStackRequestActionData) action; + if (!(checkNetId(session, inventory, swapAction.getSource()) && checkNetId(session, inventory, swapAction.getDestination()))) { + return rejectRequest(request); + } + if (isCraftingGrid(swapAction.getSource()) || isCraftingGrid(swapAction.getDestination())) { + return rejectRequest(request, false); + } + + if (isCursor(swapAction.getDestination())) { + int sourceSlot = bedrockSlotToJava(swapAction.getSource()); + GeyserItemStack sourceItem = inventory.getItem(sourceSlot); + GeyserItemStack destItem = playerInv.getCursor(); + + playerInv.setCursor(sourceItem, session); + inventory.setItem(sourceSlot, destItem, session); + + affectedSlots.add(sourceSlot); + } else if (isCursor(swapAction.getSource())) { + int destSlot = bedrockSlotToJava(swapAction.getDestination()); + GeyserItemStack sourceItem = playerInv.getCursor(); + GeyserItemStack destItem = inventory.getItem(destSlot); + + inventory.setItem(destSlot, sourceItem, session); + playerInv.setCursor(destItem, session); + + affectedSlots.add(destSlot); + } else { + int sourceSlot = bedrockSlotToJava(swapAction.getSource()); + int destSlot = bedrockSlotToJava(swapAction.getDestination()); + GeyserItemStack sourceItem = inventory.getItem(sourceSlot); + GeyserItemStack destItem = inventory.getItem(destSlot); + + inventory.setItem(destSlot, sourceItem, session); + inventory.setItem(sourceSlot, destItem, session); + + affectedSlots.add(sourceSlot); + affectedSlots.add(destSlot); + } + } + case DROP -> { + DropStackRequestActionData dropAction = (DropStackRequestActionData) action; + if (!checkNetId(session, inventory, dropAction.getSource())) { + return rejectRequest(request); + } + if (isCraftingGrid(dropAction.getSource())) { + return rejectRequest(request, false); + } + + GeyserItemStack sourceItem; + if (isCursor(dropAction.getSource())) { + sourceItem = playerInv.getCursor(); + } else { + int sourceSlot = bedrockSlotToJava(dropAction.getSource()); + sourceItem = inventory.getItem(sourceSlot); + affectedSlots.add(sourceSlot); + } + + if (sourceItem.isEmpty()) { + return rejectRequest(request); + } + + ServerboundSetCreativeModeSlotPacket creativeDropPacket = new ServerboundSetCreativeModeSlotPacket(-1, sourceItem.getItemStack(dropAction.getCount())); + session.sendDownstreamPacket(creativeDropPacket); + + sourceItem.sub(dropAction.getCount()); + } + case DESTROY -> { + // Only called when a creative client wants to destroy an item... I think - Camotoy + DestroyStackRequestActionData destroyAction = (DestroyStackRequestActionData) action; + if (!checkNetId(session, inventory, destroyAction.getSource())) { + return rejectRequest(request); + } + if (isCraftingGrid(destroyAction.getSource())) { + return rejectRequest(request, false); + } + + if (!isCursor(destroyAction.getSource())) { + // Item exists; let's remove it from the inventory + int javaSlot = bedrockSlotToJava(destroyAction.getSource()); + GeyserItemStack existingItem = inventory.getItem(javaSlot); + existingItem.sub(destroyAction.getCount()); + affectedSlots.add(javaSlot); + } else { + // Just sync up the item on our end, since the server doesn't care what's in our cursor + playerInv.getCursor().sub(destroyAction.getCount()); + } + } + default -> { + session.getGeyser().getLogger().error("Unknown crafting state induced by " + session.name()); + return rejectRequest(request); + } + } + } + for (int slot : affectedSlots) { + sendCreativeAction(session, inventory, slot); + } + return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); + } + + @Override + public ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + ItemStack javaCreativeItem = null; + IntSet affectedSlots = new IntOpenHashSet(); + CraftState craftState = CraftState.START; + for (StackRequestActionData action : request.getActions()) { + switch (action.getType()) { + case CRAFT_CREATIVE: { + CraftCreativeStackRequestActionData creativeAction = (CraftCreativeStackRequestActionData) action; + if (craftState != CraftState.START) { + return rejectRequest(request); + } + craftState = CraftState.RECIPE_ID; + + int creativeId = creativeAction.getCreativeItemNetworkId() - 1; + ItemData[] creativeItems = session.getItemMappings().getCreativeItems(); + if (creativeId < 0 || creativeId >= creativeItems.length) { + return rejectRequest(request); + } + // Reference the creative items list we send to the client to know what it's asking of us + ItemData creativeItem = creativeItems[creativeId]; + javaCreativeItem = ItemTranslator.translateToJava(creativeItem, session.getItemMappings()); + break; + } + case CRAFT_RESULTS_DEPRECATED: { + CraftResultsDeprecatedStackRequestActionData deprecatedCraftAction = (CraftResultsDeprecatedStackRequestActionData) action; + if (craftState != CraftState.RECIPE_ID) { + return rejectRequest(request); + } + craftState = CraftState.DEPRECATED; + break; + } + case DESTROY: { + DestroyStackRequestActionData destroyAction = (DestroyStackRequestActionData) action; + if (craftState != CraftState.DEPRECATED) { + return rejectRequest(request); + } + + int sourceSlot = bedrockSlotToJava(destroyAction.getSource()); + inventory.setItem(sourceSlot, GeyserItemStack.EMPTY, session); //assume all creative destroy requests will empty the slot + affectedSlots.add(sourceSlot); + break; + } + case TAKE: + case PLACE: { + TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action; + if (!(craftState == CraftState.DEPRECATED || craftState == CraftState.TRANSFER)) { + return rejectRequest(request); + } + craftState = CraftState.TRANSFER; + + if (transferAction.getSource().getContainer() != ContainerSlotType.CREATIVE_OUTPUT) { + return rejectRequest(request); + } + + if (isCursor(transferAction.getDestination())) { + if (session.getPlayerInventory().getCursor().isEmpty()) { + GeyserItemStack newItemStack = GeyserItemStack.from(javaCreativeItem); + newItemStack.setAmount(transferAction.getCount()); + session.getPlayerInventory().setCursor(newItemStack, session); + } else { + session.getPlayerInventory().getCursor().add(transferAction.getCount()); + } + //cursor is always included in response + } else { + int destSlot = bedrockSlotToJava(transferAction.getDestination()); + if (inventory.getItem(destSlot).isEmpty()) { + GeyserItemStack newItemStack = GeyserItemStack.from(javaCreativeItem); + newItemStack.setAmount(transferAction.getCount()); + inventory.setItem(destSlot, newItemStack, session); + } else { + inventory.getItem(destSlot).add(transferAction.getCount()); + } + affectedSlots.add(destSlot); + } + break; + } + default: + return rejectRequest(request); + } + } + for (int slot : affectedSlots) { + sendCreativeAction(session, inventory, slot); + } + return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); + } + + private static void sendCreativeAction(GeyserSession session, Inventory inventory, int slot) { + GeyserItemStack item = inventory.getItem(slot); + ItemStack itemStack = item.isEmpty() ? new ItemStack(-1, 0, null) : item.getItemStack(); + + ServerboundSetCreativeModeSlotPacket creativePacket = new ServerboundSetCreativeModeSlotPacket(slot, itemStack); + session.sendDownstreamPacket(creativePacket); + } + + private static boolean isCraftingGrid(StackRequestSlotInfoData slotInfoData) { + return slotInfoData.getContainer() == ContainerSlotType.CRAFTING_INPUT; + } + + @Override + public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) { + throw new UnsupportedOperationException(); + } + + @Override + public void prepareInventory(GeyserSession session, Inventory inventory) { + } + + @Override + public void openInventory(GeyserSession session, Inventory inventory) { + } + + @Override + public void closeInventory(GeyserSession session, Inventory inventory) { + } + + @Override + public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/ShulkerInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/ShulkerInventoryTranslator.java new file mode 100644 index 000000000..f92474fb3 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/ShulkerInventoryTranslator.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory; + +import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.inventory.BedrockContainerSlot; +import org.geysermc.geyser.inventory.holder.BlockInventoryHolder; +import org.geysermc.geyser.inventory.updater.ContainerInventoryUpdater; +import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator; +import org.geysermc.geyser.registry.Registries; + +public class ShulkerInventoryTranslator extends AbstractBlockInventoryTranslator { + public ShulkerInventoryTranslator() { + super(27, new BlockInventoryHolder("minecraft:shulker_box[facing=north]", ContainerType.CONTAINER) { + private final BlockEntityTranslator shulkerBoxTranslator = Registries.BLOCK_ENTITIES.get(BlockEntityType.SHULKER_BOX); + + @Override + protected boolean isValidBlock(String[] javaBlockString) { + return javaBlockString[0].contains("shulker_box"); + } + + @Override + protected void setCustomName(GeyserSession session, Vector3i position, Inventory inventory, int javaBlockState) { + NbtMapBuilder tag = NbtMap.builder() + .putInt("x", position.getX()) + .putInt("y", position.getY()) + .putInt("z", position.getZ()) + .putString("CustomName", inventory.getTitle()); + // Don't reset facing property + shulkerBoxTranslator.translateTag(tag, null, javaBlockState); + + BlockEntityDataPacket dataPacket = new BlockEntityDataPacket(); + dataPacket.setData(tag.build()); + dataPacket.setBlockPosition(position); + session.sendUpstreamPacket(dataPacket); + } + }, ContainerInventoryUpdater.INSTANCE); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot) { + if (javaSlot < this.size) { + return new BedrockContainerSlot(ContainerSlotType.SHULKER, javaSlot); + } + return super.javaSlotToBedrockContainer(javaSlot); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/SmithingInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/SmithingInventoryTranslator.java new file mode 100644 index 000000000..4e34bca14 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/SmithingInventoryTranslator.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import org.geysermc.geyser.inventory.BedrockContainerSlot; +import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; + +public class SmithingInventoryTranslator extends AbstractBlockInventoryTranslator { + public SmithingInventoryTranslator() { + super(3, "minecraft:smithing_table", ContainerType.SMITHING_TABLE, UIInventoryUpdater.INSTANCE); + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + return switch (slotInfoData.getContainer()) { + case SMITHING_TABLE_INPUT -> 0; + case SMITHING_TABLE_MATERIAL -> 1; + case SMITHING_TABLE_RESULT, CREATIVE_OUTPUT -> 2; + default -> super.bedrockSlotToJava(slotInfoData); + }; + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + return switch (slot) { + case 0 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_INPUT, 51); + case 1 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_MATERIAL, 52); + case 2 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_RESULT, 50); + default -> super.javaSlotToBedrockContainer(slot); + }; + } + + @Override + public int javaSlotToBedrock(int slot) { + return switch (slot) { + case 0 -> 51; + case 1 -> 52; + case 2 -> 50; + default -> super.javaSlotToBedrock(slot); + }; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/StonecutterInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/StonecutterInventoryTranslator.java new file mode 100644 index 000000000..e7140f7b2 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/StonecutterInventoryTranslator.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerButtonClickPacket; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftResultsDeprecatedStackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; +import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; +import it.unimi.dsi.fastutil.ints.IntList; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.inventory.PlayerInventory; +import org.geysermc.geyser.inventory.StonecutterContainer; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.inventory.BedrockContainerSlot; +import org.geysermc.geyser.inventory.SlotType; +import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; +import org.geysermc.geyser.translator.inventory.item.ItemTranslator; + +public class StonecutterInventoryTranslator extends AbstractBlockInventoryTranslator { + public StonecutterInventoryTranslator() { + super(2, "minecraft:stonecutter[facing=north]", com.nukkitx.protocol.bedrock.data.inventory.ContainerType.STONECUTTER, UIInventoryUpdater.INSTANCE); + } + + @Override + public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { + // First is pre-1.18. TODO remove after 1.17.40 support is dropped and refactor stonecutter support to use CraftRecipeStackRequestActionData's recipe ID + return action.getType() == StackRequestActionType.CRAFT_NON_IMPLEMENTED_DEPRECATED || action.getType() == StackRequestActionType.CRAFT_RECIPE; + } + + @Override + public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { + // TODO: Also surely to change in the future + StackRequestActionData data = request.getActions()[1]; + if (!(data instanceof CraftResultsDeprecatedStackRequestActionData craftData)) { + return rejectRequest(request); + } + + StonecutterContainer container = (StonecutterContainer) inventory; + // Get the ID of the item we are cutting + int id = inventory.getItem(0).getJavaId(); + // Look up all possible options of cutting from this ID + IntList results = session.getStonecutterRecipes().get(id); + if (results == null) { + return rejectRequest(request); + } + + ItemStack javaOutput = ItemTranslator.translateToJava(craftData.getResultItems()[0], session.getItemMappings()); + int button = results.indexOf(javaOutput.getId()); + // If we've already pressed the button with this item, no need to press it again! + if (container.getStonecutterButton() != button) { + // Getting the index of the item in the Java stonecutter list + ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getId(), button); + session.sendDownstreamPacket(packet); + container.setStonecutterButton(button); + if (inventory.getItem(1).getJavaId() != javaOutput.getId()) { + // We don't know there is an output here, so we tell ourselves that there is + inventory.setItem(1, GeyserItemStack.from(javaOutput), session); + } + } + + return translateRequest(session, inventory, request); + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + return switch (slotInfoData.getContainer()) { + case STONECUTTER_INPUT -> 0; + case STONECUTTER_RESULT, CREATIVE_OUTPUT -> 1; + default -> super.bedrockSlotToJava(slotInfoData); + }; + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot == 0) { + return new BedrockContainerSlot(ContainerSlotType.STONECUTTER_INPUT, 3); + } + if (slot == 1) { + return new BedrockContainerSlot(ContainerSlotType.STONECUTTER_RESULT, 50); + } + return super.javaSlotToBedrockContainer(slot); + } + + @Override + public int javaSlotToBedrock(int slot) { + if (slot == 0) { + return 3; + } + if (slot == 1) { + return 50; + } + return super.javaSlotToBedrock(slot); + } + + @Override + public SlotType getSlotType(int javaSlot) { + if (javaSlot == 1) { + return SlotType.OUTPUT; + } + return super.getSlotType(javaSlot); + } + + @Override + public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) { + return new StonecutterContainer(name, windowId, this.size, containerType, playerInventory); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/ChestInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/chest/ChestInventoryTranslator.java similarity index 55% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/ChestInventoryTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/inventory/chest/ChestInventoryTranslator.java index 152f4a852..417aa5bf3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/ChestInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/chest/ChestInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,16 +23,15 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.inventory; +package org.geysermc.geyser.translator.inventory.chest; -import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.updater.ChestInventoryUpdater; -import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; -import org.geysermc.connector.utils.InventoryUtils; - -import java.util.List; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.inventory.BedrockContainerSlot; +import org.geysermc.geyser.translator.inventory.BaseInventoryTranslator; +import org.geysermc.geyser.inventory.updater.ChestInventoryUpdater; +import org.geysermc.geyser.inventory.updater.InventoryUpdater; public abstract class ChestInventoryTranslator extends BaseInventoryTranslator { private final InventoryUpdater updater; @@ -42,6 +41,16 @@ public abstract class ChestInventoryTranslator extends BaseInventoryTranslator { this.updater = new ChestInventoryUpdater(paddedSize); } + @Override + public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, ContainerSlotType bedrockSourceContainer, + int javaSourceSlot, ContainerSlotType bedrockDestinationContainer, int javaDestinationSlot) { + // Reject any item placements that occur in the unusable inventory space + if (bedrockSourceContainer == ContainerSlotType.CONTAINER && javaSourceSlot >= this.size) { + return true; + } + return bedrockDestinationContainer == ContainerSlotType.CONTAINER && javaDestinationSlot >= this.size; + } + @Override public void updateInventory(GeyserSession session, Inventory inventory) { updater.updateInventory(this, session, inventory); @@ -53,17 +62,10 @@ public abstract class ChestInventoryTranslator extends BaseInventoryTranslator { } @Override - public void translateActions(GeyserSession session, Inventory inventory, List actions) { - for (InventoryActionData action : actions) { - if (action.getSource().getContainerId() == inventory.getId()) { - if (action.getSlot() >= size) { - updateInventory(session, inventory); - InventoryUtils.updateCursor(session); - return; - } - } + public BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot) { + if (javaSlot < this.size) { + return new BedrockContainerSlot(ContainerSlotType.CONTAINER, javaSlot); } - - super.translateActions(session, inventory, actions); + return super.javaSlotToBedrockContainer(javaSlot); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/DoubleChestInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/chest/DoubleChestInventoryTranslator.java similarity index 54% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/DoubleChestInventoryTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/inventory/chest/DoubleChestInventoryTranslator.java index 1183b21da..a42001d34 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/DoubleChestInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/chest/DoubleChestInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,37 +23,71 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.inventory; +package org.geysermc.geyser.translator.inventory.chest; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; +import com.nukkitx.protocol.bedrock.packet.ContainerClosePacket; import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.geyser.inventory.Container; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.level.block.DoubleChestValue; +import org.geysermc.geyser.translator.level.block.entity.DoubleChestBlockEntityTranslator; +import org.geysermc.geyser.registry.BlockRegistries; public class DoubleChestInventoryTranslator extends ChestInventoryTranslator { - private final int blockId; + private final int defaultJavaBlockState; public DoubleChestInventoryTranslator(int size) { super(size, 54); - int javaBlockState = BlockTranslator.getJavaBlockState("minecraft:chest[facing=north,type=single,waterlogged=false]"); - this.blockId = BlockTranslator.getBedrockBlockId(javaBlockState); + this.defaultJavaBlockState = BlockRegistries.JAVA_IDENTIFIERS.get("minecraft:chest[facing=north,type=single,waterlogged=false]"); } @Override public void prepareInventory(GeyserSession session, Inventory inventory) { + // See BlockInventoryHolder - same concept there except we're also dealing with a specific block state + if (session.getLastInteractionPlayerPosition().equals(session.getPlayerEntity().getPosition())) { + int javaBlockId = session.getGeyser().getWorldManager().getBlockAt(session, session.getLastInteractionBlockPosition()); + String[] javaBlockString = BlockRegistries.JAVA_IDENTIFIERS.get().getOrDefault(javaBlockId, "minecraft:air").split("\\["); + if (javaBlockString.length > 1 && (javaBlockString[0].equals("minecraft:chest") || javaBlockString[0].equals("minecraft:trapped_chest")) + && !javaBlockString[1].contains("type=single")) { + inventory.setHolderPosition(session.getLastInteractionBlockPosition()); + ((Container) inventory).setUsingRealBlock(true, javaBlockString[0]); + + NbtMapBuilder tag = NbtMap.builder() + .putString("id", "Chest") + .putInt("x", session.getLastInteractionBlockPosition().getX()) + .putInt("y", session.getLastInteractionBlockPosition().getY()) + .putInt("z", session.getLastInteractionBlockPosition().getZ()) + .putString("CustomName", inventory.getTitle()) + .putString("id", "Chest"); + + DoubleChestValue chestValue = BlockStateValues.getDoubleChestValues().get(javaBlockId); + DoubleChestBlockEntityTranslator.translateChestValue(tag, chestValue, + session.getLastInteractionBlockPosition().getX(), session.getLastInteractionBlockPosition().getZ()); + + BlockEntityDataPacket dataPacket = new BlockEntityDataPacket(); + dataPacket.setData(tag.build()); + dataPacket.setBlockPosition(session.getLastInteractionBlockPosition()); + session.sendUpstreamPacket(dataPacket); + return; + } + } + Vector3i position = session.getPlayerEntity().getPosition().toInt().add(Vector3i.UP); Vector3i pairPosition = position.add(Vector3i.UNIT_X); + int bedrockBlockId = session.getBlockMappings().getBedrockBlockId(defaultJavaBlockState); UpdateBlockPacket blockPacket = new UpdateBlockPacket(); blockPacket.setDataLayer(0); blockPacket.setBlockPosition(position); - blockPacket.setRuntimeId(blockId); + blockPacket.setRuntimeId(bedrockBlockId); blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); session.sendUpstreamPacket(blockPacket); @@ -73,7 +107,7 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator { blockPacket = new UpdateBlockPacket(); blockPacket.setDataLayer(0); blockPacket.setBlockPosition(pairPosition); - blockPacket.setRuntimeId(blockId); + blockPacket.setRuntimeId(bedrockBlockId); blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); session.sendUpstreamPacket(blockPacket); @@ -105,22 +139,30 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator { @Override public void closeInventory(GeyserSession session, Inventory inventory) { + if (((Container) inventory).isUsingRealBlock()) { + // No need to reset a block since we didn't change any blocks + // But send a container close packet because we aren't destroying the original. + ContainerClosePacket packet = new ContainerClosePacket(); + packet.setId((byte) inventory.getId()); + packet.setUnknownBool0(true); //TODO needs to be changed in Protocol to "server-side" or something + session.sendUpstreamPacket(packet); + return; + } + Vector3i holderPos = inventory.getHolderPosition(); - Position pos = new Position(holderPos.getX(), holderPos.getY(), holderPos.getZ()); - int realBlock = session.getConnector().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ()); + int realBlock = session.getGeyser().getWorldManager().getBlockAt(session, holderPos); UpdateBlockPacket blockPacket = new UpdateBlockPacket(); blockPacket.setDataLayer(0); blockPacket.setBlockPosition(holderPos); - blockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(realBlock)); + blockPacket.setRuntimeId(session.getBlockMappings().getBedrockBlockId(realBlock)); session.sendUpstreamPacket(blockPacket); holderPos = holderPos.add(Vector3i.UNIT_X); - pos = new Position(holderPos.getX(), holderPos.getY(), holderPos.getZ()); - realBlock = session.getConnector().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ()); + realBlock = session.getGeyser().getWorldManager().getBlockAt(session, holderPos); blockPacket = new UpdateBlockPacket(); blockPacket.setDataLayer(0); blockPacket.setBlockPosition(holderPos); - blockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(realBlock)); + blockPacket.setRuntimeId(session.getBlockMappings().getBedrockBlockId(realBlock)); session.sendUpstreamPacket(blockPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SingleChestInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/chest/SingleChestInventoryTranslator.java similarity index 64% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/SingleChestInventoryTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/inventory/chest/SingleChestInventoryTranslator.java index 45860dcd3..86696b21b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SingleChestInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/chest/SingleChestInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,22 +23,32 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.inventory; +package org.geysermc.geyser.translator.inventory.chest; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.holder.BlockInventoryHolder; -import org.geysermc.connector.network.translators.inventory.holder.InventoryHolder; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.inventory.holder.BlockInventoryHolder; +import org.geysermc.geyser.inventory.holder.InventoryHolder; public class SingleChestInventoryTranslator extends ChestInventoryTranslator { private final InventoryHolder holder; public SingleChestInventoryTranslator(int size) { super(size, 27); - int javaBlockState = BlockTranslator.getJavaBlockState("minecraft:chest[facing=north,type=single,waterlogged=false]"); - this.holder = new BlockInventoryHolder(BlockTranslator.getBedrockBlockId(javaBlockState), ContainerType.CONTAINER); + this.holder = new BlockInventoryHolder("minecraft:chest[facing=north,type=single,waterlogged=false]", ContainerType.CONTAINER, + "minecraft:ender_chest", "minecraft:trapped_chest") { + @Override + protected boolean isValidBlock(String[] javaBlockString) { + if (javaBlockString[0].equals("minecraft:ender_chest")) { + // Can't have double ender chests + return true; + } + + // Add provision to ensure this isn't a double chest + return super.isValidBlock(javaBlockString) && (javaBlockString.length > 1 && javaBlockString[1].contains("type=single")); + } + }; } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/FurnaceInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/furnace/AbstractFurnaceInventoryTranslator.java similarity index 64% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/FurnaceInventoryTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/inventory/furnace/AbstractFurnaceInventoryTranslator.java index 1f148e024..de7ea4de7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/FurnaceInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/furnace/AbstractFurnaceInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,18 +23,21 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.inventory; +package org.geysermc.geyser.translator.inventory.furnace; -import com.github.steveice10.mc.protocol.data.game.window.WindowType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.packet.ContainerSetDataPacket; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.inventory.BedrockContainerSlot; +import org.geysermc.geyser.inventory.SlotType; +import org.geysermc.geyser.translator.inventory.AbstractBlockInventoryTranslator; +import org.geysermc.geyser.inventory.updater.ContainerInventoryUpdater; -public class FurnaceInventoryTranslator extends BlockInventoryTranslator { - public FurnaceInventoryTranslator() { - super(3, "minecraft:furnace[facing=north,lit=false]", ContainerType.FURNACE, new ContainerInventoryUpdater()); +public abstract class AbstractFurnaceInventoryTranslator extends AbstractBlockInventoryTranslator { + AbstractFurnaceInventoryTranslator(String javaBlockIdentifier, ContainerType containerType) { + super(3, javaBlockIdentifier, containerType, ContainerInventoryUpdater.INSTANCE); } @Override @@ -50,9 +53,6 @@ public class FurnaceInventoryTranslator extends BlockInventoryTranslator { break; case 2: dataPacket.setProperty(ContainerSetDataPacket.FURNACE_TICK_COUNT); - if (inventory.getWindowType() == WindowType.BLAST_FURNACE || inventory.getWindowType() == WindowType.SMOKER) { - value *= 2; - } break; default: return; @@ -67,4 +67,15 @@ public class FurnaceInventoryTranslator extends BlockInventoryTranslator { return SlotType.FURNACE_OUTPUT; return SlotType.NORMAL; } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot == 1) { + return new BedrockContainerSlot(ContainerSlotType.FURNACE_FUEL, javaSlotToBedrock(slot)); + } + if (slot == 2) { + return new BedrockContainerSlot(ContainerSlotType.FURNACE_OUTPUT, javaSlotToBedrock(slot)); + } + return super.javaSlotToBedrockContainer(slot); + } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/furnace/BlastFurnaceInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/furnace/BlastFurnaceInventoryTranslator.java new file mode 100644 index 000000000..7839d5370 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/furnace/BlastFurnaceInventoryTranslator.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory.furnace; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import org.geysermc.geyser.inventory.BedrockContainerSlot; + +public class BlastFurnaceInventoryTranslator extends AbstractFurnaceInventoryTranslator { + public BlastFurnaceInventoryTranslator() { + super("minecraft:blast_furnace[facing=north,lit=false]", ContainerType.BLAST_FURNACE); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot == 0) { + return new BedrockContainerSlot(ContainerSlotType.BLAST_FURNACE_INGREDIENT, javaSlotToBedrock(slot)); + } + return super.javaSlotToBedrockContainer(slot); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/furnace/FurnaceInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/furnace/FurnaceInventoryTranslator.java new file mode 100644 index 000000000..5c9030197 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/furnace/FurnaceInventoryTranslator.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory.furnace; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import org.geysermc.geyser.inventory.BedrockContainerSlot; + +public class FurnaceInventoryTranslator extends AbstractFurnaceInventoryTranslator { + public FurnaceInventoryTranslator() { + super("minecraft:furnace[facing=north,lit=false]", ContainerType.FURNACE); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot == 0) { + return new BedrockContainerSlot(ContainerSlotType.FURNACE_INGREDIENT, javaSlotToBedrock(slot)); + } + return super.javaSlotToBedrockContainer(slot); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/furnace/SmokerInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/furnace/SmokerInventoryTranslator.java new file mode 100644 index 000000000..350e00de3 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/furnace/SmokerInventoryTranslator.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory.furnace; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import org.geysermc.geyser.inventory.BedrockContainerSlot; + +public class SmokerInventoryTranslator extends AbstractFurnaceInventoryTranslator { + public SmokerInventoryTranslator() { + super("minecraft:smoker[facing=north,lit=false]", ContainerType.SMOKER); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot == 0) { + return new BedrockContainerSlot(ContainerSlotType.SMOKER_INGREDIENT, javaSlotToBedrock(slot)); + } + return super.javaSlotToBedrockContainer(slot); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BlockInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/AbstractHorseInventoryTranslator.java similarity index 58% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/BlockInventoryTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/inventory/horse/AbstractHorseInventoryTranslator.java index 4b8b57e8f..b4c49cb12 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BlockInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/AbstractHorseInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,41 +23,32 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.inventory; +package org.geysermc.geyser.translator.inventory.horse; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.network.translators.inventory.holder.BlockInventoryHolder; -import org.geysermc.connector.network.translators.inventory.holder.InventoryHolder; -import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.BaseInventoryTranslator; +import org.geysermc.geyser.inventory.updater.HorseInventoryUpdater; +import org.geysermc.geyser.inventory.updater.InventoryUpdater; -public class BlockInventoryTranslator extends BaseInventoryTranslator { - private final InventoryHolder holder; +public abstract class AbstractHorseInventoryTranslator extends BaseInventoryTranslator { private final InventoryUpdater updater; - public BlockInventoryTranslator(int size, String javaBlockIdentifier, ContainerType containerType, InventoryUpdater updater) { + public AbstractHorseInventoryTranslator(int size) { super(size); - int javaBlockState = BlockTranslator.getJavaBlockState(javaBlockIdentifier); - int blockId = BlockTranslator.getBedrockBlockId(javaBlockState); - this.holder = new BlockInventoryHolder(blockId, containerType); - this.updater = updater; + this.updater = HorseInventoryUpdater.INSTANCE; } @Override public void prepareInventory(GeyserSession session, Inventory inventory) { - holder.prepareInventory(this, session, inventory); } @Override public void openInventory(GeyserSession session, Inventory inventory) { - holder.openInventory(this, session, inventory); } @Override public void closeInventory(GeyserSession session, Inventory inventory) { - holder.closeInventory(this, session, inventory); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/ChestedHorseInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/ChestedHorseInventoryTranslator.java new file mode 100644 index 000000000..79f34da57 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/ChestedHorseInventoryTranslator.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory.horse; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.inventory.BedrockContainerSlot; + +import java.util.Arrays; + +public abstract class ChestedHorseInventoryTranslator extends AbstractHorseInventoryTranslator { + private final int chestSize; + private final int equipSlot; + + /** + * @param size the total Java size of the inventory + * @param equipSlot the Java equipment slot. Java always has two slots - one for armor and one for saddle. Chested horses + * on Bedrock only acknowledge one slot. + */ + public ChestedHorseInventoryTranslator(int size, int equipSlot) { + super(size); + this.chestSize = size - 2; + this.equipSlot = equipSlot; + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + if (slotInfoData.getContainer() == ContainerSlotType.HORSE_EQUIP) { + return this.equipSlot; + } + if (slotInfoData.getContainer() == ContainerSlotType.CONTAINER) { + return slotInfoData.getSlot() + 1; + } + return super.bedrockSlotToJava(slotInfoData); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot == this.equipSlot) { + return new BedrockContainerSlot(ContainerSlotType.HORSE_EQUIP, 0); + } + if (slot <= this.size - 1) { // Accommodate for the lack of one slot (saddle or armor) + return new BedrockContainerSlot(ContainerSlotType.CONTAINER, slot - 1); + } + return super.javaSlotToBedrockContainer(slot); + } + + @Override + public int javaSlotToBedrock(int slot) { + if (slot == 0 && this.equipSlot == 0) { + return 0; + } + if (slot <= this.size - 1) { + return slot - 1; + } + return super.javaSlotToBedrock(slot); + } + + @Override + public void updateInventory(GeyserSession session, Inventory inventory) { + ItemData[] bedrockItems = new ItemData[36]; + for (int i = 0; i < 36; i++) { + final int offset = i < 9 ? 27 : -9; + bedrockItems[i] = inventory.getItem(this.size + i + offset).getItemData(session); + } + InventoryContentPacket contentPacket = new InventoryContentPacket(); + contentPacket.setContainerId(ContainerId.INVENTORY); + contentPacket.setContents(Arrays.asList(bedrockItems)); + session.sendUpstreamPacket(contentPacket); + + ItemData[] horseItems = new ItemData[chestSize + 1]; + // Manually specify the first slot - Java always has two slots (armor and saddle) and one is invisible. + // Bedrock doesn't have this invisible slot. + horseItems[0] = inventory.getItem(this.equipSlot).getItemData(session); + for (int i = 1; i < horseItems.length; i++) { + horseItems[i] = inventory.getItem(i + 1).getItemData(session); + } + + InventoryContentPacket horseContentsPacket = new InventoryContentPacket(); + horseContentsPacket.setContainerId(inventory.getId()); + horseContentsPacket.setContents(Arrays.asList(horseItems)); + session.sendUpstreamPacket(horseContentsPacket); + } +} diff --git a/common/src/main/java/org/geysermc/common/window/component/FormComponent.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/DonkeyInventoryTranslator.java similarity index 80% rename from common/src/main/java/org/geysermc/common/window/component/FormComponent.java rename to core/src/main/java/org/geysermc/geyser/translator/inventory/horse/DonkeyInventoryTranslator.java index 5a56ae0cc..ea0a580f0 100644 --- a/common/src/main/java/org/geysermc/common/window/component/FormComponent.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/DonkeyInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,16 +23,10 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.component; +package org.geysermc.geyser.translator.inventory.horse; -import lombok.Getter; - -public abstract class FormComponent { - - @Getter - private final String type; - - public FormComponent(String type) { - this.type = type; +public class DonkeyInventoryTranslator extends ChestedHorseInventoryTranslator { + public DonkeyInventoryTranslator(int size) { + super(size, 0); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/HorseInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/HorseInventoryTranslator.java new file mode 100644 index 000000000..429125362 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/HorseInventoryTranslator.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory.horse; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import org.geysermc.geyser.inventory.BedrockContainerSlot; + +public class HorseInventoryTranslator extends AbstractHorseInventoryTranslator { + public HorseInventoryTranslator(int size) { + super(size); + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + if (slotInfoData.getContainer() == ContainerSlotType.HORSE_EQUIP) { + return slotInfoData.getSlot(); + } + return super.bedrockSlotToJava(slotInfoData); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot == 0 || slot == 1) { + return new BedrockContainerSlot(ContainerSlotType.HORSE_EQUIP, slot); + } + return super.javaSlotToBedrockContainer(slot); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/LlamaInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/LlamaInventoryTranslator.java new file mode 100644 index 000000000..97aa7edf2 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/horse/LlamaInventoryTranslator.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory.horse; + +public class LlamaInventoryTranslator extends ChestedHorseInventoryTranslator { + public LlamaInventoryTranslator(int size) { + super(size, 1); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/BannerTranslator.java similarity index 56% rename from connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/inventory/item/BannerTranslator.java index 14b934362..d2c9e5dd6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/BannerTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.item.translators; +package org.geysermc.geyser.translator.inventory.item; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.opennbt.tag.builtin.*; @@ -32,10 +32,10 @@ import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import org.geysermc.connector.network.translators.ItemRemapper; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.item.ItemTranslator; +import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.registry.type.ItemMappings; import java.util.ArrayList; import java.util.HashMap; @@ -45,10 +45,43 @@ import java.util.stream.Collectors; @ItemRemapper public class BannerTranslator extends ItemTranslator { - private final List appliedItems; + /** + * Holds what a Java ominous banner pattern looks like. + * + * Translating the patterns over to Bedrock does not work effectively, but Bedrock has a dedicated type for + * ominous banners that we set instead. This variable is used to detect Java ominous banner patterns, and apply + * the correct ominous banner pattern if Bedrock pulls the item from creative. + */ + public static final ListTag OMINOUS_BANNER_PATTERN; + + private final List appliedItems; + + static { + OMINOUS_BANNER_PATTERN = new ListTag("Patterns"); + // Construct what an ominous banner is supposed to look like + OMINOUS_BANNER_PATTERN.add(getPatternTag("mr", 9)); + OMINOUS_BANNER_PATTERN.add(getPatternTag("bs", 8)); + OMINOUS_BANNER_PATTERN.add(getPatternTag("cs", 7)); + OMINOUS_BANNER_PATTERN.add(getPatternTag("bo", 8)); + OMINOUS_BANNER_PATTERN.add(getPatternTag("ms", 15)); + OMINOUS_BANNER_PATTERN.add(getPatternTag("hh", 8)); + OMINOUS_BANNER_PATTERN.add(getPatternTag("mc", 8)); + OMINOUS_BANNER_PATTERN.add(getPatternTag("bo", 15)); + } + + private static CompoundTag getPatternTag(String pattern, int color) { + StringTag patternType = new StringTag("Pattern", pattern); + IntTag colorTag = new IntTag("Color", color); + CompoundTag tag = new CompoundTag(""); + tag.put(patternType); + tag.put(colorTag); + return tag; + } public BannerTranslator() { - appliedItems = ItemRegistry.ITEM_ENTRIES.values() + appliedItems = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) + .getItems() + .values() .stream() .filter(entry -> entry.getJavaIdentifier().endsWith("banner")) .collect(Collectors.toList()); @@ -62,7 +95,7 @@ public class BannerTranslator extends ItemTranslator { */ public static NbtList convertBannerPattern(ListTag patterns) { List tagsList = new ArrayList<>(); - for (com.github.steveice10.opennbt.tag.builtin.Tag patternTag : patterns.getValue()) { + for (Tag patternTag : patterns.getValue()) { NbtMap newPatternTag = getBedrockBannerPattern((CompoundTag) patternTag); if (newPatternTag != null) { tagsList.add(newPatternTag); @@ -100,8 +133,8 @@ public class BannerTranslator extends ItemTranslator { */ public static ListTag convertBannerPattern(List patterns) { List tagsList = new ArrayList<>(); - for (Object patternTag : patterns) { - tagsList.add(getJavaBannerPattern((NbtMap) patternTag)); + for (NbtMap patternTag : patterns) { + tagsList.add(getJavaBannerPattern(patternTag)); } return new ListTag("Patterns", tagsList); @@ -110,7 +143,7 @@ public class BannerTranslator extends ItemTranslator { /** * Convert the Bedrock edition banner pattern nbt to Java edition * - * @param pattern Bedorck edition pattern nbt + * @param pattern Bedrock edition pattern nbt * @return The Java edition format pattern nbt */ public static CompoundTag getJavaBannerPattern(NbtMap pattern) { @@ -122,49 +155,63 @@ public class BannerTranslator extends ItemTranslator { } @Override - public ItemData translateToBedrock(ItemStack itemStack, ItemEntry itemEntry) { + public ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { if (itemStack.getNbt() == null) { - return super.translateToBedrock(itemStack, itemEntry); + return super.translateToBedrock(itemStack, mapping, mappings); } - ItemData itemData = super.translateToBedrock(itemStack, itemEntry); + ItemData.Builder builder = super.translateToBedrock(itemStack, mapping, mappings); CompoundTag blockEntityTag = itemStack.getNbt().get("BlockEntityTag"); if (blockEntityTag != null && blockEntityTag.contains("Patterns")) { ListTag patterns = blockEntityTag.get("Patterns"); - NbtMapBuilder builder = itemData.getTag().toBuilder(); - builder.put("Patterns", convertBannerPattern(patterns)); + NbtMapBuilder nbtBuilder = builder.build().getTag().toBuilder(); //TODO fix ugly hack + if (patterns.equals(OMINOUS_BANNER_PATTERN)) { + // Remove the current patterns and set the ominous banner type + nbtBuilder.remove("Patterns"); + nbtBuilder.putInt("Type", 1); + } else { + nbtBuilder.put("Patterns", convertBannerPattern(patterns)); + } - itemData = ItemData.of(itemData.getId(), itemData.getDamage(), itemData.getCount(), builder.build()); + builder.tag(nbtBuilder.build()); } - return itemData; + return builder; } @Override - public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { + public ItemStack translateToJava(ItemData itemData, ItemMapping mapping, ItemMappings mappings) { if (itemData.getTag() == null) { - return super.translateToJava(itemData, itemEntry); + return super.translateToJava(itemData, mapping, mappings); } - ItemStack itemStack = super.translateToJava(itemData, itemEntry); + ItemStack itemStack = super.translateToJava(itemData, mapping, mappings); NbtMap nbtTag = itemData.getTag(); - if (nbtTag.containsKey("Patterns", NbtType.COMPOUND)) { + if (nbtTag.containsKey("Type", NbtType.INT) && nbtTag.getInt("Type") == 1) { + // Ominous banner pattern + itemStack.getNbt().remove("Type"); + CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag"); + blockEntityTag.put(OMINOUS_BANNER_PATTERN); + + itemStack.getNbt().put(blockEntityTag); + } else if (nbtTag.containsKey("Patterns", NbtType.LIST)) { List patterns = nbtTag.getList("Patterns", NbtType.COMPOUND); CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag"); blockEntityTag.put(convertBannerPattern(patterns)); itemStack.getNbt().put(blockEntityTag); + itemStack.getNbt().remove("Patterns"); // Remove the old Bedrock patterns list } return itemStack; } @Override - public List getAppliedItems() { + public List getAppliedItems() { return appliedItems; } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CompassTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CompassTranslator.java new file mode 100644 index 000000000..7367e28db --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CompassTranslator.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory.item; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.opennbt.tag.builtin.*; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.registry.type.ItemMappings; + +import java.util.List; +import java.util.stream.Collectors; + +@ItemRemapper +public class CompassTranslator extends ItemTranslator { + + private final List appliedItems; + + public CompassTranslator() { + appliedItems = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) + .getItems() + .values() + .stream() + .filter(entry -> entry.getJavaIdentifier().endsWith("compass")) + .collect(Collectors.toList()); + } + + @Override + public ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { + if (itemStack.getNbt() == null) return super.translateToBedrock(itemStack, mapping, mappings); + + Tag lodestoneTag = itemStack.getNbt().get("LodestoneTracked"); + if (lodestoneTag instanceof ByteTag) { + // Get the fake lodestonecompass entry + mapping = mappings.getStoredItems().lodestoneCompass(); + // NBT will be translated in nbt/LodestoneCompassTranslator + } + + return super.translateToBedrock(itemStack, mapping, mappings); + } + + @Override + public ItemStack translateToJava(ItemData itemData, ItemMapping mapping, ItemMappings mappings) { + if (mapping.getBedrockIdentifier().equals("minecraft:lodestone_compass")) { + // Revert the entry back to the compass + mapping = mappings.getStoredItems().compass(); + } + + return super.translateToJava(itemData, mapping, mappings); + } + + @Override + public List getAppliedItems() { + return appliedItems; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/ItemRemapper.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemRemapper.java similarity index 92% rename from connector/src/main/java/org/geysermc/connector/network/translators/ItemRemapper.java rename to core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemRemapper.java index 6c286da2f..651228c15 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/ItemRemapper.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemRemapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators; +package org.geysermc.geyser.translator.inventory.item; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java similarity index 61% rename from connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java index 0df179cf4..84ab0dc55 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.item; +package org.geysermc.geyser.translator.inventory.item; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.opennbt.tag.builtin.*; @@ -34,15 +34,16 @@ import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.ItemRemapper; -import org.geysermc.connector.network.translators.chat.MessageTranslator; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.LanguageUtils; -import org.reflections.Reflections; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.registry.type.ItemMappings; +import org.geysermc.geyser.util.FileUtils; +import org.geysermc.geyser.text.MinecraftLocale; +import javax.annotation.Nonnull; import java.util.*; import java.util.stream.Collectors; @@ -59,13 +60,11 @@ public abstract class ItemTranslator { static { /* Load item translators */ - Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.item") : new Reflections("org.geysermc.connector.network.translators.item"); - Map loadedNbtItemTranslators = new HashMap<>(); - for (Class clazz : ref.getTypesAnnotatedWith(ItemRemapper.class)) { + for (Class clazz : FileUtils.getGeneratedClassesForAnnotation(ItemRemapper.class)) { int priority = clazz.getAnnotation(ItemRemapper.class).priority(); - GeyserConnector.getInstance().getLogger().debug("Found annotated item translator: " + clazz.getCanonicalName()); + GeyserImpl.getInstance().getLogger().debug("Found annotated item translator: " + clazz.getCanonicalName()); try { if (NbtItemStackTranslator.class.isAssignableFrom(clazz)) { @@ -74,35 +73,42 @@ public abstract class ItemTranslator { continue; } ItemTranslator itemStackTranslator = (ItemTranslator) clazz.newInstance(); - List appliedItems = itemStackTranslator.getAppliedItems(); - for (ItemEntry item : appliedItems) { + List appliedItems = itemStackTranslator.getAppliedItems(); + for (ItemMapping item : appliedItems) { ItemTranslator registered = ITEM_STACK_TRANSLATORS.get(item.getJavaId()); if (registered != null) { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.item.already_registered", clazz.getCanonicalName(), registered.getClass().getCanonicalName(), item.getJavaIdentifier())); + GeyserImpl.getInstance().getLogger().error("Could not instantiate annotated item translator " + + clazz.getCanonicalName() + ". Item translator " + registered.getClass().getCanonicalName() + + " is already registered for the item " + item.getJavaIdentifier()); continue; } ITEM_STACK_TRANSLATORS.put(item.getJavaId(), itemStackTranslator); } } catch (InstantiationException | IllegalAccessException e) { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.item.failed", clazz.getCanonicalName())); + GeyserImpl.getInstance().getLogger().error("Could not instantiate annotated item translator " + clazz.getCanonicalName()); } } NBT_TRANSLATORS = loadedNbtItemTranslators.keySet().stream().sorted(Comparator.comparingInt(loadedNbtItemTranslators::get)).collect(Collectors.toList()); } - public static ItemStack translateToJava(ItemData data) { + /** + * @param mappings item mappings to use while translating. This can't just be a Geyser session as this method is used + * when loading recipes. + */ + public static ItemStack translateToJava(ItemData data, ItemMappings mappings) { if (data == null) { return new ItemStack(0); } - ItemEntry javaItem = ItemRegistry.getItem(data); + + ItemMapping javaItem = mappings.getMapping(data); ItemStack itemStack; ItemTranslator itemStackTranslator = ITEM_STACK_TRANSLATORS.get(javaItem.getJavaId()); if (itemStackTranslator != null) { - itemStack = itemStackTranslator.translateToJava(data, javaItem); + itemStack = itemStackTranslator.translateToJava(data, javaItem, mappings); } else { - itemStack = DEFAULT_TRANSLATOR.translateToJava(data, javaItem); + itemStack = DEFAULT_TRANSLATOR.translateToJava(data, javaItem, mappings); } if (itemStack != null && itemStack.getNbt() != null) { @@ -111,27 +117,34 @@ public abstract class ItemTranslator { translator.translateToJava(itemStack.getNbt(), javaItem); } } + if (itemStack.getNbt().isEmpty()) { + // Otherwise, seems to causes issues with villagers accepting books, and I don't see how this will break anything else. - Camotoy + itemStack = new ItemStack(itemStack.getId(), itemStack.getAmount(), null); + } } return itemStack; } + @Nonnull public static ItemData translateToBedrock(GeyserSession session, ItemStack stack) { if (stack == null) { return ItemData.AIR; } - ItemEntry bedrockItem = ItemRegistry.getItem(stack); + ItemMapping bedrockItem = session.getItemMappings().getMapping(stack); + if (bedrockItem == null) { + session.getGeyser().getLogger().debug("No matching ItemMapping for " + stack); + return ItemData.AIR; + } - com.github.steveice10.opennbt.tag.builtin.CompoundTag nbt = stack.getNbt() != null ? stack.getNbt().clone() : null; + CompoundTag nbt = stack.getNbt() != null ? stack.getNbt().clone() : null; // This is a fallback for maps with no nbt if (nbt == null && bedrockItem.getJavaIdentifier().equals("minecraft:filled_map")) { - nbt = new com.github.steveice10.opennbt.tag.builtin.CompoundTag(""); + nbt = new CompoundTag(""); nbt.put(new IntTag("map", 0)); } - ItemStack itemStack = new ItemStack(stack.getId(), stack.getAmount(), nbt); - if (nbt != null) { for (NbtItemStackTranslator translator : NBT_TRANSLATORS) { if (translator.acceptItem(bedrockItem)) { @@ -140,14 +153,22 @@ public abstract class ItemTranslator { } } - translateDisplayProperties(session, nbt); + nbt = translateDisplayProperties(session, nbt, bedrockItem); + if (session.isAdvancedTooltips()) { + nbt = addAdvancedTooltips(nbt, session.getItemMappings().getMapping(stack), session.getLocale()); + } - ItemData itemData; + ItemStack itemStack = new ItemStack(stack.getId(), stack.getAmount(), nbt); + + ItemData.Builder builder; ItemTranslator itemStackTranslator = ITEM_STACK_TRANSLATORS.get(bedrockItem.getJavaId()); if (itemStackTranslator != null) { - itemData = itemStackTranslator.translateToBedrock(itemStack, bedrockItem); + builder = itemStackTranslator.translateToBedrock(itemStack, bedrockItem, session.getItemMappings()); } else { - itemData = DEFAULT_TRANSLATOR.translateToBedrock(itemStack, bedrockItem); + builder = DEFAULT_TRANSLATOR.translateToBedrock(itemStack, bedrockItem, session.getItemMappings()); + } + if (bedrockItem.isBlock()) { + builder.blockRuntimeId(bedrockItem.getBedrockBlockId()); } if (nbt != null) { @@ -158,15 +179,52 @@ public abstract class ItemTranslator { String[] canPlace = new String[0]; canBreak = getCanModify(canDestroy, canBreak); canPlace = getCanModify(canPlaceOn, canPlace); - itemData = ItemData.of(itemData.getId(), itemData.getDamage(), itemData.getCount(), itemData.getTag(), canPlace, canBreak); + builder.canBreak(canBreak); + builder.canPlace(canPlace); } - return itemData; + return builder.build(); + } + + private static CompoundTag addAdvancedTooltips(CompoundTag nbt, ItemMapping mapping, String language) { + CompoundTag newNbt = nbt; + if (newNbt == null) { + newNbt = new CompoundTag("nbt"); + CompoundTag display = new CompoundTag("display"); + display.put(new ListTag("Lore")); + newNbt.put(display); + } + CompoundTag compoundTag = newNbt.get("display"); + if (compoundTag == null) { + compoundTag = new CompoundTag("display"); + } + ListTag listTag = compoundTag.get("Lore"); + + if (listTag == null) { + listTag = new ListTag("Lore"); + } + int maxDurability = mapping.getMaxDamage(); + + if (maxDurability != 0) { + int durability = maxDurability - ((IntTag) newNbt.get("Damage")).getValue(); + if (durability != maxDurability) { + listTag.add(new StringTag("", "§r§f" + String.format(MessageTranslator.convertMessage("item.durability", language), durability, maxDurability))); + } + } + + listTag.add(new StringTag("", "§r§8" + mapping.getJavaIdentifier())); + if (nbt != null) { + listTag.add(new StringTag("", "§r§8" + String.format(MessageTranslator.convertMessage("item.nbt_tags", language), nbt.size()))); + } + compoundTag.put(listTag); + newNbt.put(compoundTag); + return newNbt; } /** * Translates the Java NBT of canDestroy and canPlaceOn to its Bedrock counterparts. * In Java, this is treated as normal NBT, but in Bedrock, these arguments are extra parts of the item data itself. + * * @param canModifyJava the list of items in Java * @param canModifyBedrock the empty list of items in Bedrock * @return the new list of items in Bedrock @@ -181,7 +239,7 @@ public abstract class ItemTranslator { if (!block.startsWith("minecraft:")) block = "minecraft:" + block; // Get the Bedrock identifier of the item and replace it. // This will unfortunately be limited - for example, beds and banners will be translated weirdly - canModifyBedrock[i] = BlockTranslator.getBedrockBlockIdentifier(block).replace("minecraft:", ""); + canModifyBedrock[i] = BlockRegistries.JAVA_TO_BEDROCK_IDENTIFIERS.getOrDefault(block, block).replace("minecraft:", ""); } } return canModifyBedrock; @@ -189,36 +247,41 @@ public abstract class ItemTranslator { private static final ItemTranslator DEFAULT_TRANSLATOR = new ItemTranslator() { @Override - public List getAppliedItems() { + public List getAppliedItems() { return null; } }; - public ItemData translateToBedrock(ItemStack itemStack, ItemEntry itemEntry) { + public ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { if (itemStack == null) { - return ItemData.AIR; + // Return, essentially, air + return ItemData.builder(); } - if (itemStack.getNbt() == null) { - return ItemData.of(itemEntry.getBedrockId(), (short) itemEntry.getBedrockData(), itemStack.getAmount()); + ItemData.Builder builder = ItemData.builder() + .id(mapping.getBedrockId()) + .damage(mapping.getBedrockData()) + .count(itemStack.getAmount()); + if (itemStack.getNbt() != null) { + builder.tag(this.translateNbtToBedrock(itemStack.getNbt())); } - return ItemData.of(itemEntry.getBedrockId(), (short) itemEntry.getBedrockData(), itemStack.getAmount(), this.translateNbtToBedrock(itemStack.getNbt())); + return builder; } - public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { + public ItemStack translateToJava(ItemData itemData, ItemMapping mapping, ItemMappings mappings) { if (itemData == null) return null; if (itemData.getTag() == null) { - return new ItemStack(itemEntry.getJavaId(), itemData.getCount(), new com.github.steveice10.opennbt.tag.builtin.CompoundTag("")); + return new ItemStack(mapping.getJavaId(), itemData.getCount(), new CompoundTag("")); } - return new ItemStack(itemEntry.getJavaId(), itemData.getCount(), this.translateToJavaNBT("", itemData.getTag())); + return new ItemStack(mapping.getJavaId(), itemData.getCount(), this.translateToJavaNBT("", itemData.getTag())); } - public abstract List getAppliedItems(); + public abstract List getAppliedItems(); - public NbtMap translateNbtToBedrock(com.github.steveice10.opennbt.tag.builtin.CompoundTag tag) { + public NbtMap translateNbtToBedrock(CompoundTag tag) { NbtMapBuilder builder = NbtMap.builder(); if (tag.getValue() != null && !tag.getValue().isEmpty()) { for (String str : tag.getValue().keySet()) { - com.github.steveice10.opennbt.tag.builtin.Tag javaTag = tag.get(str); + Tag javaTag = tag.get(str); Object translatedTag = translateToBedrockNBT(javaTag); if (translatedTag == null) continue; @@ -229,7 +292,7 @@ public abstract class ItemTranslator { return builder.build(); } - private Object translateToBedrockNBT(com.github.steveice10.opennbt.tag.builtin.Tag tag) { + private Object translateToBedrockNBT(Tag tag) { if (tag instanceof ByteArrayTag) { return ((ByteArrayTag) tag).getValue(); } @@ -273,11 +336,10 @@ public abstract class ItemTranslator { return ((StringTag) tag).getValue(); } - if (tag instanceof ListTag) { - ListTag listTag = (ListTag) tag; + if (tag instanceof ListTag listTag) { List tagList = new ArrayList<>(); - for (com.github.steveice10.opennbt.tag.builtin.Tag value : listTag) { + for (Tag value : listTag) { tagList.add(translateToBedrockNBT(value)); } NbtType type = NbtType.COMPOUND; @@ -287,21 +349,19 @@ public abstract class ItemTranslator { return new NbtList(type, tagList); } - if (tag instanceof com.github.steveice10.opennbt.tag.builtin.CompoundTag) { - com.github.steveice10.opennbt.tag.builtin.CompoundTag compoundTag = (com.github.steveice10.opennbt.tag.builtin.CompoundTag) tag; + if (tag instanceof CompoundTag compoundTag) { return translateNbtToBedrock(compoundTag); } return null; } - public com.github.steveice10.opennbt.tag.builtin.CompoundTag translateToJavaNBT(String name, NbtMap tag) { - com.github.steveice10.opennbt.tag.builtin.CompoundTag javaTag = new com.github.steveice10.opennbt.tag.builtin.CompoundTag(name); - Map javaValue = javaTag.getValue(); + public CompoundTag translateToJavaNBT(String name, NbtMap tag) { + CompoundTag javaTag = new CompoundTag(name); + Map javaValue = javaTag.getValue(); if (tag != null && !tag.isEmpty()) { - for (String str : tag.keySet()) { - Object bedrockTag = tag.get(str); - com.github.steveice10.opennbt.tag.builtin.Tag translatedTag = translateToJavaNBT(str, bedrockTag); + for (Map.Entry entry : tag.entrySet()) { + Tag translatedTag = translateToJavaNBT(entry.getKey(), entry.getValue()); if (translatedTag == null) continue; @@ -313,7 +373,7 @@ public abstract class ItemTranslator { return javaTag; } - private com.github.steveice10.opennbt.tag.builtin.Tag translateToJavaNBT(String name, Object object) { + private Tag translateToJavaNBT(String name, Object object) { if (object instanceof int[]) { return new IntArrayTag(name, (int[]) object); } @@ -355,18 +415,17 @@ public abstract class ItemTranslator { } if (object instanceof List) { - List tags = new ArrayList<>(); + List tags = new ArrayList<>(); for (Object value : (List) object) { - com.github.steveice10.opennbt.tag.builtin.Tag javaTag = translateToJavaNBT("", value); + Tag javaTag = translateToJavaNBT("", value); if (javaTag != null) tags.add(javaTag); } return new ListTag(name, tags); } - if (object instanceof NbtMap) { - NbtMap map = (NbtMap) object; + if (object instanceof NbtMap map) { return translateToJavaNBT(name, map); } @@ -377,8 +436,20 @@ public abstract class ItemTranslator { * Translates the display name of the item * @param session the Bedrock client's session * @param tag the tag to translate + * @param mapping the item entry, in case it requires translation + * + * @return the new tag to use, should the current one be null */ - public static void translateDisplayProperties(GeyserSession session, CompoundTag tag) { + public static CompoundTag translateDisplayProperties(GeyserSession session, CompoundTag tag, ItemMapping mapping) { + return translateDisplayProperties(session, tag, mapping, 'f'); + } + + /** + * @param translationColor if this item is not available on Java, the color that the new name should be. + * Normally, this should just be white, but for shulker boxes this should be gray. + */ + public static CompoundTag translateDisplayProperties(GeyserSession session, CompoundTag tag, ItemMapping mapping, char translationColor) { + boolean hasCustomName = false; if (tag != null) { CompoundTag display = tag.get("display"); if (display != null && display.contains("Name")) { @@ -389,11 +460,32 @@ public abstract class ItemTranslator { // Add the new name tag display.put(new StringTag("Name", name)); + // Indicate that a custom name is present + hasCustomName = true; // Add to the new root tag tag.put(display); } } + + if (!hasCustomName && mapping.hasTranslation()) { + // No custom name, but we need to localize the item's name + if (tag == null) { + tag = new CompoundTag(""); + } + CompoundTag display = tag.get("display"); + if (display == null) { + display = new CompoundTag("display"); + // Add to the new root tag + tag.put(display); + } + + String translationKey = mapping.getTranslationString(); + // Reset formatting since Bedrock defaults to italics + display.put(new StringTag("Name", "§r§" + translationColor + MinecraftLocale.getLocaleString(translationKey, session.getLocale()))); + } + + return tag; } /** diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/NbtItemStackTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/NbtItemStackTranslator.java similarity index 74% rename from connector/src/main/java/org/geysermc/connector/network/translators/item/NbtItemStackTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/inventory/item/NbtItemStackTranslator.java index 89d41e98d..6c2c501fd 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/NbtItemStackTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/NbtItemStackTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,10 +23,11 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.item; +package org.geysermc.geyser.translator.inventory.item; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.registry.type.ItemMapping; public class NbtItemStackTranslator { @@ -34,26 +35,28 @@ public class NbtItemStackTranslator { * Translate the item NBT to Bedrock * @param session the client's current session * @param itemTag the item's CompoundTag - * @param itemEntry Geyser's item entry + * @param mapping Geyser's item mapping */ - public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) { } /** * Translate the item NBT to Java. * @param itemTag the item's CompoundTag - * @param itemEntry Geyser's item entry + * @param mapping Geyser's item mapping */ - public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { + public void translateToJava(CompoundTag itemTag, ItemMapping mapping) { } /** - * @param itemEntry Geyser's item entry + * Gets whether this nbt translator takes in this item. + * + * @param mapping Geyser's item mapping * @return if the item should be processed under this class */ - public boolean acceptItem(ItemEntry itemEntry) { + public boolean acceptItem(ItemMapping mapping) { return true; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/PotionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/PotionTranslator.java similarity index 58% rename from connector/src/main/java/org/geysermc/connector/network/translators/item/translators/PotionTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/inventory/item/PotionTranslator.java index 24130a7f5..5455b1189 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/PotionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/PotionTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,18 +23,18 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.item.translators; +package org.geysermc.geyser.translator.inventory.item; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.geysermc.connector.network.translators.ItemRemapper; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.Potion; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.inventory.item.Potion; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.registry.type.ItemMappings; import java.util.List; import java.util.stream.Collectors; @@ -42,30 +42,39 @@ import java.util.stream.Collectors; @ItemRemapper public class PotionTranslator extends ItemTranslator { - private final List appliedItems; + private final List appliedItems; public PotionTranslator() { - appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry -> entry.getJavaIdentifier().endsWith("potion")).collect(Collectors.toList()); + appliedItems = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) + .getItems() + .values() + .stream() + .filter(entry -> entry.getJavaIdentifier().endsWith("potion")) + .collect(Collectors.toList()); } @Override - public ItemData translateToBedrock(ItemStack itemStack, ItemEntry itemEntry) { - if (itemStack.getNbt() == null) return super.translateToBedrock(itemStack, itemEntry); + public ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { + if (itemStack.getNbt() == null) return super.translateToBedrock(itemStack, mapping, mappings); Tag potionTag = itemStack.getNbt().get("Potion"); if (potionTag instanceof StringTag) { Potion potion = Potion.getByJavaIdentifier(((StringTag) potionTag).getValue()); if (potion != null) { - return ItemData.of(itemEntry.getBedrockId(), potion.getBedrockId(), itemStack.getAmount(), translateNbtToBedrock(itemStack.getNbt())); + return ItemData.builder() + .id(mapping.getBedrockId()) + .damage(potion.getBedrockId()) + .count(itemStack.getAmount()) + .tag(translateNbtToBedrock(itemStack.getNbt())); } - GeyserConnector.getInstance().getLogger().debug("Unknown Java potion: " + potionTag.getValue()); + GeyserImpl.getInstance().getLogger().debug("Unknown Java potion: " + potionTag.getValue()); } - return super.translateToBedrock(itemStack, itemEntry); + return super.translateToBedrock(itemStack, mapping, mappings); } @Override - public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { + public ItemStack translateToJava(ItemData itemData, ItemMapping mapping, ItemMappings mappings) { Potion potion = Potion.getByBedrockId(itemData.getDamage()); - ItemStack itemStack = super.translateToJava(itemData, itemEntry); + ItemStack itemStack = super.translateToJava(itemData, mapping, mappings); if (potion != null) { StringTag potionTag = new StringTag("Potion", potion.getJavaIdentifier()); itemStack.getNbt().put(potionTag); @@ -74,7 +83,7 @@ public class PotionTranslator extends ItemTranslator { } @Override - public List getAppliedItems() { + public List getAppliedItems() { return appliedItems; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/TippedArrowTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/TippedArrowTranslator.java similarity index 54% rename from connector/src/main/java/org/geysermc/connector/network/translators/item/translators/TippedArrowTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/inventory/item/TippedArrowTranslator.java index 0b69d6a2e..01ecbb09f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/TippedArrowTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/TippedArrowTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,18 +23,18 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.item.translators; +package org.geysermc.geyser.translator.inventory.item; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.network.translators.ItemRemapper; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.geysermc.connector.network.translators.item.TippedArrowPotion; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.inventory.item.TippedArrowPotion; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.registry.type.ItemMappings; import java.util.List; import java.util.stream.Collectors; @@ -42,36 +42,47 @@ import java.util.stream.Collectors; @ItemRemapper public class TippedArrowTranslator extends ItemTranslator { - private final List appliedItems; + private final List appliedItems; - private static final int TIPPED_ARROW_JAVA_ID = ItemRegistry.getItemEntry("minecraft:tipped_arrow").getJavaId(); + private static final int TIPPED_ARROW_JAVA_ID = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) + .getMapping("minecraft:tipped_arrow") + .getJavaId(); public TippedArrowTranslator() { - appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry -> - entry.getJavaIdentifier().contains("arrow") && !entry.getJavaIdentifier().contains("spectral")).collect(Collectors.toList()); + appliedItems = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) + .getItems() + .values() + .stream() + .filter(entry -> entry.getJavaIdentifier().contains("arrow") + && !entry.getJavaIdentifier().contains("spectral")) + .collect(Collectors.toList()); } @Override - public ItemData translateToBedrock(ItemStack itemStack, ItemEntry itemEntry) { - if (!itemEntry.getJavaIdentifier().equals("minecraft:tipped_arrow") || itemStack.getNbt() == null) { + public ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { + if (!mapping.getJavaIdentifier().equals("minecraft:tipped_arrow") || itemStack.getNbt() == null) { // We're only concerned about minecraft:arrow when translating Bedrock -> Java - return super.translateToBedrock(itemStack, itemEntry); + return super.translateToBedrock(itemStack, mapping, mappings); } Tag potionTag = itemStack.getNbt().get("Potion"); if (potionTag instanceof StringTag) { TippedArrowPotion tippedArrowPotion = TippedArrowPotion.getByJavaIdentifier(((StringTag) potionTag).getValue()); if (tippedArrowPotion != null) { - return ItemData.of(itemEntry.getBedrockId(), tippedArrowPotion.getBedrockId(), itemStack.getAmount(), translateNbtToBedrock(itemStack.getNbt())); + return ItemData.builder() + .id(mapping.getBedrockId()) + .damage(tippedArrowPotion.getBedrockId()) + .count(itemStack.getAmount()) + .tag(translateNbtToBedrock(itemStack.getNbt())); } - GeyserConnector.getInstance().getLogger().debug("Unknown Java potion (tipped arrow): " + potionTag.getValue()); + GeyserImpl.getInstance().getLogger().debug("Unknown Java potion (tipped arrow): " + potionTag.getValue()); } - return super.translateToBedrock(itemStack, itemEntry); + return super.translateToBedrock(itemStack, mapping, mappings); } @Override - public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { + public ItemStack translateToJava(ItemData itemData, ItemMapping mapping, ItemMappings mappings) { TippedArrowPotion tippedArrowPotion = TippedArrowPotion.getByBedrockId(itemData.getDamage()); - ItemStack itemStack = super.translateToJava(itemData, itemEntry); + ItemStack itemStack = super.translateToJava(itemData, mapping, mappings); if (tippedArrowPotion != null) { itemStack = new ItemStack(TIPPED_ARROW_JAVA_ID, itemStack.getAmount(), itemStack.getNbt()); StringTag potionTag = new StringTag("Potion", tippedArrowPotion.getJavaIdentifier()); @@ -81,7 +92,7 @@ public class TippedArrowTranslator extends ItemTranslator { } @Override - public List getAppliedItems() { + public List getAppliedItems() { return appliedItems; } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/AxolotlBucketTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/AxolotlBucketTranslator.java new file mode 100644 index 000000000..071ead230 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/AxolotlBucketTranslator.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory.item.nbt; + +import com.github.steveice10.opennbt.tag.builtin.ByteTag; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.ItemRemapper; +import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.text.MinecraftLocale; + +@ItemRemapper +public class AxolotlBucketTranslator extends NbtItemStackTranslator { + + @Override + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) { + // Bedrock Edition displays the properties of the axolotl. Java does not. + // To work around this, set the custom name to the Axolotl translation and it's displayed correctly + itemTag.put(new ByteTag("AppendCustomName", (byte) 1)); + itemTag.put(new StringTag("CustomName", MinecraftLocale.getLocaleString("entity.minecraft.axolotl", session.getLocale()))); + // Boilerplate required so the nametag does not appear as "Bucket of " + itemTag.put(new StringTag("ColorID", "")); + itemTag.put(new StringTag("BodyID", "")); + } + + @Override + public boolean acceptItem(ItemMapping mapping) { + return mapping.getJavaIdentifier().equals("minecraft:axolotl_bucket"); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BasicItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BasicItemTranslator.java new file mode 100644 index 000000000..10ad58544 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BasicItemTranslator.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory.item.nbt; + +import com.github.steveice10.opennbt.tag.builtin.*; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.ItemRemapper; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.util.ItemUtils; + +import java.util.ArrayList; +import java.util.List; + +@ItemRemapper(priority = -1) +public class BasicItemTranslator extends NbtItemStackTranslator { + + @Override + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) { + Tag damage = itemTag.get("Damage"); + if (damage instanceof IntTag) { + int originalDurability = ((IntTag) damage).getValue(); + int durability = ItemUtils.getCorrectBedrockDurability(session, mapping.getJavaId(), originalDurability); + if (durability != originalDurability) { + // Fix damage tag inconsistencies + itemTag.put(new IntTag("Damage", durability)); + } + } + + CompoundTag displayTag = itemTag.get("display"); + if (displayTag == null) { + return; + } + + Tag loreTag = displayTag.get("Lore"); + if (loreTag instanceof ListTag listTag) { + List lore = new ArrayList<>(); + for (Tag tag : listTag.getValue()) { + if (!(tag instanceof StringTag)) continue; + lore.add(new StringTag("", MessageTranslator.convertMessageLenient(((StringTag) tag).getValue(), session.getLocale()))); + } + displayTag.put(new ListTag("Lore", lore)); + } + } + + @Override + public void translateToJava(CompoundTag itemTag, ItemMapping mapping) { + CompoundTag displayTag = itemTag.get("display"); + if (displayTag == null) { + return; + } + + if (displayTag.contains("Name")) { + StringTag nameTag = displayTag.get("Name"); + displayTag.put(new StringTag("Name", MessageTranslator.convertToJavaMessage(nameTag.getValue()))); + } + + if (displayTag.contains("Lore")) { + ListTag loreTag = displayTag.get("Lore"); + List lore = new ArrayList<>(); + for (Tag tag : loreTag.getValue()) { + if (!(tag instanceof StringTag)) continue; + lore.add(new StringTag("", MessageTranslator.convertToJavaMessage(((StringTag) tag).getValue()))); + } + displayTag.put(new ListTag("Lore", lore)); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BookPagesTranslator.java similarity index 74% rename from connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BookPagesTranslator.java index 294dd81ed..b3371aab7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/BookPagesTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,17 +23,17 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.item.translators.nbt; +package org.geysermc.geyser.translator.inventory.item.nbt; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.github.steveice10.opennbt.tag.builtin.Tag; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.ItemRemapper; -import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.chat.MessageTranslator; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.ItemRemapper; +import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.geyser.registry.type.ItemMapping; import java.util.ArrayList; import java.util.List; @@ -42,18 +42,16 @@ import java.util.List; public class BookPagesTranslator extends NbtItemStackTranslator { @Override - public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) { if (!itemTag.contains("pages")) { return; } List pages = new ArrayList<>(); ListTag pagesTag = itemTag.get("pages"); for (Tag tag : pagesTag.getValue()) { - if (!(tag instanceof StringTag)) + if (!(tag instanceof StringTag textTag)) continue; - StringTag textTag = (StringTag) tag; - CompoundTag pageTag = new CompoundTag(""); pageTag.put(new StringTag("photoname", "")); pageTag.put(new StringTag("text", MessageTranslator.convertMessageLenient(textTag.getValue()))); @@ -65,22 +63,19 @@ public class BookPagesTranslator extends NbtItemStackTranslator { } @Override - public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { + public void translateToJava(CompoundTag itemTag, ItemMapping mapping) { if (!itemTag.contains("pages")) { return; } List pages = new ArrayList<>(); ListTag pagesTag = itemTag.get("pages"); for (Tag tag : pagesTag.getValue()) { - if (!(tag instanceof CompoundTag)) + if (!(tag instanceof CompoundTag pageTag)) continue; - CompoundTag pageTag = (CompoundTag) tag; - StringTag textTag = pageTag.get("text"); - pages.add(new StringTag(MessageTranslator.convertToJavaMessage(textTag.getValue()))); + pages.add(new StringTag("", textTag.getValue())); } - itemTag.remove("pages"); itemTag.put(new ListTag("pages", pages)); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/CrossbowTranslator.java similarity index 70% rename from connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/CrossbowTranslator.java index 97da86696..e1dda45ed 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/CrossbowTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,39 +23,38 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.item.translators.nbt; +package org.geysermc.geyser.translator.inventory.item.nbt; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.opennbt.tag.builtin.*; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.ItemRemapper; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.ItemRemapper; +import org.geysermc.geyser.translator.inventory.item.ItemTranslator; +import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; +import org.geysermc.geyser.registry.type.ItemMapping; @ItemRemapper public class CrossbowTranslator extends NbtItemStackTranslator { @Override - public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) { if (itemTag.get("ChargedProjectiles") != null) { ListTag chargedProjectiles = itemTag.get("ChargedProjectiles"); if (!chargedProjectiles.getValue().isEmpty()) { CompoundTag projectile = (CompoundTag) chargedProjectiles.getValue().get(0); - ItemEntry projectileEntry = ItemRegistry.getItemEntry((String) projectile.get("id").getValue()); - if (projectileEntry == null) return; + ItemMapping projectileMapping = session.getItemMappings().getMapping((String) projectile.get("id").getValue()); + if (projectileMapping == null) return; CompoundTag tag = projectile.get("tag"); - ItemStack itemStack = new ItemStack(itemEntry.getJavaId(), (byte) projectile.get("Count").getValue(), tag); + ItemStack itemStack = new ItemStack(mapping.getJavaId(), (byte) projectile.get("Count").getValue(), tag); ItemData itemData = ItemTranslator.translateToBedrock(session, itemStack); CompoundTag newProjectile = new CompoundTag("chargedItem"); newProjectile.put(new ByteTag("Count", (byte) itemData.getCount())); - newProjectile.put(new StringTag("Name", projectileEntry.getBedrockIdentifier())); + newProjectile.put(new StringTag("Name", projectileMapping.getBedrockIdentifier())); - newProjectile.put(new ShortTag("Damage", itemData.getDamage())); + newProjectile.put(new ShortTag("Damage", (short) itemData.getDamage())); itemTag.put(newProjectile); } @@ -63,7 +62,7 @@ public class CrossbowTranslator extends NbtItemStackTranslator { } @Override - public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { + public void translateToJava(CompoundTag itemTag, ItemMapping mapping) { if (itemTag.get("chargedItem") != null) { CompoundTag chargedItem = itemTag.get("chargedItem"); @@ -79,7 +78,7 @@ public class CrossbowTranslator extends NbtItemStackTranslator { } @Override - public boolean acceptItem(ItemEntry itemEntry) { - return "minecraft:crossbow".equals(itemEntry.getJavaIdentifier()); + public boolean acceptItem(ItemMapping mapping) { + return "minecraft:crossbow".equals(mapping.getJavaIdentifier()); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantedBookTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantedBookTranslator.java similarity index 77% rename from connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantedBookTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantedBookTranslator.java index 990d5a7ad..8a009bc2d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantedBookTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantedBookTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,21 +23,21 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.item.translators.nbt; +package org.geysermc.geyser.translator.inventory.item.nbt; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.github.steveice10.opennbt.tag.builtin.Tag; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.ItemRemapper; -import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; -import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.ItemRemapper; +import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; +import org.geysermc.geyser.registry.type.ItemMapping; @ItemRemapper(priority = 1) public class EnchantedBookTranslator extends NbtItemStackTranslator { @Override - public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) { if (!itemTag.contains("StoredEnchantments")) { return; } @@ -50,7 +50,7 @@ public class EnchantedBookTranslator extends NbtItemStackTranslator { } @Override - public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { + public void translateToJava(CompoundTag itemTag, ItemMapping mapping) { if (!itemTag.contains("Enchantments")) { return; } @@ -63,7 +63,7 @@ public class EnchantedBookTranslator extends NbtItemStackTranslator { } @Override - public boolean acceptItem(ItemEntry itemEntry) { - return "minecraft:enchanted_book".equals(itemEntry.getJavaIdentifier()); + public boolean acceptItem(ItemMapping mapping) { + return "minecraft:enchanted_book".equals(mapping.getJavaIdentifier()); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantmentTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java similarity index 78% rename from connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantmentTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java index 6884c00ba..ddc5f3780 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantmentTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/EnchantmentTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,15 +23,15 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.item.translators.nbt; +package org.geysermc.geyser.translator.inventory.item.nbt; import com.github.steveice10.opennbt.tag.builtin.*; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.ItemRemapper; -import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; -import org.geysermc.connector.network.translators.item.Enchantment; -import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.ItemRemapper; +import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; +import org.geysermc.geyser.inventory.item.Enchantment; +import org.geysermc.geyser.registry.type.ItemMapping; import java.util.ArrayList; import java.util.List; @@ -41,11 +41,11 @@ import java.util.Map; public class EnchantmentTranslator extends NbtItemStackTranslator { @Override - public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) { List newTags = new ArrayList<>(); - if (itemTag.contains("Enchantments")) { - ListTag enchantmentTag = itemTag.get("Enchantments"); - for (Tag tag : enchantmentTag.getValue()) { + Tag enchantmentTag = itemTag.get("Enchantments"); + if (enchantmentTag instanceof ListTag listTag) { + for (Tag tag : listTag.getValue()) { if (!(tag instanceof CompoundTag)) continue; CompoundTag bedrockTag = remapEnchantment((CompoundTag) tag); @@ -53,9 +53,9 @@ public class EnchantmentTranslator extends NbtItemStackTranslator { } itemTag.remove("Enchantments"); } - if (itemTag.contains("StoredEnchantments")) { - ListTag enchantmentTag = itemTag.get("StoredEnchantments"); - for (Tag tag : enchantmentTag.getValue()) { + enchantmentTag = itemTag.get("StoredEnchantments"); + if (enchantmentTag instanceof ListTag listTag) { + for (Tag tag : listTag.getValue()) { if (!(tag instanceof CompoundTag)) continue; CompoundTag bedrockTag = remapEnchantment((CompoundTag) tag); @@ -73,7 +73,7 @@ public class EnchantmentTranslator extends NbtItemStackTranslator { } @Override - public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { + public void translateToJava(CompoundTag itemTag, ItemMapping mapping) { if (!itemTag.contains("ench")) { return; } @@ -82,10 +82,9 @@ public class EnchantmentTranslator extends NbtItemStackTranslator { List enchantments = new ArrayList<>(); List storedEnchantments = new ArrayList<>(); for (Tag value : enchantmentTag.getValue()) { - if (!(value instanceof CompoundTag)) + if (!(value instanceof CompoundTag tagValue)) continue; - CompoundTag tagValue = (CompoundTag) value; ShortTag bedrockId = tagValue.get("id"); if (bedrockId == null) continue; @@ -108,7 +107,7 @@ public class EnchantmentTranslator extends NbtItemStackTranslator { enchantments.add(javaTag); } } else { - GeyserConnector.getInstance().getLogger().debug("Unknown bedrock enchantment: " + bedrockId); + GeyserImpl.getInstance().getLogger().debug("Unknown bedrock enchantment: " + bedrockId); } } if (!enchantments.isEmpty()) { @@ -132,7 +131,7 @@ public class EnchantmentTranslator extends NbtItemStackTranslator { Enchantment enchantment = Enchantment.getByJavaIdentifier(((StringTag) javaEnchId).getValue()); if (enchantment == null) { - GeyserConnector.getInstance().getLogger().debug("Unknown java enchantment: " + javaEnchId.getValue()); + GeyserImpl.getInstance().getLogger().debug("Unknown java enchantment: " + javaEnchId.getValue()); return null; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/FireworkBaseTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/FireworkBaseTranslator.java new file mode 100644 index 000000000..c68f4f4d9 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/FireworkBaseTranslator.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory.item.nbt; + +import com.github.steveice10.opennbt.tag.builtin.ByteArrayTag; +import com.github.steveice10.opennbt.tag.builtin.ByteTag; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.IntArrayTag; +import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; +import org.geysermc.geyser.level.FireworkColor; +import org.geysermc.geyser.util.MathUtils; + +/** + * Stores common code for firework rockets and firework stars. + */ +public abstract class FireworkBaseTranslator extends NbtItemStackTranslator { + + protected CompoundTag translateExplosionToBedrock(CompoundTag explosion, String newName) { + CompoundTag newExplosionData = new CompoundTag(newName); + + if (explosion.get("Type") != null) { + newExplosionData.put(new ByteTag("FireworkType", MathUtils.getNbtByte(explosion.get("Type").getValue()))); + } + + if (explosion.get("Colors") != null) { + int[] oldColors = (int[]) explosion.get("Colors").getValue(); + byte[] colors = new byte[oldColors.length]; + + int i = 0; + for (int color : oldColors) { + colors[i++] = FireworkColor.fromJavaRGB(color); + } + + newExplosionData.put(new ByteArrayTag("FireworkColor", colors)); + } + + if (explosion.get("FadeColors") != null) { + int[] oldColors = (int[]) explosion.get("FadeColors").getValue(); + byte[] colors = new byte[oldColors.length]; + + int i = 0; + for (int color : oldColors) { + colors[i++] = FireworkColor.fromJavaRGB(color); + } + + newExplosionData.put(new ByteArrayTag("FireworkFade", colors)); + } + + if (explosion.get("Trail") != null) { + newExplosionData.put(new ByteTag("FireworkTrail", MathUtils.getNbtByte(explosion.get("Trail").getValue()))); + } + + if (explosion.get("Flicker") != null) { + newExplosionData.put(new ByteTag("FireworkFlicker", MathUtils.getNbtByte(explosion.get("Flicker").getValue()))); + } + + return newExplosionData; + } + + protected CompoundTag translateExplosionToJava(CompoundTag explosion, String newName) { + CompoundTag newExplosionData = new CompoundTag(newName); + + if (explosion.get("FireworkType") != null) { + newExplosionData.put(new ByteTag("Type", MathUtils.getNbtByte(explosion.get("FireworkType").getValue()))); + } + + if (explosion.get("FireworkColor") != null) { + byte[] oldColors = (byte[]) explosion.get("FireworkColor").getValue(); + int[] colors = new int[oldColors.length]; + + int i = 0; + for (byte color : oldColors) { + colors[i++] = FireworkColor.fromBedrockId(color); + } + + newExplosionData.put(new IntArrayTag("Colors", colors)); + } + + if (explosion.get("FireworkFade") != null) { + byte[] oldColors = (byte[]) explosion.get("FireworkFade").getValue(); + int[] colors = new int[oldColors.length]; + + int i = 0; + for (byte color : oldColors) { + colors[i++] = FireworkColor.fromBedrockId(color); + } + + newExplosionData.put(new IntArrayTag("FadeColors", colors)); + } + + if (explosion.get("FireworkTrail") != null) { + newExplosionData.put(new ByteTag("Trail", MathUtils.getNbtByte(explosion.get("FireworkTrail").getValue()))); + } + + if (explosion.get("FireworkFlicker") != null) { + newExplosionData.put(new ByteTag("Flicker", MathUtils.getNbtByte(explosion.get("FireworkFlicker").getValue()))); + } + + return newExplosionData; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/FireworkRocketTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/FireworkRocketTranslator.java new file mode 100644 index 000000000..8c1154dea --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/FireworkRocketTranslator.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory.item.nbt; + +import com.github.steveice10.opennbt.tag.builtin.ByteTag; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.ItemRemapper; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.util.MathUtils; + +@ItemRemapper +public class FireworkRocketTranslator extends FireworkBaseTranslator { + + @Override + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) { + CompoundTag fireworks = itemTag.get("Fireworks"); + if (fireworks == null) { + return; + } + + if (fireworks.get("Flight") != null) { + fireworks.put(new ByteTag("Flight", MathUtils.getNbtByte(fireworks.get("Flight").getValue()))); + } + + ListTag explosions = fireworks.get("Explosions"); + if (explosions == null) { + return; + } + for (Tag effect : explosions.getValue()) { + CompoundTag effectData = (CompoundTag) effect; + CompoundTag newEffectData = translateExplosionToBedrock(effectData, ""); + + explosions.remove(effectData); + explosions.add(newEffectData); + } + } + + @Override + public void translateToJava(CompoundTag itemTag, ItemMapping mapping) { + CompoundTag fireworks = itemTag.get("Fireworks"); + if (fireworks == null) { + return; + } + + if (fireworks.contains("Flight")) { + fireworks.put(new ByteTag("Flight", MathUtils.getNbtByte(fireworks.get("Flight").getValue()))); + } + + ListTag explosions = fireworks.get("Explosions"); + if (explosions == null) { + return; + } + for (Tag effect : explosions.getValue()) { + CompoundTag effectData = (CompoundTag) effect; + CompoundTag newEffectData = translateExplosionToJava(effectData, ""); + + explosions.remove(effect); + explosions.add(newEffectData); + } + } + + @Override + public boolean acceptItem(ItemMapping mapping) { + return "minecraft:firework_rocket".equals(mapping.getJavaIdentifier()); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/FireworkStarTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/FireworkStarTranslator.java new file mode 100644 index 000000000..e66589f6d --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/FireworkStarTranslator.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory.item.nbt; + +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.IntArrayTag; +import com.github.steveice10.opennbt.tag.builtin.IntTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.ItemRemapper; +import org.geysermc.geyser.registry.type.ItemMapping; + +@ItemRemapper +public class FireworkStarTranslator extends FireworkBaseTranslator { + + @Override + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) { + Tag explosion = itemTag.get("Explosion"); + if (explosion instanceof CompoundTag) { + CompoundTag newExplosion = translateExplosionToBedrock((CompoundTag) explosion, "FireworksItem"); + itemTag.remove("Explosion"); + itemTag.put(newExplosion); + Tag color = ((CompoundTag) explosion).get("Colors"); + if (color instanceof IntArrayTag) { + // Determine the custom color, if any. + // Mostly replicates Java's own rendering code, as Java determines the final firework star color client-side + // while Bedrock determines it server-side. + int[] colors = ((IntArrayTag) color).getValue(); + if (colors.length == 0) { + return; + } + int finalColor; + if (colors.length == 1) { + finalColor = colors[0]; + } else { + int r = 0; + int g = 0; + int b = 0; + + for (int fireworkColor : colors) { + r += (fireworkColor & (255 << 16)) >> 16; + g += (fireworkColor & (255 << 8)) >> 8; + b += fireworkColor & 255; + } + + r /= colors.length; + g /= colors.length; + b /= colors.length; + finalColor = r << 16 | g << 8 | b; + } + + itemTag.put(new IntTag("customColor", finalColor)); + } + } + } + + @Override + public void translateToJava(CompoundTag itemTag, ItemMapping mapping) { + Tag explosion = itemTag.get("FireworksItem"); + if (explosion instanceof CompoundTag) { + CompoundTag newExplosion = translateExplosionToJava((CompoundTag) explosion, "Explosion"); + itemTag.remove("FireworksItem"); + itemTag.put(newExplosion); + } + // Remove custom color, if any, since this only exists on Bedrock + itemTag.remove("customColor"); + } + + @Override + public boolean acceptItem(ItemMapping mapping) { + return "minecraft:firework_star".equals(mapping.getJavaIdentifier()); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/LeatherArmorTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/LeatherArmorTranslator.java similarity index 60% rename from connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/LeatherArmorTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/LeatherArmorTranslator.java index 93af3e709..ddf76b595 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/LeatherArmorTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/LeatherArmorTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,41 +23,43 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.item.translators.nbt; +package org.geysermc.geyser.translator.inventory.item.nbt; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.IntTag; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.ItemRemapper; -import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; -import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.ItemRemapper; +import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; +import org.geysermc.geyser.registry.type.ItemMapping; + +import java.util.Arrays; +import java.util.List; @ItemRemapper public class LeatherArmorTranslator extends NbtItemStackTranslator { - private static final String[] ITEMS = new String[]{"minecraft:leather_helmet", "minecraft:leather_chestplate", "minecraft:leather_leggings", "minecraft:leather_boots"}; + private static final List ITEMS = Arrays.asList("minecraft:leather_helmet", "minecraft:leather_chestplate", + "minecraft:leather_leggings", "minecraft:leather_boots", "minecraft:leather_horse_armor"); @Override - public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { - if (!itemTag.contains("display")) { + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) { + CompoundTag displayTag = itemTag.get("display"); + if (displayTag == null) { return; } - CompoundTag displayTag = itemTag.get("display"); - if (displayTag.contains("color")) { - IntTag color = displayTag.get("color"); - if (color != null) { - itemTag.put(new IntTag("customColor", color.getValue())); - displayTag.remove("color"); - } + IntTag color = displayTag.get("color"); + if (color != null) { + itemTag.put(new IntTag("customColor", color.getValue())); + displayTag.remove("color"); } } @Override - public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { - if (!itemTag.contains("customColor")) { + public void translateToJava(CompoundTag itemTag, ItemMapping mapping) { + IntTag color = itemTag.get("customColor"); + if (color == null) { return; } - IntTag color = itemTag.get("customColor"); CompoundTag displayTag = itemTag.get("display"); if (displayTag == null) { displayTag = new CompoundTag("display"); @@ -67,10 +69,7 @@ public class LeatherArmorTranslator extends NbtItemStackTranslator { } @Override - public boolean acceptItem(ItemEntry itemEntry) { - for (String item : ITEMS) { - if (itemEntry.getJavaIdentifier().equals(item)) return true; - } - return false; + public boolean acceptItem(ItemMapping mapping) { + return ITEMS.contains(mapping.getJavaIdentifier()); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/LodestoneCompassTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/LodestoneCompassTranslator.java new file mode 100644 index 000000000..924735626 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/LodestoneCompassTranslator.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory.item.nbt; + +import com.github.steveice10.opennbt.tag.builtin.*; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.ItemRemapper; +import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; +import org.geysermc.geyser.registry.type.ItemMapping; + +@ItemRemapper +public class LodestoneCompassTranslator extends NbtItemStackTranslator { + + @Override + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) { + Tag lodestoneTag = itemTag.get("LodestoneTracked"); + if (lodestoneTag instanceof ByteTag) { + int trackId = session.getLodestoneCache().store(itemTag); + // Set the bedrock tracking id - will return 0 if invalid + itemTag.put(new IntTag("trackingHandle", trackId)); + } + } + + // NBT does not need to be translated from Bedrock Edition to Java Edition. + // translateToJava is called in three places: extra recipe loading, creative menu, and stonecutters + // Lodestone compasses cannot be touched in any of those places. + + @Override + public boolean acceptItem(ItemMapping mapping) { + return mapping.getJavaIdentifier().equals("minecraft:compass"); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/MapItemTranslator.java similarity index 77% rename from connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/MapItemTranslator.java index d325af486..23739340e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/MapItemTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,19 +23,19 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.item.translators.nbt; +package org.geysermc.geyser.translator.inventory.item.nbt; import com.github.steveice10.opennbt.tag.builtin.*; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.ItemRemapper; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.ItemRemapper; +import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; +import org.geysermc.geyser.registry.type.ItemMapping; @ItemRemapper public class MapItemTranslator extends NbtItemStackTranslator { @Override - public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) { // Can be either an IntTag or ShortTag Tag mapId = itemTag.get("map"); if (mapId == null) return; @@ -55,7 +55,7 @@ public class MapItemTranslator extends NbtItemStackTranslator { } @Override - public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { + public void translateToJava(CompoundTag itemTag, ItemMapping mapping) { IntTag tag = itemTag.get("map_name_index"); if (tag != null) { itemTag.put(new IntTag("map", tag.getValue())); @@ -65,7 +65,7 @@ public class MapItemTranslator extends NbtItemStackTranslator { } @Override - public boolean acceptItem(ItemEntry itemEntry) { - return itemEntry.getJavaIdentifier().equals("minecraft:filled_map"); + public boolean acceptItem(ItemMapping mapping) { + return mapping.getJavaIdentifier().equals("minecraft:filled_map"); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/PlayerHeadTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/PlayerHeadTranslator.java new file mode 100644 index 000000000..1e182568a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/PlayerHeadTranslator.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory.item.nbt; + +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.ItemRemapper; +import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.text.MinecraftLocale; + +@ItemRemapper +public class PlayerHeadTranslator extends NbtItemStackTranslator { + + @Override + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) { + if (!itemTag.contains("display") || !((CompoundTag) itemTag.get("display")).contains("Name")) { + if (itemTag.contains("SkullOwner")) { + StringTag name; + Tag skullOwner = itemTag.get("SkullOwner"); + if (skullOwner instanceof StringTag) { + name = (StringTag) skullOwner; + } else { + StringTag skullName; + if (skullOwner instanceof CompoundTag && (skullName = ((CompoundTag) skullOwner).get("Name")) != null) { + name = skullName; + } else { + session.getGeyser().getLogger().debug("Not sure how to handle skull head item display. " + itemTag); + return; + } + } + // Add correct name of player skull + // TODO: It's always yellow, even with a custom name. Handle? + String displayName = "\u00a7r\u00a7e" + MinecraftLocale.getLocaleString("block.minecraft.player_head.named", session.getLocale()).replace("%s", name.getValue()); + if (!itemTag.contains("display")) { + itemTag.put(new CompoundTag("display")); + } + ((CompoundTag) itemTag.get("display")).put(new StringTag("Name", displayName)); + } + } + } + + @Override + public boolean acceptItem(ItemMapping mapping) { + return mapping.getJavaIdentifier().equals("minecraft:player_head"); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/ShulkerBoxItemTranslator.java similarity index 58% rename from connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/ShulkerBoxItemTranslator.java index 126d2e1f5..f4160ec08 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/ShulkerBoxItemTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,21 +23,22 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.item.translators.nbt; +package org.geysermc.geyser.translator.inventory.item.nbt; +import com.github.steveice10.mc.protocol.data.game.Identifier; import com.github.steveice10.opennbt.tag.builtin.*; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.ItemRemapper; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.ItemRemapper; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.translator.inventory.item.ItemTranslator; +import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; +import org.geysermc.geyser.util.MathUtils; @ItemRemapper public class ShulkerBoxItemTranslator extends NbtItemStackTranslator { @Override - public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) { if (!itemTag.contains("BlockEntityTag")) return; // Empty shulker box CompoundTag blockEntityTag = itemTag.get("BlockEntityTag"); @@ -46,19 +47,21 @@ public class ShulkerBoxItemTranslator extends NbtItemStackTranslator { for (Tag item : (ListTag) blockEntityTag.get("Items")) { CompoundTag itemData = (CompoundTag) item; // Information about the item CompoundTag boxItemTag = new CompoundTag(""); // Final item tag to add to the list - boxItemTag.put(new ByteTag("Slot", ((ByteTag) itemData.get("Slot")).getValue())); + boxItemTag.put(new ByteTag("Slot", (byte) (MathUtils.getNbtByte(itemData.get("Slot").getValue()) & 255))); boxItemTag.put(new ByteTag("WasPickedUp", (byte) 0)); // ??? - ItemEntry boxItemEntry = ItemRegistry.getItemEntry(((StringTag) itemData.get("id")).getValue()); + ItemMapping boxMapping = session.getItemMappings().getMapping(Identifier.formalize(((StringTag) itemData.get("id")).getValue())); - boxItemTag.put(new StringTag("Name", boxItemEntry.getBedrockIdentifier())); - boxItemTag.put(new ShortTag("Damage", (short) boxItemEntry.getBedrockData())); - boxItemTag.put(new ByteTag("Count", ((ByteTag) itemData.get("Count")).getValue())); - if (itemData.contains("tag")) { - // Only the display name is what we have interest in, so just translate that if relevant - CompoundTag displayTag = itemData.get("tag"); - ItemTranslator.translateDisplayProperties(session, displayTag); - boxItemTag.put(displayTag); + boxItemTag.put(new StringTag("Name", boxMapping.getBedrockIdentifier())); + boxItemTag.put(new ShortTag("Damage", (short) boxMapping.getBedrockData())); + boxItemTag.put(new ByteTag("Count", MathUtils.getNbtByte(itemData.get("Count").getValue()))); + // Only the display name is what we have interest in, so just translate that if relevant + CompoundTag displayTag = itemData.get("tag"); + if (displayTag == null && boxMapping.hasTranslation()) { + displayTag = new CompoundTag("tag"); + } + if (displayTag != null) { + boxItemTag.put(ItemTranslator.translateDisplayProperties(session, displayTag, boxMapping, '7')); } itemsList.add(boxItemTag); @@ -70,14 +73,14 @@ public class ShulkerBoxItemTranslator extends NbtItemStackTranslator { } @Override - public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { + public void translateToJava(CompoundTag itemTag, ItemMapping mapping) { if (itemTag.contains("Items")) { // Remove any extraneous Bedrock tag and don't touch the Java one itemTag.remove("Items"); } } @Override - public boolean acceptItem(ItemEntry itemEntry) { - return itemEntry.getJavaIdentifier().contains("shulker_box"); + public boolean acceptItem(ItemMapping mapping) { + return mapping.getJavaIdentifier().contains("shulker_box"); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/TropicalFishBucketTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/TropicalFishBucketTranslator.java new file mode 100644 index 000000000..316ebc1ae --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/nbt/TropicalFishBucketTranslator.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.inventory.item.nbt; + +import com.github.steveice10.opennbt.tag.builtin.*; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.TextDecoration; +import org.geysermc.geyser.entity.type.living.animal.TropicalFishEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.ItemRemapper; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.text.MinecraftLocale; + +import java.util.ArrayList; +import java.util.List; + +@ItemRemapper +public class TropicalFishBucketTranslator extends NbtItemStackTranslator { + + private static final Style LORE_STYLE = Style.style(NamedTextColor.GRAY, TextDecoration.ITALIC); + + @Override + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) { + // Prevent name from appearing as "Bucket of" + itemTag.put(new ByteTag("AppendCustomName", (byte) 1)); + itemTag.put(new StringTag("CustomName", MinecraftLocale.getLocaleString("entity.minecraft.tropical_fish", session.getLocale()))); + // Add Java's client side lore tag + Tag bucketVariantTag = itemTag.get("BucketVariantTag"); + if (bucketVariantTag instanceof IntTag) { + CompoundTag displayTag = itemTag.get("display"); + if (displayTag == null) { + displayTag = new CompoundTag("display"); + itemTag.put(displayTag); + } + + List lore = new ArrayList<>(); + + int varNumber = ((IntTag) bucketVariantTag).getValue(); + int predefinedVariantId = TropicalFishEntity.getPredefinedId(varNumber); + if (predefinedVariantId != -1) { + Component tooltip = Component.translatable("entity.minecraft.tropical_fish.predefined." + predefinedVariantId, LORE_STYLE); + lore.add(0, new StringTag("", MessageTranslator.convertMessage(tooltip, session.getLocale()))); + } else { + Component typeTooltip = Component.translatable("entity.minecraft.tropical_fish.type." + TropicalFishEntity.getVariantName(varNumber), LORE_STYLE); + lore.add(0, new StringTag("", MessageTranslator.convertMessage(typeTooltip, session.getLocale()))); + + byte baseColor = TropicalFishEntity.getBaseColor(varNumber); + byte patternColor = TropicalFishEntity.getPatternColor(varNumber); + Component colorTooltip = Component.translatable("color.minecraft." + TropicalFishEntity.getColorName(baseColor), LORE_STYLE); + if (baseColor != patternColor) { + colorTooltip = colorTooltip.append(Component.text(", ", LORE_STYLE)) + .append(Component.translatable("color.minecraft." + TropicalFishEntity.getColorName(patternColor), LORE_STYLE)); + } + lore.add(1, new StringTag("", MessageTranslator.convertMessage(colorTooltip, session.getLocale()))); + } + + ListTag loreTag = displayTag.get("Lore"); + if (loreTag != null) { + lore.addAll(loreTag.getValue()); + } + displayTag.put(new ListTag("Lore", lore)); + } + } + + @Override + public boolean acceptItem(ItemMapping mapping) { + return mapping.getJavaIdentifier().equals("minecraft:tropical_fish_bucket"); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/BiomeTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/BiomeTranslator.java new file mode 100644 index 000000000..6cafb6012 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/level/BiomeTranslator.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.level; + +import com.github.steveice10.mc.protocol.data.game.chunk.BitStorage; +import com.github.steveice10.mc.protocol.data.game.chunk.DataPalette; +import com.github.steveice10.mc.protocol.data.game.chunk.palette.GlobalPalette; +import com.github.steveice10.mc.protocol.data.game.chunk.palette.Palette; +import com.github.steveice10.mc.protocol.data.game.chunk.palette.SingletonPalette; +import com.github.steveice10.opennbt.tag.builtin.*; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.IntLists; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.chunk.BlockStorage; +import org.geysermc.geyser.level.chunk.GeyserChunkSection; +import org.geysermc.geyser.level.chunk.bitarray.BitArray; +import org.geysermc.geyser.level.chunk.bitarray.BitArrayVersion; +import org.geysermc.geyser.level.chunk.bitarray.SingletonBitArray; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.util.MathUtils; + +// Array index formula by https://wiki.vg/Chunk_Format +public class BiomeTranslator { + + public static void loadServerBiomes(GeyserSession session, CompoundTag codec) { + Int2IntMap biomeTranslations = session.getBiomeTranslations(); + biomeTranslations.clear(); + + CompoundTag worldGen = codec.get("minecraft:worldgen/biome"); + ListTag serverBiomes = worldGen.get("value"); + session.setBiomeGlobalPalette(MathUtils.getGlobalPaletteForSize(serverBiomes.size())); + + for (Tag tag : serverBiomes) { + CompoundTag biomeTag = (CompoundTag) tag; + + String javaIdentifier = ((StringTag) biomeTag.get("name")).getValue(); + int bedrockId = Registries.BIOME_IDENTIFIERS.get().getOrDefault(javaIdentifier, -1); + int javaId = ((IntTag) biomeTag.get("id")).getValue(); + + if (bedrockId == -1) { + // There is no matching Bedrock variation for this biome; let's set the closest match based on biome category + String category = ((StringTag) ((CompoundTag) biomeTag.get("element")).get("category")).getValue(); + String replacementBiome = switch (category) { + case "extreme_hills" -> "minecraft:mountains"; + case "icy" -> "minecraft:ice_spikes"; + case "mesa" -> "minecraft:badlands"; + case "mushroom" -> "minecraft:mushroom_fields"; + case "nether" -> "minecraft:nether_wastes"; + default -> "minecraft:ocean"; // Typically ID 0 so a good default + case "taiga", "jungle", "plains", "savanna", "the_end", "beach", "ocean", "desert", "river", "swamp" -> "minecraft:" + category; + }; + bedrockId = Registries.BIOME_IDENTIFIERS.get().getInt(replacementBiome); + } + + // When we see the Java ID, we should instead apply the Bedrock ID + biomeTranslations.put(javaId, bedrockId); + + if (javaId == 0) { + // Matches Java behavior when it sees an invalid biome - it just replaces it with ID 0 + biomeTranslations.defaultReturnValue(bedrockId); + } + } + } + + public static BlockStorage toNewBedrockBiome(GeyserSession session, DataPalette biomeData) { + Int2IntMap biomeTranslations = session.getBiomeTranslations(); + // As of 1.17.10: the client expects the same format as a chunk but filled with biomes + // As of 1.18 this is the same as Java Edition + + Palette palette = biomeData.getPalette(); + if (palette instanceof SingletonPalette) { + int biomeId = biomeTranslations.get(palette.idToState(0)); + return new BlockStorage(SingletonBitArray.INSTANCE, IntLists.singleton(biomeId)); + } else { + BlockStorage storage; + if (!(palette instanceof GlobalPalette)) { + // Prevent resizing by allocating what we can ahead of time + BitStorage bitStorage = biomeData.getStorage(); + int size = palette.size(); + BitArray bitArray = BitArrayVersion.forBitsCeil(bitStorage.getBitsPerEntry()) + .createArray(BlockStorage.SIZE); + + IntList bedrockPalette = new IntArrayList(size); + + for (int i = 0; i < size; i++) { + int javaId = palette.idToState(i); + bedrockPalette.add(biomeTranslations.get(javaId)); + } + + // Each section of biome corresponding to a chunk section contains 4 * 4 * 4 entries + for (int i = 0; i < 64; i++) { + int idx = bitStorage.get(i); + int x = i & 3; + int y = (i >> 4) & 3; + int z = (i >> 2) & 3; + // Convert biome coordinates into block coordinates + // Bedrock expects a full 4096 blocks + multiplyIdToStorage(bitArray, idx, x, y, z); + } + + storage = new BlockStorage(bitArray, bedrockPalette); + } else { + storage = new BlockStorage(0); + BitArray bitArray = storage.getBitArray(); + + // Each section of biome corresponding to a chunk section contains 4 * 4 * 4 entries + for (int i = 0; i < 64; i++) { + int javaId = biomeData.getPalette().idToState(biomeData.getStorage().get(i)); + int x = i & 3; + int y = (i >> 4) & 3; + int z = (i >> 2) & 3; + // Get the Bedrock biome ID override + int biomeId = biomeTranslations.get(javaId); + int idx = storage.idFor(biomeId); + // Convert biome coordinates into block coordinates + // Bedrock expects a full 4096 blocks + multiplyIdToStorage(bitArray, idx, x, y, z); + } + } + return storage; + } + } + + private static void multiplyIdToStorage(BitArray bitArray, int idx, int x, int y, int z) { + for (int blockX = x << 2; blockX < (x << 2) + 4; blockX++) { + for (int blockZ = z << 2; blockZ < (z << 2) + 4; blockZ++) { + for (int blockY = y << 2; blockY < (y << 2) + 4; blockY++) { + bitArray.set(GeyserChunkSection.blockPosition(blockX, blockY, blockZ), idx); + } + } + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BannerBlockEntityTranslator.java similarity index 70% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BannerBlockEntityTranslator.java index f5e1d5948..9cd1981c6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BannerBlockEntityTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,21 +23,17 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.block.entity; +package org.geysermc.geyser.translator.level.block.entity; +import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.nukkitx.nbt.NbtMapBuilder; -import org.geysermc.connector.network.translators.item.translators.BannerTranslator; -import org.geysermc.connector.network.translators.world.block.BlockStateValues; +import org.geysermc.geyser.translator.inventory.item.BannerTranslator; +import org.geysermc.geyser.level.block.BlockStateValues; -@BlockEntity(name = "Banner", regex = "banner") +@BlockEntity(type = BlockEntityType.BANNER) public class BannerBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { - @Override - public boolean isBlock(int blockState) { - return BlockStateValues.getBannerColor(blockState) != -1; - } - @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { int bannerColor = BlockStateValues.getBannerColor(blockState); @@ -47,7 +43,13 @@ public class BannerBlockEntityTranslator extends BlockEntityTranslator implement if (tag.contains("Patterns")) { ListTag patterns = tag.get("Patterns"); - builder.put("Patterns", BannerTranslator.convertBannerPattern(patterns)); + if (patterns.equals(BannerTranslator.OMINOUS_BANNER_PATTERN)) { + // This is an ominous banner; don't try to translate the raw patterns (it doesn't translate correctly) + // and tell the Bedrock client that this is an ominous banner + builder.putInt("Type", 1); + } else { + builder.put("Patterns", BannerTranslator.convertBannerPattern(patterns)); + } } if (tag.contains("CustomName")) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BeaconBlockEntityTranslator.java similarity index 60% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BeaconBlockEntityTranslator.java index c5f479948..d6492bffe 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BeaconBlockEntityTranslator.java @@ -1,50 +1,42 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * 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. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.world.block.entity; - -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.nukkitx.nbt.NbtMapBuilder; -import org.geysermc.connector.network.translators.world.block.BlockStateValues; - -@BlockEntity(name = "Skull", regex = "skull") -public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { - @Override - public boolean isBlock(int blockState) { - return BlockStateValues.getSkullVariant(blockState) != -1; - } - - @Override - public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { - byte skullVariant = BlockStateValues.getSkullVariant(blockState); - float rotation = BlockStateValues.getSkullRotation(blockState) * 22.5f; - // Just in case... - if (skullVariant == -1) { - skullVariant = 0; - } - builder.put("Rotation", rotation); - builder.put("SkullType", skullVariant); - } -} +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.level.block.entity; + +import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.NbtMapBuilder; + +@BlockEntity(type = BlockEntityType.BEACON) +public class BeaconBlockEntityTranslator extends BlockEntityTranslator { + @Override + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { + int primary = getOrDefault(tag.get("Primary"), 0); + // The effects here generally map one-to-one Java <-> Bedrock. Only the newer ones get more complicated + builder.putInt("primary", primary == -1 ? 0 : primary); + int secondary = getOrDefault(tag.get("Secondary"), 0); + builder.putInt("secondary", secondary == -1 ? 0 : secondary); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BedBlockEntityTranslator.java similarity index 81% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BedBlockEntityTranslator.java index 0067cc41f..fe7971105 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BedBlockEntityTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,19 +23,15 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.block.entity; +package org.geysermc.geyser.translator.level.block.entity; +import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.nbt.NbtMapBuilder; -import org.geysermc.connector.network.translators.world.block.BlockStateValues; +import org.geysermc.geyser.level.block.BlockStateValues; -@BlockEntity(name = "Bed", regex = "bed") +@BlockEntity(type = BlockEntityType.BED) public class BedBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { - @Override - public boolean isBlock(int blockState) { - return BlockStateValues.getBedColor(blockState) != -1; - } - @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { byte bedcolor = BlockStateValues.getBedColor(blockState); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BedrockOnlyBlockEntity.java similarity index 76% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java rename to core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BedrockOnlyBlockEntity.java index 646929f32..3534e5be7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BedrockOnlyBlockEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,19 +23,26 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.block.entity; +package org.geysermc.geyser.translator.level.block.entity; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.session.GeyserSession; /** * Implemented only if a block is a block entity in Bedrock and not Java Edition. */ -public interface BedrockOnlyBlockEntity { +public interface BedrockOnlyBlockEntity extends RequiresBlockState { + /** + * Determines if block is part of class + * @param blockState BlockState to be compared + * @return true if part of the class + */ + boolean isBlock(int blockState); + /** * Update the block on Bedrock Edition. - * @param session GeyserSession. + * @param session GeyserConnection. * @param blockState The Java block state. * @param position The Bedrock block position. */ @@ -47,9 +54,9 @@ public interface BedrockOnlyBlockEntity { * @param blockState Java BlockState of block. * @return Bedrock tag, or null if not a Bedrock-only Block Entity */ - static NbtMap getTag(Vector3i position, int blockState) { + static NbtMap getTag(GeyserSession session, Vector3i position, int blockState) { if (FlowerPotBlockEntityTranslator.isFlowerBlock(blockState)) { - return FlowerPotBlockEntityTranslator.getTag(blockState, position); + return FlowerPotBlockEntityTranslator.getTag(session, blockState, position); } else if (PistonBlockEntityTranslator.isBlock(blockState)) { return PistonBlockEntityTranslator.getTag(blockState, position); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntity.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BlockEntity.java similarity index 78% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntity.java rename to core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BlockEntity.java index 11bfe0ea4..32b765166 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntity.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BlockEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,9 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.block.entity; +package org.geysermc.geyser.translator.level.block.entity; + +import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -32,14 +34,8 @@ import java.lang.annotation.RetentionPolicy; public @interface BlockEntity { /** - * The block entity name - * @return the name of the block entity + * The Java block entity type + * @return the type of the block entity */ - String name(); - - /** - * The search term used in BlockTranslator - * @return the search term used in BlockTranslator - */ - String regex(); + BlockEntityType[] type(); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BlockEntityTranslator.java new file mode 100644 index 000000000..602ac140c --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BlockEntityTranslator.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.level.block.entity; + +import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import org.geysermc.geyser.util.BlockEntityUtils; + +/** + * The class that all block entities (on both Java and Bedrock) should translate with + */ +public abstract class BlockEntityTranslator { + protected BlockEntityTranslator() { + } + + public abstract void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState); + + public NbtMap getBlockEntityTag(BlockEntityType type, int x, int y, int z, CompoundTag tag, int blockState) { + NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(type), x, y, z); + translateTag(tagBuilder, tag, blockState); + return tagBuilder.build(); + } + + protected NbtMapBuilder getConstantBedrockTag(String bedrockId, int x, int y, int z) { + return NbtMap.builder() + .putInt("x", x) + .putInt("y", y) + .putInt("z", z) + .putString("id", bedrockId); + } + + @SuppressWarnings("unchecked") + protected T getOrDefault(Tag tag, T defaultValue) { + return (tag != null && tag.getValue() != null) ? (T) tag.getValue() : defaultValue; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/CampfireBlockEntityTranslator.java similarity index 73% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/level/block/entity/CampfireBlockEntityTranslator.java index 3e4f9fb90..7880f8106 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/CampfireBlockEntityTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,16 +23,18 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.block.entity; +package org.geysermc.geyser.translator.level.block.entity; +import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.geyser.network.MinecraftProtocol; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.registry.type.ItemMapping; -@BlockEntity(name = "Campfire", regex = "campfire") +@BlockEntity(type = BlockEntityType.CAMPFIRE) public class CampfireBlockEntityTranslator extends BlockEntityTranslator { @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { @@ -45,11 +47,12 @@ public class CampfireBlockEntityTranslator extends BlockEntityTranslator { } protected NbtMap getItem(CompoundTag tag) { - ItemEntry entry = ItemRegistry.getItemEntry((String) tag.get("id").getValue()); + // TODO: Version independent mappings + ItemMapping mapping = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getMapping((String) tag.get("id").getValue()); NbtMapBuilder tagBuilder = NbtMap.builder() - .putShort("id", (short) entry.getBedrockId()) + .putString("Name", mapping.getBedrockIdentifier()) .putByte("Count", (byte) tag.get("Count").getValue()) - .putShort("Damage", (short) entry.getBedrockData()); + .putShort("Damage", (short) mapping.getBedrockData()); tagBuilder.put("tag", NbtMap.builder().build()); return tagBuilder.build(); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/CommandBlockBlockEntityTranslator.java similarity index 83% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/level/block/entity/CommandBlockBlockEntityTranslator.java index 1eb50ffe7..87221ab01 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/CommandBlockBlockEntityTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,18 +23,19 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.block.entity; +package org.geysermc.geyser.translator.level.block.entity; +import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.github.steveice10.opennbt.tag.builtin.*; import com.nukkitx.nbt.NbtMapBuilder; -import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import org.geysermc.connector.network.translators.chat.MessageTranslator; +import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.translator.text.MessageTranslator; -@BlockEntity(name = "CommandBlock", regex = "command_block") +@BlockEntity(type = BlockEntityType.COMMAND_BLOCK) public class CommandBlockBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { - if (tag.size() < 5) { + if (tag == null || tag.size() < 5) { return; // These values aren't here } // Java infers from the block state, but Bedrock needs it in the tag @@ -54,9 +55,4 @@ public class CommandBlockBlockEntityTranslator extends BlockEntityTranslator imp builder.put("LastExecution", (long) 0); } } - - @Override - public boolean isBlock(int blockState) { - return BlockStateValues.getCommandBlockValues().containsKey(blockState); - } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/DoubleChestBlockEntityTranslator.java similarity index 51% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/level/block/entity/DoubleChestBlockEntityTranslator.java index 47bcf4897..b6a10dd8f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/DoubleChestBlockEntityTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,21 +23,22 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.block.entity; +package org.geysermc.geyser.translator.level.block.entity; +import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMapBuilder; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import org.geysermc.connector.network.translators.world.block.DoubleChestValue; -import org.geysermc.connector.utils.BlockEntityUtils; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.level.block.DoubleChestValue; +import org.geysermc.geyser.util.BlockEntityUtils; /** * Chests have more block entity properties in Bedrock, which is solved by implementing the BedrockOnlyBlockEntity */ -@BlockEntity(name = "Chest", regex = "chest") -public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator implements BedrockOnlyBlockEntity, RequiresBlockState { +@BlockEntity(type = { BlockEntityType.CHEST, BlockEntityType.TRAPPED_CHEST }) +public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator implements BedrockOnlyBlockEntity { @Override public boolean isBlock(int blockState) { return BlockStateValues.getDoubleChestValues().containsKey(blockState); @@ -45,41 +46,52 @@ public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator impl @Override public void updateBlock(GeyserSession session, int blockState, Vector3i position) { - CompoundTag javaTag = getConstantJavaTag("chest", position.getX(), position.getY(), position.getZ()); - NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId("chest"), position.getX(), position.getY(), position.getZ()).toBuilder(); - translateTag(tagBuilder, javaTag, blockState); + NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(BlockEntityType.CHEST), position.getX(), position.getY(), position.getZ()); + translateTag(tagBuilder, null, blockState); BlockEntityUtils.updateBlockEntity(session, tagBuilder.build(), position); } @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { - DoubleChestValue chestValues = BlockStateValues.getDoubleChestValues().getOrDefault(blockState, null); + DoubleChestValue chestValues = BlockStateValues.getDoubleChestValues().get(blockState); if (chestValues != null) { - int x = (int) tag.getValue().get("x").getValue(); - int z = (int) tag.getValue().get("z").getValue(); - // Calculate the position of the other chest based on the Java block state - if (chestValues.isFacingEast) { - if (chestValues.isDirectionPositive) { - // East - z = z + (chestValues.isLeft ? 1 : -1); - } else { - // West - z = z + (chestValues.isLeft ? -1 : 1); - } + int x = (int) builder.get("x"); + int z = (int) builder.get("z"); + translateChestValue(builder, chestValues, x, z); + } + } + + /** + * Add Bedrock block entity tags to a NbtMap based on Java properties + * + * @param builder the NbtMapBuilder to apply properties to + * @param chestValues the position properties of this double chest + * @param x the x position of this chest pair + * @param z the z position of this chest pair + */ + public static void translateChestValue(NbtMapBuilder builder, DoubleChestValue chestValues, int x, int z) { + // Calculate the position of the other chest based on the Java block state + if (chestValues.isFacingEast()) { + if (chestValues.isDirectionPositive()) { + // East + z = z + (chestValues.isLeft() ? 1 : -1); } else { - if (chestValues.isDirectionPositive) { - // South - x = x + (chestValues.isLeft ? -1 : 1); - } else { - // North - x = x + (chestValues.isLeft ? 1 : -1); - } + // West + z = z + (chestValues.isLeft() ? -1 : 1); } - builder.put("pairx", x); - builder.put("pairz", z); - if (!chestValues.isLeft) { - builder.put("pairlead", (byte) 1); + } else { + if (chestValues.isDirectionPositive()) { + // South + x = x + (chestValues.isLeft() ? -1 : 1); + } else { + // North + x = x + (chestValues.isLeft() ? 1 : -1); } } + builder.put("pairx", x); + builder.put("pairz", z); + if (!chestValues.isLeft()) { + builder.put("pairlead", (byte) 1); + } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/EmptyBlockEntityTranslator.java similarity index 89% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/level/block/entity/EmptyBlockEntityTranslator.java index 3926b8664..e5abe74a7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/EmptyBlockEntityTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,12 +23,11 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.block.entity; +package org.geysermc.geyser.translator.level.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.nbt.NbtMapBuilder; -@BlockEntity(name = "Empty", regex = "") public class EmptyBlockEntityTranslator extends BlockEntityTranslator { @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/EndGatewayBlockEntityTranslator.java similarity index 83% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/level/block/entity/EndGatewayBlockEntityTranslator.java index 0bf588226..530bcf241 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/EndGatewayBlockEntityTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,10 +23,13 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.block.entity; +package org.geysermc.geyser.translator.level.block.entity; +import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.IntTag; +import com.github.steveice10.opennbt.tag.builtin.LongTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.nbt.NbtList; import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; @@ -35,11 +38,14 @@ import it.unimi.dsi.fastutil.ints.IntList; import java.util.LinkedHashMap; -@BlockEntity(name = "EndGateway", regex = "end_gateway") +@BlockEntity(type = BlockEntityType.END_GATEWAY) public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator { @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { - builder.put("Age", (int) ((long) tag.get("Age").getValue())); + Tag ageTag = tag.get("Age"); + if (ageTag instanceof LongTag) { + builder.put("Age", (int) ((long) ageTag.getValue())); + } // Java sometimes does not provide this tag, but Bedrock crashes if it doesn't exist // Linked coordinates IntList tagsList = new IntArrayList(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/FlowerPotBlockEntityTranslator.java similarity index 79% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/level/block/entity/FlowerPotBlockEntityTranslator.java index f64474ae1..b6c498d9f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/FlowerPotBlockEntityTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,18 +23,17 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.block.entity; +package org.geysermc.geyser.translator.level.block.entity; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.utils.BlockEntityUtils; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.util.BlockEntityUtils; -public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity, RequiresBlockState { +public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity { /** * @param blockState the Java block state of a potential flower pot block * @return true if the block is a flower pot @@ -50,7 +49,7 @@ public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity, R * @param position Bedrock position of flower pot. * @return Bedrock tag of flower pot. */ - public static NbtMap getTag(int blockState, Vector3i position) { + public static NbtMap getTag(GeyserSession session, int blockState, Vector3i position) { NbtMapBuilder tagBuilder = NbtMap.builder() .putInt("x", position.getX()) .putInt("y", position.getY()) @@ -62,7 +61,7 @@ public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity, R if (name != null) { // Get the Bedrock CompoundTag of the block. // This is where we need to store the *Java* name because Bedrock has six minecraft:sapling blocks with different block states. - NbtMap plant = BlockStateValues.getFlowerPotBlocks().get(name); + NbtMap plant = session.getBlockMappings().getFlowerPotBlocks().get(name); if (plant != null) { tagBuilder.put("PlantBlock", plant.toBuilder().build()); } @@ -77,15 +76,16 @@ public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity, R @Override public void updateBlock(GeyserSession session, int blockState, Vector3i position) { - BlockEntityUtils.updateBlockEntity(session, getTag(blockState, position), position); + NbtMap tag = getTag(session, blockState, position); + BlockEntityUtils.updateBlockEntity(session, tag, position); UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); updateBlockPacket.setDataLayer(0); - updateBlockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(blockState)); + updateBlockPacket.setRuntimeId(session.getBlockMappings().getBedrockBlockId(blockState)); updateBlockPacket.setBlockPosition(position); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); session.sendUpstreamPacket(updateBlockPacket); - BlockEntityUtils.updateBlockEntity(session, getTag(blockState, position), position); + BlockEntityUtils.updateBlockEntity(session, tag, position); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/JigsawBlockBlockEntityTranslator.java similarity index 88% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/level/block/entity/JigsawBlockBlockEntityTranslator.java index 4fcdfe54d..a94804164 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/JigsawBlockBlockEntityTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,13 +23,14 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.block.entity; +package org.geysermc.geyser.translator.level.block.entity; +import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.nukkitx.nbt.NbtMapBuilder; -@BlockEntity(name = "JigsawBlock", regex = "jigsaw") +@BlockEntity(type = BlockEntityType.JIGSAW) public class JigsawBlockBlockEntityTranslator extends BlockEntityTranslator { @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/NoteblockBlockEntityTranslator.java similarity index 71% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/level/block/entity/NoteblockBlockEntityTranslator.java index fce0a0561..1d61d0e30 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/NoteblockBlockEntityTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,28 +23,21 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.block.entity; +package org.geysermc.geyser.translator.level.block.entity; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.packet.BlockEventPacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import org.geysermc.connector.utils.ChunkUtils; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.block.BlockStateValues; /** * Does not implement BlockEntityTranslator because it's only a block entity in Bedrock */ -public class NoteblockBlockEntityTranslator implements RequiresBlockState { - @Override - public boolean isBlock(int blockState) { - return BlockStateValues.getNoteblockPitch(blockState) != -1; - } +public class NoteblockBlockEntityTranslator { public static void translate(GeyserSession session, Position position) { - int blockState = session.getConnector().getConfig().isCacheChunks() ? - session.getConnector().getWorldManager().getBlockAt(session, position) : - ChunkUtils.CACHED_BLOCK_ENTITIES.removeInt(position); + int blockState = session.getGeyser().getWorldManager().getBlockAt(session, position); BlockEventPacket blockEventPacket = new BlockEventPacket(); blockEventPacket.setBlockPosition(Vector3i.from(position.getX(), position.getY(), position.getZ())); blockEventPacket.setEventType(0); diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/PistonBlockEntity.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/PistonBlockEntity.java new file mode 100644 index 000000000..1363f6641 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/PistonBlockEntity.java @@ -0,0 +1,797 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.level.block.entity; + +import com.github.steveice10.mc.protocol.data.game.level.block.value.PistonValueType; +import com.nukkitx.math.vector.Vector3d; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import lombok.Getter; +import org.geysermc.common.PlatformType; +import org.geysermc.geyser.level.physics.Axis; +import org.geysermc.geyser.level.physics.Direction; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.PistonCache; +import org.geysermc.geyser.level.physics.BoundingBox; +import org.geysermc.geyser.level.physics.CollisionManager; +import org.geysermc.geyser.translator.collision.BlockCollision; +import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.util.*; + +import java.util.LinkedList; +import java.util.Map; +import java.util.Queue; +import java.util.Set; + +public class PistonBlockEntity { + private final GeyserSession session; + @Getter + private final Vector3i position; + private final Direction orientation; + private final boolean sticky; + + @Getter + private PistonValueType action; + + /** + * A map of attached block positions to Java ids. + */ + private final Object2IntMap attachedBlocks = new Object2IntOpenHashMap<>(); + /** + * A flattened array of the positions of attached blocks, stored in XYZ order. + */ + private int[] flattenedAttachedBlocks = new int[0]; + + private boolean placedFinalBlocks = true; + + /** + * The position of the piston head + */ + private float progress; + private float lastProgress; + + private long timeSinceCompletion = 0; + + private static final BoundingBox SOLID_BOUNDING_BOX = new BoundingBox(0.5, 0.5, 0.5, 1, 1, 1); + private static final BoundingBox HONEY_BOUNDING_BOX; + + /** + * The number of ticks to wait after a piston finishes its movement before + * it can be removed + */ + private static final int REMOVAL_DELAY = 5; + + static { + // Create a ~1 x ~0.5 x ~1 bounding box above the honey block + BlockCollision blockCollision = Registries.COLLISIONS.get(BlockStateValues.JAVA_HONEY_BLOCK_ID); + if (blockCollision == null) { + throw new RuntimeException("Failed to find honey block collision"); + } + BoundingBox blockBoundingBox = blockCollision.getBoundingBoxes()[0]; + + double honeyHeight = blockBoundingBox.getMax().getY(); + double boundingBoxHeight = 1.5 - honeyHeight; + HONEY_BOUNDING_BOX = new BoundingBox(0.5, honeyHeight + boundingBoxHeight / 2, 0.5, blockBoundingBox.getSizeX(), boundingBoxHeight, blockBoundingBox.getSizeZ()); + } + + public PistonBlockEntity(GeyserSession session, Vector3i position, Direction orientation, boolean sticky, boolean extended) { + this.session = session; + this.position = position; + this.orientation = orientation; + this.sticky = sticky; + + if (extended) { + // Fully extended + this.action = PistonValueType.PUSHING; + this.progress = 1.0f; + } else { + // Fully retracted + this.action = PistonValueType.PULLING; + this.progress = 0.0f; + } + this.lastProgress = this.progress; + } + + /** + * Set whether the piston is pulling or pushing blocks + * + * @param action PULLING or PUSHING or CANCELED_MID_PUSH + */ + public void setAction(PistonValueType action) { + if (this.action == action) { + return; + } + placeFinalBlocks(); + removeMovingBlocks(); + + this.action = action; + if (action == PistonValueType.PUSHING || (action == PistonValueType.PULLING && sticky)) { + // Blocks only move when pushing or pulling with sticky pistons + findAffectedBlocks(); + removeBlocks(); + createMovingBlocks(); + } else { + removePistonHead(); + } + placedFinalBlocks = false; + + // Set progress and lastProgress to allow 0 tick pistons to animate + switch (action) { + case PUSHING -> progress = 0; + case PULLING, CANCELLED_MID_PUSH -> progress = 1; + } + lastProgress = progress; + + BlockEntityUtils.updateBlockEntity(session, buildPistonTag(), position); + } + + public void setAction(PistonValueType action, Object2IntMap attachedBlocks) { + // Don't check if this.action == action, since on some Paper versions BlockPistonRetractEvent is called multiple times + // with the first 1-2 events being empty. + placeFinalBlocks(); + removeMovingBlocks(); + + this.action = action; + if (action == PistonValueType.PUSHING || (action == PistonValueType.PULLING && sticky)) { + // Blocks only move when pushing or pulling with sticky pistons + if (attachedBlocks.size() <= 12) { + this.attachedBlocks.putAll(attachedBlocks); + flattenPositions(); + } + removeBlocks(); + createMovingBlocks(); + } else { + removePistonHead(); + } + placedFinalBlocks = false; + + // Set progress and lastProgress to allow 0 tick pistons to animate + switch (action) { + case PUSHING -> progress = 0; + case PULLING, CANCELLED_MID_PUSH -> progress = 1; + } + lastProgress = progress; + + BlockEntityUtils.updateBlockEntity(session, buildPistonTag(), position); + } + + /** + * Update the position of the piston head, moving blocks, and players. + */ + public void updateMovement() { + if (isDone()) { + timeSinceCompletion++; + return; + } else { + timeSinceCompletion = 0; + } + updateProgress(); + pushPlayer(); + BlockEntityUtils.updateBlockEntity(session, buildPistonTag(), position); + } + + /** + * Place attached blocks in their final position when done pushing or pulling + */ + public void updateBlocks() { + if (isDone()) { + // Update blocks only once + if (timeSinceCompletion == 0) { + placeFinalBlocks(); + } + // Give a few ticks for player collisions to be fully resolved + if (timeSinceCompletion >= REMOVAL_DELAY) { + removeMovingBlocks(); + } + } + } + + private void removePistonHead() { + Vector3i blockInFront = position.add(orientation.getUnitVector()); + int blockId = session.getGeyser().getWorldManager().getBlockAt(session, blockInFront); + if (BlockStateValues.isPistonHead(blockId)) { + ChunkUtils.updateBlock(session, BlockStateValues.JAVA_AIR_ID, blockInFront); + } else if (session.getGeyser().getPlatformType() == PlatformType.SPIGOT && blockId == BlockStateValues.JAVA_AIR_ID) { + // Spigot removes the piston head from the cache, but we need to send the block update ourselves + ChunkUtils.updateBlock(session, BlockStateValues.JAVA_AIR_ID, blockInFront); + } + } + + /** + * Find the blocks that will be pushed or pulled by the piston + */ + private void findAffectedBlocks() { + Set blocksChecked = new ObjectOpenHashSet<>(); + Queue blocksToCheck = new LinkedList<>(); + + Vector3i directionOffset = orientation.getUnitVector(); + Vector3i movement = getMovement(); + blocksChecked.add(position); // Don't check the piston itself + if (action == PistonValueType.PULLING) { + blocksChecked.add(getPistonHeadPos()); // Don't check the piston head + blocksToCheck.add(position.add(directionOffset.mul(2))); + } else if (action == PistonValueType.PUSHING) { + removePistonHead(); // Remove lingering piston heads + blocksToCheck.add(position.add(directionOffset)); + } + + boolean moveBlocks = true; + while (!blocksToCheck.isEmpty() && attachedBlocks.size() <= 12) { + Vector3i blockPos = blocksToCheck.remove(); + // Skip blocks we've already checked + if (!blocksChecked.add(blockPos)) { + continue; + } + int blockId = session.getGeyser().getWorldManager().getBlockAt(session, blockPos); + if (blockId == BlockStateValues.JAVA_AIR_ID) { + continue; + } + if (BlockStateValues.canPistonMoveBlock(blockId, action == PistonValueType.PUSHING)) { + attachedBlocks.put(blockPos, blockId); + if (BlockStateValues.isBlockSticky(blockId)) { + // For honey blocks and slime blocks check the blocks adjacent to it + for (Direction direction : Direction.VALUES) { + Vector3i offset = direction.getUnitVector(); + // Only check blocks that aren't being pushed by the current block + if (offset.equals(movement)) { + continue; + } + Vector3i adjacentPos = blockPos.add(offset); + // Ignore the piston block itself + if (adjacentPos.equals(position)) { + continue; + } + // Ignore the piston head + if (action == PistonValueType.PULLING && position.add(directionOffset).equals(adjacentPos)) { + continue; + } + int adjacentBlockId = session.getGeyser().getWorldManager().getBlockAt(session, adjacentPos); + if (adjacentBlockId != BlockStateValues.JAVA_AIR_ID && BlockStateValues.isBlockAttached(blockId, adjacentBlockId) && BlockStateValues.canPistonMoveBlock(adjacentBlockId, false)) { + // If it is another slime/honey block we need to check its adjacent blocks + if (BlockStateValues.isBlockSticky(adjacentBlockId)) { + blocksToCheck.add(adjacentPos); + } else { + attachedBlocks.put(adjacentPos, adjacentBlockId); + blocksChecked.add(adjacentPos); + blocksToCheck.add(adjacentPos.add(movement)); + } + } + } + } + // Check next block in line + blocksToCheck.add(blockPos.add(movement)); + } else if (!BlockStateValues.canPistonDestroyBlock(blockId)) { + // Block can't be moved or destroyed, so it blocks all block movement + moveBlocks = false; + break; + } + } + if (!moveBlocks || attachedBlocks.size() > 12) { + attachedBlocks.clear(); + } else { + flattenPositions(); + } + } + + /** + * Get the unit vector for the direction of movement + * + * @return The movement of the blocks + */ + private Vector3i getMovement() { + if (action == PistonValueType.PULLING) { + return orientation.reversed().getUnitVector(); + } + return orientation.getUnitVector(); // PUSHING and CANCELLED_MID_PUSH + } + + /** + * Replace all attached blocks with air + */ + private void removeBlocks() { + for (Vector3i blockPos : attachedBlocks.keySet()) { + ChunkUtils.updateBlock(session, BlockStateValues.JAVA_AIR_ID, blockPos); + } + if (action != PistonValueType.PUSHING) { + removePistonHead(); + } + } + + /** + * Push the player + * If the player is pushed, the displacement is added to playerDisplacement in PistonCache + * If the player contacts a slime block, playerMotion in PistonCache is updated + */ + public void pushPlayer() { + Vector3i direction = orientation.getUnitVector(); + double blockMovement = lastProgress; + if (action == PistonValueType.PULLING || action == PistonValueType.CANCELLED_MID_PUSH) { + blockMovement = 1f - lastProgress; + } + + BoundingBox playerBoundingBox = session.getCollisionManager().getPlayerBoundingBox(); + // Shrink the collision in the other axes slightly, to avoid false positives when pressed up against the side of blocks + Vector3d shrink = Vector3i.ONE.sub(direction.abs()).toDouble().mul(CollisionManager.COLLISION_TOLERANCE * 2); + playerBoundingBox.setSizeX(playerBoundingBox.getSizeX() - shrink.getX()); + playerBoundingBox.setSizeY(playerBoundingBox.getSizeY() - shrink.getY()); + playerBoundingBox.setSizeZ(playerBoundingBox.getSizeZ() - shrink.getZ()); + + // Resolve collision with the piston head + int pistonHeadId = BlockStateValues.getPistonHead(orientation); + pushPlayerBlock(pistonHeadId, getPistonHeadPos().toDouble(), blockMovement, playerBoundingBox); + + // Resolve collision with any attached moving blocks, but skip slime blocks + // This prevents players from being launched by slime blocks covered by other blocks + for (Object2IntMap.Entry entry : attachedBlocks.object2IntEntrySet()) { + int blockId = entry.getIntValue(); + if (blockId != BlockStateValues.JAVA_SLIME_BLOCK_ID) { + Vector3d blockPos = entry.getKey().toDouble(); + pushPlayerBlock(blockId, blockPos, blockMovement, playerBoundingBox); + } + } + // Resolve collision with slime blocks + for (Object2IntMap.Entry entry : attachedBlocks.object2IntEntrySet()) { + int blockId = entry.getIntValue(); + if (blockId == BlockStateValues.JAVA_SLIME_BLOCK_ID) { + Vector3d blockPos = entry.getKey().toDouble(); + pushPlayerBlock(blockId, blockPos, blockMovement, playerBoundingBox); + } + } + + // Undo shrink + playerBoundingBox.setSizeX(playerBoundingBox.getSizeX() + shrink.getX()); + playerBoundingBox.setSizeY(playerBoundingBox.getSizeY() + shrink.getY()); + playerBoundingBox.setSizeZ(playerBoundingBox.getSizeZ() + shrink.getZ()); + } + + /** + * Checks if a player is attached to the top of a honey block + * + * @param blockPos The position of the honey block + * @param playerBoundingBox The player's bounding box + * @return True if the player attached, otherwise false + */ + private boolean isPlayerAttached(Vector3d blockPos, BoundingBox playerBoundingBox) { + if (orientation.isVertical()) { + return false; + } + return session.getPlayerEntity().isOnGround() && HONEY_BOUNDING_BOX.checkIntersection(blockPos, playerBoundingBox); + } + + /** + * Launches a player if the player is on the pushing side of the slime block + * + * @param blockPos The position of the slime block + * @param playerPos The player's position + */ + private void applySlimeBlockMotion(Vector3d blockPos, Vector3d playerPos) { + Direction movementDirection = orientation; + // Invert direction when pulling + if (action == PistonValueType.PULLING) { + movementDirection = movementDirection.reversed(); + } + + Vector3f movement = getMovement().toFloat(); + Vector3f motion = session.getPistonCache().getPlayerMotion(); + double motionX = motion.getX(); + double motionY = motion.getY(); + double motionZ = motion.getZ(); + blockPos = blockPos.add(0.5, 0.5, 0.5); // Move to the center of the slime block + switch (movementDirection) { + case DOWN: + if (playerPos.getY() < blockPos.getY()) { + motionY = movement.getY(); + } + break; + case UP: + if (playerPos.getY() > blockPos.getY()) { + motionY = movement.getY(); + } + break; + case NORTH: + if (playerPos.getZ() < blockPos.getZ()) { + motionZ = movement.getZ(); + } + break; + case SOUTH: + if (playerPos.getZ() > blockPos.getZ()) { + motionZ = movement.getZ(); + } + break; + case WEST: + if (playerPos.getX() < blockPos.getX()) { + motionX = movement.getX(); + } + break; + case EAST: + if (playerPos.getX() > blockPos.getX()) { + motionX = movement.getX(); + } + break; + } + session.getPistonCache().setPlayerMotion(Vector3f.from(motionX, motionY, motionZ)); + } + + private double getBlockIntersection(BlockCollision blockCollision, Vector3d blockPos, Vector3d extend, BoundingBox boundingBox, Direction direction) { + Direction oppositeDirection = direction.reversed(); + double maxIntersection = 0; + for (BoundingBox b : blockCollision.getBoundingBoxes()) { + b = b.clone(); + b.extend(extend); + b.translate(blockPos.getX(), blockPos.getY(), blockPos.getZ()); + if (b.checkIntersection(Vector3d.ZERO, boundingBox)) { + double intersection = boundingBox.getIntersectionSize(b, direction); + double oppositeIntersection = boundingBox.getIntersectionSize(b, oppositeDirection); + if (intersection < oppositeIntersection) { + maxIntersection = Math.max(intersection, maxIntersection); + } + } + } + return maxIntersection; + } + + private void pushPlayerBlock(int javaId, Vector3d startingPos, double blockMovement, BoundingBox playerBoundingBox) { + PistonCache pistonCache = session.getPistonCache(); + Vector3d movement = getMovement().toDouble(); + // Check if the player collides with the movingBlock block entity + Vector3d finalBlockPos = startingPos.add(movement); + if (SOLID_BOUNDING_BOX.checkIntersection(finalBlockPos, playerBoundingBox)) { + pistonCache.setPlayerCollided(true); + + if (javaId == BlockStateValues.JAVA_SLIME_BLOCK_ID) { + pistonCache.setPlayerSlimeCollision(true); + applySlimeBlockMotion(finalBlockPos, Vector3d.from(playerBoundingBox.getMiddleX(), playerBoundingBox.getMiddleY(), playerBoundingBox.getMiddleZ())); + } + } + + Vector3d blockPos = startingPos.add(movement.mul(blockMovement)); + if (javaId == BlockStateValues.JAVA_HONEY_BLOCK_ID && isPlayerAttached(blockPos, playerBoundingBox)) { + pistonCache.setPlayerCollided(true); + pistonCache.setPlayerAttachedToHoney(true); + + double delta = Math.abs(progress - lastProgress); + pistonCache.displacePlayer(movement.mul(delta)); + } else { + // Move the player out of collision + BlockCollision blockCollision = Registries.COLLISIONS.get(javaId); + if (blockCollision != null) { + Vector3d extend = movement.mul(Math.min(1 - blockMovement, 0.5)); + Direction movementDirection = orientation; + if (action == PistonValueType.PULLING) { + movementDirection = orientation.reversed(); + } + + double intersection = getBlockIntersection(blockCollision, blockPos, extend, playerBoundingBox, movementDirection); + if (intersection > 0) { + pistonCache.setPlayerCollided(true); + pistonCache.displacePlayer(movement.mul(intersection + 0.01d)); + + if (javaId == BlockStateValues.JAVA_SLIME_BLOCK_ID) { + pistonCache.setPlayerSlimeCollision(true); + applySlimeBlockMotion(blockPos, Vector3d.from(playerBoundingBox.getMiddleX(), playerBoundingBox.getMiddleY(), playerBoundingBox.getMiddleZ())); + } + } + } + } + } + + private BlockCollision getCollision(Vector3i blockPos) { + return BlockUtils.getCollision(getAttachedBlockId(blockPos)); + } + + /** + * Compute the maximum movement of a bounding box that won't collide with the moving block attached to this piston + * + * @param blockPos The position of the moving block + * @param boundingBox The bounding box of the moving entity + * @param axis The axis of movement + * @param movement The movement in the axis + * @return The adjusted movement + */ + public double computeCollisionOffset(Vector3i blockPos, BoundingBox boundingBox, Axis axis, double movement) { + BlockCollision blockCollision = getCollision(blockPos); + if (blockCollision != null) { + double movementProgress = progress; + if (action == PistonValueType.PULLING || action == PistonValueType.CANCELLED_MID_PUSH) { + movementProgress = 1f - progress; + } + Vector3i movementVec = getMovement(); + double x = blockPos.getX() + movementVec.getX() * movementProgress; + double y = blockPos.getY() + movementVec.getY() * movementProgress; + double z = blockPos.getZ() + movementVec.getZ() * movementProgress; + double adjustedMovement = blockCollision.computeCollisionOffset(x, y, z, boundingBox, axis, movement); + if (getAttachedBlockId(blockPos) == BlockStateValues.JAVA_SLIME_BLOCK_ID && adjustedMovement != movement) { + session.getPistonCache().setPlayerSlimeCollision(true); + } + return adjustedMovement; + } + return movement; + } + + public boolean checkCollision(Vector3i blockPos, BoundingBox boundingBox) { + BlockCollision blockCollision = getCollision(blockPos); + if (blockCollision != null) { + double movementProgress = progress; + if (action == PistonValueType.PULLING || action == PistonValueType.CANCELLED_MID_PUSH) { + movementProgress = 1f - progress; + } + Vector3i movementVec = getMovement(); + double x = blockPos.getX() + movementVec.getX() * movementProgress; + double y = blockPos.getY() + movementVec.getY() * movementProgress; + double z = blockPos.getZ() + movementVec.getZ() * movementProgress; + return blockCollision.checkIntersection(x, y, z, boundingBox); + } + return false; + } + + private int getAttachedBlockId(Vector3i blockPos) { + if (blockPos.equals(getPistonHeadPos())) { + return BlockStateValues.getPistonHead(orientation); + } else { + return attachedBlocks.getOrDefault(blockPos, BlockStateValues.JAVA_AIR_ID); + } + } + + /** + * Create moving block entities for each attached block + */ + private void createMovingBlocks() { + // Map the final position of each block to this block entity + Map movingBlockMap = session.getPistonCache().getMovingBlocksMap(); + attachedBlocks.forEach((blockPos, javaId) -> movingBlockMap.put(blockPos, this)); + movingBlockMap.put(getPistonHeadPos(), this); + + Vector3i movement = getMovement(); + BoundingBox playerBoundingBox = session.getCollisionManager().getPlayerBoundingBox().clone(); + if (orientation == Direction.UP) { + // Extend the bounding box down, to catch collisions when the player is falling down + playerBoundingBox.extend(0, -256, 0); + playerBoundingBox.setSizeX(playerBoundingBox.getSizeX() + 0.5); + playerBoundingBox.setSizeZ(playerBoundingBox.getSizeZ() + 0.5); + } + attachedBlocks.forEach((blockPos, javaId) -> { + Vector3i newPos = blockPos.add(movement); + if (SOLID_BOUNDING_BOX.checkIntersection(blockPos.toDouble(), playerBoundingBox) || + SOLID_BOUNDING_BOX.checkIntersection(newPos.toDouble(), playerBoundingBox)) { + session.getPistonCache().setPlayerCollided(true); + if (javaId == BlockStateValues.JAVA_SLIME_BLOCK_ID) { + session.getPistonCache().setPlayerSlimeCollision(true); + } + // Don't place moving blocks that collide with the player + // because of https://bugs.mojang.com/browse/MCPE-96035 + return; + } + // Place a moving block at the new location of the block + UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); + updateBlockPacket.setBlockPosition(newPos); + updateBlockPacket.setRuntimeId(session.getBlockMappings().getBedrockMovingBlockId()); + updateBlockPacket.setDataLayer(0); + session.sendUpstreamPacket(updateBlockPacket); + // Update moving block with correct details + BlockEntityUtils.updateBlockEntity(session, buildMovingBlockTag(newPos, javaId, position), newPos); + }); + } + + /** + * Place blocks that don't collide with the player into their final position + * otherwise the player will fall off the block. + * The Java server will handle updating the blocks that do collide later. + */ + private void placeFinalBlocks() { + // Prevent blocks from being placed multiple times since it is called in + // setAction and updateBlocks + if (placedFinalBlocks) { + return; + } + placedFinalBlocks = true; + Vector3i movement = getMovement(); + attachedBlocks.forEach((blockPos, javaId) -> { + blockPos = blockPos.add(movement); + // Send a final block entity packet to detach blocks + BlockEntityUtils.updateBlockEntity(session, buildMovingBlockTag(blockPos, javaId, Direction.DOWN.getUnitVector()), blockPos); + // Don't place blocks that collide with the player + if (!SOLID_BOUNDING_BOX.checkIntersection(blockPos.toDouble(), session.getCollisionManager().getPlayerBoundingBox())) { + ChunkUtils.updateBlock(session, javaId, blockPos); + } + }); + if (action == PistonValueType.PUSHING) { + Vector3i pistonHeadPos = getPistonHeadPos().add(movement); + if (!SOLID_BOUNDING_BOX.checkIntersection(pistonHeadPos.toDouble(), session.getCollisionManager().getPlayerBoundingBox())) { + ChunkUtils.updateBlock(session, BlockStateValues.getPistonHead(orientation), pistonHeadPos); + } + } + } + + /** + * Remove moving blocks from the piston cache + */ + private void removeMovingBlocks() { + Map movingBlockMap = session.getPistonCache().getMovingBlocksMap(); + attachedBlocks.forEach((blockPos, javaId) -> movingBlockMap.remove(blockPos)); + attachedBlocks.clear(); + movingBlockMap.remove(getPistonHeadPos()); + flattenedAttachedBlocks = new int[0]; + } + + /** + * Flatten the positions of attached blocks into a 1D array + */ + private void flattenPositions() { + flattenedAttachedBlocks = new int[3 * attachedBlocks.size()]; + int i = 0; + for (Vector3i position : attachedBlocks.keySet()) { + flattenedAttachedBlocks[3 * i] = position.getX(); + flattenedAttachedBlocks[3 * i + 1] = position.getY(); + flattenedAttachedBlocks[3 * i + 2] = position.getZ(); + i++; + } + } + + /** + * Get the Bedrock state of the piston + * + * @return 0 - Fully retracted, 1 - Extending, 2 - Fully extended, 3 - Retracting + */ + private byte getState() { + switch (action) { + case PUSHING: + return (byte) (isDone() ? 2 : 1); + case PULLING: + return (byte) (isDone() ? 0 : 3); + default: + if (progress == 1.0f) { + return 2; + } + return (byte) (isDone() ? 0 : 2); + } + } + + /** + * @return The starting position of the piston head + */ + private Vector3i getPistonHeadPos() { + if (action == PistonValueType.PUSHING) { + return position; + } + return position.add(orientation.getUnitVector()); + } + + /** + * Update the progress or position of the piston head + */ + private void updateProgress() { + switch (action) { + case PUSHING -> { + lastProgress = progress; + progress += 0.5f; + if (progress >= 1.0f) { + progress = 1.0f; + } + } + case CANCELLED_MID_PUSH, PULLING -> { + lastProgress = progress; + progress -= 0.5f; + if (progress <= 0.0f) { + progress = 0.0f; + } + } + } + } + + /** + * @return True if the piston has finished its movement, otherwise false + */ + public boolean isDone() { + return switch (action) { + case PUSHING -> progress == 1.0f && lastProgress == 1.0f; + case PULLING, CANCELLED_MID_PUSH -> progress == 0.0f && lastProgress == 0.0f; + }; + } + + public boolean canBeRemoved() { + return isDone() && timeSinceCompletion > REMOVAL_DELAY; + } + + /** + * Create a piston data tag with the data in this block entity + * + * @return A piston data tag + */ + private NbtMap buildPistonTag() { + NbtMapBuilder builder = NbtMap.builder() + .putString("id", "PistonArm") + .putIntArray("AttachedBlocks", flattenedAttachedBlocks) + .putFloat("Progress", progress) + .putFloat("LastProgress", lastProgress) + .putByte("NewState", getState()) + .putByte("State", getState()) + .putByte("Sticky", (byte) (sticky ? 1 : 0)) + .putByte("isMovable", (byte) 0) + .putInt("x", position.getX()) + .putInt("y", position.getY()) + .putInt("z", position.getZ()); + return builder.build(); + } + + /** + * Create a piston data tag that has fully extended/retracted + * + * @param position The position for the base of the piston + * @param extended Whether the piston is extended or retracted + * @param sticky Whether the piston is a sticky piston or a regular piston + * @return A piston data tag for a fully extended/retracted piston + */ + public static NbtMap buildStaticPistonTag(Vector3i position, boolean extended, boolean sticky) { + NbtMapBuilder builder = NbtMap.builder() + .putString("id", "PistonArm") + .putFloat("Progress", extended ? 1.0f : 0.0f) + .putFloat("LastProgress", extended ? 1.0f : 0.0f) + .putByte("NewState", (byte) (extended ? 2 : 0)) + .putByte("State", (byte) (extended ? 2 : 0)) + .putByte("Sticky", (byte) (sticky ? 1 : 0)) + .putByte("isMovable", (byte) 0) + .putInt("x", position.getX()) + .putInt("y", position.getY()) + .putInt("z", position.getZ()); + return builder.build(); + } + + /** + * Create a moving block tag of a block that will be moved by a piston + * + * @param position The ending position of the block (The location of the movingBlock block entity) + * @param javaId The Java Id of the block that is moving + * @param pistonPosition The position for the base of the piston that's moving the block + * @return A moving block data tag + */ + private NbtMap buildMovingBlockTag(Vector3i position, int javaId, Vector3i pistonPosition) { + // Get Bedrock block state data + NbtMap movingBlock = session.getBlockMappings().getBedrockBlockStates().get(session.getBlockMappings().getBedrockBlockId(javaId)); + NbtMapBuilder builder = NbtMap.builder() + .putString("id", "MovingBlock") + .putCompound("movingBlock", movingBlock) + .putByte("isMovable", (byte) 1) + .putInt("pistonPosX", pistonPosition.getX()) + .putInt("pistonPosY", pistonPosition.getY()) + .putInt("pistonPosZ", pistonPosition.getZ()) + .putInt("x", position.getX()) + .putInt("y", position.getY()) + .putInt("z", position.getZ()); + if (PistonBlockEntityTranslator.isBlock(javaId)) { + builder.putCompound("movingEntity", PistonBlockEntityTranslator.getTag(javaId, position)); + } + return builder.build(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/PistonBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/PistonBlockEntityTranslator.java similarity index 71% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/PistonBlockEntityTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/level/block/entity/PistonBlockEntityTranslator.java index c8a6e868f..2f29cdef6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/PistonBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/PistonBlockEntityTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,12 +23,11 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.block.entity; +package org.geysermc.geyser.translator.level.block.entity; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; -import com.nukkitx.nbt.NbtMapBuilder; -import org.geysermc.connector.network.translators.world.block.BlockStateValues; +import org.geysermc.geyser.level.block.BlockStateValues; /** * Pistons are a special case where they are only a block entity on Bedrock. @@ -52,19 +51,8 @@ public class PistonBlockEntityTranslator { * @return Bedrock tag of piston. */ public static NbtMap getTag(int blockState, Vector3i position) { - NbtMapBuilder tagBuilder = NbtMap.builder() - .putInt("x", position.getX()) - .putInt("y", position.getY()) - .putInt("z", position.getZ()) - .putByte("isMovable", (byte) 1) - .putString("id", "PistonArm"); - boolean extended = BlockStateValues.getPistonValues().get(blockState); - // 1f if extended, otherwise 0f - tagBuilder.putFloat("Progress", (extended) ? 1.0f : 0.0f); - // 1 if sticky, 0 if not - tagBuilder.putByte("Sticky", (byte) ((BlockStateValues.isStickyPiston(blockState)) ? 1 : 0)); - - return tagBuilder.build(); + boolean sticky = BlockStateValues.isStickyPiston(blockState); + return PistonBlockEntity.buildStaticPistonTag(position, extended, sticky); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/RequiresBlockState.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/RequiresBlockState.java similarity index 80% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/RequiresBlockState.java rename to core/src/main/java/org/geysermc/geyser/translator/level/block/entity/RequiresBlockState.java index 0db306aa5..990ae314e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/RequiresBlockState.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/RequiresBlockState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,18 +23,10 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.block.entity; +package org.geysermc.geyser.translator.level.block.entity; /** * Implemented in block entities if their Java block state is required for additional values in Bedrock */ public interface RequiresBlockState { - - /** - * Determines if block is part of class - * @param blockState BlockState to be compared - * @return true if part of the class - */ - boolean isBlock(int blockState); - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/ShulkerBoxBlockEntityTranslator.java similarity index 69% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/level/block/entity/ShulkerBoxBlockEntityTranslator.java index 69fa10845..8b934f29b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/ShulkerBoxBlockEntityTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,16 +23,24 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.block.entity; +package org.geysermc.geyser.translator.level.block.entity; +import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.nbt.NbtMapBuilder; -import org.geysermc.connector.network.translators.world.block.BlockStateValues; +import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.translator.inventory.ShulkerInventoryTranslator; -@BlockEntity(name = "ShulkerBox", regex = "shulker_box") -public class ShulkerBoxBlockEntityTranslator extends BlockEntityTranslator { +import javax.annotation.Nullable; + +@BlockEntity(type = BlockEntityType.SHULKER_BOX) +public class ShulkerBoxBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { + /** + * Also used in {@link ShulkerInventoryTranslator} + * where {@code tag} is passed as null. + */ @Override - public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { + public void translateTag(NbtMapBuilder builder, @Nullable CompoundTag tag, int blockState) { byte direction = BlockStateValues.getShulkerBoxDirection(blockState); // Just in case... if (direction == -1) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SignBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SignBlockEntityTranslator.java new file mode 100644 index 000000000..0eaabae12 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SignBlockEntityTranslator.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.level.block.entity; + +import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.nukkitx.nbt.NbtMapBuilder; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.geyser.util.SignUtils; + +@BlockEntity(type = BlockEntityType.SIGN) +public class SignBlockEntityTranslator extends BlockEntityTranslator { + /** + * Maps a color stored in a sign's Color tag to its ARGB value. + * + * @param javaColor The dye color stored in the sign's Color tag. + * @return Java Edition's integer matching the color specified + */ + private int getBedrockSignColor(String javaColor) { + //TODO create a DyeColor class and combine with FireworkColor??? + int dyeColor = switch (javaColor) { + case "white" -> 16383998; + case "orange" -> 16351261; + case "magenta" -> 13061821; + case "light_blue" -> 3847130; + case "yellow" -> 16701501; + case "lime" -> 8439583; + case "pink" -> 15961002; + case "gray" -> 4673362; + case "light_gray" -> 10329495; + case "cyan" -> 1481884; + case "purple" -> 8991416; + case "blue" -> 3949738; + case "brown" -> 8606770; + case "green" -> 6192150; + case "red" -> 11546150; + default -> 0; // The proper Java color is 1908001, but this does not render well with glow text. + }; + // Add the transparency of the color, too. + return dyeColor | (255 << 24); + } + + @Override + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { + StringBuilder signText = new StringBuilder(); + for (int i = 0; i < 4; i++) { + int currentLine = i + 1; + String signLine = getOrDefault(tag.getValue().get("Text" + currentLine), ""); + signLine = MessageTranslator.convertMessageLenient(signLine); + + // Check the character width on the sign to ensure there is no overflow that is usually hidden + // to Java Edition clients but will appear to Bedrock clients + int signWidth = 0; + StringBuilder finalSignLine = new StringBuilder(); + boolean previousCharacterWasFormatting = false; // Color changes do not count for maximum width + for (char c : signLine.toCharArray()) { + if (c == '\u00a7') { + // Don't count this character + previousCharacterWasFormatting = true; + } else if (previousCharacterWasFormatting) { + // Don't count this character either + previousCharacterWasFormatting = false; + } else { + signWidth += SignUtils.getCharacterWidth(c); + } + + if (signWidth <= SignUtils.BEDROCK_CHARACTER_WIDTH_MAX) { + finalSignLine.append(c); + } else { + // Adding the character would make Bedrock move to the next line - Java doesn't do that, so we do not want to + break; + } + } + + signText.append(finalSignLine); + signText.append("\n"); + } + + builder.putString("Text", signText.toString()); + + // Java Edition 1.14 added the ability to change the text color of the whole sign using dye + Tag color = tag.get("Color"); + if (color != null) { + builder.putInt("SignTextColor", getBedrockSignColor(color.getValue().toString())); + } + + // Glowing text + boolean isGlowing = getOrDefault(tag.getValue().get("GlowingText"), (byte) 0) != (byte) 0; + builder.putBoolean("IgnoreLighting", isGlowing); + builder.putBoolean("TextIgnoreLegacyBugResolved", isGlowing); // ??? required + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java new file mode 100644 index 000000000..4286b2b21 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.level.block.entity; + +import com.github.steveice10.mc.auth.data.GameProfile; +import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.geyser.entity.type.player.SkullPlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.skin.SkinProvider; +import org.geysermc.geyser.skin.SkullSkinManager; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +@BlockEntity(type = BlockEntityType.SKULL) +public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { + + @Override + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { + byte skullVariant = BlockStateValues.getSkullVariant(blockState); + float rotation = BlockStateValues.getSkullRotation(blockState) * 22.5f; + // Just in case... + if (skullVariant == -1) { + skullVariant = 0; + } + builder.put("Rotation", rotation); + builder.put("SkullType", skullVariant); + } + + public static CompletableFuture getProfile(CompoundTag tag) { + CompoundTag owner = tag.get("SkullOwner"); + if (owner != null) { + CompoundTag properties = owner.get("Properties"); + if (properties == null) { + return SkinProvider.requestTexturesFromUsername(owner); + } + + ListTag textures = properties.get("textures"); + LinkedHashMap tag1 = (LinkedHashMap) textures.get(0).getValue(); + StringTag texture = (StringTag) tag1.get("Value"); + + List profileProperties = new ArrayList<>(); + + GameProfile gameProfile = new GameProfile(UUID.randomUUID(), ""); + profileProperties.add(new GameProfile.Property("textures", texture.getValue())); + gameProfile.setProperties(profileProperties); + return CompletableFuture.completedFuture(gameProfile); + } + return CompletableFuture.completedFuture(null); + } + + public static void spawnPlayer(GeyserSession session, CompoundTag tag, int posX, int posY, int posZ, int blockState) { + float x = posX + .5f; + float y = posY - .01f; + float z = posZ + .5f; + float rotation; + + byte floorRotation = BlockStateValues.getSkullRotation(blockState); + if (floorRotation == -1) { + // Wall skull + y += 0.25f; + rotation = BlockStateValues.getSkullWallDirections().get(blockState); + switch ((int) rotation) { + case 180 -> z += 0.24f; // North + case 0 -> z -= 0.24f; // South + case 90 -> x += 0.24f; // West + case 270 -> x -= 0.24f; // East + } + } else { + rotation = (180f + (floorRotation * 22.5f)) % 360; + } + + Vector3i blockPosition = Vector3i.from(posX, posY, posZ); + Vector3f entityPosition = Vector3f.from(x, y, z); + + getProfile(tag).whenComplete((gameProfile, throwable) -> { + if (gameProfile == null) { + session.getGeyser().getLogger().debug("Custom skull with invalid SkullOwner tag: " + blockPosition + " " + tag); + return; + } + + if (session.getEventLoop().inEventLoop()) { + spawnPlayer(session, gameProfile, blockPosition, entityPosition, rotation, blockState); + } else { + session.executeInEventLoop(() -> spawnPlayer(session, gameProfile, blockPosition, entityPosition, rotation, blockState)); + } + }); + } + + private static void spawnPlayer(GeyserSession session, GameProfile profile, Vector3i blockPosition, + Vector3f entityPosition, float rotation, int blockState) { + long geyserId = session.getEntityCache().getNextEntityId().incrementAndGet(); + + SkullPlayerEntity existingSkull = session.getSkullCache().get(blockPosition); + if (existingSkull != null) { + // Ensure that two skulls can't spawn on the same point + existingSkull.despawnEntity(blockPosition); + } + + SkullPlayerEntity player = new SkullPlayerEntity(session, geyserId, profile, entityPosition, rotation, blockState); + + // Cache entity + session.getSkullCache().put(blockPosition, player); + + player.spawnEntity(); + + SkullSkinManager.requestAndHandleSkin(player, session, (skin -> session.scheduleInEventLoop(() -> { + // Delay to minimize split-second "player" pop-in + player.setFlag(EntityFlag.INVISIBLE, false); + player.updateBedrockMetadata(); + }, 250, TimeUnit.MILLISECONDS))); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SpawnerBlockEntityTranslator.java similarity index 78% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SpawnerBlockEntityTranslator.java index 38507f54a..d9aebc479 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SpawnerBlockEntityTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,14 +23,16 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.block.entity; +package org.geysermc.geyser.translator.level.block.entity; +import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.nbt.NbtMapBuilder; -import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.registry.Registries; -@BlockEntity(name = "MobSpawner", regex = "mob_spawner") +@BlockEntity(type = BlockEntityType.MOB_SPAWNER) public class SpawnerBlockEntityTranslator extends BlockEntityTranslator { @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { @@ -66,13 +68,15 @@ public class SpawnerBlockEntityTranslator extends BlockEntityTranslator { CompoundTag spawnData = tag.get("SpawnData"); if (spawnData != null) { - String entityID = (String) spawnData.get("id").getValue(); + String entityID = (String) ((CompoundTag) spawnData.get("entity")) + .get("id") + .getValue(); builder.put("EntityIdentifier", entityID); - EntityType type = EntityType.getFromIdentifier(entityID); - if (type != null) { - builder.put("DisplayEntityWidth", type.getWidth()); - builder.put("DisplayEntityHeight", type.getHeight()); + EntityDefinition definition = Registries.JAVA_ENTITY_IDENTIFIERS.get(entityID); + if (definition != null) { + builder.put("DisplayEntityWidth", definition.width()); + builder.put("DisplayEntityHeight", definition.height()); builder.put("DisplayEntityScale", 1.0f); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/effect/Effect.java b/core/src/main/java/org/geysermc/geyser/translator/level/event/LevelEventTranslator.java similarity index 67% rename from connector/src/main/java/org/geysermc/connector/network/translators/effect/Effect.java rename to core/src/main/java/org/geysermc/geyser/translator/level/event/LevelEventTranslator.java index b827bb93e..c06998af4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/effect/Effect.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/event/LevelEventTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,20 +23,20 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.effect; +package org.geysermc.geyser.translator.level.event; -import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerPlayEffectPacket; -import org.geysermc.connector.network.session.GeyserSession; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundLevelEventPacket; +import org.geysermc.geyser.session.GeyserSession; /** - * Represents an effect capable of translating itself into bedrock + * Represents an event capable of translating itself into bedrock */ -public interface Effect { +public interface LevelEventTranslator { /** - * Translates the given {@link ServerPlayEffectPacket} into bedrock and sends it upstream. + * Translates the given {@link ClientboundLevelEventPacket} into bedrock and sends it upstream. * - * @param session GeyserSession + * @param session GeyserConnection * @param packet the effect packet to handle */ - void handleEffectPacket(GeyserSession session, ServerPlayEffectPacket packet); + void translate(GeyserSession session, ClientboundLevelEventPacket packet); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/event/PlaySoundEventTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/event/PlaySoundEventTranslator.java new file mode 100644 index 000000000..df882d0d0 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/level/event/PlaySoundEventTranslator.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.level.event; + +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundLevelEventPacket; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.packet.PlaySoundPacket; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +public record PlaySoundEventTranslator(String name, float volume, boolean pitchSub, float pitchMul, + float pitchAdd, boolean relative) implements LevelEventTranslator { + @Override + public void translate(GeyserSession session, ClientboundLevelEventPacket packet) { + Random rand = ThreadLocalRandom.current(); + PlaySoundPacket playSoundPacket = new PlaySoundPacket(); + playSoundPacket.setSound(name); + playSoundPacket.setPosition(!relative ? session.getPlayerEntity().getPosition() : Vector3f.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ()).add(0.5f, 0.5f, 0.5f)); + playSoundPacket.setVolume(volume); + playSoundPacket.setPitch((pitchSub ? (rand.nextFloat() - rand.nextFloat()) : rand.nextFloat()) * pitchMul + pitchAdd); //replicates java client randomness + session.sendUpstreamPacket(playSoundPacket); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/event/SoundEventEventTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/event/SoundEventEventTranslator.java new file mode 100644 index 000000000..5c3e890a2 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/level/event/SoundEventEventTranslator.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.level.event; + +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundLevelEventPacket; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.SoundEvent; +import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; +import org.geysermc.geyser.session.GeyserSession; + +public record SoundEventEventTranslator(SoundEvent soundEvent, + String identifier, int extraData) implements LevelEventTranslator { + @Override + public void translate(GeyserSession session, ClientboundLevelEventPacket packet) { + LevelSoundEventPacket levelSoundEvent = new LevelSoundEventPacket(); + levelSoundEvent.setSound(soundEvent); + levelSoundEvent.setIdentifier(identifier); + levelSoundEvent.setExtraData(extraData); + levelSoundEvent.setRelativeVolumeDisabled(packet.isBroadcast()); + levelSoundEvent.setPosition(Vector3f.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ()).add(0.5f, 0.5f, 0.5f)); + levelSoundEvent.setBabySound(false); + session.sendUpstreamPacket(levelSoundEvent); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/event/SoundLevelEventTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/event/SoundLevelEventTranslator.java new file mode 100644 index 000000000..48a4bf2fd --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/level/event/SoundLevelEventTranslator.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.level.event; + +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundLevelEventPacket; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; +import org.geysermc.geyser.session.GeyserSession; + +public record SoundLevelEventTranslator(LevelEventType levelEventType, + int data) implements LevelEventTranslator { + @Override + public void translate(GeyserSession session, ClientboundLevelEventPacket packet) { + LevelEventPacket eventPacket = new LevelEventPacket(); + eventPacket.setType(levelEventType); + eventPacket.setData(data); + eventPacket.setPosition(Vector3f.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ()).add(0.5f, 0.5f, 0.5f)); + session.sendUpstreamPacket(eventPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/PacketTranslator.java similarity index 70% rename from connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/PacketTranslator.java index 066aa1c51..37485120d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/PacketTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,12 +23,19 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators; +package org.geysermc.geyser.translator.protocol; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.geyser.session.GeyserSession; public abstract class PacketTranslator { - public abstract void translate(T packet, GeyserSession session); + public abstract void translate(GeyserSession session, T packet); + /** + * Determines if this packet should be handled in the session's event loop. This should generally be true - + * only when the packet has to be executed immediately should it be false. + */ + public boolean shouldExecuteInEventLoop() { + return true; + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/Translator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/Translator.java similarity index 92% rename from connector/src/main/java/org/geysermc/connector/network/translators/Translator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/Translator.java index 8e097ba4d..3dc812170 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/Translator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/Translator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,7 @@ * @author GeyserMC * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators; +package org.geysermc.geyser.translator.protocol; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -30,5 +30,4 @@ import java.lang.annotation.RetentionPolicy; @Retention(value = RetentionPolicy.RUNTIME) public @interface Translator { Class packet(); - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockAdventureSettingsTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAdventureSettingsTranslator.java similarity index 54% rename from connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockAdventureSettingsTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAdventureSettingsTranslator.java index f1efc2223..d4de9e22a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockAdventureSettingsTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAdventureSettingsTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,22 +23,36 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.bedrock; +package org.geysermc.geyser.translator.protocol.bedrock; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerAbilitiesPacket; +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerAbilitiesPacket; import com.nukkitx.protocol.bedrock.data.AdventureSetting; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.AdventureSettingsPacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; @Translator(packet = AdventureSettingsPacket.class) public class BedrockAdventureSettingsTranslator extends PacketTranslator { @Override - public void translate(AdventureSettingsPacket packet, GeyserSession session) { - ClientPlayerAbilitiesPacket abilitiesPacket = - new ClientPlayerAbilitiesPacket(packet.getSettings().contains(AdventureSetting.FLYING)); + public void translate(GeyserSession session, AdventureSettingsPacket packet) { + boolean isFlying = packet.getSettings().contains(AdventureSetting.FLYING); + if (!isFlying && session.getGameMode() == GameMode.SPECTATOR) { + // We should always be flying in spectator mode + session.sendAdventureSettings(); + return; + } + + session.setFlying(isFlying); + ServerboundPlayerAbilitiesPacket abilitiesPacket = new ServerboundPlayerAbilitiesPacket(isFlying); session.sendDownstreamPacket(abilitiesPacket); + + if (isFlying && session.getPlayerEntity().getFlag(EntityFlag.SWIMMING)) { + // Bedrock can fly and swim at the same time? Make sure that can't happen + session.setSwimming(false); + } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockAnimateTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAnimateTranslator.java similarity index 61% rename from connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockAnimateTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAnimateTranslator.java index 012582da5..d52a66be7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockAnimateTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAnimateTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,53 +23,48 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.bedrock; - -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; +package org.geysermc.geyser.translator.protocol.bedrock; import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerSwingArmPacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientSteerBoatPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.level.ServerboundPaddleBoatPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundSwingPacket; import com.nukkitx.protocol.bedrock.packet.AnimatePacket; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; import java.util.concurrent.TimeUnit; @Translator(packet = AnimatePacket.class) public class BedrockAnimateTranslator extends PacketTranslator { - private boolean isSteeringLeft; - private boolean isSteeringRight; - @Override - public void translate(AnimatePacket packet, GeyserSession session) { + public void translate(GeyserSession session, AnimatePacket packet) { // Stop the player sending animations before they have fully spawned into the server if (!session.isSpawned()) { return; } switch (packet.getAction()) { - case SWING_ARM: + case SWING_ARM -> // Delay so entity damage can be processed first - session.getConnector().getGeneralThreadPool().schedule(() -> - session.sendDownstreamPacket(new ClientPlayerSwingArmPacket(Hand.MAIN_HAND)), + session.scheduleInEventLoop(() -> + session.sendDownstreamPacket(new ServerboundSwingPacket(Hand.MAIN_HAND)), 25, TimeUnit.MILLISECONDS ); - break; // These two might need to be flipped, but my recommendation is getting moving working first - case ROW_LEFT: + case ROW_LEFT -> { // Packet value is a float of how long one has been rowing, so we convert that into a boolean - isSteeringLeft = packet.getRowingTime() > 0.0; - ClientSteerBoatPacket steerLeftPacket = new ClientSteerBoatPacket(isSteeringRight, isSteeringLeft); + session.setSteeringLeft(packet.getRowingTime() > 0.0); + ServerboundPaddleBoatPacket steerLeftPacket = new ServerboundPaddleBoatPacket(session.isSteeringLeft(), session.isSteeringRight()); session.sendDownstreamPacket(steerLeftPacket); - break; - case ROW_RIGHT: - isSteeringRight = packet.getRowingTime() > 0.0; - ClientSteerBoatPacket steerRightPacket = new ClientSteerBoatPacket(isSteeringRight, isSteeringLeft); + } + case ROW_RIGHT -> { + session.setSteeringRight(packet.getRowingTime() > 0.0); + ServerboundPaddleBoatPacket steerRightPacket = new ServerboundPaddleBoatPacket(session.isSteeringLeft(), session.isSteeringRight()); session.sendDownstreamPacket(steerRightPacket); - break; + } } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java similarity index 87% rename from connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java index 3522b4d56..828f5a934 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBlockEntityDataTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,23 +23,23 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.bedrock; +package org.geysermc.geyser.translator.protocol.bedrock; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientUpdateJigsawBlockPacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientUpdateSignPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSetJigsawBlockPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.level.ServerboundSignUpdatePacket; import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.SignUtils; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.util.SignUtils; @Translator(packet = BlockEntityDataPacket.class) public class BedrockBlockEntityDataTranslator extends PacketTranslator { @Override - public void translate(BlockEntityDataPacket packet, GeyserSession session) { + public void translate(GeyserSession session, BlockEntityDataPacket packet) { NbtMap tag = packet.getData(); if (tag.getString("id").equals("Sign")) { // This is the reason why this all works - Bedrock sends packets every time you update the sign, Java only wants the final packet @@ -105,8 +105,8 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, BlockPickRequestPacket packet) { + Vector3i vector = packet.getBlockPosition(); + int blockToPick = session.getGeyser().getWorldManager().getBlockAt(session, vector.getX(), vector.getY(), vector.getZ()); + + // Block is air - chunk caching is probably off + if (blockToPick == BlockStateValues.JAVA_AIR_ID) { + // Check for an item frame since the client thinks that's a block when it's an entity in Java + ItemFrameEntity entity = ItemFrameEntity.getItemFrameEntity(session, packet.getBlockPosition()); + if (entity != null) { + // Check to see if the item frame has an item in it first + if (entity.getHeldItem() != null && entity.getHeldItem().getId() != 0) { + // Grab the item in the frame + InventoryUtils.findOrCreateItem(session, entity.getHeldItem()); + } else { + // Grab the frame as the item + InventoryUtils.findOrCreateItem(session, entity.getDefinition() == EntityDefinitions.GLOW_ITEM_FRAME ? "minecraft:glow_item_frame" : "minecraft:item_frame"); + } + } + return; + } + + InventoryUtils.findOrCreateItem(session, BlockRegistries.JAVA_BLOCKS.get(blockToPick).getPickItem()); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBookEditTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBookEditTranslator.java new file mode 100644 index 000000000..bead427db --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBookEditTranslator.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.bedrock; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundEditBookPacket; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.nukkitx.protocol.bedrock.packet.BookEditPacket; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +@Translator(packet = BookEditPacket.class) +public class BedrockBookEditTranslator extends PacketTranslator { + private static final int MAXIMUM_PAGE_LENGTH = 8192 * 4; + private static final int MAXIMUM_TITLE_LENGTH = 128 * 4; + + @Override + public void translate(GeyserSession session, BookEditPacket packet) { + if (packet.getText() != null && !packet.getText().isEmpty() && packet.getText().getBytes(StandardCharsets.UTF_8).length > MAXIMUM_PAGE_LENGTH) { + session.getGeyser().getLogger().warning("Page length greater than server allowed!"); + return; + } + + GeyserItemStack itemStack = session.getPlayerInventory().getItemInHand(); + if (itemStack != null) { + CompoundTag tag = itemStack.getNbt() != null ? itemStack.getNbt() : new CompoundTag(""); + ItemStack bookItem = new ItemStack(itemStack.getJavaId(), itemStack.getAmount(), tag); + List pages = tag.contains("pages") ? new LinkedList<>(((ListTag) tag.get("pages")).getValue()) : new LinkedList<>(); + + int page = packet.getPageNumber(); + switch (packet.getAction()) { + case ADD_PAGE: { + // Add empty pages in between + for (int i = pages.size(); i < page; i++) { + pages.add(i, new StringTag("", "")); + } + pages.add(page, new StringTag("", packet.getText())); + break; + } + // Called whenever a page is modified + case REPLACE_PAGE: { + if (page < pages.size()) { + pages.set(page, new StringTag("", packet.getText())); + } else { + // Add empty pages in between + for (int i = pages.size(); i < page; i++) { + pages.add(i, new StringTag("", "")); + } + pages.add(page, new StringTag("", packet.getText())); + } + break; + } + case DELETE_PAGE: { + if (page < pages.size()) { + pages.remove(page); + } + break; + } + case SWAP_PAGES: { + int page2 = packet.getSecondaryPageNumber(); + if (page < pages.size() && page2 < pages.size()) { + Collections.swap(pages, page, page2); + } + break; + } + case SIGN_BOOK: { + tag.put(new StringTag("author", packet.getAuthor())); + tag.put(new StringTag("title", packet.getTitle())); + break; + } + default: + return; + } + // Remove empty pages at the end + while (pages.size() > 0) { + StringTag currentPage = (StringTag) pages.get(pages.size() - 1); + if (currentPage.getValue() == null || currentPage.getValue().isEmpty()) { + pages.remove(pages.size() - 1); + } else { + break; + } + } + tag.put(new ListTag("pages", pages)); + // Update local copy + session.getPlayerInventory().setItem(36 + session.getPlayerInventory().getHeldItemSlot(), GeyserItemStack.from(bookItem), session); + session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory()); + + List networkPages = new ArrayList<>(); + for (Tag pageTag : pages) { + networkPages.add(((StringTag) pageTag).getValue()); + } + + String title; + if (packet.getAction() == BookEditPacket.Action.SIGN_BOOK) { + // Add title to packet so the server knows we're signing + if (packet.getTitle().getBytes(StandardCharsets.UTF_8).length > MAXIMUM_TITLE_LENGTH) { + session.getGeyser().getLogger().warning("Book title larger than server allows!"); + return; + } + + title = packet.getTitle(); + } else { + title = null; + } + + session.getBookEditCache().setPacket(new ServerboundEditBookPacket(session.getPlayerInventory().getHeldItemSlot(), networkPages, title)); + // There won't be any more book updates after this, so we can try sending the edit packet immediately + if (packet.getAction() == BookEditPacket.Action.SIGN_BOOK) { + session.getBookEditCache().checkForSend(); + } + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandBlockUpdateTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandBlockUpdateTranslator.java similarity index 63% rename from connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandBlockUpdateTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandBlockUpdateTranslator.java index b1e732ffe..f0cf4ff8c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandBlockUpdateTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandBlockUpdateTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,45 +23,41 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.bedrock; +package org.geysermc.geyser.translator.protocol.bedrock; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; -import com.github.steveice10.mc.protocol.data.game.world.block.CommandBlockMode; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientUpdateCommandBlockMinecartPacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientUpdateCommandBlockPacket; +import com.github.steveice10.mc.protocol.data.game.level.block.CommandBlockMode; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSetCommandMinecartPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSetCommandBlockPacket; import com.nukkitx.protocol.bedrock.packet.CommandBlockUpdatePacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; @Translator(packet = CommandBlockUpdatePacket.class) public class BedrockCommandBlockUpdateTranslator extends PacketTranslator { @Override - public void translate(CommandBlockUpdatePacket packet, GeyserSession session) { + public void translate(GeyserSession session, CommandBlockUpdatePacket packet) { String command = packet.getCommand(); boolean outputTracked = packet.isOutputTracked(); if (packet.isBlock()) { - CommandBlockMode mode; - switch (packet.getMode()) { - case CHAIN: // The green one - mode = CommandBlockMode.SEQUENCE; - break; - case REPEATING: // The purple one - mode = CommandBlockMode.AUTO; - break; - default: // NORMAL, the orange one - mode = CommandBlockMode.REDSTONE; - break; - } + CommandBlockMode mode = switch (packet.getMode()) { + case CHAIN -> // The green one + CommandBlockMode.SEQUENCE; + case REPEATING -> // The purple one + CommandBlockMode.AUTO; + default -> // NORMAL, the orange one + CommandBlockMode.REDSTONE; + }; boolean isConditional = packet.isConditional(); boolean automatic = !packet.isRedstoneMode(); // Automatic = Always Active option in Java - ClientUpdateCommandBlockPacket commandBlockPacket = new ClientUpdateCommandBlockPacket( + ServerboundSetCommandBlockPacket commandBlockPacket = new ServerboundSetCommandBlockPacket( new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()), command, mode, outputTracked, isConditional, automatic); session.sendDownstreamPacket(commandBlockPacket); } else { - ClientUpdateCommandBlockMinecartPacket commandMinecartPacket = new ClientUpdateCommandBlockMinecartPacket( + ServerboundSetCommandMinecartPacket commandMinecartPacket = new ServerboundSetCommandMinecartPacket( (int) session.getEntityCache().getEntityByGeyserId(packet.getMinecartRuntimeEntityId()).getEntityId(), command, outputTracked ); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java similarity index 64% rename from connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java index f572538e7..a67b81434 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,27 +23,27 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.bedrock; +package org.geysermc.geyser.translator.protocol.bedrock; import org.geysermc.common.PlatformType; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.CommandManager; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandManager; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; -import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundChatPacket; import com.nukkitx.protocol.bedrock.packet.CommandRequestPacket; -import org.geysermc.connector.network.translators.chat.MessageTranslator; +import org.geysermc.geyser.translator.text.MessageTranslator; @Translator(packet = CommandRequestPacket.class) public class BedrockCommandRequestTranslator extends PacketTranslator { @Override - public void translate(CommandRequestPacket packet, GeyserSession session) { + public void translate(GeyserSession session, CommandRequestPacket packet) { String command = packet.getCommand().replace("/", ""); - CommandManager commandManager = GeyserConnector.getInstance().getCommandManager(); - if (session.getConnector().getPlatformType() == PlatformType.STANDALONE && command.startsWith("geyser ") && commandManager.getCommands().containsKey(command.split(" ")[1])) { + CommandManager commandManager = GeyserImpl.getInstance().getCommandManager(); + if (session.getGeyser().getPlatformType() == PlatformType.STANDALONE && command.trim().startsWith("geyser ") && commandManager.getCommands().containsKey(command.split(" ")[1])) { commandManager.runCommand(session, command); } else { String message = packet.getCommand().trim(); @@ -52,7 +52,7 @@ public class BedrockCommandRequestTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ContainerClosePacket packet) { + byte windowId = packet.getId(); + + //Client wants close confirmation + session.sendUpstreamPacket(packet); + session.setClosingInventory(false); + + if (windowId == -1 && session.getOpenInventory() instanceof MerchantContainer) { + // 1.16.200 - window ID is always -1 sent from Bedrock + windowId = (byte) session.getOpenInventory().getId(); + } + + Inventory openInventory = session.getOpenInventory(); + if (openInventory != null) { + if (windowId == openInventory.getId()) { + ServerboundContainerClosePacket closeWindowPacket = new ServerboundContainerClosePacket(windowId); + session.sendDownstreamPacket(closeWindowPacket); + InventoryUtils.closeInventory(session, windowId, false); + } else if (openInventory.isPending()) { + InventoryUtils.displayInventory(session, openInventory); + openInventory.setPending(false); + + if (openInventory instanceof MerchantContainer merchantContainer && merchantContainer.getPendingOffersPacket() != null) { + JavaMerchantOffersTranslator.openMerchant(session, merchantContainer.getPendingOffersPacket(), merchantContainer); + } + } + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteListTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockEmoteListTranslator.java similarity index 70% rename from connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteListTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockEmoteListTranslator.java index ef5b1a569..dac5fe638 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteListTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockEmoteListTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,18 +23,23 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.bedrock; +package org.geysermc.geyser.translator.protocol.bedrock; import com.nukkitx.protocol.bedrock.packet.EmoteListPacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; +import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; @Translator(packet = EmoteListPacket.class) public class BedrockEmoteListTranslator extends PacketTranslator { @Override - public void translate(EmoteListPacket packet, GeyserSession session) { + public void translate(GeyserSession session, EmoteListPacket packet) { + if (session.getGeyser().getConfig().getEmoteOffhandWorkaround() == EmoteOffhandWorkaroundOption.NO_EMOTES) { + return; + } + session.refreshEmotes(packet.getPieceIds()); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockEntityPickRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockEntityPickRequestTranslator.java new file mode 100644 index 000000000..580dccc88 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockEntityPickRequestTranslator.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.bedrock; + +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.nukkitx.protocol.bedrock.packet.EntityPickRequestPacket; +import org.geysermc.geyser.entity.type.BoatEntity; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.util.InventoryUtils; + +import java.util.Locale; + +/** + * Called when the Bedrock user uses the pick block button on an entity + */ +@Translator(packet = EntityPickRequestPacket.class) +public class BedrockEntityPickRequestTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, EntityPickRequestPacket packet) { + if (session.getGameMode() != GameMode.CREATIVE) return; // Apparently Java behavior + Entity entity = session.getEntityCache().getEntityByGeyserId(packet.getRuntimeEntityId()); + if (entity == null) return; + + // Get the corresponding item + String itemName; + switch (entity.getDefinition().entityType()) { + case BOAT -> { + // Include type of boat in the name + int variant = ((BoatEntity) entity).getVariant(); + String typeOfBoat = switch (variant) { + case 1 -> "spruce"; + case 2 -> "birch"; + case 3 -> "jungle"; + case 4 -> "acacia"; + case 5 -> "dark_oak"; + default -> "oak"; + }; + itemName = typeOfBoat + "_boat"; + } + case LEASH_KNOT -> itemName = "lead"; + case CHEST_MINECART, COMMAND_BLOCK_MINECART, FURNACE_MINECART, HOPPER_MINECART, TNT_MINECART -> + // The Bedrock identifier matches the item name which moves MINECART to the end of the name + // TODO test + itemName = entity.getDefinition().identifier(); + case SPAWNER_MINECART -> itemName = "minecart"; // Turns into a normal minecart + //case ITEM_FRAME -> Not an entity in Bedrock Edition + //case GLOW_ITEM_FRAME -> + case ARMOR_STAND, END_CRYSTAL, MINECART, PAINTING -> + // No spawn egg, just an item + itemName = entity.getDefinition().entityType().toString().toLowerCase(Locale.ROOT); + default -> itemName = entity.getDefinition().entityType().toString().toLowerCase(Locale.ROOT) + "_spawn_egg"; + } + + String fullItemName = "minecraft:" + itemName; + ItemMapping mapping = session.getItemMappings().getMapping(fullItemName); + // Verify it is, indeed, an item + if (mapping == null) return; + + InventoryUtils.findOrCreateItem(session, fullItemName); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockFilterTextTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockFilterTextTranslator.java new file mode 100644 index 000000000..b7961c8fb --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockFilterTextTranslator.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.bedrock; + +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundRenameItemPacket; +import com.nukkitx.protocol.bedrock.packet.FilterTextPacket; +import org.geysermc.geyser.inventory.AnvilContainer; +import org.geysermc.geyser.inventory.CartographyContainer; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.geyser.util.ItemUtils; + +/** + * Used to send strings to the server and filter out unwanted words. + * Java doesn't care, so we don't care, and we approve all strings. + */ +@Translator(packet = FilterTextPacket.class) +public class BedrockFilterTextTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, FilterTextPacket packet) { + if (session.getOpenInventory() instanceof CartographyContainer) { + // We don't want to be able to rename in the cartography table + return; + } + packet.setFromServer(true); + if (session.getOpenInventory() instanceof AnvilContainer anvilContainer) { + anvilContainer.setNewName(packet.getText()); + + String originalName = ItemUtils.getCustomName(anvilContainer.getInput().getNbt()); + + String plainOriginalName = MessageTranslator.convertToPlainText(originalName, session.getLocale()); + String plainNewName = MessageTranslator.convertToPlainText(packet.getText(), session.getLocale()); + if (!plainOriginalName.equals(plainNewName)) { + // Strip out formatting since Java Edition does not allow it + packet.setText(plainNewName); + // Java Edition sends a packet every time an item is renamed even slightly in GUI. Fortunately, this works out for us now + ServerboundRenameItemPacket renameItemPacket = new ServerboundRenameItemPacket(plainNewName); + session.sendDownstreamPacket(renameItemPacket); + } else { + // Restore formatting for item since we're not renaming + packet.setText(MessageTranslator.convertMessageLenient(originalName)); + // Java Edition sends the original custom name when not renaming, + // if there isn't a custom name an empty string is sent + ServerboundRenameItemPacket renameItemPacket = new ServerboundRenameItemPacket(plainOriginalName); + session.sendDownstreamPacket(renameItemPacket); + } + + anvilContainer.setUseJavaLevelCost(false); + session.getInventoryTranslator().updateSlot(session, anvilContainer, 1); + } + session.sendUpstreamPacket(packet); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java new file mode 100644 index 000000000..d779ab339 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java @@ -0,0 +1,418 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.bedrock; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.data.game.entity.object.Direction; +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; +import com.github.steveice10.mc.protocol.data.game.entity.player.InteractAction; +import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundUseItemOnPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundUseItemPacket; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.data.inventory.*; +import com.nukkitx.protocol.bedrock.packet.*; +import org.geysermc.geyser.entity.type.CommandBlockMinecartEntity; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.entity.type.ItemFrameEntity; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.translator.sound.EntitySoundInteractionTranslator; +import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.registry.type.ItemMappings; +import org.geysermc.geyser.util.BlockUtils; + +import java.util.concurrent.TimeUnit; + +/** + * BedrockInventoryTransactionTranslator handles most interactions between the client and the world, + * or the client and their inventory. + */ +@Translator(packet = InventoryTransactionPacket.class) +public class BedrockInventoryTransactionTranslator extends PacketTranslator { + + private static final float MAXIMUM_BLOCK_PLACING_DISTANCE = 64f; + private static final int CREATIVE_EYE_HEIGHT_PLACE_DISTANCE = 49; + private static final int SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE = 36; + private static final float MAXIMUM_BLOCK_DESTROYING_DISTANCE = 36f; + + @Override + public void translate(GeyserSession session, InventoryTransactionPacket packet) { + // Send book updates before opening inventories + session.getBookEditCache().checkForSend(); + + ItemMappings mappings = session.getItemMappings(); + + switch (packet.getTransactionType()) { + case NORMAL: + if (packet.getActions().size() == 2) { + InventoryActionData worldAction = packet.getActions().get(0); + InventoryActionData containerAction = packet.getActions().get(1); + if (worldAction.getSource().getType() == InventorySource.Type.WORLD_INTERACTION + && worldAction.getSource().getFlag() == InventorySource.Flag.DROP_ITEM) { + if (session.getPlayerInventory().getHeldItemSlot() != containerAction.getSlot() || + session.getPlayerInventory().getItemInHand().isEmpty()) { + return; + } + + boolean dropAll = worldAction.getToItem().getCount() > 1; + ServerboundPlayerActionPacket dropAllPacket = new ServerboundPlayerActionPacket( + dropAll ? PlayerAction.DROP_ITEM_STACK : PlayerAction.DROP_ITEM, + BlockUtils.POSITION_ZERO, + Direction.DOWN + ); + session.sendDownstreamPacket(dropAllPacket); + + if (dropAll) { + session.getPlayerInventory().setItemInHand(GeyserItemStack.EMPTY); + } else { + session.getPlayerInventory().getItemInHand().sub(1); + } + } + } + break; + case INVENTORY_MISMATCH: + break; + case ITEM_USE: + switch (packet.getActionType()) { + case 0 -> { + // Check to make sure the client isn't spamming interaction + // Based on Nukkit 1.0, with changes to ensure holding down still works + boolean hasAlreadyClicked = System.currentTimeMillis() - session.getLastInteractionTime() < 110.0 && + packet.getBlockPosition().distanceSquared(session.getLastInteractionBlockPosition()) < 0.00001; + session.setLastInteractionBlockPosition(packet.getBlockPosition()); + session.setLastInteractionPlayerPosition(session.getPlayerEntity().getPosition()); + if (hasAlreadyClicked) { + break; + } else { + // Only update the interaction time if it's valid - that way holding down still works. + session.setLastInteractionTime(System.currentTimeMillis()); + } + + // Bedrock sends block interact code for a Java entity so we send entity code back to Java + if (session.getBlockMappings().isItemFrame(packet.getBlockRuntimeId())) { + Entity itemFrameEntity = ItemFrameEntity.getItemFrameEntity(session, packet.getBlockPosition()); + if (itemFrameEntity != null) { + int entityId = (int) itemFrameEntity.getEntityId(); + Vector3f vector = packet.getClickPosition(); + ServerboundInteractPacket interactPacket = new ServerboundInteractPacket(entityId, + InteractAction.INTERACT, Hand.MAIN_HAND, session.isSneaking()); + ServerboundInteractPacket interactAtPacket = new ServerboundInteractPacket(entityId, + InteractAction.INTERACT_AT, vector.getX(), vector.getY(), vector.getZ(), Hand.MAIN_HAND, session.isSneaking()); + session.sendDownstreamPacket(interactPacket); + session.sendDownstreamPacket(interactAtPacket); + break; + } + } + + Vector3i blockPos = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getBlockFace()); + /* + Checks to ensure that the range will be accepted by the server. + "Not in range" doesn't refer to how far a vanilla client goes (that's a whole other mess), + but how much a server will accept from the client maximum + */ + // Blocks cannot be placed or destroyed outside of the world border + if (!session.getWorldBorder().isInsideBorderBoundaries()) { + restoreCorrectBlock(session, blockPos, packet); + return; + } + + // CraftBukkit+ check - see https://github.com/PaperMC/Paper/blob/458db6206daae76327a64f4e2a17b67a7e38b426/Spigot-Server-Patches/0532-Move-range-check-for-block-placing-up.patch + Vector3f playerPosition = session.getPlayerEntity().getPosition(); + + // Adjust position for current eye height + switch (session.getPose()) { + case SNEAKING -> + playerPosition = playerPosition.sub(0, (EntityDefinitions.PLAYER.offset() - 1.27f), 0); + case SWIMMING, + FALL_FLYING, // Elytra + SPIN_ATTACK -> // Trident spin attack + playerPosition = playerPosition.sub(0, (EntityDefinitions.PLAYER.offset() - 0.4f), 0); + case SLEEPING -> + playerPosition = playerPosition.sub(0, (EntityDefinitions.PLAYER.offset() - 0.2f), 0); + } // else, we don't have to modify the position + + float diffX = playerPosition.getX() - packet.getBlockPosition().getX(); + float diffY = playerPosition.getY() - packet.getBlockPosition().getY(); + float diffZ = playerPosition.getZ() - packet.getBlockPosition().getZ(); + if (((diffX * diffX) + (diffY * diffY) + (diffZ * diffZ)) > + (session.getGameMode().equals(GameMode.CREATIVE) ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE)) { + restoreCorrectBlock(session, blockPos, packet); + return; + } + + // Vanilla check + if (!(session.getPlayerEntity().getPosition().sub(0, EntityDefinitions.PLAYER.offset(), 0) + .distanceSquared(packet.getBlockPosition().toFloat().add(0.5f, 0.5f, 0.5f)) < MAXIMUM_BLOCK_PLACING_DISTANCE)) { + // The client thinks that its blocks have been successfully placed. Restore the server's blocks instead. + restoreCorrectBlock(session, blockPos, packet); + return; + } + /* + Block place checks end - client is good to go + */ + + if (packet.getItemInHand() != null && session.getItemMappings().getSpawnEggIds().contains(packet.getItemInHand().getId())) { + int blockState = session.getGeyser().getWorldManager().getBlockAt(session, packet.getBlockPosition()); + if (blockState == BlockStateValues.JAVA_WATER_ID) { + // Otherwise causes multiple mobs to spawn - just send a use item packet + // TODO when we fix mobile bucket rotation, use it for this, too + ServerboundUseItemPacket itemPacket = new ServerboundUseItemPacket(Hand.MAIN_HAND); + session.sendDownstreamPacket(itemPacket); + break; + } + } + + ServerboundUseItemOnPacket blockPacket = new ServerboundUseItemOnPacket( + new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()), + Direction.VALUES[packet.getBlockFace()], + Hand.MAIN_HAND, + packet.getClickPosition().getX(), packet.getClickPosition().getY(), packet.getClickPosition().getZ(), + false); + session.sendDownstreamPacket(blockPacket); + + if (packet.getItemInHand() != null) { + // Otherwise boats will not be able to be placed in survival and buckets won't work on mobile + if (session.getItemMappings().getBoatIds().contains(packet.getItemInHand().getId())) { + ServerboundUseItemPacket itemPacket = new ServerboundUseItemPacket(Hand.MAIN_HAND); + session.sendDownstreamPacket(itemPacket); + } else if (session.getItemMappings().getBucketIds().contains(packet.getItemInHand().getId())) { + // Let the server decide if the bucket item should change, not the client, and revert the changes the client made + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.INVENTORY); + slotPacket.setSlot(packet.getHotbarSlot()); + slotPacket.setItem(packet.getItemInHand()); + session.sendUpstreamPacket(slotPacket); + // Don't send ServerboundUseItemPacket for powder snow buckets + if (packet.getItemInHand().getId() != session.getItemMappings().getStoredItems().powderSnowBucket().getBedrockId()) { + // Special check for crafting tables since clients don't send BLOCK_INTERACT when interacting + int blockState = session.getGeyser().getWorldManager().getBlockAt(session, packet.getBlockPosition()); + if (session.isSneaking() || blockState != BlockRegistries.JAVA_IDENTIFIERS.get("minecraft:crafting_table")) { + // Delay the interaction in case the client doesn't intend to actually use the bucket + // See BedrockActionTranslator.java + session.setBucketScheduledFuture(session.scheduleInEventLoop(() -> { + ServerboundUseItemPacket itemPacket = new ServerboundUseItemPacket(Hand.MAIN_HAND); + session.sendDownstreamPacket(itemPacket); + }, 5, TimeUnit.MILLISECONDS)); + } + } + } + } + + if (packet.getActions().isEmpty()) { + if (session.getOpPermissionLevel() >= 2 && session.getGameMode() == GameMode.CREATIVE) { + // Otherwise insufficient permissions + if (session.getBlockMappings().getJigsawStateIds().contains(packet.getBlockRuntimeId())) { + ContainerOpenPacket openPacket = new ContainerOpenPacket(); + openPacket.setBlockPosition(packet.getBlockPosition()); + openPacket.setId((byte) 1); + openPacket.setType(ContainerType.JIGSAW_EDITOR); + openPacket.setUniqueEntityId(-1); + session.sendUpstreamPacket(openPacket); + } + } + } + ItemMapping handItem = mappings.getMapping(packet.getItemInHand()); + if (handItem.isBlock()) { + session.setLastBlockPlacePosition(blockPos); + session.setLastBlockPlacedId(handItem.getJavaIdentifier()); + } + session.setInteracting(true); + } + case 1 -> { + if (packet.getActions().size() == 1 && packet.getLegacySlots().size() > 0) { + InventoryActionData actionData = packet.getActions().get(0); + LegacySetItemSlotData slotData = packet.getLegacySlots().get(0); + if (slotData.getContainerId() == 6 && actionData.getToItem().getId() != 0) { + // The player is trying to swap out an armor piece that already has an item in it + // Java Edition does not allow this; let's revert it + session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory()); + } + } + + // Handled when sneaking + if (session.getPlayerInventory().getItemInHand().getJavaId() == mappings.getStoredItems().shield().getJavaId()) { + break; + } + + // Handled in ITEM_USE if the item is not milk + if (packet.getItemInHand() != null) { + if (session.getItemMappings().getBucketIds().contains(packet.getItemInHand().getId()) && + packet.getItemInHand().getId() != session.getItemMappings().getStoredItems().milkBucket().getBedrockId()) { + // Handled in case 0 if the item is not milk + break; + } else if (session.getItemMappings().getSpawnEggIds().contains(packet.getItemInHand().getId())) { + // Handled in case 0 + break; + } + } + + ServerboundUseItemPacket useItemPacket = new ServerboundUseItemPacket(Hand.MAIN_HAND); + session.sendDownstreamPacket(useItemPacket); + } + case 2 -> { + int blockState = session.getGameMode() == GameMode.CREATIVE ? + session.getGeyser().getWorldManager().getBlockAt(session, packet.getBlockPosition()) : session.getBreakingBlock(); + + session.setLastBlockPlacedId(null); + session.setLastBlockPlacePosition(null); + + // Same deal with vanilla block placing as above. + if (!session.getWorldBorder().isInsideBorderBoundaries()) { + restoreCorrectBlock(session, packet.getBlockPosition(), packet); + return; + } + + // This is working out the distance using 3d Pythagoras and the extra value added to the Y is the sneaking height of a java player. + Vector3f playerPosition = session.getPlayerEntity().getPosition(); + Vector3f floatBlockPosition = packet.getBlockPosition().toFloat(); + float diffX = playerPosition.getX() - (floatBlockPosition.getX() + 0.5f); + float diffY = (playerPosition.getY() - EntityDefinitions.PLAYER.offset()) - (floatBlockPosition.getY() + 0.5f) + 1.5f; + float diffZ = playerPosition.getZ() - (floatBlockPosition.getZ() + 0.5f); + float distanceSquared = diffX * diffX + diffY * diffY + diffZ * diffZ; + if (distanceSquared > MAXIMUM_BLOCK_DESTROYING_DISTANCE) { + restoreCorrectBlock(session, packet.getBlockPosition(), packet); + return; + } + + LevelEventPacket blockBreakPacket = new LevelEventPacket(); + blockBreakPacket.setType(LevelEventType.PARTICLE_DESTROY_BLOCK); + blockBreakPacket.setPosition(packet.getBlockPosition().toFloat()); + blockBreakPacket.setData(session.getBlockMappings().getBedrockBlockId(blockState)); + session.sendUpstreamPacket(blockBreakPacket); + session.setBreakingBlock(BlockStateValues.JAVA_AIR_ID); + + Entity itemFrameEntity = ItemFrameEntity.getItemFrameEntity(session, packet.getBlockPosition()); + if (itemFrameEntity != null) { + ServerboundInteractPacket attackPacket = new ServerboundInteractPacket((int) itemFrameEntity.getEntityId(), + InteractAction.ATTACK, session.isSneaking()); + session.sendDownstreamPacket(attackPacket); + break; + } + + PlayerAction action = session.getGameMode() == GameMode.CREATIVE ? PlayerAction.START_DIGGING : PlayerAction.FINISH_DIGGING; + Position pos = new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()); + ServerboundPlayerActionPacket breakPacket = new ServerboundPlayerActionPacket(action, pos, Direction.VALUES[packet.getBlockFace()]); + session.sendDownstreamPacket(breakPacket); + } + } + break; + case ITEM_RELEASE: + if (packet.getActionType() == 0) { + // Followed to the Minecraft Protocol specification outlined at wiki.vg + ServerboundPlayerActionPacket releaseItemPacket = new ServerboundPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, BlockUtils.POSITION_ZERO, + Direction.DOWN); + session.sendDownstreamPacket(releaseItemPacket); + } + break; + case ITEM_USE_ON_ENTITY: + Entity entity = session.getEntityCache().getEntityByGeyserId(packet.getRuntimeEntityId()); + if (entity == null) + return; + + //https://wiki.vg/Protocol#Interact_Entity + switch (packet.getActionType()) { + case 0: //Interact + if (entity instanceof CommandBlockMinecartEntity) { + // The UI is handled client-side on Java Edition + // Ensure OP permission level and gamemode is appropriate + if (session.getOpPermissionLevel() < 2 || session.getGameMode() != GameMode.CREATIVE) return; + ContainerOpenPacket openPacket = new ContainerOpenPacket(); + openPacket.setBlockPosition(Vector3i.ZERO); + openPacket.setId((byte) 1); + openPacket.setType(ContainerType.COMMAND_BLOCK); + openPacket.setUniqueEntityId(entity.getGeyserId()); + session.sendUpstreamPacket(openPacket); + break; + } + Vector3f vector = packet.getClickPosition().sub(entity.getPosition()); + ServerboundInteractPacket interactPacket = new ServerboundInteractPacket((int) entity.getEntityId(), + InteractAction.INTERACT, Hand.MAIN_HAND, session.isSneaking()); + ServerboundInteractPacket interactAtPacket = new ServerboundInteractPacket((int) entity.getEntityId(), + InteractAction.INTERACT_AT, vector.getX(), vector.getY(), vector.getZ(), Hand.MAIN_HAND, session.isSneaking()); + session.sendDownstreamPacket(interactPacket); + session.sendDownstreamPacket(interactAtPacket); + + EntitySoundInteractionTranslator.handleEntityInteraction(session, packet.getClickPosition(), entity); + break; + case 1: //Attack + if (entity.getDefinition() == EntityDefinitions.ENDER_DRAGON) { + // Redirects the attack to its body entity, this only happens when + // attacking the underbelly of the ender dragon + ServerboundInteractPacket attackPacket = new ServerboundInteractPacket((int) entity.getEntityId() + 3, + InteractAction.ATTACK, session.isSneaking()); + session.sendDownstreamPacket(attackPacket); + } else { + ServerboundInteractPacket attackPacket = new ServerboundInteractPacket((int) entity.getEntityId(), + InteractAction.ATTACK, session.isSneaking()); + session.sendDownstreamPacket(attackPacket); + } + break; + } + break; + } + } + + /** + * Restore the correct block state from the server without updating the chunk cache. + * + * @param session the session of the Bedrock client + * @param blockPos the block position to restore + */ + private void restoreCorrectBlock(GeyserSession session, Vector3i blockPos, InventoryTransactionPacket packet) { + int javaBlockState = session.getGeyser().getWorldManager().getBlockAt(session, blockPos); + UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); + updateBlockPacket.setDataLayer(0); + updateBlockPacket.setBlockPosition(blockPos); + updateBlockPacket.setRuntimeId(session.getBlockMappings().getBedrockBlockId(javaBlockState)); + updateBlockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); + session.sendUpstreamPacket(updateBlockPacket); + + UpdateBlockPacket updateWaterPacket = new UpdateBlockPacket(); + updateWaterPacket.setDataLayer(1); + updateWaterPacket.setBlockPosition(blockPos); + updateWaterPacket.setRuntimeId(BlockRegistries.WATERLOGGED.get().contains(javaBlockState) ? session.getBlockMappings().getBedrockWaterId() : session.getBlockMappings().getBedrockAirId()); + updateWaterPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); + session.sendUpstreamPacket(updateWaterPacket); + + // Reset the item in hand to prevent "missing" blocks + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.INVENTORY); + slotPacket.setSlot(packet.getHotbarSlot()); + slotPacket.setItem(packet.getItemInHand()); + session.sendUpstreamPacket(slotPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemFrameDropItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockItemFrameDropItemTranslator.java similarity index 58% rename from connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemFrameDropItemTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockItemFrameDropItemTranslator.java index 8f563c0e0..7ed6e8866 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemFrameDropItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockItemFrameDropItemTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,27 +23,33 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.bedrock; +package org.geysermc.geyser.translator.protocol.bedrock; import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.github.steveice10.mc.protocol.data.game.entity.player.InteractAction; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerInteractEntityPacket; -import com.nukkitx.math.vector.Vector3i; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket; import com.nukkitx.protocol.bedrock.packet.ItemFrameDropItemPacket; -import org.geysermc.connector.entity.ItemFrameEntity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.type.ItemFrameEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +/** + * Pre-1.16.210: used for both survival and creative item frame item removal + * + * 1.16.210: only used in creative. + */ @Translator(packet = ItemFrameDropItemPacket.class) public class BedrockItemFrameDropItemTranslator extends PacketTranslator { @Override - public void translate(ItemFrameDropItemPacket packet, GeyserSession session) { - Vector3i position = Vector3i.from(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()); - ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) ItemFrameEntity.getItemFrameEntityId(session, position), - InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking()); - session.sendDownstreamPacket(interactPacket); + public void translate(GeyserSession session, ItemFrameDropItemPacket packet) { + Entity entity = ItemFrameEntity.getItemFrameEntity(session, packet.getBlockPosition()); + if (entity != null) { + ServerboundInteractPacket interactPacket = new ServerboundInteractPacket((int) entity.getEntityId(), + InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking()); + session.sendDownstreamPacket(interactPacket); + } } - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockItemStackRequestTranslator.java similarity index 54% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockItemStackRequestTranslator.java index 93cfa08e4..efa0dabcc 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockItemStackRequestTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,20 +23,28 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java.window; +package org.geysermc.geyser.translator.protocol.bedrock; -import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerCloseWindowPacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.InventoryUtils; +import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.translator.inventory.InventoryTranslator; -@Translator(packet = ServerCloseWindowPacket.class) -public class JavaCloseWindowTranslator extends PacketTranslator { +/** + * The packet sent for server-authoritative-style inventory transactions. + */ +@Translator(packet = ItemStackRequestPacket.class) +public class BedrockItemStackRequestTranslator extends PacketTranslator { @Override - public void translate(ServerCloseWindowPacket packet, GeyserSession session) { - InventoryUtils.closeWindow(session, packet.getWindowId()); - InventoryUtils.closeInventory(session, packet.getWindowId()); + public void translate(GeyserSession session, ItemStackRequestPacket packet) { + Inventory inventory = session.getOpenInventory(); + if (inventory == null) + return; + + InventoryTranslator translator = session.getInventoryTranslator(); + translator.translateRequests(session, inventory, packet.getRequests()); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockLecternUpdateTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockLecternUpdateTranslator.java new file mode 100644 index 000000000..d7e97825c --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockLecternUpdateTranslator.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.bedrock; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.data.game.entity.object.Direction; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerButtonClickPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClosePacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundUseItemOnPacket; +import com.nukkitx.protocol.bedrock.packet.LecternUpdatePacket; +import org.geysermc.geyser.inventory.LecternContainer; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.util.InventoryUtils; + +/** + * Used to translate moving pages, or closing the inventory + */ +@Translator(packet = LecternUpdatePacket.class) +public class BedrockLecternUpdateTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, LecternUpdatePacket packet) { + if (packet.isDroppingBook()) { + // Bedrock drops the book outside of the GUI. Java drops it in the GUI + // So, we enter the GUI and then drop it! :) + session.setDroppingLecternBook(true); + + // Emulate an interact packet + ServerboundUseItemOnPacket blockPacket = new ServerboundUseItemOnPacket( + new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()), + Direction.DOWN, + Hand.MAIN_HAND, + 0, 0, 0, // Java doesn't care about these when dealing with a lectern + false); + session.sendDownstreamPacket(blockPacket); + } else { + // Bedrock wants to either move a page or exit + if (!(session.getOpenInventory() instanceof LecternContainer lecternContainer)) { + session.getGeyser().getLogger().debug("Expected lectern but it wasn't open!"); + return; + } + + if (lecternContainer.getCurrentBedrockPage() == packet.getPage()) { + // The same page means Bedrock is closing the window + ServerboundContainerClosePacket closeWindowPacket = new ServerboundContainerClosePacket(lecternContainer.getId()); + session.sendDownstreamPacket(closeWindowPacket); + InventoryUtils.closeInventory(session, lecternContainer.getId(), false); + } else { + // Each "page" Bedrock gives to us actually represents two pages (think opening a book and seeing two pages) + // Each "page" on Java is just one page (think a spiral notebook folded back to only show one page) + int newJavaPage = (packet.getPage() * 2); + int currentJavaPage = (lecternContainer.getCurrentBedrockPage() * 2); + + // Send as many click button packets as we need to + // Java has the option to specify exact page numbers by adding 100 to the number, but buttonId variable + // is a byte when transmitted over the network and therefore this stops us at 128 + if (newJavaPage > currentJavaPage) { + for (int i = currentJavaPage; i < newJavaPage; i++) { + ServerboundContainerButtonClickPacket clickButtonPacket = new ServerboundContainerButtonClickPacket(session.getOpenInventory().getId(), 2); + session.sendDownstreamPacket(clickButtonPacket); + } + } else { + for (int i = currentJavaPage; i > newJavaPage; i--) { + ServerboundContainerButtonClickPacket clickButtonPacket = new ServerboundContainerButtonClickPacket(session.getOpenInventory().getId(), 1); + session.sendDownstreamPacket(clickButtonPacket); + } + } + } + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMapInfoRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMapInfoRequestTranslator.java similarity index 64% rename from connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMapInfoRequestTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMapInfoRequestTranslator.java index 11dfe46e0..788f65134 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMapInfoRequestTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMapInfoRequestTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,14 +23,13 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.bedrock; +package org.geysermc.geyser.translator.protocol.bedrock; import com.nukkitx.protocol.bedrock.packet.ClientboundMapItemDataPacket; import com.nukkitx.protocol.bedrock.packet.MapInfoRequestPacket; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; import java.util.concurrent.TimeUnit; @@ -38,17 +37,14 @@ import java.util.concurrent.TimeUnit; public class BedrockMapInfoRequestTranslator extends PacketTranslator { @Override - public void translate(MapInfoRequestPacket packet, GeyserSession session) { - long mapID = packet.getUniqueMapId(); + public void translate(GeyserSession session, MapInfoRequestPacket packet) { + long mapId = packet.getUniqueMapId(); - if (session.getStoredMaps().containsKey(mapID)) { + ClientboundMapItemDataPacket mapPacket = session.getStoredMaps().remove(mapId); + if (mapPacket != null) { // Delay the packet 100ms to prevent the client from ignoring the packet - GeyserConnector.getInstance().getGeneralThreadPool().schedule(() -> { - ClientboundMapItemDataPacket mapPacket = session.getStoredMaps().remove(mapID); - if (mapPacket != null) { - session.sendUpstreamPacket(mapPacket); - } - }, 100, TimeUnit.MILLISECONDS); + session.scheduleInEventLoop(() -> session.sendUpstreamPacket(mapPacket), + 100, TimeUnit.MILLISECONDS); } } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMobEquipmentTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMobEquipmentTranslator.java new file mode 100644 index 000000000..7a077c8b3 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMobEquipmentTranslator.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.bedrock; + +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundSetCarriedItemPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundUseItemPacket; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; +import com.nukkitx.protocol.bedrock.packet.MobEquipmentPacket; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.util.CooldownUtils; +import org.geysermc.geyser.entity.InteractiveTagManager; + +import java.util.concurrent.TimeUnit; + +@Translator(packet = MobEquipmentPacket.class) +public class BedrockMobEquipmentTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, MobEquipmentPacket packet) { + if (!session.isSpawned() || packet.getHotbarSlot() > 8 || + packet.getContainerId() != ContainerId.INVENTORY || session.getPlayerInventory().getHeldItemSlot() == packet.getHotbarSlot()) { + // For the last condition - Don't update the slot if the slot is the same - not Java Edition behavior and messes with plugins such as Grief Prevention + return; + } + + // Send book update before switching hotbar slot + session.getBookEditCache().checkForSend(); + + session.getPlayerInventory().setHeldItemSlot(packet.getHotbarSlot()); + + ServerboundSetCarriedItemPacket setCarriedItemPacket = new ServerboundSetCarriedItemPacket(packet.getHotbarSlot()); + session.sendDownstreamPacket(setCarriedItemPacket); + + if (session.isSneaking() && session.getPlayerInventory().getItemInHand().getJavaId() == session.getItemMappings().getStoredItems().shield().getJavaId()) { + // Activate shield since we are already sneaking + // (No need to send a release item packet - Java doesn't do this when swapping items) + // Required to do it a tick later or else it doesn't register + session.scheduleInEventLoop(() -> session.sendDownstreamPacket(new ServerboundUseItemPacket(Hand.MAIN_HAND)), + 50, TimeUnit.MILLISECONDS); + } + + // Java sends a cooldown indicator whenever you switch an item + CooldownUtils.sendCooldown(session); + + // Update the interactive tag, if an entity is present + if (session.getMouseoverEntity() != null) { + InteractiveTagManager.updateTag(session, session.getMouseoverEntity()); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMoveEntityAbsoluteTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMoveEntityAbsoluteTranslator.java new file mode 100644 index 000000000..6d971daeb --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMoveEntityAbsoluteTranslator.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.bedrock; + +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.level.ServerboundMoveVehiclePacket; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; +import org.geysermc.geyser.entity.type.BoatEntity; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; + +/** + * Sent by the client when moving a horse. + */ +@Translator(packet = MoveEntityAbsolutePacket.class) +public class BedrockMoveEntityAbsoluteTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, MoveEntityAbsolutePacket packet) { + session.setLastVehicleMoveTimestamp(System.currentTimeMillis()); + + Entity ridingEntity = session.getRidingVehicleEntity(); + if (ridingEntity != null && session.getWorldBorder().isPassingIntoBorderBoundaries(packet.getPosition(), false)) { + Vector3f position = Vector3f.from(ridingEntity.getPosition().getX(), packet.getPosition().getY(), + ridingEntity.getPosition().getZ()); + if (ridingEntity instanceof BoatEntity) { + // Undo the changes usually applied to the boat + ridingEntity.as(BoatEntity.class) + .moveAbsoluteWithoutAdjustments(position, ridingEntity.getYaw(), + ridingEntity.isOnGround(), true); + } else { + // This doesn't work if teleported is false + ridingEntity.moveAbsolute(position, + ridingEntity.getYaw(), ridingEntity.getPitch(), ridingEntity.getHeadYaw(), + ridingEntity.isOnGround(), true); + } + return; + } + + float y = packet.getPosition().getY(); + if (ridingEntity instanceof BoatEntity) { + // Remove the offset to prevents boats from looking like they're floating in water + y -= EntityDefinitions.BOAT.offset(); + } + ServerboundMoveVehiclePacket ServerboundMoveVehiclePacket = new ServerboundMoveVehiclePacket( + packet.getPosition().getX(), y, packet.getPosition().getZ(), + packet.getRotation().getY() - 90, packet.getRotation().getX() + ); + session.sendDownstreamPacket(ServerboundMoveVehiclePacket); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockNetworkStackLatencyTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockNetworkStackLatencyTranslator.java new file mode 100644 index 000000000..9ecce9a57 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockNetworkStackLatencyTranslator.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.bedrock; + +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundKeepAlivePacket; +import com.nukkitx.protocol.bedrock.data.AttributeData; +import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket; +import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; +import org.geysermc.geyser.entity.attribute.GeyserAttributeType; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.floodgate.util.DeviceOs; + +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +/** + * Used to send the forwarded keep alive packet back to the server + */ +@Translator(packet = NetworkStackLatencyPacket.class) +public class BedrockNetworkStackLatencyTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, NetworkStackLatencyPacket packet) { + long pingId; + // so apparently, as of 1.16.200 + // PS4 divides the network stack latency timestamp FOR US!!! + // WTF + if (session.getClientData().getDeviceOs().equals(DeviceOs.PS4)) { + pingId = packet.getTimestamp(); + } else { + pingId = packet.getTimestamp() / 1000; + } + + // negative timestamps are used as hack to fix the url image loading bug + if (packet.getTimestamp() > 0) { + if (session.getGeyser().getConfig().isForwardPlayerPing()) { + ServerboundKeepAlivePacket keepAlivePacket = new ServerboundKeepAlivePacket(pingId); + session.sendDownstreamPacket(keepAlivePacket); + } + return; + } + + // Hack to fix the url image loading bug + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); + attributesPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId()); + + AttributeData attribute = session.getPlayerEntity().getAttributes().get(GeyserAttributeType.EXPERIENCE_LEVEL); + if (attribute != null) { + attributesPacket.setAttributes(Collections.singletonList(attribute)); + } else { + attributesPacket.setAttributes(Collections.singletonList(GeyserAttributeType.EXPERIENCE_LEVEL.getAttribute(0))); + } + + session.scheduleInEventLoop(() -> session.sendUpstreamPacket(attributesPacket), + 500, TimeUnit.MILLISECONDS); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPacketViolationWarningTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockPacketViolationWarningTranslator.java similarity index 74% rename from connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPacketViolationWarningTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockPacketViolationWarningTranslator.java index d4a0e8d33..f3967ddf5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPacketViolationWarningTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockPacketViolationWarningTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,19 +23,19 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.bedrock; +package org.geysermc.geyser.translator.protocol.bedrock; import com.nukkitx.protocol.bedrock.packet.PacketViolationWarningPacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; @Translator(packet = PacketViolationWarningPacket.class) public class BedrockPacketViolationWarningTranslator extends PacketTranslator { @Override - public void translate(PacketViolationWarningPacket packet, GeyserSession session) { + public void translate(GeyserSession session, PacketViolationWarningPacket packet) { // Not translated since this is something that the developers need to know - session.getConnector().getLogger().error("Packet violation warning sent from client! " + packet.toString()); + session.getGeyser().getLogger().error("Packet violation warning sent from client! " + packet.toString()); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockPlayerInputTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockPlayerInputTranslator.java new file mode 100644 index 000000000..75199ae74 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockPlayerInputTranslator.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.bedrock; + +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.level.ServerboundMoveVehiclePacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.level.ServerboundPlayerInputPacket; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.packet.PlayerInputPacket; +import org.geysermc.geyser.entity.type.BoatEntity; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.entity.type.living.animal.horse.AbstractHorseEntity; +import org.geysermc.geyser.entity.type.living.animal.horse.LlamaEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; + +/** + * Sent by the client for minecarts and boats. + */ +@Translator(packet = PlayerInputPacket.class) +public class BedrockPlayerInputTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, PlayerInputPacket packet) { + ServerboundPlayerInputPacket playerInputPacket = new ServerboundPlayerInputPacket( + packet.getInputMotion().getX(), packet.getInputMotion().getY(), packet.isJumping(), packet.isSneaking() + ); + + session.sendDownstreamPacket(playerInputPacket); + + // Bedrock only sends movement vehicle packets while moving + // This allows horses to take damage while standing on magma + Entity vehicle = session.getRidingVehicleEntity(); + boolean sendMovement = false; + if (vehicle instanceof AbstractHorseEntity && !(vehicle instanceof LlamaEntity)) { + sendMovement = vehicle.isOnGround(); + } else if (vehicle instanceof BoatEntity) { + if (vehicle.getPassengers().size() == 1) { + // The player is the only rider + sendMovement = true; + } else { + // Check if the player is the front rider + if (session.getPlayerEntity().isRidingInFront()) { + sendMovement = true; + } + } + } + if (sendMovement) { + long timeSinceVehicleMove = System.currentTimeMillis() - session.getLastVehicleMoveTimestamp(); + if (timeSinceVehicleMove >= 100) { + Vector3f vehiclePosition = vehicle.getPosition(); + + if (vehicle instanceof BoatEntity) { + // Remove some Y position to prevents boats flying up + vehiclePosition = vehiclePosition.down(EntityDefinitions.BOAT.offset()); + } + + ServerboundMoveVehiclePacket moveVehiclePacket = new ServerboundMoveVehiclePacket( + vehiclePosition.getX(), vehiclePosition.getY(), vehiclePosition.getZ(), + vehicle.getYaw() - 90, vehicle.getPitch() + ); + session.sendDownstreamPacket(moveVehiclePacket); + } + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java similarity index 71% rename from connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java index 75f60ffdc..59082ccdc 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockPositionTrackingDBClientRequestTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,31 +23,29 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.bedrock; +package org.geysermc.geyser.translator.protocol.bedrock; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.packet.PositionTrackingDBClientRequestPacket; import com.nukkitx.protocol.bedrock.packet.PositionTrackingDBServerBroadcastPacket; -import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.ints.IntList; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.DimensionUtils; -import org.geysermc.connector.utils.LoadstoneTracker; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.LodestoneCache; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.util.DimensionUtils; @Translator(packet = PositionTrackingDBClientRequestPacket.class) public class BedrockPositionTrackingDBClientRequestTranslator extends PacketTranslator { @Override - public void translate(PositionTrackingDBClientRequestPacket packet, GeyserSession session) { + public void translate(GeyserSession session, PositionTrackingDBClientRequestPacket packet) { PositionTrackingDBServerBroadcastPacket broadcastPacket = new PositionTrackingDBServerBroadcastPacket(); broadcastPacket.setTrackingId(packet.getTrackingId()); - // Fetch the stored Loadstone - LoadstoneTracker.LoadstonePos pos = LoadstoneTracker.getPos(packet.getTrackingId()); + // Fetch the stored lodestone + LodestoneCache.LodestonePos pos = session.getLodestoneCache().getPos(packet.getTrackingId()); // If we don't have data for that ID tell the client its not found if (pos == null) { @@ -58,20 +56,16 @@ public class BedrockPositionTrackingDBClientRequestTranslator extends PacketTran broadcastPacket.setAction(PositionTrackingDBServerBroadcastPacket.Action.UPDATE); - // Build the nbt data for the update + // Build the NBT data for the update NbtMapBuilder builder = NbtMap.builder(); builder.putInt("dim", DimensionUtils.javaToBedrock(pos.getDimension())); - builder.putString("id", String.format("%08X", packet.getTrackingId())); + builder.putString("id", "0x" + String.format("%08X", packet.getTrackingId())); builder.putByte("version", (byte) 1); // Not sure what this is for builder.putByte("status", (byte) 0); // Not sure what this is for // Build the position for the update - IntList posList = new IntArrayList(); - posList.add(pos.getX()); - posList.add(pos.getY()); - posList.add(pos.getZ()); - builder.putList("pos", NbtType.INT, posList); + builder.putList("pos", NbtType.INT, pos.getX(), pos.getY(), pos.getZ()); broadcastPacket.setTag(builder.build()); session.sendUpstreamPacket(broadcastPacket); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockRespawnTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockRespawnTranslator.java similarity index 71% rename from connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockRespawnTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockRespawnTranslator.java index dc98c3630..59712da7f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockRespawnTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockRespawnTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,24 +23,23 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.bedrock; +package org.geysermc.geyser.translator.protocol.bedrock; -import com.github.steveice10.mc.protocol.data.game.ClientRequest; -import com.github.steveice10.mc.protocol.packet.ingame.client.ClientRequestPacket; +import com.github.steveice10.mc.protocol.data.game.ClientCommand; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundClientCommandPacket; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; import com.nukkitx.protocol.bedrock.packet.RespawnPacket; -import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; -import org.geysermc.connector.entity.PlayerEntity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; @Translator(packet = RespawnPacket.class) public class BedrockRespawnTranslator extends PacketTranslator { @Override - public void translate(RespawnPacket packet, GeyserSession session) { + public void translate(GeyserSession session, RespawnPacket packet) { if (packet.getState() == RespawnPacket.State.CLIENT_READY) { // Previously we only sent the respawn packet before the server finished loading // The message included was 'Otherwise when immediate respawn is on the client never loads' @@ -54,11 +53,7 @@ public class BedrockRespawnTranslator extends PacketTranslator { if (session.isSpawned()) { // Client might be stuck; resend spawn information PlayerEntity entity = session.getPlayerEntity(); - if (entity == null) return; - SetEntityDataPacket entityDataPacket = new SetEntityDataPacket(); - entityDataPacket.setRuntimeEntityId(entity.getGeyserId()); - entityDataPacket.getMetadata().putAll(entity.getMetadata()); - session.sendUpstreamPacket(entityDataPacket); + entity.updateBedrockMetadata(); // TODO test? MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); movePlayerPacket.setRuntimeEntityId(entity.getGeyserId()); @@ -68,7 +63,7 @@ public class BedrockRespawnTranslator extends PacketTranslator { session.sendUpstreamPacket(movePlayerPacket); } - ClientRequestPacket javaRespawnPacket = new ClientRequestPacket(ClientRequest.RESPAWN); + ServerboundClientCommandPacket javaRespawnPacket = new ServerboundClientCommandPacket(ClientCommand.RESPAWN); session.sendDownstreamPacket(javaRespawnPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockServerSettingsRequestTranslator.java similarity index 57% rename from connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockServerSettingsRequestTranslator.java index a8591cd7f..1840c9b0d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockServerSettingsRequestTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,25 +23,31 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.bedrock; +package org.geysermc.geyser.translator.protocol.bedrock; import com.nukkitx.protocol.bedrock.packet.ServerSettingsRequestPacket; import com.nukkitx.protocol.bedrock.packet.ServerSettingsResponsePacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.SettingsUtils; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.util.SettingsUtils; +import org.geysermc.cumulus.CustomForm; + +import java.util.concurrent.TimeUnit; @Translator(packet = ServerSettingsRequestPacket.class) public class BedrockServerSettingsRequestTranslator extends PacketTranslator { - @Override - public void translate(ServerSettingsRequestPacket packet, GeyserSession session) { - SettingsUtils.buildForm(session); + public void translate(GeyserSession session, ServerSettingsRequestPacket packet) { + CustomForm window = SettingsUtils.buildForm(session); + int windowId = session.getFormCache().addForm(window); - ServerSettingsResponsePacket serverSettingsResponsePacket = new ServerSettingsResponsePacket(); - serverSettingsResponsePacket.setFormData(session.getSettingsForm().getJSONData()); - serverSettingsResponsePacket.setFormId(SettingsUtils.SETTINGS_FORM_ID); - session.sendUpstreamPacket(serverSettingsResponsePacket); + // Fixes https://bugs.mojang.com/browse/MCPE-94012 because of the delay + session.scheduleInEventLoop(() -> { + ServerSettingsResponsePacket serverSettingsResponsePacket = new ServerSettingsResponsePacket(); + serverSettingsResponsePacket.setFormData(window.getJsonData()); + serverSettingsResponsePacket.setFormId(windowId); + session.sendUpstreamPacket(serverSettingsResponsePacket); + }, 1, TimeUnit.SECONDS); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockSetLocalPlayerAsInitializedTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockSetLocalPlayerAsInitializedTranslator.java similarity index 56% rename from connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockSetLocalPlayerAsInitializedTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockSetLocalPlayerAsInitializedTranslator.java index c1ad7409a..a2c0806b0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockSetLocalPlayerAsInitializedTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockSetLocalPlayerAsInitializedTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,29 +23,37 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.bedrock; - -import org.geysermc.connector.entity.PlayerEntity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.SkinUtils; +package org.geysermc.geyser.translator.protocol.bedrock; import com.nukkitx.protocol.bedrock.packet.SetLocalPlayerAsInitializedPacket; +import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.util.InventoryUtils; +import org.geysermc.geyser.util.LoginEncryptionUtils; @Translator(packet = SetLocalPlayerAsInitializedPacket.class) public class BedrockSetLocalPlayerAsInitializedTranslator extends PacketTranslator { @Override - public void translate(SetLocalPlayerAsInitializedPacket packet, GeyserSession session) { + public void translate(GeyserSession session, SetLocalPlayerAsInitializedPacket packet) { if (session.getPlayerEntity().getGeyserId() == packet.getRuntimeEntityId()) { if (!session.getUpstream().isInitialized()) { session.getUpstream().setInitialized(true); - session.login(); - for (PlayerEntity entity : session.getEntityCache().getEntitiesByType(PlayerEntity.class)) { - if (!entity.isValid()) { - SkinUtils.requestAndHandleSkinAndCape(entity, session, null); - entity.sendPlayer(session); + if (session.getRemoteAuthType() == AuthType.ONLINE) { + if (!session.isLoggedIn()) { + LoginEncryptionUtils.buildAndShowLoginWindow(session); + } + // else we were able to log the user in + } + if (session.isLoggedIn()) { + // Sigh - as of Bedrock 1.18 + session.getEntityCache().updateBossBars(); + + // Double sigh - https://github.com/GeyserMC/Geyser/issues/2677 - as of Bedrock 1.18 + if (session.getOpenInventory() != null && session.getOpenInventory().isPending()) { + InventoryUtils.openInventory(session, session.getOpenInventory()); } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockShowCreditsTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockShowCreditsTranslator.java similarity index 70% rename from connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockShowCreditsTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockShowCreditsTranslator.java index 4f0af78c6..19797b8a2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockShowCreditsTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockShowCreditsTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,23 +23,23 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.bedrock; +package org.geysermc.geyser.translator.protocol.bedrock; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; -import com.github.steveice10.mc.protocol.data.game.ClientRequest; -import com.github.steveice10.mc.protocol.packet.ingame.client.ClientRequestPacket; +import com.github.steveice10.mc.protocol.data.game.ClientCommand; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundClientCommandPacket; import com.nukkitx.protocol.bedrock.packet.ShowCreditsPacket; @Translator(packet = ShowCreditsPacket.class) public class BedrockShowCreditsTranslator extends PacketTranslator { @Override - public void translate(ShowCreditsPacket packet, GeyserSession session) { + public void translate(GeyserSession session, ShowCreditsPacket packet) { if (packet.getStatus() == ShowCreditsPacket.Status.END_CREDITS) { - ClientRequestPacket javaRespawnPacket = new ClientRequestPacket(ClientRequest.RESPAWN); + ServerboundClientCommandPacket javaRespawnPacket = new ServerboundClientCommandPacket(ClientCommand.RESPAWN); session.sendDownstreamPacket(javaRespawnPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockTextTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockTextTranslator.java similarity index 64% rename from connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockTextTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockTextTranslator.java index e4a765694..3cf121cf9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockTextTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockTextTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,28 +23,32 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.bedrock; +package org.geysermc.geyser.translator.protocol.bedrock; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - -import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundChatPacket; import com.nukkitx.protocol.bedrock.packet.TextPacket; -import org.geysermc.connector.network.translators.chat.MessageTranslator; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.translator.text.MessageTranslator; @Translator(packet = TextPacket.class) public class BedrockTextTranslator extends PacketTranslator { @Override - public void translate(TextPacket packet, GeyserSession session) { - String message = packet.getMessage().replaceAll("^\\.", "/").trim(); + public void translate(GeyserSession session, TextPacket packet) { + String message = packet.getMessage(); + + if (message.isBlank()) { + // Java Edition (as of 1.17.1) just doesn't pass on these messages, so... we won't either! + return; + } if (MessageTranslator.isTooLong(message, session)) { return; } - ClientChatPacket chatPacket = new ClientChatPacket(message); + ServerboundChatPacket chatPacket = new ServerboundChatPacket(message); session.sendDownstreamPacket(chatPacket); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/BedrockEntityEventTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/BedrockEntityEventTranslator.java new file mode 100644 index 000000000..775ec15b5 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/BedrockEntityEventTranslator.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.bedrock.entity; + +import com.github.steveice10.mc.protocol.data.game.inventory.VillagerTrade; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSelectTradePacket; +import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; +import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.inventory.MerchantContainer; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; + +import java.util.concurrent.TimeUnit; + +@Translator(packet = EntityEventPacket.class) +public class BedrockEntityEventTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, EntityEventPacket packet) { + switch (packet.getType()) { + case EATING_ITEM -> { + // Resend the packet so we get the eating sounds + session.sendUpstreamPacket(packet); + return; + } + case COMPLETE_TRADE -> { + ServerboundSelectTradePacket selectTradePacket = new ServerboundSelectTradePacket(packet.getData()); + session.sendDownstreamPacket(selectTradePacket); + + session.scheduleInEventLoop(() -> { + SessionPlayerEntity villager = session.getPlayerEntity(); + Inventory openInventory = session.getOpenInventory(); + if (openInventory instanceof MerchantContainer merchantInventory) { + VillagerTrade[] trades = merchantInventory.getVillagerTrades(); + if (trades != null && packet.getData() >= 0 && packet.getData() < trades.length) { + VillagerTrade trade = merchantInventory.getVillagerTrades()[packet.getData()]; + openInventory.setItem(2, GeyserItemStack.from(trade.getOutput()), session); + // TODO this logic doesn't add up + villager.addFakeTradeExperience(trade.getXp()); + villager.updateBedrockMetadata(); + } + } + }, 100, TimeUnit.MILLISECONDS); + return; + } + } + session.getGeyser().getLogger().debug("Did not translate incoming EntityEventPacket: " + packet.toString()); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java new file mode 100644 index 000000000..8494daade --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.bedrock.entity.player; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.data.game.entity.object.Direction; +import com.github.steveice10.mc.protocol.data.game.entity.player.*; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.*; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.data.PlayerActionType; +import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.*; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.type.ItemFrameEntity; +import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; +import org.geysermc.geyser.inventory.PlayerInventory; +import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.util.BlockUtils; + +@Translator(packet = PlayerActionPacket.class) +public class BedrockActionTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, PlayerActionPacket packet) { + SessionPlayerEntity entity = session.getPlayerEntity(); + + // Send book update before any player action + if (packet.getAction() != PlayerActionType.RESPAWN) { + session.getBookEditCache().checkForSend(); + } + + Vector3i vector = packet.getBlockPosition(); + + switch (packet.getAction()) { + case RESPAWN: + // Respawn process is finished and the server and client are both OK with respawning. + EntityEventPacket eventPacket = new EntityEventPacket(); + eventPacket.setRuntimeEntityId(entity.getGeyserId()); + eventPacket.setType(EntityEventType.RESPAWN); + eventPacket.setData(0); + session.sendUpstreamPacket(eventPacket); + // Resend attributes or else in rare cases the user can think they're not dead when they are, upon joining the server + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); + attributesPacket.setRuntimeEntityId(entity.getGeyserId()); + attributesPacket.getAttributes().addAll(entity.getAttributes().values()); + session.sendUpstreamPacket(attributesPacket); + break; + case START_SWIMMING: + ServerboundPlayerCommandPacket startSwimPacket = new ServerboundPlayerCommandPacket((int) entity.getEntityId(), PlayerState.START_SPRINTING); + session.sendDownstreamPacket(startSwimPacket); + + session.setSwimming(true); + break; + case STOP_SWIMMING: + ServerboundPlayerCommandPacket stopSwimPacket = new ServerboundPlayerCommandPacket((int) entity.getEntityId(), PlayerState.STOP_SPRINTING); + session.sendDownstreamPacket(stopSwimPacket); + + session.setSwimming(false); + break; + case START_GLIDE: + // Otherwise gliding will not work in creative + ServerboundPlayerAbilitiesPacket playerAbilitiesPacket = new ServerboundPlayerAbilitiesPacket(false); + session.sendDownstreamPacket(playerAbilitiesPacket); + case STOP_GLIDE: + ServerboundPlayerCommandPacket glidePacket = new ServerboundPlayerCommandPacket((int) entity.getEntityId(), PlayerState.START_ELYTRA_FLYING); + session.sendDownstreamPacket(glidePacket); + break; + case START_SNEAK: + ServerboundPlayerCommandPacket startSneakPacket = new ServerboundPlayerCommandPacket((int) entity.getEntityId(), PlayerState.START_SNEAKING); + session.sendDownstreamPacket(startSneakPacket); + + // Toggle the shield, if relevant + PlayerInventory playerInv = session.getPlayerInventory(); + ItemMapping shield = session.getItemMappings().getMapping("minecraft:shield"); + if ((playerInv.getItemInHand().getJavaId() == shield.getJavaId()) || + (playerInv.getOffhand().getJavaId() == shield.getJavaId())) { + ServerboundUseItemPacket useItemPacket; + if (playerInv.getItemInHand().getJavaId() == shield.getJavaId()) { + useItemPacket = new ServerboundUseItemPacket(Hand.MAIN_HAND); + } else { + // Else we just assume it's the offhand, to simplify logic and to assure the packet gets sent + useItemPacket = new ServerboundUseItemPacket(Hand.OFF_HAND); + } + session.sendDownstreamPacket(useItemPacket); + session.getPlayerEntity().setFlag(EntityFlag.BLOCKING, true); + // metadata will be updated when sneaking + } + + session.setSneaking(true); + break; + case STOP_SNEAK: + ServerboundPlayerCommandPacket stopSneakPacket = new ServerboundPlayerCommandPacket((int) entity.getEntityId(), PlayerState.STOP_SNEAKING); + session.sendDownstreamPacket(stopSneakPacket); + + // Stop shield, if necessary + if (session.getPlayerEntity().getFlag(EntityFlag.BLOCKING)) { + ServerboundPlayerActionPacket releaseItemPacket = new ServerboundPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, BlockUtils.POSITION_ZERO, Direction.DOWN); + session.sendDownstreamPacket(releaseItemPacket); + session.getPlayerEntity().setFlag(EntityFlag.BLOCKING, false); + // metadata will be updated when sneaking + } + + session.setSneaking(false); + break; + case START_SPRINT: + ServerboundPlayerCommandPacket startSprintPacket = new ServerboundPlayerCommandPacket((int) entity.getEntityId(), PlayerState.START_SPRINTING); + session.sendDownstreamPacket(startSprintPacket); + session.setSprinting(true); + break; + case STOP_SPRINT: + ServerboundPlayerCommandPacket stopSprintPacket = new ServerboundPlayerCommandPacket((int) entity.getEntityId(), PlayerState.STOP_SPRINTING); + session.sendDownstreamPacket(stopSprintPacket); + session.setSprinting(false); + break; + case DROP_ITEM: + Position position = new Position(vector.getX(), vector.getY(), vector.getZ()); + ServerboundPlayerActionPacket dropItemPacket = new ServerboundPlayerActionPacket(PlayerAction.DROP_ITEM, position, Direction.VALUES[packet.getFace()]); + session.sendDownstreamPacket(dropItemPacket); + break; + case STOP_SLEEP: + ServerboundPlayerCommandPacket stopSleepingPacket = new ServerboundPlayerCommandPacket((int) entity.getEntityId(), PlayerState.LEAVE_BED); + session.sendDownstreamPacket(stopSleepingPacket); + break; + case BLOCK_INTERACT: + // Client means to interact with a block; cancel bucket interaction, if any + if (session.getBucketScheduledFuture() != null) { + session.getBucketScheduledFuture().cancel(true); + session.setBucketScheduledFuture(null); + } + // Otherwise handled in BedrockInventoryTransactionTranslator + break; + case START_BREAK: + // Start the block breaking animation + if (session.getGameMode() != GameMode.CREATIVE) { + int blockState = session.getGeyser().getWorldManager().getBlockAt(session, vector); + LevelEventPacket startBreak = new LevelEventPacket(); + startBreak.setType(LevelEventType.BLOCK_START_BREAK); + startBreak.setPosition(vector.toFloat()); + double breakTime = BlockUtils.getSessionBreakTime(session, BlockRegistries.JAVA_BLOCKS.get(blockState)) * 20; + startBreak.setData((int) (65535 / breakTime)); + session.setBreakingBlock(blockState); + session.sendUpstreamPacket(startBreak); + } + + // Account for fire - the client likes to hit the block behind. + Vector3i fireBlockPos = BlockUtils.getBlockPosition(vector, packet.getFace()); + int blockUp = session.getGeyser().getWorldManager().getBlockAt(session, fireBlockPos); + String identifier = BlockRegistries.JAVA_IDENTIFIERS.get().get(blockUp); + if (identifier.startsWith("minecraft:fire") || identifier.startsWith("minecraft:soul_fire")) { + ServerboundPlayerActionPacket startBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.START_DIGGING, new Position(fireBlockPos.getX(), + fireBlockPos.getY(), fireBlockPos.getZ()), Direction.VALUES[packet.getFace()]); + session.sendDownstreamPacket(startBreakingPacket); + if (session.getGameMode() == GameMode.CREATIVE) { + break; + } + } + + position = new Position(vector.getX(), vector.getY(), vector.getZ()); + ServerboundPlayerActionPacket startBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.START_DIGGING, position, Direction.VALUES[packet.getFace()]); + session.sendDownstreamPacket(startBreakingPacket); + break; + case CONTINUE_BREAK: + if (session.getGameMode() == GameMode.CREATIVE) { + break; + } + Vector3f vectorFloat = vector.toFloat(); + LevelEventPacket continueBreakPacket = new LevelEventPacket(); + continueBreakPacket.setType(LevelEventType.PARTICLE_CRACK_BLOCK); + continueBreakPacket.setData((session.getBlockMappings().getBedrockBlockId(session.getBreakingBlock())) | (packet.getFace() << 24)); + continueBreakPacket.setPosition(vectorFloat); + session.sendUpstreamPacket(continueBreakPacket); + + // Update the break time in the event that player conditions changed (jumping, effects applied) + LevelEventPacket updateBreak = new LevelEventPacket(); + updateBreak.setType(LevelEventType.BLOCK_UPDATE_BREAK); + updateBreak.setPosition(vectorFloat); + double breakTime = BlockUtils.getSessionBreakTime(session, BlockRegistries.JAVA_BLOCKS.get(session.getBreakingBlock())) * 20; + updateBreak.setData((int) (65535 / breakTime)); + session.sendUpstreamPacket(updateBreak); + break; + case ABORT_BREAK: + if (session.getGameMode() != GameMode.CREATIVE) { + // As of 1.16.210: item frame items are taken out here. + // Survival also sends START_BREAK, but by attaching our process here adventure mode also works + Entity itemFrameEntity = ItemFrameEntity.getItemFrameEntity(session, vector); + if (itemFrameEntity != null) { + ServerboundInteractPacket interactPacket = new ServerboundInteractPacket((int) itemFrameEntity.getEntityId(), + InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking()); + session.sendDownstreamPacket(interactPacket); + break; + } + } + + position = new Position(vector.getX(), vector.getY(), vector.getZ()); + ServerboundPlayerActionPacket abortBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.CANCEL_DIGGING, position, Direction.DOWN); + session.sendDownstreamPacket(abortBreakingPacket); + LevelEventPacket stopBreak = new LevelEventPacket(); + stopBreak.setType(LevelEventType.BLOCK_STOP_BREAK); + stopBreak.setPosition(vector.toFloat()); + stopBreak.setData(0); + session.setBreakingBlock(BlockStateValues.JAVA_AIR_ID); + session.sendUpstreamPacket(stopBreak); + break; + case STOP_BREAK: + // Handled in BedrockInventoryTransactionTranslator + break; + case DIMENSION_CHANGE_SUCCESS: + //sometimes the client doesn't feel like loading + PlayStatusPacket spawnPacket = new PlayStatusPacket(); + spawnPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN); + session.sendUpstreamPacket(spawnPacket); + + attributesPacket = new UpdateAttributesPacket(); + attributesPacket.setRuntimeEntityId(entity.getGeyserId()); + attributesPacket.getAttributes().addAll(entity.getAttributes().values()); + session.sendUpstreamPacket(attributesPacket); + + session.getEntityCache().updateBossBars(); + break; + case JUMP: + entity.setOnGround(false); // Increase block break time while jumping + break; + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockEmoteTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockEmoteTranslator.java new file mode 100644 index 000000000..a4fe12d4e --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockEmoteTranslator.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.bedrock.entity.player; + +import com.github.steveice10.mc.protocol.data.game.entity.object.Direction; +import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket; +import com.nukkitx.protocol.bedrock.packet.EmotePacket; +import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.util.BlockUtils; + +@Translator(packet = EmotePacket.class) +public class BedrockEmoteTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, EmotePacket packet) { + if (session.getGeyser().getConfig().getEmoteOffhandWorkaround() != EmoteOffhandWorkaroundOption.DISABLED) { + // Activate the workaround - we should trigger the offhand now + ServerboundPlayerActionPacket swapHandsPacket = new ServerboundPlayerActionPacket(PlayerAction.SWAP_HANDS, BlockUtils.POSITION_ZERO, + Direction.DOWN); + session.sendDownstreamPacket(swapHandsPacket); + + if (session.getGeyser().getConfig().getEmoteOffhandWorkaround() == EmoteOffhandWorkaroundOption.NO_EMOTES) { + return; + } + } + + long javaId = session.getPlayerEntity().getEntityId(); + for (GeyserSession otherSession : session.getGeyser().getSessionManager().getSessions().values()) { + if (otherSession != session) { + if (otherSession.isClosed()) continue; + if (otherSession.getEventLoop().inEventLoop()) { + playEmote(otherSession, javaId, packet.getEmoteId()); + } else { + session.executeInEventLoop(() -> playEmote(otherSession, javaId, packet.getEmoteId())); + } + } + } + } + + private void playEmote(GeyserSession otherSession, long javaId, String emoteId) { + Entity otherEntity = otherSession.getEntityCache().getEntityByJavaId(javaId); // Must be ran on same thread + if (otherEntity == null) return; + EmotePacket otherEmotePacket = new EmotePacket(); + otherEmotePacket.setEmoteId(emoteId); + otherEmotePacket.setRuntimeEntityId(otherEntity.getGeyserId()); + otherSession.sendUpstreamPacket(otherEmotePacket); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockInteractTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockInteractTranslator.java new file mode 100644 index 000000000..26a25725e --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockInteractTranslator.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.bedrock.entity.player; + +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; +import com.github.steveice10.mc.protocol.data.game.entity.player.InteractAction; +import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerState; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerCommandPacket; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; +import com.nukkitx.protocol.bedrock.packet.InteractPacket; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.type.living.animal.horse.AbstractHorseEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.entity.InteractiveTagManager; + +@Translator(packet = InteractPacket.class) +public class BedrockInteractTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, InteractPacket packet) { + Entity entity; + if (packet.getRuntimeEntityId() == session.getPlayerEntity().getGeyserId()) { + //Player is not in entity cache + entity = session.getPlayerEntity(); + } else { + entity = session.getEntityCache().getEntityByGeyserId(packet.getRuntimeEntityId()); + } + if (entity == null) + return; + + switch (packet.getAction()) { + case INTERACT: + if (session.getPlayerInventory().getItemInHand().getJavaId() == session.getItemMappings().getStoredItems().shield().getJavaId()) { + break; + } + ServerboundInteractPacket interactPacket = new ServerboundInteractPacket((int) entity.getEntityId(), + InteractAction.INTERACT, Hand.MAIN_HAND, session.isSneaking()); + session.sendDownstreamPacket(interactPacket); + break; + case DAMAGE: + ServerboundInteractPacket attackPacket = new ServerboundInteractPacket((int) entity.getEntityId(), + InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking()); + session.sendDownstreamPacket(attackPacket); + break; + case LEAVE_VEHICLE: + ServerboundPlayerCommandPacket sneakPacket = new ServerboundPlayerCommandPacket((int) entity.getEntityId(), PlayerState.START_SNEAKING); + session.sendDownstreamPacket(sneakPacket); + session.setRidingVehicleEntity(null); + break; + case MOUSEOVER: + // Handle the buttons for mobile - "Mount", etc; and the suggestions for console - "ZL: Mount", etc + if (packet.getRuntimeEntityId() != 0) { + Entity interactEntity = session.getEntityCache().getEntityByGeyserId(packet.getRuntimeEntityId()); + session.setMouseoverEntity(interactEntity); + if (interactEntity == null) { + return; + } + + InteractiveTagManager.updateTag(session, interactEntity); + } else { + if (session.getMouseoverEntity() != null) { + // No interactive tag should be sent + session.setMouseoverEntity(null); + session.getPlayerEntity().getDirtyMetadata().put(EntityData.INTERACTIVE_TAG, ""); + session.getPlayerEntity().updateBedrockMetadata(); + } + } + break; + case OPEN_INVENTORY: + if (session.getOpenInventory() == null) { + Entity ridingEntity = session.getRidingVehicleEntity(); + if (ridingEntity instanceof AbstractHorseEntity) { + if (ridingEntity.getFlag(EntityFlag.TAMED)) { + // We should request to open the horse inventory instead + ServerboundPlayerCommandPacket openHorseWindowPacket = new ServerboundPlayerCommandPacket((int) session.getPlayerEntity().getEntityId(), PlayerState.OPEN_HORSE_INVENTORY); + session.sendDownstreamPacket(openHorseWindowPacket); + } + } else { + session.setOpenInventory(session.getPlayerInventory()); + + ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket(); + containerOpenPacket.setId((byte) 0); + containerOpenPacket.setType(ContainerType.INVENTORY); + containerOpenPacket.setUniqueEntityId(-1); + containerOpenPacket.setBlockPosition(entity.getPosition().toInt()); + session.sendUpstreamPacket(containerOpenPacket); + } + } + break; + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java new file mode 100644 index 000000000..1555722d6 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.bedrock.entity.player; + +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosRotPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerRotPacket; +import com.github.steveice10.packetlib.packet.Packet; +import com.nukkitx.math.vector.Vector3d; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; +import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; +import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.ChatColor; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; + +@Translator(packet = MovePlayerPacket.class) +public class BedrockMovePlayerTranslator extends PacketTranslator { + /* The upper and lower bounds to check for the void floor that only exists in Bedrock. These are the constants for the overworld. */ + private static final int BEDROCK_OVERWORLD_VOID_FLOOR_UPPER_Y = -104; + private static final int BEDROCK_OVERWORLD_VOID_FLOOR_LOWER_Y = BEDROCK_OVERWORLD_VOID_FLOOR_UPPER_Y + 2; + + @Override + public void translate(GeyserSession session, MovePlayerPacket packet) { + SessionPlayerEntity entity = session.getPlayerEntity(); + if (!session.isSpawned()) return; + + if (!session.getUpstream().isInitialized()) { + MoveEntityAbsolutePacket moveEntityBack = new MoveEntityAbsolutePacket(); + moveEntityBack.setRuntimeEntityId(entity.getGeyserId()); + moveEntityBack.setPosition(entity.getPosition()); + moveEntityBack.setRotation(entity.getBedrockRotation()); + moveEntityBack.setTeleported(true); + moveEntityBack.setOnGround(true); + session.sendUpstreamPacketImmediately(moveEntityBack); + return; + } + + session.setLastMovementTimestamp(System.currentTimeMillis()); + + // Send book update before the player moves + session.getBookEditCache().checkForSend(); + + if (!session.getTeleportMap().isEmpty()) { + session.confirmTeleport(packet.getPosition().toDouble().sub(0, EntityDefinitions.PLAYER.offset(), 0)); + return; + } + float yaw = packet.getRotation().getY(); + float pitch = packet.getRotation().getX(); + float headYaw = packet.getRotation().getY(); + + boolean positionChanged = !entity.getPosition().equals(packet.getPosition()); + boolean rotationChanged = entity.getYaw() != yaw || entity.getPitch() != pitch || entity.getHeadYaw() != headYaw; + + // If only the pitch and yaw changed + // This isn't needed, but it makes the packets closer to vanilla + // It also means you can't "lag back" while only looking, in theory + if (!positionChanged && rotationChanged) { + ServerboundMovePlayerRotPacket playerRotationPacket = new ServerboundMovePlayerRotPacket( + packet.isOnGround(), packet.getRotation().getY(), packet.getRotation().getX()); + + entity.setYaw(yaw); + entity.setPitch(pitch); + entity.setHeadYaw(headYaw); + entity.setOnGround(packet.isOnGround()); + + session.sendDownstreamPacket(playerRotationPacket); + } else { + if (session.getWorldBorder().isPassingIntoBorderBoundaries(packet.getPosition(), true)) { + return; + } + + if (isValidMove(session, entity.getPosition(), packet.getPosition())) { + Vector3d position = session.getCollisionManager().adjustBedrockPosition(packet.getPosition(), packet.isOnGround(), packet.getMode() == MovePlayerPacket.Mode.TELEPORT); + if (position != null) { // A null return value cancels the packet + Packet movePacket; + if (rotationChanged) { + // Send rotation updates as well + movePacket = new ServerboundMovePlayerPosRotPacket(packet.isOnGround(), position.getX(), position.getY(), position.getZ(), + packet.getRotation().getY(), packet.getRotation().getX()); + entity.setYaw(yaw); + entity.setPitch(pitch); + entity.setHeadYaw(headYaw); + } else { + // Rotation did not change; don't send an update with rotation + movePacket = new ServerboundMovePlayerPosPacket(packet.isOnGround(), position.getX(), position.getY(), position.getZ()); + } + + // Compare positions here for void floor fix below before the player's position variable is set to the packet position + boolean notMovingUp = entity.getPosition().getY() >= packet.getPosition().getY(); + + entity.setPositionManual(packet.getPosition()); + entity.setOnGround(packet.isOnGround()); + + // Send final movement changes + session.sendDownstreamPacket(movePacket); + + if (notMovingUp) { + int floorY = position.getFloorY(); + // If the client believes the world has extended height, then it also believes the void floor + // still exists, just at a lower spot + boolean extendedWorld = session.getChunkCache().isExtendedHeight(); + if (floorY <= (extendedWorld ? BEDROCK_OVERWORLD_VOID_FLOOR_LOWER_Y : -38) + && floorY >= (extendedWorld ? BEDROCK_OVERWORLD_VOID_FLOOR_UPPER_Y : -40)) { + // Work around there being a floor at the bottom of the world and teleport the player below it + // Moving from below to above the void floor works fine + entity.setPosition(entity.getPosition().sub(0, 4f, 0)); + MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); + movePlayerPacket.setRuntimeEntityId(entity.getGeyserId()); + movePlayerPacket.setPosition(entity.getPosition()); + movePlayerPacket.setRotation(entity.getBedrockRotation()); + movePlayerPacket.setMode(MovePlayerPacket.Mode.TELEPORT); + movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR); + session.sendUpstreamPacket(movePlayerPacket); + } + } + } + } else { + // Not a valid move + session.getGeyser().getLogger().debug("Recalculating position..."); + session.getCollisionManager().recalculatePosition(); + } + } + + // Move parrots to match if applicable + if (entity.getLeftParrot() != null) { + entity.getLeftParrot().moveAbsolute(entity.getPosition(), entity.getYaw(), entity.getPitch(), entity.getHeadYaw(), true, false); + } + if (entity.getRightParrot() != null) { + entity.getRightParrot().moveAbsolute(entity.getPosition(), entity.getYaw(), entity.getPitch(), entity.getHeadYaw(), true, false); + } + } + + private boolean isInvalidNumber(float val) { + return Float.isNaN(val) || Float.isInfinite(val); + } + + private boolean isValidMove(GeyserSession session, Vector3f currentPosition, Vector3f newPosition) { + if (isInvalidNumber(newPosition.getX()) || isInvalidNumber(newPosition.getY()) || isInvalidNumber(newPosition.getZ())) { + return false; + } + if (currentPosition.distanceSquared(newPosition) > 300) { + session.getGeyser().getLogger().debug(ChatColor.RED + session.name() + " moved too quickly." + + " current position: " + currentPosition + ", new position: " + newPosition); + + return false; + } + + return true; + } +} + diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockRiderJumpTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockRiderJumpTranslator.java new file mode 100644 index 000000000..79911b5e7 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockRiderJumpTranslator.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.bedrock.entity.player; + +import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerState; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerCommandPacket; +import com.nukkitx.protocol.bedrock.packet.RiderJumpPacket; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.type.living.animal.horse.AbstractHorseEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; + +@Translator(packet = RiderJumpPacket.class) +public class BedrockRiderJumpTranslator extends PacketTranslator { + @Override + public void translate(GeyserSession session, RiderJumpPacket packet) { + Entity vehicle = session.getRidingVehicleEntity(); + if (vehicle instanceof AbstractHorseEntity) { + ServerboundPlayerCommandPacket playerCommandPacket = new ServerboundPlayerCommandPacket((int) vehicle.getEntityId(), PlayerState.START_HORSE_JUMP, packet.getJumpStrength()); + session.sendDownstreamPacket(playerCommandPacket); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockSetPlayerGameTypeTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetPlayerGameTypeTranslator.java similarity index 82% rename from connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockSetPlayerGameTypeTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetPlayerGameTypeTranslator.java index d61b37863..b149f8836 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockSetPlayerGameTypeTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetPlayerGameTypeTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,12 +23,12 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.bedrock.entity.player; +package org.geysermc.geyser.translator.protocol.bedrock.entity.player; import com.nukkitx.protocol.bedrock.packet.SetPlayerGameTypePacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; /** * In vanilla Bedrock, if you have operator status, this sets the player's gamemode without confirmation from the server. @@ -38,7 +38,7 @@ import org.geysermc.connector.network.translators.Translator; public class BedrockSetPlayerGameTypeTranslator extends PacketTranslator { @Override - public void translate(SetPlayerGameTypePacket packet, GeyserSession session) { + public void translate(GeyserSession session, SetPlayerGameTypePacket packet) { // no SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket(); playerGameTypePacket.setGamemode(session.getGameMode().ordinal()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/world/BedrockLevelSoundEventTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/world/BedrockLevelSoundEventTranslator.java similarity index 81% rename from connector/src/main/java/org/geysermc/connector/network/translators/bedrock/world/BedrockLevelSoundEventTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/world/BedrockLevelSoundEventTranslator.java index 44553e82e..2ac8587bf 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/world/BedrockLevelSoundEventTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/world/BedrockLevelSoundEventTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,20 +23,20 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.bedrock.world; +package org.geysermc.geyser.translator.protocol.bedrock.world; import com.nukkitx.protocol.bedrock.data.SoundEvent; import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.CooldownUtils; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.util.CooldownUtils; @Translator(packet = LevelSoundEventPacket.class) public class BedrockLevelSoundEventTranslator extends PacketTranslator { @Override - public void translate(LevelSoundEventPacket packet, GeyserSession session) { + public void translate(GeyserSession session, LevelSoundEventPacket packet) { // lol what even :thinking: session.sendUpstreamPacket(packet); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaAwardStatsTranslator.java similarity index 63% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaAwardStatsTranslator.java index 9a80254b0..8f4e93635 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaAwardStatsTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,24 +23,24 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java; +package org.geysermc.geyser.translator.protocol.java; -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerStatisticsPacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.StatisticsUtils; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundAwardStatsPacket; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.util.StatisticsUtils; -@Translator(packet = ServerStatisticsPacket.class) -public class JavaStatisticsTranslator extends PacketTranslator { +@Translator(packet = ClientboundAwardStatsPacket.class) +public class JavaAwardStatsTranslator extends PacketTranslator { @Override - public void translate(ServerStatisticsPacket packet, GeyserSession session) { + public void translate(GeyserSession session, ClientboundAwardStatsPacket packet) { session.updateStatistics(packet.getStatistics()); if (session.isWaitingForStatistics()) { session.setWaitingForStatistics(false); - session.sendForm(StatisticsUtils.buildMenuForm(session), StatisticsUtils.STATISTICS_MENU_FORM_ID); + StatisticsUtils.buildAndSendStatisticsMenu(session); } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaBossBarTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaBossEventTranslator.java similarity index 70% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/JavaBossBarTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaBossEventTranslator.java index 3da76a228..c9512b87b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaBossBarTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaBossEventTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,24 +23,25 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java; +package org.geysermc.geyser.translator.protocol.java; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.session.cache.BossBar; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.BossBar; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerBossBarPacket; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundBossEventPacket; + +@Translator(packet = ClientboundBossEventPacket.class) +public class JavaBossEventTranslator extends PacketTranslator { -@Translator(packet = ServerBossBarPacket.class) -public class JavaBossBarTranslator extends PacketTranslator { @Override - public void translate(ServerBossBarPacket packet, GeyserSession session) { + public void translate(GeyserSession session, ClientboundBossEventPacket packet) { BossBar bossBar = session.getEntityCache().getBossBar(packet.getUuid()); switch (packet.getAction()) { case ADD: long entityId = session.getEntityCache().getNextEntityId().incrementAndGet(); - bossBar = new BossBar(session, entityId, packet.getTitle(), packet.getHealth(), 0, 1, 0); + bossBar = new BossBar(session, entityId, packet.getTitle(), packet.getHealth(), packet.getColor().ordinal(), 1, 0); session.getEntityCache().addBossBar(packet.getUuid(), bossBar); break; case UPDATE_TITLE: @@ -53,9 +54,10 @@ public class JavaBossBarTranslator extends PacketTranslator session.getEntityCache().removeBossBar(packet.getUuid()); break; case UPDATE_STYLE: + if (bossBar != null) bossBar.updateColor(packet.getColor().ordinal()); + break; case UPDATE_FLAGS: //todo - return; } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDifficultyTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaChangeDifficultyTranslator.java similarity index 70% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDifficultyTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaChangeDifficultyTranslator.java index 601b0fc48..e0c3a9b95 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDifficultyTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaChangeDifficultyTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,20 +23,19 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java; +package org.geysermc.geyser.translator.protocol.java; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerDifficultyPacket; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundChangeDifficultyPacket; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; import com.nukkitx.protocol.bedrock.packet.SetDifficultyPacket; -@Translator(packet = ServerDifficultyPacket.class) -public class JavaDifficultyTranslator extends PacketTranslator { +@Translator(packet = ClientboundChangeDifficultyPacket.class) +public class JavaChangeDifficultyTranslator extends PacketTranslator { @Override - public void translate(ServerDifficultyPacket packet, GeyserSession session) { + public void translate(GeyserSession session, ClientboundChangeDifficultyPacket packet) { SetDifficultyPacket setDifficultyPacket = new SetDifficultyPacket(); setDifficultyPacket.setDifficulty(packet.getDifficulty().ordinal()); session.sendUpstreamPacket(setDifficultyPacket); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaChatTranslator.java similarity index 55% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaChatTranslator.java index f5128ed6f..1c771ff52 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaChatTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,41 +23,33 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java; +package org.geysermc.geyser.translator.protocol.java; -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerChatPacket; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundChatPacket; import com.nukkitx.protocol.bedrock.packet.TextPacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.chat.MessageTranslator; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.translator.text.MessageTranslator; -@Translator(packet = ServerChatPacket.class) -public class JavaChatTranslator extends PacketTranslator { +@Translator(packet = ClientboundChatPacket.class) +public class JavaChatTranslator extends PacketTranslator { @Override - public void translate(ServerChatPacket packet, GeyserSession session) { + public void translate(GeyserSession session, ClientboundChatPacket packet) { TextPacket textPacket = new TextPacket(); textPacket.setPlatformChatId(""); textPacket.setSourceName(""); - textPacket.setXuid(session.getAuthData().getXboxUUID()); - switch (packet.getType()) { - case CHAT: - textPacket.setType(TextPacket.Type.CHAT); - break; - case SYSTEM: - textPacket.setType(TextPacket.Type.SYSTEM); - break; - case NOTIFICATION: - textPacket.setType(TextPacket.Type.TIP); - break; - default: - textPacket.setType(TextPacket.Type.RAW); - break; - } + textPacket.setXuid(session.getAuthData().xuid()); + textPacket.setType(switch (packet.getType()) { + case CHAT -> TextPacket.Type.CHAT; + case SYSTEM -> TextPacket.Type.SYSTEM; + case NOTIFICATION -> TextPacket.Type.TIP; + default -> TextPacket.Type.RAW; + }); textPacket.setNeedsTranslation(false); - textPacket.setMessage(MessageTranslator.convertMessage(packet.getMessage().toString(), session.getLocale())); + textPacket.setMessage(MessageTranslator.convertMessage(packet.getMessage(), session.getLocale())); session.sendUpstreamPacket(textPacket); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java new file mode 100644 index 000000000..5e918d5d3 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java @@ -0,0 +1,414 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java; + +import com.github.steveice10.mc.protocol.data.game.command.CommandNode; +import com.github.steveice10.mc.protocol.data.game.command.CommandParser; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundCommandsPacket; +import com.nukkitx.protocol.bedrock.data.command.CommandData; +import com.nukkitx.protocol.bedrock.data.command.CommandEnumData; +import com.nukkitx.protocol.bedrock.data.command.CommandParam; +import com.nukkitx.protocol.bedrock.data.command.CommandParamData; +import com.nukkitx.protocol.bedrock.packet.AvailableCommandsPacket; +import it.unimi.dsi.fastutil.Hash; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap; +import lombok.Getter; +import lombok.ToString; +import net.kyori.adventure.text.format.NamedTextColor; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandManager; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.inventory.item.Enchantment; +import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.util.EntityUtils; + +import java.util.*; + +@Translator(packet = ClientboundCommandsPacket.class) +public class JavaCommandsTranslator extends PacketTranslator { + + private static final String[] ALL_EFFECT_IDENTIFIERS = EntityUtils.getAllEffectIdentifiers(); + private static final String[] ENUM_BOOLEAN = {"true", "false"}; + private static final String[] VALID_COLORS; + private static final String[] VALID_SCOREBOARD_SLOTS; + + private static final Hash.Strategy PARAM_STRATEGY = new Hash.Strategy<>() { + @Override + public int hashCode(BedrockCommandInfo o) { + int paramHash = Arrays.deepHashCode(o.paramData()); + return 31 * paramHash + o.description().hashCode(); + } + + @Override + public boolean equals(BedrockCommandInfo a, BedrockCommandInfo b) { + if (a == b) return true; + if (a == null || b == null) return false; + if (!a.description().equals(b.description())) return false; + if (a.paramData().length != b.paramData().length) return false; + for (int i = 0; i < a.paramData().length; i++) { + CommandParamData[] a1 = a.paramData()[i]; + CommandParamData[] b1 = b.paramData()[i]; + if (a1.length != b1.length) return false; + + for (int j = 0; j < a1.length; j++) { + if (!a1[j].equals(b1[j])) return false; + } + } + return true; + } + }; + + static { + List validColors = new ArrayList<>(NamedTextColor.NAMES.keys()); + validColors.add("reset"); + VALID_COLORS = validColors.toArray(new String[0]); + + List teamOptions = new ArrayList<>(Arrays.asList("list", "sidebar", "belowName")); + for (String color : NamedTextColor.NAMES.keys()) { + teamOptions.add("sidebar.team." + color); + } + VALID_SCOREBOARD_SLOTS = teamOptions.toArray(new String[0]); + } + + @Override + public void translate(GeyserSession session, ClientboundCommandsPacket packet) { + // Don't send command suggestions if they are disabled + if (!session.getGeyser().getConfig().isCommandSuggestions()) { + session.getGeyser().getLogger().debug("Not sending translated command suggestions as they are disabled."); + + // Send an empty packet so Bedrock doesn't override /help with its own, built-in help command. + AvailableCommandsPacket emptyPacket = new AvailableCommandsPacket(); + session.sendUpstreamPacket(emptyPacket); + return; + } + + CommandManager manager = session.getGeyser().getCommandManager(); + CommandNode[] nodes = packet.getNodes(); + List commandData = new ArrayList<>(); + IntSet commandNodes = new IntOpenHashSet(); + Set knownAliases = new HashSet<>(); + Map> commands = new Object2ObjectOpenCustomHashMap<>(PARAM_STRATEGY); + Int2ObjectMap> commandArgs = new Int2ObjectOpenHashMap<>(); + + // Get the first node, it should be a root node + CommandNode rootNode = nodes[packet.getFirstNodeIndex()]; + + // Loop through the root nodes to get all commands + for (int nodeIndex : rootNode.getChildIndices()) { + CommandNode node = nodes[nodeIndex]; + + // Make sure we don't have duplicated commands (happens if there is more than 1 root node) + if (!commandNodes.add(nodeIndex) || !knownAliases.add(node.getName().toLowerCase())) continue; + + // Get and update the commandArgs list with the found arguments + if (node.getChildIndices().length >= 1) { + for (int childIndex : node.getChildIndices()) { + commandArgs.computeIfAbsent(nodeIndex, ArrayList::new).add(nodes[childIndex]); + } + } + + // Get and parse all params + CommandParamData[][] params = getParams(session, nodes[nodeIndex], nodes); + + // Insert the alias name into the command list + commands.computeIfAbsent(new BedrockCommandInfo(manager.getDescription(node.getName().toLowerCase(Locale.ROOT)), params), + index -> new HashSet<>()).add(node.getName().toLowerCase()); + } + + // The command flags, not sure what these do apart from break things + List flags = Collections.emptyList(); + + // Loop through all the found commands + + for (Map.Entry> entry : commands.entrySet()) { + String commandName = entry.getValue().iterator().next(); // We know this has a value + + // Create a basic alias + CommandEnumData aliases = new CommandEnumData(commandName + "Aliases", entry.getValue().toArray(new String[0]), false); + + // Build the completed command and add it to the final list + CommandData data = new CommandData(commandName, entry.getKey().description(), flags, (byte) 0, aliases, entry.getKey().paramData()); + commandData.add(data); + } + + // Add our commands to the AvailableCommandsPacket for the bedrock client + AvailableCommandsPacket availableCommandsPacket = new AvailableCommandsPacket(); + availableCommandsPacket.getCommands().addAll(commandData); + + session.getGeyser().getLogger().debug("Sending command packet of " + commandData.size() + " commands"); + + // Finally, send the commands to the client + session.sendUpstreamPacket(availableCommandsPacket); + } + + /** + * Build the command parameter array for the given command + * + * @param session the session + * @param commandNode The command to build the parameters for + * @param allNodes Every command node + * @return An array of parameter option arrays + */ + private static CommandParamData[][] getParams(GeyserSession session, CommandNode commandNode, CommandNode[] allNodes) { + // Check if the command is an alias and redirect it + if (commandNode.getRedirectIndex() != -1) { + GeyserImpl.getInstance().getLogger().debug("Redirecting command " + commandNode.getName() + " to " + allNodes[commandNode.getRedirectIndex()].getName()); + commandNode = allNodes[commandNode.getRedirectIndex()]; + } + + if (commandNode.getChildIndices().length >= 1) { + // Create the root param node and build all the children + ParamInfo rootParam = new ParamInfo(commandNode, null); + rootParam.buildChildren(session, allNodes); + + List treeData = rootParam.getTree(); + + return treeData.toArray(new CommandParamData[0][]); + } + + return new CommandParamData[0][0]; + } + + /** + * Convert Java edition command types to Bedrock edition + * + * @param session the session + * @param parser Command type to convert + * @return Bedrock parameter data type + */ + private static Object mapCommandType(GeyserSession session, CommandParser parser) { + if (parser == null) { + return CommandParam.STRING; + } + + return switch (parser) { + case FLOAT, ROTATION, DOUBLE -> CommandParam.FLOAT; + case INTEGER, LONG -> CommandParam.INT; + case ENTITY, GAME_PROFILE -> CommandParam.TARGET; + case BLOCK_POS -> CommandParam.BLOCK_POSITION; + case COLUMN_POS, VEC3 -> CommandParam.POSITION; + case MESSAGE -> CommandParam.MESSAGE; + case NBT, NBT_COMPOUND_TAG, NBT_TAG, NBT_PATH -> CommandParam.JSON; + case RESOURCE_LOCATION, FUNCTION -> CommandParam.FILE_PATH; + case BOOL -> ENUM_BOOLEAN; + case OPERATION -> CommandParam.OPERATOR; // ">=", "==", etc + case BLOCK_STATE -> BlockRegistries.JAVA_TO_BEDROCK_IDENTIFIERS.get().keySet().toArray(new String[0]); + case ITEM_STACK -> session.getItemMappings().getItemNames(); + case ITEM_ENCHANTMENT -> Enchantment.JavaEnchantment.ALL_JAVA_IDENTIFIERS; + case ENTITY_SUMMON -> Registries.JAVA_ENTITY_IDENTIFIERS.get().keySet().toArray(new String[0]); + case COLOR -> VALID_COLORS; + case SCOREBOARD_SLOT -> VALID_SCOREBOARD_SLOTS; + case MOB_EFFECT -> ALL_EFFECT_IDENTIFIERS; + default -> CommandParam.STRING; + }; + } + + /** + * Stores the command description and parameter data for best optimizing the Bedrock commands packet. + */ + private static record BedrockCommandInfo(String description, CommandParamData[][] paramData) { + } + + @Getter + @ToString + private static class ParamInfo { + private final CommandNode paramNode; + private final CommandParamData paramData; + private final List children; + + /** + * Create a new parameter info object + * + * @param paramNode CommandNode the parameter is for + * @param paramData The existing parameters for the command + */ + public ParamInfo(CommandNode paramNode, CommandParamData paramData) { + this.paramNode = paramNode; + this.paramData = paramData; + this.children = new ArrayList<>(); + } + + /** + * Build the array of all the child parameters (recursive) + * + * @param session the session + * @param allNodes Every command node + */ + public void buildChildren(GeyserSession session, CommandNode[] allNodes) { + for (int paramID : paramNode.getChildIndices()) { + CommandNode paramNode = allNodes[paramID]; + + if (paramNode == this.paramNode) { + // Fixes a StackOverflowError when an argument has itself as a child + continue; + } + + if (paramNode.getParser() == null) { + boolean foundCompatible = false; + for (int i = 0; i < children.size(); i++) { + ParamInfo enumParamInfo = children.get(i); + // Check to make sure all descending nodes of this command are compatible - otherwise, create a new overload + if (isCompatible(allNodes, enumParamInfo.getParamNode(), paramNode)) { + foundCompatible = true; + // Extend the current list of enum values + String[] enumOptions = Arrays.copyOf(enumParamInfo.getParamData().getEnumData().getValues(), enumParamInfo.getParamData().getEnumData().getValues().length + 1); + enumOptions[enumOptions.length - 1] = paramNode.getName(); + + // Re-create the command using the updated values + CommandEnumData enumData = new CommandEnumData(enumParamInfo.getParamData().getEnumData().getName(), enumOptions, false); + children.set(i, new ParamInfo(enumParamInfo.getParamNode(), new CommandParamData(enumParamInfo.getParamData().getName(), this.paramNode.isExecutable(), enumData, null, null, Collections.emptyList()))); + break; + } + } + + if (!foundCompatible) { + // Create a new subcommand with this exact type + CommandEnumData enumData = new CommandEnumData(paramNode.getName(), new String[]{paramNode.getName()}, false); + + // On setting optional: + // isExecutable is defined as a node "constitutes a valid command." + // Therefore, any children of the parameter must simply be optional. + children.add(new ParamInfo(paramNode, new CommandParamData(paramNode.getName(), this.paramNode.isExecutable(), enumData, null, null, Collections.emptyList()))); + } + } else { + // Put the non-enum param into the list + Object mappedType = mapCommandType(session, paramNode.getParser()); + CommandEnumData enumData = null; + CommandParam type = null; + if (mappedType instanceof String[]) { + enumData = new CommandEnumData(paramNode.getParser().name().toLowerCase(), (String[]) mappedType, false); + } else { + type = (CommandParam) mappedType; + } + // IF enumData != null: + // In game, this will show up like + // So if paramNode.getName() == "value" and enumData.getName() == "bool": + children.add(new ParamInfo(paramNode, new CommandParamData(paramNode.getName(), this.paramNode.isExecutable(), enumData, type, null, Collections.emptyList()))); + } + } + + // Recursively build all child options + for (ParamInfo child : children) { + child.buildChildren(session, allNodes); + } + } + + /** + * Comparing CommandNode type a and b, determine if they are in the same overload. + *

+ * Take the gamerule command, and let's present three "subcommands" you can perform: + * + *

    + *
  • gamerule doDaylightCycle true
  • + *
  • gamerule announceAdvancements false
  • + *
  • gamerule randomTickSpeed 3
  • + *
+ * + * While all three of them are indeed part of the same command, the command setting randomTickSpeed parses an int, + * while the others use boolean. In Bedrock, this should be presented as a separate overload to indicate that this + * does something a little different. + *

+ * Therefore, this function will return true if the first two are compared, as they use the same + * parsers. If the third is compared with either of the others, this function will return false. + *

+ * Here's an example of how the above would be presented to Bedrock (as of 1.16.200). Notice how the top two CommandParamData + * classes of each array are identical in type, but the following class is different: + *

+         *     overloads=[
+         *         [
+         *            CommandParamData(name=doDaylightCycle, optional=false, enumData=CommandEnumData(name=announceAdvancements, values=[announceAdvancements, doDaylightCycle], isSoft=false), type=STRING, postfix=null, options=[])
+         *            CommandParamData(name=value, optional=false, enumData=CommandEnumData(name=value, values=[true, false], isSoft=false), type=null, postfix=null, options=[])
+         *         ]
+         *         [
+         *            CommandParamData(name=randomTickSpeed, optional=false, enumData=CommandEnumData(name=randomTickSpeed, values=[randomTickSpeed], isSoft=false), type=STRING, postfix=null, options=[])
+         *            CommandParamData(name=value, optional=false, enumData=null, type=INT, postfix=null, options=[])
+         *         ]
+         *     ]
+         * 
+ * + * @return if these two can be merged into one overload. + */ + private boolean isCompatible(CommandNode[] allNodes, CommandNode a, CommandNode b) { + if (a == b) return true; + if (a.getParser() != b.getParser()) return false; + if (a.getChildIndices().length != b.getChildIndices().length) return false; + + for (int i = 0; i < a.getChildIndices().length; i++) { + boolean hasSimilarity = false; + CommandNode a1 = allNodes[a.getChildIndices()[i]]; + // Search "b" until we find a child that matches this one + for (int j = 0; j < b.getChildIndices().length; j++) { + if (isCompatible(allNodes, a1, allNodes[b.getChildIndices()[j]])) { + hasSimilarity = true; + break; + } + } + + if (!hasSimilarity) { + return false; + } + } + return true; + } + + /** + * Get the tree of every parameter node (recursive) + * + * @return List of parameter options arrays for the command + */ + public List getTree() { + List treeParamData = new ArrayList<>(); + + for (ParamInfo child : children) { + // Get the tree from the child + List childTree = child.getTree(); + + // Un-pack the tree append the child node to it and push into the list + for (CommandParamData[] subChild : childTree) { + CommandParamData[] tmpTree = new CommandParamData[subChild.length + 1]; + tmpTree[0] = child.getParamData(); + System.arraycopy(subChild, 0, tmpTree, 1, subChild.length); + + treeParamData.add(tmpTree); + } + + // If we have no more child parameters just the child + if (childTree.size() == 0) { + treeParamData.add(new CommandParamData[] { child.getParamData() }); + } + } + + return treeParamData; + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java new file mode 100644 index 000000000..c0b56fb63 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java; + +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundCustomPayloadPacket; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundCustomPayloadPacket; +import com.google.common.base.Charsets; +import com.nukkitx.protocol.bedrock.packet.TransferPacket; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.GeyserLogger; +import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.cumulus.Form; +import org.geysermc.cumulus.Forms; +import org.geysermc.cumulus.util.FormType; + +import java.nio.charset.StandardCharsets; + +@Translator(packet = ClientboundCustomPayloadPacket.class) +public class JavaCustomPayloadTranslator extends PacketTranslator { + private final GeyserLogger logger = GeyserImpl.getInstance().getLogger(); + + @Override + public void translate(GeyserSession session, ClientboundCustomPayloadPacket packet) { + // The only plugin messages it has to listen for are Floodgate plugin messages + if (session.getRemoteAuthType() != AuthType.FLOODGATE) { + return; + } + + String channel = packet.getChannel(); + + if (channel.equals("floodgate:form")) { + byte[] data = packet.getData(); + + // receive: first byte is form type, second and third are the id, remaining is the form data + // respond: first and second byte id, remaining is form response data + + FormType type = FormType.getByOrdinal(data[0]); + if (type == null) { + throw new NullPointerException( + "Got type " + data[0] + " which isn't a valid form type!"); + } + + String dataString = new String(data, 3, data.length - 3, Charsets.UTF_8); + + Form form = Forms.fromJson(dataString, type); + form.setResponseHandler(response -> { + byte[] raw = response.getBytes(StandardCharsets.UTF_8); + byte[] finalData = new byte[raw.length + 2]; + + finalData[0] = data[1]; + finalData[1] = data[2]; + System.arraycopy(raw, 0, finalData, 2, raw.length); + + session.sendDownstreamPacket(new ServerboundCustomPayloadPacket(channel, finalData)); + }); + session.sendForm(form); + + } else if (channel.equals("floodgate:transfer")) { + byte[] data = packet.getData(); + + // port, 4 bytes. remaining data, address. + + if (data.length < 5) { + throw new NullPointerException("Transfer data should be at least 5 bytes long"); + } + + int port = data[0] << 24 | (data[1] & 0xFF) << 16 | (data[2] & 0xFF) << 8 | data[3] & 0xFF; + String address = new String(data, 4, data.length - 4); + + if (logger.isDebug()) { + logger.info("Transferring client to: " + address + ":" + port); + } + + TransferPacket transferPacket = new TransferPacket(); + transferPacket.setAddress(address); + transferPacket.setPort(port); + session.sendUpstreamPacket(transferPacket); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginPluginMessageTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomQueryTranslator.java similarity index 59% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginPluginMessageTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomQueryTranslator.java index d084c524c..268b69f45 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginPluginMessageTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomQueryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,22 +23,25 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java; +package org.geysermc.geyser.translator.protocol.java; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; +import com.github.steveice10.mc.protocol.packet.login.clientbound.ClientboundCustomQueryPacket; +import com.github.steveice10.mc.protocol.packet.login.serverbound.ServerboundCustomQueryPacket; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; -import com.github.steveice10.mc.protocol.packet.login.client.LoginPluginResponsePacket; -import com.github.steveice10.mc.protocol.packet.login.server.LoginPluginRequestPacket; - -@Translator(packet = LoginPluginRequestPacket.class) -public class JavaLoginPluginMessageTranslator extends PacketTranslator { +/** + * For the login cycle. + */ +@Translator(packet = ClientboundCustomQueryPacket.class) +public class JavaCustomQueryTranslator extends PacketTranslator { @Override - public void translate(LoginPluginRequestPacket packet, GeyserSession session) { + public void translate(GeyserSession session, ClientboundCustomQueryPacket packet) { // A vanilla client doesn't know any PluginMessage in the Login state, so we don't know any either. + // Note: Fabric Networking API v1 will not let the client log in without sending this session.sendDownstreamPacket( - new LoginPluginResponsePacket(packet.getMessageId(), null) + new ServerboundCustomQueryPacket(packet.getMessageId(), null) ); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaDisconnectTranslator.java similarity index 60% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaDisconnectTranslator.java index 1945a8e10..e073e6f27 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaDisconnectTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,19 +23,24 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java; +package org.geysermc.geyser.translator.protocol.java; -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerDisconnectPacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.chat.MessageTranslator; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundDisconnectPacket; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.translator.text.MessageTranslator; -@Translator(packet = ServerDisconnectPacket.class) -public class JavaDisconnectPacket extends PacketTranslator { +@Translator(packet = ClientboundDisconnectPacket.class) +public class JavaDisconnectTranslator extends PacketTranslator { @Override - public void translate(ServerDisconnectPacket packet, GeyserSession session) { - session.disconnect(MessageTranslator.convertMessage(packet.getReason().toString(), session.getLocale())); + public void translate(GeyserSession session, ClientboundDisconnectPacket packet) { + session.disconnect(MessageTranslator.convertMessage(packet.getReason(), session.getLocale())); + } + + @Override + public boolean shouldExecuteInEventLoop() { + return false; } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaGameProfileTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaGameProfileTranslator.java new file mode 100644 index 000000000..307c27c29 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaGameProfileTranslator.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java; + +import com.github.steveice10.mc.auth.data.GameProfile; +import com.github.steveice10.mc.protocol.packet.login.clientbound.ClientboundGameProfilePacket; +import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.skin.SkinManager; + +@Translator(packet = ClientboundGameProfilePacket.class) +public class JavaGameProfileTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundGameProfilePacket packet) { + PlayerEntity playerEntity = session.getPlayerEntity(); + AuthType remoteAuthType = session.getRemoteAuthType(); + + // Required, or else Floodgate players break with Spigot chunk caching + GameProfile profile = packet.getProfile(); + playerEntity.setUsername(profile.getName()); + playerEntity.setUuid(profile.getId()); + + session.getGeyser().getSessionManager().addSession(playerEntity.getUuid(), session); + + // Check if they are not using a linked account + if (remoteAuthType == AuthType.OFFLINE || playerEntity.getUuid().getMostSignificantBits() == 0) { + SkinManager.handleBedrockSkin(playerEntity, session.getClientData()); + } + + if (remoteAuthType == AuthType.FLOODGATE) { + // We'll send the skin upload a bit after the handshake packet (aka this packet), + // because otherwise the global server returns the data too fast. + session.getAuthData().upload(session.getGeyser()); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaKeepAliveTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaKeepAliveTranslator.java similarity index 66% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/JavaKeepAliveTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaKeepAliveTranslator.java index 1dd156e4f..680debe49 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaKeepAliveTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaKeepAliveTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,26 +23,28 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java; +package org.geysermc.geyser.translator.protocol.java; -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerKeepAlivePacket; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundKeepAlivePacket; import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; /** * Used to forward the keep alive packet to the client in order to get back a reliable ping. */ -@Translator(packet = ServerKeepAlivePacket.class) -public class JavaKeepAliveTranslator extends PacketTranslator { +@Translator(packet = ClientboundKeepAlivePacket.class) +public class JavaKeepAliveTranslator extends PacketTranslator { @Override - public void translate(ServerKeepAlivePacket packet, GeyserSession session) { - session.setLastKeepAliveTimestamp(packet.getPingId()); + public void translate(GeyserSession session, ClientboundKeepAlivePacket packet) { + if (!session.getGeyser().getConfig().isForwardPlayerPing()) { + return; + } NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket(); latencyPacket.setFromServer(true); - latencyPacket.setTimestamp(packet.getPingId()); + latencyPacket.setTimestamp(packet.getPingId() * 1000); session.sendUpstreamPacket(latencyPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginDisconnectTranslator.java similarity index 64% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginDisconnectTranslator.java index 0a1cc3ddb..ce5fabe30 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginDisconnectTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,20 +23,25 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java; +package org.geysermc.geyser.translator.protocol.java; -import com.github.steveice10.mc.protocol.packet.login.server.LoginDisconnectPacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.chat.MessageTranslator; +import com.github.steveice10.mc.protocol.packet.login.clientbound.ClientboundLoginDisconnectPacket; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.translator.text.MessageTranslator; -@Translator(packet = LoginDisconnectPacket.class) -public class JavaLoginDisconnectTranslator extends PacketTranslator { +@Translator(packet = ClientboundLoginDisconnectPacket.class) +public class JavaLoginDisconnectTranslator extends PacketTranslator { @Override - public void translate(LoginDisconnectPacket packet, GeyserSession session) { + public void translate(GeyserSession session, ClientboundLoginDisconnectPacket packet) { // The client doesn't manually get disconnected so we have to do it ourselves - session.disconnect(MessageTranslator.convertMessage(packet.getReason().toString(), session.getLocale())); + session.disconnect(MessageTranslator.convertMessage(packet.getReason(), session.getLocale())); + } + + @Override + public boolean shouldExecuteInEventLoop() { + return false; } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java new file mode 100644 index 000000000..76a867e30 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java; + +import com.github.steveice10.mc.protocol.data.game.entity.player.HandPreference; +import com.github.steveice10.mc.protocol.data.game.setting.ChatVisibility; +import com.github.steveice10.mc.protocol.data.game.setting.SkinPart; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundLoginPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundClientInformationPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundCustomPayloadPacket; +import com.nukkitx.protocol.bedrock.data.GameRuleData; +import com.nukkitx.protocol.bedrock.data.PlayerPermission; +import com.nukkitx.protocol.bedrock.packet.*; +import org.geysermc.geyser.session.auth.AuthType; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.translator.level.BiomeTranslator; +import org.geysermc.geyser.util.ChunkUtils; +import org.geysermc.geyser.util.DimensionUtils; +import org.geysermc.geyser.util.PluginMessageUtils; + +import java.util.Arrays; +import java.util.List; + +@Translator(packet = ClientboundLoginPacket.class) +public class JavaLoginTranslator extends PacketTranslator { + private static final List SKIN_PART_VALUES = Arrays.asList(SkinPart.values()); + + @Override + public void translate(GeyserSession session, ClientboundLoginPacket packet) { + PlayerEntity entity = session.getPlayerEntity(); + entity.setEntityId(packet.getEntityId()); + + // If the player is already initialized and a join game packet is sent, they + // are swapping servers + String newDimension = DimensionUtils.getNewDimension(packet.getDimension()); + if (session.isSpawned()) { + String fakeDim = DimensionUtils.getTemporaryDimension(session.getDimension(), newDimension); + DimensionUtils.switchDimension(session, fakeDim); + + session.getWorldCache().removeScoreboard(); + } + session.setWorldName(packet.getWorldName()); + + BiomeTranslator.loadServerBiomes(session, packet.getDimensionCodec()); + session.getTagCache().clear(); + + session.setGameMode(packet.getGameMode()); + + boolean needsSpawnPacket = !session.isSentSpawnPacket(); + if (needsSpawnPacket) { + // The player has yet to spawn so let's do that using some of the information in this Java packet + session.setDimension(newDimension); + session.connect(); + } + + AdventureSettingsPacket bedrockPacket = new AdventureSettingsPacket(); + bedrockPacket.setUniqueEntityId(session.getPlayerEntity().getGeyserId()); + bedrockPacket.setPlayerPermission(PlayerPermission.MEMBER); + session.sendUpstreamPacket(bedrockPacket); + + if (!needsSpawnPacket) { + SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket(); + playerGameTypePacket.setGamemode(packet.getGameMode().ordinal()); + session.sendUpstreamPacket(playerGameTypePacket); + } + + entity.updateBedrockMetadata(); + + // Send if client should show respawn screen + GameRulesChangedPacket gamerulePacket = new GameRulesChangedPacket(); + gamerulePacket.getGameRules().add(new GameRuleData<>("doimmediaterespawn", !packet.isEnableRespawnScreen())); + session.sendUpstreamPacket(gamerulePacket); + + session.setReducedDebugInfo(packet.isReducedDebugInfo()); + + session.setRenderDistance(packet.getViewDistance()); + + // We need to send our skin parts to the server otherwise java sees us with no hat, jacket etc + String locale = session.getLocale(); + // TODO customize + ServerboundClientInformationPacket infoPacket = new ServerboundClientInformationPacket(locale, (byte) session.getRenderDistance(), ChatVisibility.FULL, true, SKIN_PART_VALUES, HandPreference.RIGHT_HAND, false, true); + session.sendDownstreamPacket(infoPacket); + + session.sendDownstreamPacket(new ServerboundCustomPayloadPacket("minecraft:brand", PluginMessageUtils.getGeyserBrandData())); + + // register the plugin messaging channels used in Floodgate + if (session.getRemoteAuthType() == AuthType.FLOODGATE) { + session.sendDownstreamPacket(new ServerboundCustomPayloadPacket("minecraft:register", PluginMessageUtils.getFloodgateRegisterData())); + } + + if (!newDimension.equals(session.getDimension())) { + DimensionUtils.switchDimension(session, newDimension); + } else if (DimensionUtils.isCustomBedrockNetherId() && newDimension.equalsIgnoreCase(DimensionUtils.NETHER)) { + // If the player is spawning into the "fake" nether, send them some fog + session.sendFog("minecraft:fog_hell"); + } + + ChunkUtils.loadDimensionTag(session, packet.getDimension()); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaPingTranslator.java similarity index 58% rename from connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaPingTranslator.java index 3fcae17db..a3b2e34b7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaPingTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,29 +23,20 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.item; +package org.geysermc.geyser.translator.protocol.java; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.ToString; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundPongPacket; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundPingPacket; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; -@Getter -@AllArgsConstructor -@ToString -public class ItemEntry { - - public static ItemEntry AIR = new ItemEntry("minecraft:air", "minecraft:air", 0, 0, 0, false); - - private final String javaIdentifier; - private final String bedrockIdentifier; - private final int javaId; - private final int bedrockId; - private final int bedrockData; - - private final boolean block; +// Why does this packet exist? Whatever, we better implement it +@Translator(packet = ClientboundPingPacket.class) +public class JavaPingTranslator extends PacketTranslator { @Override - public boolean equals(Object obj) { - return obj == this || (obj instanceof ItemEntry && ((ItemEntry) obj).getBedrockId() == this.getBedrockId() && ((ItemEntry) obj).getJavaIdentifier().equals(this.getJavaIdentifier())); + public void translate(GeyserSession session, ClientboundPingPacket packet) { + session.sendDownstreamPacket(new ServerboundPongPacket(packet.getId())); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRecipeTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRecipeTranslator.java new file mode 100644 index 000000000..0a329f607 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRecipeTranslator.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java; + +import com.github.steveice10.mc.protocol.data.game.UnlockRecipesAction; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundRecipePacket; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; + +import java.util.Arrays; + +/** + * Used to list recipes that we can definitely use the recipe book for (and therefore save on packet usage) + */ +@Translator(packet = ClientboundRecipePacket.class) +public class JavaRecipeTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundRecipePacket packet) { + if (packet.getAction() == UnlockRecipesAction.REMOVE) { + session.getUnlockedRecipes().removeAll(Arrays.asList(packet.getRecipes())); + } else { + session.getUnlockedRecipes().addAll(Arrays.asList(packet.getRecipes())); + } + } +} + diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRespawnTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRespawnTranslator.java new file mode 100644 index 000000000..8f97b1a8d --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaRespawnTranslator.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java; + +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundRespawnPacket; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; +import com.nukkitx.protocol.bedrock.packet.SetPlayerGameTypePacket; +import org.geysermc.geyser.entity.attribute.GeyserAttributeType; +import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.translator.inventory.InventoryTranslator; +import org.geysermc.geyser.util.ChunkUtils; +import org.geysermc.geyser.util.DimensionUtils; + +@Translator(packet = ClientboundRespawnPacket.class) +public class JavaRespawnTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundRespawnPacket packet) { + SessionPlayerEntity entity = session.getPlayerEntity(); + + entity.setHealth(entity.getMaxHealth()); + entity.getAttributes().put(GeyserAttributeType.HEALTH, entity.createHealthAttribute()); + + session.setInventoryTranslator(InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR); + session.setOpenInventory(null); + session.setClosingInventory(false); + + SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket(); + playerGameTypePacket.setGamemode(packet.getGamemode().ordinal()); + session.sendUpstreamPacket(playerGameTypePacket); + session.setGameMode(packet.getGamemode()); + + if (session.isRaining()) { + LevelEventPacket stopRainPacket = new LevelEventPacket(); + stopRainPacket.setType(LevelEventType.STOP_RAINING); + stopRainPacket.setData(0); + stopRainPacket.setPosition(Vector3f.ZERO); + session.sendUpstreamPacket(stopRainPacket); + session.setRaining(false); + } + + if (session.isThunder()) { + LevelEventPacket stopThunderPacket = new LevelEventPacket(); + stopThunderPacket.setType(LevelEventType.STOP_THUNDERSTORM); + stopThunderPacket.setData(0); + stopThunderPacket.setPosition(Vector3f.ZERO); + session.sendUpstreamPacket(stopThunderPacket); + session.setThunder(false); + } + + String newDimension = DimensionUtils.getNewDimension(packet.getDimension()); + if (!session.getDimension().equals(newDimension) || !packet.getWorldName().equals(session.getWorldName())) { + // Switching to a new world (based off the world name change); send a fake dimension change + if (!packet.getWorldName().equals(session.getWorldName()) && (session.getDimension().equals(newDimension) + // Ensure that the player never ever dimension switches to the same dimension - BAD + // Can likely be removed if the Above Bedrock Nether Building option can be removed + || DimensionUtils.javaToBedrock(session.getDimension()) == DimensionUtils.javaToBedrock(newDimension))) { + String fakeDim = DimensionUtils.getTemporaryDimension(session.getDimension(), newDimension); + DimensionUtils.switchDimension(session, fakeDim); + } + session.setWorldName(packet.getWorldName()); + DimensionUtils.switchDimension(session, newDimension); + } + + ChunkUtils.loadDimensionTag(session, packet.getDimension()); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaSelectAdvancementsTabTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaSelectAdvancementsTabTranslator.java new file mode 100644 index 000000000..3f82dffb1 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaSelectAdvancementsTabTranslator.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java; + +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundSelectAdvancementsTabPacket; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.AdvancementsCache; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; + +/** + * Indicates that the client should open a particular advancement tab + */ +@Translator(packet = ClientboundSelectAdvancementsTabPacket.class) +public class JavaSelectAdvancementsTabTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundSelectAdvancementsTabPacket packet) { + AdvancementsCache advancementsCache = session.getAdvancementsCache(); + advancementsCache.setCurrentAdvancementCategoryId(packet.getTabId()); + advancementsCache.buildAndShowListForm(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateAdvancementsTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateAdvancementsTranslator.java new file mode 100644 index 000000000..c1f6f77d2 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateAdvancementsTranslator.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java; + +import com.github.steveice10.mc.protocol.data.game.advancement.Advancement; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundUpdateAdvancementsPacket; +import com.nukkitx.protocol.bedrock.packet.SetTitlePacket; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.geyser.session.cache.AdvancementsCache; +import org.geysermc.geyser.level.GeyserAdvancement; +import org.geysermc.geyser.text.MinecraftLocale; + +import java.util.Map; + +@Translator(packet = ClientboundUpdateAdvancementsPacket.class) +public class JavaUpdateAdvancementsTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundUpdateAdvancementsPacket packet) { + AdvancementsCache advancementsCache = session.getAdvancementsCache(); + if (packet.isReset()) { + advancementsCache.getStoredAdvancements().clear(); + advancementsCache.getStoredAdvancementProgress().clear(); + } + + // Removes removed advancements from player's stored advancements + for (String removedAdvancement : packet.getRemovedAdvancements()) { + advancementsCache.getStoredAdvancements().remove(removedAdvancement); + } + + advancementsCache.getStoredAdvancementProgress().putAll(packet.getProgress()); + + sendToolbarAdvancementUpdates(session, packet); + + // Adds advancements to the player's stored advancements when advancements are sent + for (Advancement advancement : packet.getAdvancements()) { + if (advancement.getDisplayData() != null && !advancement.getDisplayData().isHidden()) { + GeyserAdvancement geyserAdvancement = GeyserAdvancement.from(advancement); + advancementsCache.getStoredAdvancements().put(advancement.getId(), geyserAdvancement); + } else { + advancementsCache.getStoredAdvancements().remove(advancement.getId()); + } + } + } + + /** + * Handle all advancements progress updates + */ + public void sendToolbarAdvancementUpdates(GeyserSession session, ClientboundUpdateAdvancementsPacket packet) { + if (packet.isReset()) { + // Advancements are being cleared, so they can't be granted + return; + } + for (Map.Entry> progress : packet.getProgress().entrySet()) { + GeyserAdvancement advancement = session.getAdvancementsCache().getStoredAdvancements().get(progress.getKey()); + if (advancement != null && advancement.getDisplayData() != null) { + if (session.getAdvancementsCache().isEarned(advancement)) { + // Java uses some pink color for toast challenge completes + String color = advancement.getDisplayData().getFrameType() == Advancement.DisplayData.FrameType.CHALLENGE ? + "§d" : "§a"; + String advancementName = MessageTranslator.convertMessage(advancement.getDisplayData().getTitle(), session.getLocale()); + + // Send an action bar message stating they earned an achievement + // Sent for instances where broadcasting advancements through chat are disabled + SetTitlePacket titlePacket = new SetTitlePacket(); + titlePacket.setText(color + "[" + MinecraftLocale.getLocaleString("advancements.toast." + + advancement.getDisplayData().getFrameType().toString().toLowerCase(), session.getLocale()) + "]§f " + advancementName); + titlePacket.setType(SetTitlePacket.Type.ACTIONBAR); + titlePacket.setFadeOutTime(3); + titlePacket.setFadeInTime(3); + titlePacket.setStayTime(3); + titlePacket.setXuid(""); + titlePacket.setPlatformOnlineId(""); + session.sendUpstreamPacket(titlePacket); + } + } + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java new file mode 100644 index 000000000..fd8c42ffc --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateRecipesTranslator.java @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; +import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; +import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; +import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; +import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; +import com.github.steveice10.mc.protocol.data.game.recipe.data.StoneCuttingRecipeData; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundUpdateRecipesPacket; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket; +import it.unimi.dsi.fastutil.ints.*; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.translator.inventory.item.ItemTranslator; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.util.InventoryUtils; + +import java.util.*; +import java.util.stream.Collectors; + +import static org.geysermc.geyser.util.InventoryUtils.LAST_RECIPE_NET_ID; + +/** + * Used to send all valid recipes from Java to Bedrock. + * + * Bedrock REQUIRES a CraftingDataPacket to be sent in order to craft anything. + */ +@Translator(packet = ClientboundUpdateRecipesPacket.class) +public class JavaUpdateRecipesTranslator extends PacketTranslator { + /** + * Required to use the specified cartography table recipes + */ + private static final List CARTOGRAPHY_RECIPES = Arrays.asList( + CraftingData.fromMulti(UUID.fromString("8b36268c-1829-483c-a0f1-993b7156a8f2"), ++LAST_RECIPE_NET_ID), // Map extending + CraftingData.fromMulti(UUID.fromString("442d85ed-8272-4543-a6f1-418f90ded05d"), ++LAST_RECIPE_NET_ID), // Map cloning + CraftingData.fromMulti(UUID.fromString("98c84b38-1085-46bd-b1ce-dd38c159e6cc"), ++LAST_RECIPE_NET_ID), // Map upgrading + CraftingData.fromMulti(UUID.fromString("602234e4-cac1-4353-8bb7-b1ebff70024b"), ++LAST_RECIPE_NET_ID) // Map locking + ); + + @Override + public void translate(GeyserSession session, ClientboundUpdateRecipesPacket packet) { + Map> recipeTypes = Registries.CRAFTING_DATA.forVersion(session.getUpstream().getProtocolVersion()); + // Get the last known network ID (first used for the pregenerated recipes) and increment from there. + int netId = InventoryUtils.LAST_RECIPE_NET_ID + 1; + + Int2ObjectMap recipeMap = new Int2ObjectOpenHashMap<>(Registries.RECIPES.forVersion(session.getUpstream().getProtocolVersion())); + Int2ObjectMap> unsortedStonecutterData = new Int2ObjectOpenHashMap<>(); + CraftingDataPacket craftingDataPacket = new CraftingDataPacket(); + craftingDataPacket.setCleanRecipes(true); + for (Recipe recipe : packet.getRecipes()) { + switch (recipe.getType()) { + case CRAFTING_SHAPELESS -> { + ShapelessRecipeData shapelessRecipeData = (ShapelessRecipeData) recipe.getData(); + ItemData output = ItemTranslator.translateToBedrock(session, shapelessRecipeData.getResult()); + if (output.equals(ItemData.AIR)) { + // Likely modded item that Bedrock will complain about if it persists + continue; + } + // Strip NBT - tools won't appear in the recipe book otherwise + output = output.toBuilder().tag(null).build(); + ItemData[][] inputCombinations = combinations(session, shapelessRecipeData.getIngredients()); + for (ItemData[] inputs : inputCombinations) { + UUID uuid = UUID.randomUUID(); + craftingDataPacket.getCraftingData().add(CraftingData.fromShapeless(uuid.toString(), + Arrays.asList(inputs), Collections.singletonList(output), uuid, "crafting_table", 0, netId)); + recipeMap.put(netId++, recipe); + } + } + case CRAFTING_SHAPED -> { + ShapedRecipeData shapedRecipeData = (ShapedRecipeData) recipe.getData(); + ItemData output = ItemTranslator.translateToBedrock(session, shapedRecipeData.getResult()); + if (output.equals(ItemData.AIR)) { + // Likely modded item that Bedrock will complain about if it persists + continue; + } + // See above + output = output.toBuilder().tag(null).build(); + ItemData[][] inputCombinations = combinations(session, shapedRecipeData.getIngredients()); + for (ItemData[] inputs : inputCombinations) { + UUID uuid = UUID.randomUUID(); + craftingDataPacket.getCraftingData().add(CraftingData.fromShaped(uuid.toString(), + shapedRecipeData.getWidth(), shapedRecipeData.getHeight(), Arrays.asList(inputs), + Collections.singletonList(output), uuid, "crafting_table", 0, netId)); + recipeMap.put(netId++, recipe); + } + } + case STONECUTTING -> { + StoneCuttingRecipeData stoneCuttingData = (StoneCuttingRecipeData) recipe.getData(); + ItemStack ingredient = stoneCuttingData.getIngredient().getOptions()[0]; + List data = unsortedStonecutterData.get(ingredient.getId()); + if (data == null) { + data = new ArrayList<>(); + unsortedStonecutterData.put(ingredient.getId(), data); + } + data.add(stoneCuttingData); + // Save for processing after all recipes have been received + } + default -> { + List craftingData = recipeTypes.get(recipe.getType()); + if (craftingData != null) { + craftingDataPacket.getCraftingData().addAll(craftingData); + } + } + } + } + craftingDataPacket.getCraftingData().addAll(CARTOGRAPHY_RECIPES); + craftingDataPacket.getPotionMixData().addAll(Registries.POTION_MIXES.get()); + + Int2ObjectMap stonecutterRecipeMap = new Int2ObjectOpenHashMap<>(); + for (Int2ObjectMap.Entry> data : unsortedStonecutterData.int2ObjectEntrySet()) { + // Sort the list by each output item's Java identifier - this is how it's sorted on Java, and therefore + // We can get the correct order for button pressing + data.getValue().sort(Comparator.comparing((stoneCuttingRecipeData -> + session.getItemMappings().getItems() + .getOrDefault(stoneCuttingRecipeData.getResult().getId(), ItemMapping.AIR) + .getJavaIdentifier()))); + + // Now that it's sorted, let's translate these recipes + for (StoneCuttingRecipeData stoneCuttingData : data.getValue()) { + // As of 1.16.4, all stonecutter recipes have one ingredient option + ItemStack ingredient = stoneCuttingData.getIngredient().getOptions()[0]; + ItemData input = ItemTranslator.translateToBedrock(session, ingredient); + ItemData output = ItemTranslator.translateToBedrock(session, stoneCuttingData.getResult()); + if (input.equals(ItemData.AIR) || output.equals(ItemData.AIR)) { + // Probably modded items + continue; + } + UUID uuid = UUID.randomUUID(); + + // We need to register stonecutting recipes so they show up on Bedrock + craftingDataPacket.getCraftingData().add(CraftingData.fromShapeless(uuid.toString(), + Collections.singletonList(input), Collections.singletonList(output), uuid, "stonecutter", 0, netId++)); + + // Save the recipe list for reference when crafting + // Add the ingredient as the key and all possible values as the value + IntList outputs = stonecutterRecipeMap.computeIfAbsent(ingredient.getId(), ($) -> new IntArrayList()); + outputs.add(stoneCuttingData.getResult().getId()); + } + } + + session.sendUpstreamPacket(craftingDataPacket); + session.setCraftingRecipes(recipeMap); + session.getUnlockedRecipes().clear(); + session.setStonecutterRecipes(stonecutterRecipeMap); + session.getLastRecipeNetId().set(netId); + } + + //TODO: rewrite + /** + * The Java server sends an array of items for each ingredient you can use per slot in the crafting grid. + * Bedrock recipes take only one ingredient per crafting grid slot. + * + * @return the Java ingredient list as an array that Bedrock can understand + */ + private ItemData[][] combinations(GeyserSession session, Ingredient[] ingredients) { + Map, IntSet> squashedOptions = new HashMap<>(); + for (int i = 0; i < ingredients.length; i++) { + if (ingredients[i].getOptions().length == 0) { + squashedOptions.computeIfAbsent(Collections.singleton(ItemData.AIR), k -> new IntOpenHashSet()).add(i); + continue; + } + Ingredient ingredient = ingredients[i]; + Map> groupedByIds = Arrays.stream(ingredient.getOptions()) + .map(item -> ItemTranslator.translateToBedrock(session, item)) + .collect(Collectors.groupingBy(item -> new GroupedItem(item.getId(), item.getCount(), item.getTag()))); + Set optionSet = new HashSet<>(groupedByIds.size()); + for (Map.Entry> entry : groupedByIds.entrySet()) { + if (entry.getValue().size() > 1) { + GroupedItem groupedItem = entry.getKey(); + int idCount = 0; + //not optimal + for (ItemMapping mapping : session.getItemMappings().getItems().values()) { + if (mapping.getBedrockId() == groupedItem.id) { + idCount++; + } + } + if (entry.getValue().size() < idCount) { + optionSet.addAll(entry.getValue()); + } else { + optionSet.add(ItemData.builder() + .id(groupedItem.id) + .damage(Short.MAX_VALUE) + .count(groupedItem.count) + .tag(groupedItem.tag).build()); + } + } else { + ItemData item = entry.getValue().get(0); + optionSet.add(item); + } + } + squashedOptions.computeIfAbsent(optionSet, k -> new IntOpenHashSet()).add(i); + } + int totalCombinations = 1; + for (Set optionSet : squashedOptions.keySet()) { + totalCombinations *= optionSet.size(); + } + if (totalCombinations > 500) { + ItemData[] translatedItems = new ItemData[ingredients.length]; + for (int i = 0; i < ingredients.length; i++) { + if (ingredients[i].getOptions().length > 0) { + translatedItems[i] = ItemTranslator.translateToBedrock(session, ingredients[i].getOptions()[0]); + } else { + translatedItems[i] = ItemData.AIR; + } + } + return new ItemData[][]{translatedItems}; + } + List> sortedSets = new ArrayList<>(squashedOptions.keySet()); + sortedSets.sort(Comparator.comparing(Set::size, Comparator.reverseOrder())); + ItemData[][] combinations = new ItemData[totalCombinations][ingredients.length]; + int x = 1; + for (Set set : sortedSets) { + IntSet slotSet = squashedOptions.get(set); + int i = 0; + for (ItemData item : set) { + for (int j = 0; j < totalCombinations / set.size(); j++) { + final int comboIndex = (i * x) + (j % x) + ((j / x) * set.size() * x); + for (int slot : slotSet) { + combinations[comboIndex][slot] = item; + } + } + i++; + } + x *= set.size(); + } + return combinations; + } + + @EqualsAndHashCode + @AllArgsConstructor + private static class GroupedItem { + int id; + int count; + NbtMap tag; + } +} diff --git a/common/src/main/java/org/geysermc/common/window/button/FormImage.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateTagsTranslator.java similarity index 63% rename from common/src/main/java/org/geysermc/common/window/button/FormImage.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateTagsTranslator.java index 72579f7ac..5c9d84457 100644 --- a/common/src/main/java/org/geysermc/common/window/button/FormImage.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaUpdateTagsTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,40 +23,18 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.button; +package org.geysermc.geyser.translator.protocol.java; -import lombok.Getter; -import lombok.Setter; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundUpdateTagsPacket; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; -public class FormImage { +@Translator(packet = ClientboundUpdateTagsPacket.class) +public class JavaUpdateTagsTranslator extends PacketTranslator { - @Getter - @Setter - private String type; - - @Getter - @Setter - private String data; - - public FormImage(FormImageType type, String data) { - this.type = type.getName(); - this.data = data; - } - - public enum FormImageType { - PATH("path"), - URL("url"); - - @Getter - private String name; - - FormImageType(String name) { - this.name = name; - } - - @Override - public String toString() { - return name; - } + @Override + public void translate(GeyserSession session, ClientboundUpdateTagsPacket packet) { + session.getTagCache().loadPacket(packet); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaAnimateTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaAnimateTranslator.java new file mode 100644 index 000000000..d7fc79f1e --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaAnimateTranslator.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java.entity; + +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundAnimatePacket; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.packet.AnimateEntityPacket; +import com.nukkitx.protocol.bedrock.packet.AnimatePacket; +import com.nukkitx.protocol.bedrock.packet.SpawnParticleEffectPacket; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.util.DimensionUtils; + +@Translator(packet = ClientboundAnimatePacket.class) +public class JavaAnimateTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundAnimatePacket packet) { + Entity entity; + if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { + entity = session.getPlayerEntity(); + } else { + entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); + } + if (entity == null) + return; + + AnimatePacket animatePacket = new AnimatePacket(); + animatePacket.setRuntimeEntityId(entity.getGeyserId()); + switch (packet.getAnimation()) { + case SWING_ARM: + animatePacket.setAction(AnimatePacket.Action.SWING_ARM); + break; + case SWING_OFFHAND: + // Use the OptionalPack to trigger the animation + AnimateEntityPacket offHandPacket = new AnimateEntityPacket(); + offHandPacket.setAnimation("animation.player.attack.rotations.offhand"); + offHandPacket.setNextState("default"); + offHandPacket.setBlendOutTime(0.0f); + offHandPacket.setStopExpression("query.any_animation_finished"); + offHandPacket.setController("__runtime_controller"); + offHandPacket.getRuntimeEntityIds().add(entity.getGeyserId()); + + session.sendUpstreamPacket(offHandPacket); + return; + case CRITICAL_HIT: + animatePacket.setAction(AnimatePacket.Action.CRITICAL_HIT); + break; + case ENCHANTMENT_CRITICAL_HIT: + animatePacket.setAction(AnimatePacket.Action.MAGIC_CRITICAL_HIT); // Unsure if this does anything + // Spawn custom particle + SpawnParticleEffectPacket stringPacket = new SpawnParticleEffectPacket(); + stringPacket.setIdentifier("geyseropt:enchanted_hit_multiple"); + stringPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension())); + stringPacket.setPosition(Vector3f.ZERO); + stringPacket.setUniqueEntityId(entity.getGeyserId()); + session.sendUpstreamPacket(stringPacket); + break; + case LEAVE_BED: + animatePacket.setAction(AnimatePacket.Action.WAKE_UP); + break; + default: + // Unknown Animation + return; + } + + session.sendUpstreamPacket(animatePacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaEntityEventTranslator.java similarity index 62% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaEntityEventTranslator.java index 5d7da2840..eb54ad942 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaEntityEventTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,33 +23,35 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java.entity; +package org.geysermc.geyser.translator.protocol.java.entity; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityStatusPacket; -import com.nukkitx.protocol.bedrock.data.SoundEvent; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundEntityEventPacket; import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.data.SoundEvent; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; -import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; -import com.nukkitx.protocol.bedrock.packet.LevelSoundEvent2Packet; -import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; -import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; -import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.item.ItemRegistry; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import com.nukkitx.protocol.bedrock.packet.*; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.entity.type.FishingHookEntity; +import org.geysermc.geyser.entity.type.LivingEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; -@Translator(packet = ServerEntityStatusPacket.class) -public class JavaEntityStatusTranslator extends PacketTranslator { +import java.util.concurrent.ThreadLocalRandom; + +@Translator(packet = ClientboundEntityEventPacket.class) +public class JavaEntityEventTranslator extends PacketTranslator { @Override - public void translate(ServerEntityStatusPacket packet, GeyserSession session) { - Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); + public void translate(GeyserSession session, ClientboundEntityEventPacket packet) { + Entity entity; if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { entity = session.getPlayerEntity(); + } else { + entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); } if (entity == null) return; @@ -89,19 +91,21 @@ public class JavaEntityStatusTranslator extends PacketTranslator { +@Translator(packet = ClientboundMoveEntityPosRotPacket.class) +public class JavaMoveEntityPosRotTranslator extends PacketTranslator { @Override - public void translate(ServerEntityPositionPacket packet, GeyserSession session) { + public void translate(GeyserSession session, ClientboundMoveEntityPosRotPacket packet) { Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { entity = session.getPlayerEntity(); } if (entity == null) return; - entity.moveRelative(session, packet.getMoveX(), packet.getMoveY(), packet.getMoveZ(), entity.getRotation(), packet.isOnGround()); + entity.updatePositionAndRotation(packet.getMoveX(), packet.getMoveY(), packet.getMoveZ(), packet.getYaw(), packet.getPitch(), packet.isOnGround()); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaMoveEntityPosTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaMoveEntityPosTranslator.java new file mode 100644 index 000000000..aeb080465 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaMoveEntityPosTranslator.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java.entity; + +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundMoveEntityPosPacket; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; + +@Translator(packet = ClientboundMoveEntityPosPacket.class) +public class JavaMoveEntityPosTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundMoveEntityPosPacket packet) { + Entity entity; + if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { + entity = session.getPlayerEntity(); + } else { + entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); + } + if (entity == null) return; + + entity.moveRelative(packet.getMoveX(), packet.getMoveY(), packet.getMoveZ(), entity.getYaw(), entity.getPitch(), entity.getHeadYaw(), packet.isOnGround()); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityHeadLookTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaMoveEntityRotTranslator.java similarity index 64% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityHeadLookTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaMoveEntityRotTranslator.java index 115cae55e..b2b59480b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityHeadLookTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaMoveEntityRotTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,26 +23,25 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java.entity; +package org.geysermc.geyser.translator.protocol.java.entity; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityHeadLookPacket; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundMoveEntityRotPacket; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; -@Translator(packet = ServerEntityHeadLookPacket.class) -public class JavaEntityHeadLookTranslator extends PacketTranslator { +@Translator(packet = ClientboundMoveEntityRotPacket.class) +public class JavaMoveEntityRotTranslator extends PacketTranslator { @Override - public void translate(ServerEntityHeadLookPacket packet, GeyserSession session) { + public void translate(GeyserSession session, ClientboundMoveEntityRotPacket packet) { Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { entity = session.getPlayerEntity(); } - if (entity == null) return; - entity.updateHeadLookRotation(session, packet.getHeadYaw()); + entity.updateRotation(packet.getYaw(), packet.getPitch(), packet.isOnGround()); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaVehicleMoveTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaMoveVehicleTranslator.java similarity index 61% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaVehicleMoveTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaMoveVehicleTranslator.java index 782b90396..90e121250 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaVehicleMoveTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaMoveVehicleTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,24 +23,23 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java.world; +package org.geysermc.geyser.translator.protocol.java.entity; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerVehicleMovePacket; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundMoveVehiclePacket; import com.nukkitx.math.vector.Vector3f; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; -@Translator(packet = ServerVehicleMovePacket.class) -public class JavaVehicleMoveTranslator extends PacketTranslator { +@Translator(packet = ClientboundMoveVehiclePacket.class) +public class JavaMoveVehicleTranslator extends PacketTranslator { @Override - public void translate(ServerVehicleMovePacket packet, GeyserSession session) { + public void translate(GeyserSession session, ClientboundMoveVehiclePacket packet) { Entity entity = session.getRidingVehicleEntity(); if (entity == null) return; - entity.moveAbsolute(session, Vector3f.from(packet.getX(), packet.getY(), packet.getZ()), packet.getYaw(), packet.getPitch(), false, false); - + entity.moveAbsolute(Vector3f.from(packet.getX(), packet.getY(), packet.getZ()), packet.getYaw(), packet.getPitch(), false, true); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityDestroyTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaRemoveEntitiesTranslator.java similarity index 67% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityDestroyTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaRemoveEntitiesTranslator.java index 98da3892c..043756db0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityDestroyTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaRemoveEntitiesTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,23 +23,21 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java.entity; +package org.geysermc.geyser.translator.protocol.java.entity; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundRemoveEntitiesPacket; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityDestroyPacket; - -@Translator(packet = ServerEntityDestroyPacket.class) -public class JavaEntityDestroyTranslator extends PacketTranslator { +@Translator(packet = ClientboundRemoveEntitiesPacket.class) +public class JavaRemoveEntitiesTranslator extends PacketTranslator { @Override - public void translate(ServerEntityDestroyPacket packet, GeyserSession session) { + public void translate(GeyserSession session, ClientboundRemoveEntitiesPacket packet) { for (int entityId : packet.getEntityIds()) { Entity entity = session.getEntityCache().getEntityByJavaId(entityId); - if (entity != null) { session.getEntityCache().removeEntity(entity, false); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityRemoveEffectTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaRemoveMobEffectTranslator.java similarity index 63% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityRemoveEffectTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaRemoveMobEffectTranslator.java index 63b8c544e..bc78bb33e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityRemoveEffectTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaRemoveMobEffectTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,27 +23,27 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java.entity; +package org.geysermc.geyser.translator.protocol.java.entity; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.PlayerEntity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.EntityUtils; - -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityRemoveEffectPacket; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundRemoveMobEffectPacket; import com.nukkitx.protocol.bedrock.packet.MobEffectPacket; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.util.EntityUtils; -@Translator(packet = ServerEntityRemoveEffectPacket.class) -public class JavaEntityRemoveEffectTranslator extends PacketTranslator { +@Translator(packet = ClientboundRemoveMobEffectPacket.class) +public class JavaRemoveMobEffectTranslator extends PacketTranslator { @Override - public void translate(ServerEntityRemoveEffectPacket packet, GeyserSession session) { - Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); + public void translate(GeyserSession session, ClientboundRemoveMobEffectPacket packet) { + Entity entity; if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { entity = session.getPlayerEntity(); - ((PlayerEntity) entity).getEffectCache().removeEffect(packet.getEffect()); + session.getEffectCache().removeEffect(packet.getEffect()); + } else { + entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); } if (entity == null) return; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityRotationTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaRotateHeadTranslator.java similarity index 60% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityRotationTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaRotateHeadTranslator.java index c1d3e5787..edcad7af6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityRotationTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaRotateHeadTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,25 +23,28 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java.entity; +package org.geysermc.geyser.translator.protocol.java.entity; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityRotationPacket; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundRotateHeadPacket; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; -@Translator(packet = ServerEntityRotationPacket.class) -public class JavaEntityRotationTranslator extends PacketTranslator { +@Translator(packet = ClientboundRotateHeadPacket.class) +public class JavaRotateHeadTranslator extends PacketTranslator { @Override - public void translate(ServerEntityRotationPacket packet, GeyserSession session) { - Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); + public void translate(GeyserSession session, ClientboundRotateHeadPacket packet) { + Entity entity; if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { entity = session.getPlayerEntity(); + } else { + entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); } + if (entity == null) return; - entity.updateRotation(session, packet.getYaw(), packet.getPitch(), packet.isOnGround()); + entity.updateHeadLookRotation(packet.getHeadYaw()); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityDataTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityDataTranslator.java new file mode 100644 index 000000000..440fcaea1 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityDataTranslator.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java.entity; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundSetEntityDataPacket; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.entity.InteractiveTagManager; + +@Translator(packet = ClientboundSetEntityDataPacket.class) +public class JavaSetEntityDataTranslator extends PacketTranslator { + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public void translate(GeyserSession session, ClientboundSetEntityDataPacket packet) { + Entity entity; + if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { + entity = session.getPlayerEntity(); + } else { + entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); + } + if (entity == null) return; + + EntityDefinition definition = entity.getDefinition(); + for (EntityMetadata metadata : packet.getMetadata()) { + if (metadata.getId() >= definition.translators().size()) { + if (session.getGeyser().getConfig().isDebugMode()) { + // Minecraft client just ignores these + session.getGeyser().getLogger().warning("Metadata ID " + metadata.getId() + " is out of bounds of known entity metadata size " + definition.translators().size() + " for entity type " + entity.getDefinition().entityType()); + session.getGeyser().getLogger().debug(metadata.toString()); + } + continue; + } + + ((EntityDefinition) definition).translateMetadata(entity, metadata); + } + + entity.updateBedrockMetadata(); + + // Update the interactive tag, if necessary + if (session.getMouseoverEntity() != null && session.getMouseoverEntity().getEntityId() == entity.getEntityId()) { + InteractiveTagManager.updateTag(session, entity); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAttachTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityLinkTranslator.java similarity index 66% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAttachTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityLinkTranslator.java index 1a6630efa..88f1c8ae1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAttachTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEntityLinkTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,35 +23,34 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java.entity; +package org.geysermc.geyser.translator.protocol.java.entity; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityAttachPacket; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundSetEntityLinkPacket; import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.type.living.MobEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; /** * Called when a leash is attached, removed or updated from an entity */ -@Translator(packet = ServerEntityAttachPacket.class) -public class JavaEntityAttachTranslator extends PacketTranslator { +@Translator(packet = ClientboundSetEntityLinkPacket.class) +public class JavaSetEntityLinkTranslator extends PacketTranslator { @Override - public void translate(ServerEntityAttachPacket packet, GeyserSession session) { - + public void translate(GeyserSession session, ClientboundSetEntityLinkPacket packet) { Entity holderId; if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { holderId = session.getPlayerEntity(); } else { holderId = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); - if (holderId == null) { - return; - } + } + if (!(holderId instanceof MobEntity mobEntity)) { + return; } Entity attachedToId; @@ -61,9 +60,9 @@ public class JavaEntityAttachTranslator extends PacketTranslator { +@Translator(packet = ClientboundSetEntityMotionPacket.class) +public class JavaSetEntityMotionTranslator extends PacketTranslator { @Override - public void translate(ServerEntityVelocityPacket packet, GeyserSession session) { - Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); + public void translate(GeyserSession session, ClientboundSetEntityMotionPacket packet) { + Entity entity; if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { entity = session.getPlayerEntity(); + } else { + entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); } if (entity == null) return; entity.setMotion(Vector3f.from(packet.getMotionX(), packet.getMotionY(), packet.getMotionZ())); + if (entity == session.getRidingVehicleEntity() && entity instanceof AbstractHorseEntity) { + // Horses for some reason teleport back when a SetEntityMotionPacket is sent while + // a player is riding on them. Java clients seem to ignore it anyways. + return; + } + + if (entity instanceof ItemEntity) { + // Don't bother sending entity motion packets for items + // since the client doesn't seem to care + return; + } + SetEntityMotionPacket entityMotionPacket = new SetEntityMotionPacket(); entityMotionPacket.setRuntimeEntityId(entity.getGeyserId()); entityMotionPacket.setMotion(entity.getMotion()); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEquipmentTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEquipmentTranslator.java new file mode 100644 index 000000000..ebfe40832 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEquipmentTranslator.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java.entity; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Equipment; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundSetEquipmentPacket; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.type.LivingEntity; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.skin.FakeHeadProvider; +import org.geysermc.geyser.translator.inventory.item.ItemTranslator; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; + +@Translator(packet = ClientboundSetEquipmentPacket.class) +public class JavaSetEquipmentTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundSetEquipmentPacket packet) { + Entity entity; + if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { + entity = session.getPlayerEntity(); + } else { + entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); + } + + if (entity == null) + return; + + if (!(entity instanceof LivingEntity livingEntity)) { + session.getGeyser().getLogger().debug("Attempted to add armor to a non-living entity type (" + + entity.getDefinition().entityType().name() + ")."); + return; + } + + boolean armorUpdated = false; + boolean mainHandUpdated = false; + boolean offHandUpdated = false; + for (Equipment equipment : packet.getEquipment()) { + ItemData item = ItemTranslator.translateToBedrock(session, equipment.getItem()); + switch (equipment.getSlot()) { + case HELMET -> { + ItemStack javaItem = equipment.getItem(); + if (livingEntity instanceof PlayerEntity + && javaItem != null + && javaItem.getId() == session.getItemMappings().getStoredItems().playerHead().getJavaId() + && javaItem.getNbt() != null + && javaItem.getNbt().get("SkullOwner") instanceof CompoundTag profile) { + FakeHeadProvider.setHead(session, (PlayerEntity) livingEntity, profile); + } else { + FakeHeadProvider.restoreOriginalSkin(session, livingEntity); + } + + livingEntity.setHelmet(item); + armorUpdated = true; + } + case CHESTPLATE -> { + livingEntity.setChestplate(item); + armorUpdated = true; + } + case LEGGINGS -> { + livingEntity.setLeggings(item); + armorUpdated = true; + } + case BOOTS -> { + livingEntity.setBoots(item); + armorUpdated = true; + } + case MAIN_HAND -> { + livingEntity.setHand(item); + mainHandUpdated = true; + } + case OFF_HAND -> { + livingEntity.setOffHand(item); + offHandUpdated = true; + } + } + } + + if (armorUpdated) { + livingEntity.updateArmor(session); + } + if (mainHandUpdated) { + livingEntity.updateMainHand(session); + } + if (offHandUpdated) { + livingEntity.updateOffHand(session); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetPassengersTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetPassengersTranslator.java new file mode 100644 index 000000000..f4a6f94ed --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetPassengersTranslator.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java.entity; + +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundSetPassengersPacket; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData; +import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.util.EntityUtils; + +import java.util.Arrays; + +@Translator(packet = ClientboundSetPassengersPacket.class) +public class JavaSetPassengersTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundSetPassengersPacket packet) { + Entity entity; + if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { + entity = session.getPlayerEntity(); + } else { + entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); + } + + if (entity == null) return; + + LongOpenHashSet passengers = entity.getPassengers().clone(); + boolean rider = true; + for (long passengerId : packet.getPassengerIds()) { + Entity passenger = session.getEntityCache().getEntityByJavaId(passengerId); + if (passengerId == session.getPlayerEntity().getEntityId()) { + passenger = session.getPlayerEntity(); + session.setRidingVehicleEntity(entity); + // We need to confirm teleports before entering a vehicle, or else we will likely exit right out + session.confirmTeleport(passenger.getPosition().sub(0, EntityDefinitions.PLAYER.offset(), 0).toDouble()); + } + // Passenger hasn't loaded in (likely since we're waiting for a skin response) + // and entity link needs to be set later + if (passenger == null && passengerId != 0) { + session.getEntityCache().addCachedPlayerEntityLink(passengerId, packet.getEntityId()); + } + if (passenger == null) { + continue; + } + + EntityLinkData.Type type = rider ? EntityLinkData.Type.RIDER : EntityLinkData.Type.PASSENGER; + SetEntityLinkPacket linkPacket = new SetEntityLinkPacket(); + linkPacket.setEntityLink(new EntityLinkData(entity.getGeyserId(), passenger.getGeyserId(), type, false)); + session.sendUpstreamPacket(linkPacket); + passengers.add(passengerId); + + // Head rotation on boats + if (entity.getDefinition() == EntityDefinitions.BOAT) { + passenger.getDirtyMetadata().put(EntityData.RIDER_ROTATION_LOCKED, (byte) 1); + passenger.getDirtyMetadata().put(EntityData.RIDER_MAX_ROTATION, 90f); + passenger.getDirtyMetadata().put(EntityData.RIDER_MIN_ROTATION, 1f); + passenger.getDirtyMetadata().put(EntityData.RIDER_ROTATION_OFFSET, -90f); + } else { + passenger.getDirtyMetadata().put(EntityData.RIDER_ROTATION_LOCKED, (byte) 0); + passenger.getDirtyMetadata().put(EntityData.RIDER_MAX_ROTATION, 0f); + passenger.getDirtyMetadata().put(EntityData.RIDER_MIN_ROTATION, 0f); + } + + passenger.updateBedrockMetadata(); + rider = false; + } + + entity.setPassengers(passengers); + + for (long passengerId : entity.getPassengers()) { + Entity passenger = session.getEntityCache().getEntityByJavaId(passengerId); + if (passengerId == session.getPlayerEntity().getEntityId()) { + passenger = session.getPlayerEntity(); + } + if (passenger == null) { + continue; + } + if (Arrays.stream(packet.getPassengerIds()).noneMatch(id -> id == passengerId)) { + SetEntityLinkPacket linkPacket = new SetEntityLinkPacket(); + linkPacket.setEntityLink(new EntityLinkData(entity.getGeyserId(), passenger.getGeyserId(), EntityLinkData.Type.REMOVE, false)); + session.sendUpstreamPacket(linkPacket); + passengers.remove(passenger.getEntityId()); + passenger.getDirtyMetadata().put(EntityData.RIDER_ROTATION_LOCKED, (byte) 0); + passenger.getDirtyMetadata().put(EntityData.RIDER_MAX_ROTATION, 0f); + passenger.getDirtyMetadata().put(EntityData.RIDER_MIN_ROTATION, 0f); + passenger.getDirtyMetadata().put(EntityData.RIDER_ROTATION_OFFSET, 0f); + + EntityUtils.updateMountOffset(passenger, entity, false, false, (packet.getPassengerIds().length > 1)); + } else { + EntityUtils.updateMountOffset(passenger, entity, (packet.getPassengerIds()[0] == passengerId), true, (packet.getPassengerIds().length > 1)); + } + + // Force an update to the passenger metadata + passenger.updateBedrockMetadata(); + } + + switch (entity.getDefinition().entityType()) { + case HORSE, SKELETON_HORSE, DONKEY, MULE, RAVAGER -> { + entity.getDirtyMetadata().put(EntityData.RIDER_MAX_ROTATION, 181.0f); + entity.updateBedrockMetadata(); + } + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityCollectItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaTakeItemEntityTranslator.java similarity index 79% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityCollectItemTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaTakeItemEntityTranslator.java index 270c33a7a..75d7bc976 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityCollectItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaTakeItemEntityTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,28 +23,28 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java.entity; +package org.geysermc.geyser.translator.protocol.java.entity; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityCollectItemPacket; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundTakeItemEntityPacket; import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import com.nukkitx.protocol.bedrock.packet.TakeItemEntityPacket; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.ExpOrbEntity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.type.ExpOrbEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; /** * This packet is called whenever a player picks up an item. * In Java, this is called for item entities, experience orbs and arrows * Bedrock uses it for arrows and item entities, but not experience orbs. */ -@Translator(packet = ServerEntityCollectItemPacket.class) -public class JavaEntityCollectItemTranslator extends PacketTranslator { +@Translator(packet = ClientboundTakeItemEntityPacket.class) +public class JavaTakeItemEntityTranslator extends PacketTranslator { @Override - public void translate(ServerEntityCollectItemPacket packet, GeyserSession session) { + public void translate(GeyserSession session, ClientboundTakeItemEntityPacket packet) { // Collected entity is the other entity Entity collectedEntity = session.getEntityCache().getEntityByJavaId(packet.getCollectedEntityId()); if (collectedEntity == null) return; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityTeleportTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaTeleportEntityTranslator.java similarity index 63% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityTeleportTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaTeleportEntityTranslator.java index cf01d214d..cbd994d79 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityTeleportTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaTeleportEntityTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,27 +23,26 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java.entity; +package org.geysermc.geyser.translator.protocol.java.entity; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityTeleportPacket; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundTeleportEntityPacket; import com.nukkitx.math.vector.Vector3f; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; -@Translator(packet = ServerEntityTeleportPacket.class) -public class JavaEntityTeleportTranslator extends PacketTranslator { +@Translator(packet = ClientboundTeleportEntityPacket.class) +public class JavaTeleportEntityTranslator extends PacketTranslator { @Override - public void translate(ServerEntityTeleportPacket packet, GeyserSession session) { + public void translate(GeyserSession session, ClientboundTeleportEntityPacket packet) { Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { entity = session.getPlayerEntity(); } if (entity == null) return; - entity.teleport(session, Vector3f.from(packet.getX(), packet.getY(), packet.getZ()), packet.getYaw(), packet.getPitch(), packet.isOnGround()); + entity.teleport(Vector3f.from(packet.getX(), packet.getY(), packet.getZ()), packet.getYaw(), packet.getPitch(), packet.isOnGround()); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaUpdateAttributesTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaUpdateAttributesTranslator.java new file mode 100644 index 000000000..c3a4e4ac4 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaUpdateAttributesTranslator.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java.entity; + +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundUpdateAttributesPacket; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.type.LivingEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; + +@Translator(packet = ClientboundUpdateAttributesPacket.class) +public class JavaUpdateAttributesTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundUpdateAttributesPacket packet) { + Entity entity; + if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { + entity = session.getPlayerEntity(); + } else { + entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); + } + if (!(entity instanceof LivingEntity livingEntity)) return; + + livingEntity.updateBedrockAttributes(session, packet.getAttributes()); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEffectTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaUpdateMobEffectTranslator.java similarity index 66% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEffectTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaUpdateMobEffectTranslator.java index 5905b1ec9..2d4161739 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEffectTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaUpdateMobEffectTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,27 +23,27 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java.entity; +package org.geysermc.geyser.translator.protocol.java.entity; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.PlayerEntity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.EntityUtils; - -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityEffectPacket; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundUpdateMobEffectPacket; import com.nukkitx.protocol.bedrock.packet.MobEffectPacket; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.util.EntityUtils; -@Translator(packet = ServerEntityEffectPacket.class) -public class JavaEntityEffectTranslator extends PacketTranslator { +@Translator(packet = ClientboundUpdateMobEffectPacket.class) +public class JavaUpdateMobEffectTranslator extends PacketTranslator { @Override - public void translate(ServerEntityEffectPacket packet, GeyserSession session) { - Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); + public void translate(GeyserSession session, ClientboundUpdateMobEffectPacket packet) { + Entity entity; if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { entity = session.getPlayerEntity(); - ((PlayerEntity) entity).getEffectCache().addEffect(packet.getEffect(), packet.getAmplifier()); + session.getEffectCache().setEffect(packet.getEffect(), packet.getAmplifier()); + } else { + entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); } if (entity == null) return; diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaBlockBreakAckTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaBlockBreakAckTranslator.java new file mode 100644 index 000000000..6fbdb4fc6 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaBlockBreakAckTranslator.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java.entity.player; + +import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.player.ClientboundBlockBreakAckPacket; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.util.ChunkUtils; + +@Translator(packet = ClientboundBlockBreakAckPacket.class) +public class JavaBlockBreakAckTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundBlockBreakAckPacket packet) { + ChunkUtils.updateBlock(session, packet.getNewState(), packet.getPosition()); + if (packet.getAction() == PlayerAction.START_DIGGING && !packet.isSuccessful()) { + LevelEventPacket stopBreak = new LevelEventPacket(); + stopBreak.setType(LevelEventType.BLOCK_STOP_BREAK); + stopBreak.setPosition(Vector3f.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ())); + stopBreak.setData(0); + session.setBreakingBlock(BlockStateValues.JAVA_AIR_ID); + session.sendUpstreamPacket(stopBreak); + } + } +} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaDisplayScoreboardTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerAbilitiesTranslator.java similarity index 59% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaDisplayScoreboardTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerAbilitiesTranslator.java index b42ac78e4..98b69a0b8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaDisplayScoreboardTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerAbilitiesTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,20 +23,20 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java.scoreboard; +package org.geysermc.geyser.translator.protocol.java.entity.player; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.player.ClientboundPlayerAbilitiesPacket; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; -import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerDisplayScoreboardPacket; - -@Translator(packet = ServerDisplayScoreboardPacket.class) -public class JavaDisplayScoreboardTranslator extends PacketTranslator { +@Translator(packet = ClientboundPlayerAbilitiesPacket.class) +public class JavaPlayerAbilitiesTranslator extends PacketTranslator { @Override - public void translate(ServerDisplayScoreboardPacket packet, GeyserSession session) { - session.getWorldCache().getScoreboard() - .displayObjective(packet.getName(), packet.getPosition()); + public void translate(GeyserSession session, ClientboundPlayerAbilitiesPacket packet) { + session.setCanFly(packet.isCanFly()); + session.setFlying(packet.isFlying()); + session.sendAdventureSettings(); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerListEntryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoTranslator.java similarity index 63% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerListEntryTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoTranslator.java index da402f66c..d8ffdccb6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerListEntryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,25 +23,24 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java.entity.player; - -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.entity.PlayerEntity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.SkinUtils; +package org.geysermc.geyser.translator.protocol.java.entity.player; import com.github.steveice10.mc.protocol.data.game.PlayerListEntry; import com.github.steveice10.mc.protocol.data.game.PlayerListEntryAction; -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPlayerListEntryPacket; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundPlayerInfoPacket; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.skin.SkinManager; -@Translator(packet = ServerPlayerListEntryPacket.class) -public class JavaPlayerListEntryTranslator extends PacketTranslator { +@Translator(packet = ClientboundPlayerInfoPacket.class) +public class JavaPlayerInfoTranslator extends PacketTranslator { @Override - public void translate(ServerPlayerListEntryPacket packet, GeyserSession session) { + public void translate(GeyserSession session, ClientboundPlayerInfoPacket packet) { if (packet.getAction() != PlayerListEntryAction.ADD_PLAYER && packet.getAction() != PlayerListEntryAction.REMOVE_PLAYER) return; @@ -50,15 +49,13 @@ public class JavaPlayerListEntryTranslator extends PacketTranslator { PlayerEntity playerEntity; boolean self = entry.getProfile().getId().equals(session.getPlayerEntity().getUuid()); if (self) { // Entity is ourself playerEntity = session.getPlayerEntity(); - SkinUtils.requestAndHandleSkinAndCape(playerEntity, session, skinAndCape -> - GeyserConnector.getInstance().getLogger().debug("Loaded Local Bedrock Java Skin Data")); } else { playerEntity = session.getEntityCache().getPlayerEntity(entry.getProfile().getId()); } @@ -66,45 +63,54 @@ public class JavaPlayerListEntryTranslator extends PacketTranslator + GeyserImpl.getInstance().getLogger().debug("Loaded Local Bedrock Java Skin Data for " + session.getClientData().getUsername())); + } else { + playerEntity.setValid(true); + PlayerListPacket.Entry playerListEntry = SkinManager.buildCachedEntry(session, playerEntity); - translate.getEntries().add(playerListEntry); - break; - case REMOVE_PLAYER: - PlayerEntity entity = session.getEntityCache().getPlayerEntity(entry.getProfile().getId()); + translate.getEntries().add(playerListEntry); + } + } + case REMOVE_PLAYER -> { + // As the player entity is no longer present, we can remove the entry + PlayerEntity entity = session.getEntityCache().removePlayerEntity(entry.getProfile().getId()); if (entity != null) { // Just remove the entity's player list status // Don't despawn the entity - the Java server will also take care of that. entity.setPlayerList(false); } - // As the player entity is no longer present, we can remove the entry - session.getEntityCache().removePlayerEntity(entry.getProfile().getId()); if (entity == session.getPlayerEntity()) { // If removing ourself we use our AuthData UUID - translate.getEntries().add(new PlayerListPacket.Entry(session.getAuthData().getUUID())); + translate.getEntries().add(new PlayerListPacket.Entry(session.getAuthData().uuid())); } else { translate.getEntries().add(new PlayerListPacket.Entry(entry.getProfile().getId())); } - break; + } } } - if (packet.getAction() == PlayerListEntryAction.REMOVE_PLAYER || session.getUpstream().isInitialized()) { + if (!translate.getEntries().isEmpty()) { session.sendUpstreamPacket(translate); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java new file mode 100644 index 000000000..3e94321f9 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java.entity.player; + +import com.github.steveice10.mc.protocol.data.game.entity.player.PositionElement; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.player.ClientboundPlayerPositionPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.level.ServerboundAcceptTeleportationPacket; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData; +import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; +import com.nukkitx.protocol.bedrock.packet.RespawnPacket; +import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.TeleportCache; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.util.ChunkUtils; +import org.geysermc.geyser.util.EntityUtils; +import org.geysermc.geyser.text.GeyserLocale; + +@Translator(packet = ClientboundPlayerPositionPacket.class) +public class JavaPlayerPositionTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundPlayerPositionPacket packet) { + if (!session.isLoggedIn()) + return; + + PlayerEntity entity = session.getPlayerEntity(); + + if (!session.isSpawned()) { + Vector3f pos = Vector3f.from(packet.getX(), packet.getY(), packet.getZ()); + entity.setPosition(pos); + entity.setYaw(packet.getYaw()); + entity.setPitch(packet.getPitch()); + entity.setHeadYaw(packet.getYaw()); + + RespawnPacket respawnPacket = new RespawnPacket(); + respawnPacket.setRuntimeEntityId(0); // Bedrock server behavior + respawnPacket.setPosition(entity.getPosition()); + respawnPacket.setState(RespawnPacket.State.SERVER_READY); + session.sendUpstreamPacket(respawnPacket); + + entity.updateBedrockMetadata(); + + MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); + movePlayerPacket.setRuntimeEntityId(entity.getGeyserId()); + movePlayerPacket.setPosition(entity.getPosition()); + movePlayerPacket.setRotation(Vector3f.from(packet.getPitch(), packet.getYaw(), 0)); + movePlayerPacket.setMode(MovePlayerPacket.Mode.RESPAWN); + + session.sendUpstreamPacket(movePlayerPacket); + session.setSpawned(true); + + ServerboundAcceptTeleportationPacket teleportConfirmPacket = new ServerboundAcceptTeleportationPacket(packet.getTeleportId()); + session.sendDownstreamPacket(teleportConfirmPacket); + + ChunkUtils.updateChunkPosition(session, pos.toInt()); + + session.getGeyser().getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.entity.player.spawn", packet.getX(), packet.getY(), packet.getZ())); + return; + } + + session.setSpawned(true); + + if (packet.isDismountVehicle() && session.getRidingVehicleEntity() != null) { + Entity vehicle = session.getRidingVehicleEntity(); + SetEntityLinkPacket linkPacket = new SetEntityLinkPacket(); + linkPacket.setEntityLink(new EntityLinkData(vehicle.getGeyserId(), entity.getGeyserId(), EntityLinkData.Type.REMOVE, false, false)); + session.sendUpstreamPacket(linkPacket); + vehicle.getPassengers().remove(entity.getEntityId()); + entity.getDirtyMetadata().put(EntityData.RIDER_ROTATION_LOCKED, (byte) 0); + entity.getDirtyMetadata().put(EntityData.RIDER_MAX_ROTATION, 0f); + entity.getDirtyMetadata().put(EntityData.RIDER_MIN_ROTATION, 0f); + entity.getDirtyMetadata().put(EntityData.RIDER_ROTATION_OFFSET, 0f); + session.setRidingVehicleEntity(null); + entity.updateBedrockMetadata(); + + EntityUtils.updateMountOffset(entity, vehicle, false, false, entity.getPassengers().size() > 1); + } + + // If coordinates are relative, then add to the existing coordinate + double newX = packet.getX() + + (packet.getRelative().contains(PositionElement.X) ? entity.getPosition().getX() : 0); + double newY = packet.getY() + + (packet.getRelative().contains(PositionElement.Y) ? entity.getPosition().getY() - EntityDefinitions.PLAYER.offset() : 0); + double newZ = packet.getZ() + + (packet.getRelative().contains(PositionElement.Z) ? entity.getPosition().getZ() : 0); + + float newPitch = packet.getPitch() + + (packet.getRelative().contains(PositionElement.PITCH) ? entity.getBedrockRotation().getX() : 0); + float newYaw = packet.getYaw() + + (packet.getRelative().contains(PositionElement.YAW) ? entity.getBedrockRotation().getY() : 0); + + session.getGeyser().getLogger().debug("Teleport from " + entity.getPosition().getX() + " " + (entity.getPosition().getY() - EntityDefinitions.PLAYER.offset()) + " " + entity.getPosition().getZ()); + + session.addTeleport(new TeleportCache(newX, newY, newZ, newPitch, newYaw, packet.getTeleportId())); + + Vector3f lastPlayerPosition = entity.getPosition().down(EntityDefinitions.PLAYER.offset()); + float lastPlayerPitch = entity.getBedrockRotation().getX(); + Vector3f teleportDestination = Vector3f.from(newX, newY, newZ); + entity.moveAbsolute(teleportDestination, newYaw, newPitch, true, true); + + session.getGeyser().getLogger().debug("to " + entity.getPosition().getX() + " " + (entity.getPosition().getY() - EntityDefinitions.PLAYER.offset()) + " " + entity.getPosition().getZ()); + + // Bedrock ignores teleports that are extremely close to the player's original position and orientation, + // so check if we can immediately confirm the teleport + if (lastPlayerPosition.distanceSquared(teleportDestination) < 0.001 && Math.abs(newPitch - lastPlayerPitch) < 5) { + session.confirmTeleport(lastPlayerPosition.toDouble()); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerChangeHeldItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaSetCarriedItemTranslator.java similarity index 66% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerChangeHeldItemTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaSetCarriedItemTranslator.java index 8530ad2a5..dc10bad34 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerChangeHeldItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaSetCarriedItemTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,25 +23,25 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java.entity.player; +package org.geysermc.geyser.translator.protocol.java.entity.player; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerChangeHeldItemPacket; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.player.ClientboundSetCarriedItemPacket; import com.nukkitx.protocol.bedrock.packet.PlayerHotbarPacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; -@Translator(packet = ServerPlayerChangeHeldItemPacket.class) -public class JavaPlayerChangeHeldItemTranslator extends PacketTranslator { +@Translator(packet = ClientboundSetCarriedItemPacket.class) +public class JavaSetCarriedItemTranslator extends PacketTranslator { @Override - public void translate(ServerPlayerChangeHeldItemPacket packet, GeyserSession session) { + public void translate(GeyserSession session, ClientboundSetCarriedItemPacket packet) { PlayerHotbarPacket hotbarPacket = new PlayerHotbarPacket(); hotbarPacket.setContainerId(0); hotbarPacket.setSelectedHotbarSlot(packet.getSlot()); hotbarPacket.setSelectHotbarSlot(true); session.sendUpstreamPacket(hotbarPacket); - session.getInventory().setHeldItemSlot(packet.getSlot()); + session.getPlayerInventory().setHeldItemSlot(packet.getSlot()); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaSetExperienceTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaSetExperienceTranslator.java new file mode 100644 index 000000000..3ac9bf9e3 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaSetExperienceTranslator.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java.entity.player; + +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.player.ClientboundSetExperiencePacket; +import com.nukkitx.protocol.bedrock.data.AttributeData; +import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; +import org.geysermc.geyser.entity.attribute.GeyserAttributeType; +import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; + +import java.util.Arrays; + +@Translator(packet = ClientboundSetExperiencePacket.class) +public class JavaSetExperienceTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundSetExperiencePacket packet) { + SessionPlayerEntity entity = session.getPlayerEntity(); + + AttributeData experience = GeyserAttributeType.EXPERIENCE.getAttribute(packet.getExperience()); + entity.getAttributes().put(GeyserAttributeType.EXPERIENCE, experience); + AttributeData experienceLevel = GeyserAttributeType.EXPERIENCE_LEVEL.getAttribute(packet.getLevel()); + entity.getAttributes().put(GeyserAttributeType.EXPERIENCE_LEVEL, experienceLevel); + + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); + attributesPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId()); + attributesPacket.setAttributes(Arrays.asList(experience, experienceLevel)); + session.sendUpstreamPacket(attributesPacket); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaSetHealthTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaSetHealthTranslator.java new file mode 100644 index 000000000..8e96ed42e --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaSetHealthTranslator.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java.entity.player; + +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.player.ClientboundSetHealthPacket; +import com.nukkitx.protocol.bedrock.data.AttributeData; +import com.nukkitx.protocol.bedrock.packet.SetHealthPacket; +import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; +import org.geysermc.geyser.entity.attribute.GeyserAttributeType; +import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; + +import java.util.List; + +@Translator(packet = ClientboundSetHealthPacket.class) +public class JavaSetHealthTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundSetHealthPacket packet) { + SessionPlayerEntity entity = session.getPlayerEntity(); + + int health = (int) Math.ceil(packet.getHealth()); + SetHealthPacket setHealthPacket = new SetHealthPacket(); + setHealthPacket.setHealth(health); + session.sendUpstreamPacket(setHealthPacket); + + entity.setHealth(packet.getHealth()); + + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); + List attributes = attributesPacket.getAttributes(); + + AttributeData healthAttribute = entity.createHealthAttribute(); + entity.getAttributes().put(GeyserAttributeType.HEALTH, healthAttribute); + attributes.add(healthAttribute); + + AttributeData hungerAttribute = GeyserAttributeType.HUNGER.getAttribute(packet.getFood()); + entity.getAttributes().put(GeyserAttributeType.HUNGER, hungerAttribute); + attributes.add(hungerAttribute); + + AttributeData saturationAttribute = GeyserAttributeType.SATURATION.getAttribute(packet.getSaturation()); + entity.getAttributes().put(GeyserAttributeType.SATURATION, saturationAttribute); + attributes.add(saturationAttribute); + + attributesPacket.setRuntimeEntityId(entity.getGeyserId()); + session.sendUpstreamPacket(attributesPacket); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddEntityTranslator.java new file mode 100644 index 000000000..b80b59ff4 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddEntityTranslator.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java.entity.spawn; + +import com.github.steveice10.mc.protocol.data.game.entity.object.Direction; +import com.github.steveice10.mc.protocol.data.game.entity.object.FallingBlockData; +import com.github.steveice10.mc.protocol.data.game.entity.object.ProjectileData; +import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.spawn.ClientboundAddEntityPacket; +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.geyser.entity.*; +import org.geysermc.geyser.entity.factory.BaseEntityFactory; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.type.FallingBlockEntity; +import org.geysermc.geyser.entity.type.FishingHookEntity; +import org.geysermc.geyser.entity.type.ItemFrameEntity; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.text.GeyserLocale; + +@Translator(packet = ClientboundAddEntityPacket.class) +public class JavaAddEntityTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundAddEntityPacket packet) { + Vector3f position = Vector3f.from(packet.getX(), packet.getY(), packet.getZ()); + Vector3f motion = Vector3f.from(packet.getMotionX(), packet.getMotionY(), packet.getMotionZ()); + float yaw = packet.getYaw(); + float pitch = packet.getPitch(); + + EntityDefinition definition = Registries.ENTITY_DEFINITIONS.get(packet.getType()); + if (definition == null) { + session.getGeyser().getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.entity.type_null", packet.getType())); + return; + } + + Entity entity; + if (packet.getType() == EntityType.FALLING_BLOCK) { + entity = new FallingBlockEntity(session, packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), packet.getUuid(), + position, motion, yaw, pitch, ((FallingBlockData) packet.getData()).getId()); + } else if (packet.getType() == EntityType.ITEM_FRAME || packet.getType() == EntityType.GLOW_ITEM_FRAME) { + // Item frames need the hanging direction + entity = new ItemFrameEntity(session, packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), packet.getUuid(), + definition, position, motion, yaw, pitch, (Direction) packet.getData()); + } else if (packet.getType() == EntityType.FISHING_BOBBER) { + // Fishing bobbers need the owner for the line + int ownerEntityId = ((ProjectileData) packet.getData()).getOwnerId(); + Entity owner; + if (session.getPlayerEntity().getEntityId() == ownerEntityId) { + owner = session.getPlayerEntity(); + } else { + owner = session.getEntityCache().getEntityByJavaId(ownerEntityId); + } + // Java clients only spawn fishing hooks with a player as its owner + if (owner instanceof PlayerEntity) { + entity = new FishingHookEntity(session, packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), packet.getUuid(), + position, motion, yaw, pitch, (PlayerEntity) owner); + } else { + return; + } + } else { + entity = ((BaseEntityFactory) definition.factory()).create(session, packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), + packet.getUuid(), definition, position, motion, yaw, pitch, 0f); + } + session.getEntityCache().spawnEntity(entity); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnExpOrbTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddExperienceOrbTranslator.java similarity index 58% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnExpOrbTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddExperienceOrbTranslator.java index 79ba3e0f5..33e230c14 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnExpOrbTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddExperienceOrbTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,28 +23,25 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java.entity.spawn; +package org.geysermc.geyser.translator.protocol.java.entity.spawn; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.ExpOrbEntity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnExpOrbPacket; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.spawn.ClientboundAddExperienceOrbPacket; import com.nukkitx.math.vector.Vector3f; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.type.ExpOrbEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; -@Translator(packet = ServerSpawnExpOrbPacket.class) -public class JavaSpawnExpOrbTranslator extends PacketTranslator { +@Translator(packet = ClientboundAddExperienceOrbPacket.class) +public class JavaAddExperienceOrbTranslator extends PacketTranslator { @Override - public void translate(ServerSpawnExpOrbPacket packet, GeyserSession session) { + public void translate(GeyserSession session, ClientboundAddExperienceOrbPacket packet) { Vector3f position = Vector3f.from(packet.getX(), packet.getY(), packet.getZ()); Entity entity = new ExpOrbEntity( - packet.getExp(), packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), - EntityType.EXPERIENCE_ORB, position, Vector3f.ZERO, Vector3f.ZERO + session, packet.getExp(), packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), position ); session.getEntityCache().spawnEntity(entity); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddMobTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddMobTranslator.java new file mode 100644 index 000000000..e6b82f365 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddMobTranslator.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java.entity.spawn; + +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.spawn.ClientboundAddMobPacket; +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.factory.BaseEntityFactory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.text.GeyserLocale; + +@Translator(packet = ClientboundAddMobPacket.class) +public class JavaAddMobTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundAddMobPacket packet) { + Vector3f position = Vector3f.from(packet.getX(), packet.getY(), packet.getZ()); + Vector3f motion = Vector3f.from(packet.getMotionX(), packet.getMotionY(), packet.getMotionZ()); + + EntityDefinition definition = Registries.ENTITY_DEFINITIONS.get(packet.getType()); + if (definition == null) { + session.getGeyser().getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.entity.type_null", packet.getType())); + return; + } + + Entity entity = ((BaseEntityFactory) definition.factory()).create(session, packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), + packet.getUuid(), definition, position, motion, packet.getYaw(), packet.getPitch(), packet.getHeadYaw() + ); + session.getEntityCache().spawnEntity(entity); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddPaintingTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddPaintingTranslator.java new file mode 100644 index 000000000..433d78477 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddPaintingTranslator.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java.entity.spawn; + +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.spawn.ClientboundAddPaintingPacket; +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.geyser.entity.type.PaintingEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.level.PaintingType; + +@Translator(packet = ClientboundAddPaintingPacket.class) +public class JavaAddPaintingTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundAddPaintingPacket packet) { + Vector3f position = Vector3f.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ()); + + PaintingEntity entity = new PaintingEntity(session, packet.getEntityId(), + session.getEntityCache().getNextEntityId().incrementAndGet(), packet.getUuid(), + position, PaintingType.getByPaintingType(packet.getPaintingType()), packet.getDirection().getHorizontalIndex()); + + session.getEntityCache().spawnEntity(entity); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddPlayerTranslator.java similarity index 54% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddPlayerTranslator.java index 99a6b0493..74dcaf022 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddPlayerTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,46 +23,48 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java.entity.spawn; +package org.geysermc.geyser.translator.protocol.java.entity.spawn; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnPlayerPacket; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.spawn.ClientboundAddPlayerPacket; import com.nukkitx.math.vector.Vector3f; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.entity.PlayerEntity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.LanguageUtils; -import org.geysermc.connector.utils.SkinUtils; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.skin.SkinManager; -@Translator(packet = ServerSpawnPlayerPacket.class) -public class JavaSpawnPlayerTranslator extends PacketTranslator { +@Translator(packet = ClientboundAddPlayerPacket.class) +public class JavaAddPlayerTranslator extends PacketTranslator { @Override - public void translate(ServerSpawnPlayerPacket packet, GeyserSession session) { + public void translate(GeyserSession session, ClientboundAddPlayerPacket packet) { Vector3f position = Vector3f.from(packet.getX(), packet.getY(), packet.getZ()); - Vector3f rotation = Vector3f.from(packet.getYaw(), packet.getPitch(), packet.getYaw()); + float yaw = packet.getYaw(); + float pitch = packet.getPitch(); + float headYaw = packet.getYaw(); PlayerEntity entity; if (packet.getUuid().equals(session.getPlayerEntity().getUuid())) { // Server is sending a fake version of the current player - entity = new PlayerEntity(session.getPlayerEntity().getProfile(), packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), position, Vector3f.ZERO, rotation); + entity = new PlayerEntity(session, packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), session.getPlayerEntity().getProfile(), position, Vector3f.ZERO, yaw, pitch, headYaw); } else { entity = session.getEntityCache().getPlayerEntity(packet.getUuid()); if (entity == null) { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.entity.player.failed_list", packet.getUuid())); + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.entity.player.failed_list", packet.getUuid())); return; } entity.setEntityId(packet.getEntityId()); entity.setPosition(position); - entity.setRotation(rotation); + entity.setYaw(yaw); + entity.setPitch(pitch); + entity.setHeadYaw(headYaw); } session.getEntityCache().cacheEntity(entity); - if (session.getUpstream().isInitialized()) { - entity.sendPlayer(session); - SkinUtils.requestAndHandleSkinAndCape(entity, session, null); - } + entity.sendPlayer(); + SkinManager.requestAndHandleSkinAndCape(entity, session, null); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerCloseTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerCloseTranslator.java new file mode 100644 index 000000000..9a4ff8f26 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerCloseTranslator.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java.inventory; + +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.ClientboundContainerClosePacket; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.util.InventoryUtils; + +@Translator(packet = ClientboundContainerClosePacket.class) +public class JavaContainerCloseTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundContainerClosePacket packet) { + // Sometimes the server can request a window close of ID 0... when the window isn't even open + // Don't confirm in this instance + InventoryUtils.closeInventory(session, packet.getContainerId(), (session.getOpenInventory() != null && session.getOpenInventory().getId() == packet.getContainerId())); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetContentTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetContentTranslator.java new file mode 100644 index 000000000..0d572ca92 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetContentTranslator.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java.inventory; + +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.ClientboundContainerSetContentPacket; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.translator.inventory.InventoryTranslator; +import org.geysermc.geyser.util.InventoryUtils; + +@Translator(packet = ClientboundContainerSetContentPacket.class) +public class JavaContainerSetContentTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundContainerSetContentPacket packet) { + Inventory inventory = InventoryUtils.getInventory(session, packet.getContainerId()); + if (inventory == null) + return; + + inventory.setStateId(packet.getStateId()); + + for (int i = 0; i < packet.getItems().length; i++) { + GeyserItemStack newItem = GeyserItemStack.from(packet.getItems()[i]); + inventory.setItem(i, newItem, session); + } + + InventoryTranslator translator = session.getInventoryTranslator(); + if (translator != null) { + translator.updateInventory(session, inventory); + } + + session.getPlayerInventory().setCursor(GeyserItemStack.from(packet.getCarriedItem()), session); + InventoryUtils.updateCursor(session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowPropertyTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetDataTranslator.java similarity index 55% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowPropertyTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetDataTranslator.java index daebed1b6..2ffab0d38 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowPropertyTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetDataTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,25 +23,26 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java.window; +package org.geysermc.geyser.translator.protocol.java.inventory; -import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerWindowPropertyPacket; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.ClientboundContainerSetDataPacket; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.translator.inventory.InventoryTranslator; +import org.geysermc.geyser.util.InventoryUtils; -@Translator(packet = ServerWindowPropertyPacket.class) -public class JavaWindowPropertyTranslator extends PacketTranslator { +@Translator(packet = ClientboundContainerSetDataPacket.class) +public class JavaContainerSetDataTranslator extends PacketTranslator { @Override - public void translate(ServerWindowPropertyPacket packet, GeyserSession session) { - Inventory inventory = session.getInventoryCache().getInventories().get(packet.getWindowId()); - if (inventory == null || (packet.getWindowId() != 0 && inventory.getWindowType() == null)) + public void translate(GeyserSession session, ClientboundContainerSetDataPacket packet) { + Inventory inventory = InventoryUtils.getInventory(session, packet.getContainerId()); + if (inventory == null) return; - InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()); + InventoryTranslator translator = session.getInventoryTranslator(); if (translator != null) { translator.updateProperty(session, inventory, packet.getRawProperty(), packet.getValue()); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java new file mode 100644 index 000000000..54cc91ca6 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaContainerSetSlotTranslator.java @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java.inventory; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; +import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; +import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; +import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; +import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.ClientboundContainerSetSlotPacket; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; +import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket; +import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; +import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.translator.inventory.InventoryTranslator; +import org.geysermc.geyser.translator.inventory.CraftingInventoryTranslator; +import org.geysermc.geyser.translator.inventory.PlayerInventoryTranslator; +import org.geysermc.geyser.translator.inventory.item.ItemTranslator; +import org.geysermc.geyser.util.InventoryUtils; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +@Translator(packet = ClientboundContainerSetSlotPacket.class) +public class JavaContainerSetSlotTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundContainerSetSlotPacket packet) { + if (packet.getContainerId() == 255) { //cursor + GeyserItemStack newItem = GeyserItemStack.from(packet.getItem()); + session.getPlayerInventory().setCursor(newItem, session); + InventoryUtils.updateCursor(session); + return; + } + + //TODO: support window id -2, should update player inventory + Inventory inventory = InventoryUtils.getInventory(session, packet.getContainerId()); + if (inventory == null) + return; + + inventory.setStateId(packet.getStateId()); + + InventoryTranslator translator = session.getInventoryTranslator(); + if (translator != null) { + if (session.getCraftingGridFuture() != null) { + session.getCraftingGridFuture().cancel(false); + } + session.setCraftingGridFuture(session.scheduleInEventLoop(() -> updateCraftingGrid(session, packet, inventory, translator), 150, TimeUnit.MILLISECONDS)); + + GeyserItemStack newItem = GeyserItemStack.from(packet.getItem()); + if (packet.getContainerId() == 0 && !(translator instanceof PlayerInventoryTranslator)) { + // In rare cases, the window ID can still be 0 but Java treats it as valid + session.getPlayerInventory().setItem(packet.getSlot(), newItem, session); + InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR.updateSlot(session, session.getPlayerInventory(), packet.getSlot()); + } else { + inventory.setItem(packet.getSlot(), newItem, session); + translator.updateSlot(session, inventory, packet.getSlot()); + } + } + } + + private static void updateCraftingGrid(GeyserSession session, ClientboundContainerSetSlotPacket packet, Inventory inventory, InventoryTranslator translator) { + if (packet.getSlot() == 0) { + int gridSize; + if (translator instanceof PlayerInventoryTranslator) { + gridSize = 4; + } else if (translator instanceof CraftingInventoryTranslator) { + gridSize = 9; + } else { + return; + } + + if (packet.getItem() == null || packet.getItem().getId() == 0) { + return; + } + + int offset = gridSize == 4 ? 28 : 32; + int gridDimensions = gridSize == 4 ? 2 : 3; + int firstRow = -1, height = -1; + int firstCol = -1, width = -1; + for (int row = 0; row < gridDimensions; row++) { + for (int col = 0; col < gridDimensions; col++) { + if (!inventory.getItem(col + (row * gridDimensions) + 1).isEmpty()) { + if (firstRow == -1) { + firstRow = row; + firstCol = col; + } else { + firstCol = Math.min(firstCol, col); + } + height = Math.max(height, row); + width = Math.max(width, col); + } + } + } + + //empty grid + if (firstRow == -1) { + return; + } + + height += -firstRow + 1; + width += -firstCol + 1; + + recipes: + for (Recipe recipe : session.getCraftingRecipes().values()) { + if (recipe.getType() == RecipeType.CRAFTING_SHAPED) { + ShapedRecipeData data = (ShapedRecipeData) recipe.getData(); + if (!data.getResult().equals(packet.getItem())) { + continue; + } + if (data.getWidth() != width || data.getHeight() != height || width * height != data.getIngredients().length) { + continue; + } + + Ingredient[] ingredients = data.getIngredients(); + if (!testShapedRecipe(ingredients, inventory, gridDimensions, firstRow, height, firstCol, width)) { + Ingredient[] mirroredIngredients = new Ingredient[data.getIngredients().length]; + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + mirroredIngredients[col + (row * width)] = ingredients[(width - 1 - col) + (row * width)]; + } + } + + if (Arrays.equals(ingredients, mirroredIngredients) || + !testShapedRecipe(mirroredIngredients, inventory, gridDimensions, firstRow, height, firstCol, width)) { + continue; + } + } + // Recipe is had, don't sent packet + return; + } else if (recipe.getType() == RecipeType.CRAFTING_SHAPELESS) { + ShapelessRecipeData data = (ShapelessRecipeData) recipe.getData(); + if (!data.getResult().equals(packet.getItem())) { + continue; + } + for (int i = 0; i < data.getIngredients().length; i++) { + Ingredient ingredient = data.getIngredients()[i]; + for (ItemStack itemStack : ingredient.getOptions()) { + boolean inventoryHasItem = false; + for (int j = 0; j < inventory.getSize(); j++) { + GeyserItemStack geyserItemStack = inventory.getItem(j); + if (geyserItemStack.isEmpty()) { + inventoryHasItem = itemStack == null || itemStack.getId() == 0; + if (inventoryHasItem) { + break; + } + } else if (itemStack.equals(geyserItemStack.getItemStack(1))) { + inventoryHasItem = true; + break; + } + } + if (!inventoryHasItem) { + continue recipes; + } + } + } + // Recipe is had, don't sent packet + return; + } + } + + UUID uuid = UUID.randomUUID(); + int newRecipeId = session.getLastRecipeNetId().incrementAndGet(); + + ItemData[] ingredients = new ItemData[height * width]; + //construct ingredient list and clear slots on client + Ingredient[] javaIngredients = new Ingredient[height * width]; + int index = 0; + for (int row = firstRow; row < height + firstRow; row++) { + for (int col = firstCol; col < width + firstCol; col++) { + GeyserItemStack geyserItemStack = inventory.getItem(col + (row * gridDimensions) + 1); + ingredients[index] = geyserItemStack.getItemData(session); + ItemStack[] itemStacks = new ItemStack[] {geyserItemStack.isEmpty() ? null : geyserItemStack.getItemStack(1)}; + javaIngredients[index] = new Ingredient(itemStacks); + + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.UI); + slotPacket.setSlot(col + (row * gridDimensions) + offset); + slotPacket.setItem(ItemData.AIR); + session.sendUpstreamPacket(slotPacket); + index++; + } + } + + ShapedRecipeData data = new ShapedRecipeData(width, height, "", javaIngredients, packet.getItem()); + // Cache this recipe so we know the client has received it + session.getCraftingRecipes().put(newRecipeId, new Recipe(RecipeType.CRAFTING_SHAPED, uuid.toString(), data)); + + CraftingDataPacket craftPacket = new CraftingDataPacket(); + craftPacket.getCraftingData().add(CraftingData.fromShaped( + uuid.toString(), + width, + height, + Arrays.asList(ingredients), + Collections.singletonList(ItemTranslator.translateToBedrock(session, packet.getItem())), + uuid, + "crafting_table", + 0, + newRecipeId + )); + craftPacket.setCleanRecipes(false); + session.sendUpstreamPacket(craftPacket); + + index = 0; + for (int row = firstRow; row < height + firstRow; row++) { + for (int col = firstCol; col < width + firstCol; col++) { + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.UI); + slotPacket.setSlot(col + (row * gridDimensions) + offset); + slotPacket.setItem(ingredients[index]); + session.sendUpstreamPacket(slotPacket); + index++; + } + } + } + } + + private static boolean testShapedRecipe(Ingredient[] ingredients, Inventory inventory, int gridDimensions, int firstRow, int height, int firstCol, int width) { + int ingredientIndex = 0; + for (int row = firstRow; row < height + firstRow; row++) { + for (int col = firstCol; col < width + firstCol; col++) { + GeyserItemStack geyserItemStack = inventory.getItem(col + (row * gridDimensions) + 1); + Ingredient ingredient = ingredients[ingredientIndex++]; + if (ingredient.getOptions().length == 0) { + if (!geyserItemStack.isEmpty()) { + return false; + } + } else { + boolean inventoryHasItem = false; + for (ItemStack item : ingredient.getOptions()) { + if (Objects.equals(geyserItemStack.getItemStack(1), item)) { + inventoryHasItem = true; + break; + } + } + if (!inventoryHasItem) { + return false; + } + } + } + } + return true; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaHorseScreenOpenTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaHorseScreenOpenTranslator.java new file mode 100644 index 000000000..4e672bdf9 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaHorseScreenOpenTranslator.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.translator.protocol.java.inventory; + +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.ClientboundHorseScreenOpenPacket; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.nbt.NbtType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.packet.UpdateEquipPacket; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.type.living.animal.horse.ChestedHorseEntity; +import org.geysermc.geyser.entity.type.living.animal.horse.LlamaEntity; +import org.geysermc.geyser.inventory.Container; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.translator.inventory.InventoryTranslator; +import org.geysermc.geyser.translator.inventory.horse.DonkeyInventoryTranslator; +import org.geysermc.geyser.translator.inventory.horse.HorseInventoryTranslator; +import org.geysermc.geyser.translator.inventory.horse.LlamaInventoryTranslator; +import org.geysermc.geyser.util.InventoryUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Translator(packet = ClientboundHorseScreenOpenPacket.class) +public class JavaHorseScreenOpenTranslator extends PacketTranslator { + + private static final NbtMap ARMOR_SLOT; + private static final NbtMap CARPET_SLOT; + private static final NbtMap SADDLE_SLOT; + + static { + // Build the NBT mappings that Bedrock wants to lay out the GUI + String[] acceptedHorseArmorIdentifiers = new String[] {"minecraft:horsearmorleather", "minecraft:horsearmoriron", + "minecraft:horsearmorgold", "minecraft:horsearmordiamond"}; + NbtMapBuilder armorBuilder = NbtMap.builder(); + List acceptedArmors = new ArrayList<>(4); + for (String identifier : acceptedHorseArmorIdentifiers) { + NbtMapBuilder acceptedItemBuilder = NbtMap.builder() + .putShort("Aux", Short.MAX_VALUE) + .putString("Name", identifier); + acceptedArmors.add(NbtMap.builder().putCompound("slotItem", acceptedItemBuilder.build()).build()); + } + armorBuilder.putList("acceptedItems", NbtType.COMPOUND, acceptedArmors); + NbtMapBuilder armorItem = NbtMap.builder() + .putShort("Aux", Short.MAX_VALUE) + .putString("Name", "minecraft:horsearmoriron"); + armorBuilder.putCompound("item", armorItem.build()); + armorBuilder.putInt("slotNumber", 1); + ARMOR_SLOT = armorBuilder.build(); + + NbtMapBuilder carpetBuilder = NbtMap.builder(); + NbtMapBuilder carpetItem = NbtMap.builder() + .putShort("Aux", Short.MAX_VALUE) + .putString("Name", "minecraft:carpet"); + List acceptedCarpet = Collections.singletonList(NbtMap.builder().putCompound("slotItem", carpetItem.build()).build()); + carpetBuilder.putList("acceptedItems", NbtType.COMPOUND, acceptedCarpet); + carpetBuilder.putCompound("item", carpetItem.build()); + carpetBuilder.putInt("slotNumber", 1); + CARPET_SLOT = carpetBuilder.build(); + + NbtMapBuilder saddleBuilder = NbtMap.builder(); + NbtMapBuilder acceptedSaddle = NbtMap.builder() + .putShort("Aux", Short.MAX_VALUE) + .putString("Name", "minecraft:saddle"); + List acceptedItem = Collections.singletonList(NbtMap.builder().putCompound("slotItem", acceptedSaddle.build()).build()); + saddleBuilder.putList("acceptedItems", NbtType.COMPOUND, acceptedItem); + saddleBuilder.putCompound("item", acceptedSaddle.build()); + saddleBuilder.putInt("slotNumber", 0); + SADDLE_SLOT = saddleBuilder.build(); + } + + @Override + public void translate(GeyserSession session, ClientboundHorseScreenOpenPacket packet) { + Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); + if (entity == null) { + return; + } + + UpdateEquipPacket updateEquipPacket = new UpdateEquipPacket(); + updateEquipPacket.setWindowId((short) packet.getContainerId()); + updateEquipPacket.setWindowType((short) ContainerType.HORSE.getId()); + updateEquipPacket.setUniqueEntityId(entity.getGeyserId()); + + NbtMapBuilder builder = NbtMap.builder(); + List slots = new ArrayList<>(); + + InventoryTranslator inventoryTranslator; + if (entity instanceof LlamaEntity) { + inventoryTranslator = new LlamaInventoryTranslator(packet.getNumberOfSlots()); + slots.add(CARPET_SLOT); + } else if (entity instanceof ChestedHorseEntity) { + inventoryTranslator = new DonkeyInventoryTranslator(packet.getNumberOfSlots()); + slots.add(SADDLE_SLOT); + } else { + inventoryTranslator = new HorseInventoryTranslator(packet.getNumberOfSlots()); + slots.add(SADDLE_SLOT); + slots.add(ARMOR_SLOT); + } + + // Build the NbtMap that sets the icons for Bedrock (e.g. sets the saddle outline on the saddle slot) + builder.putList("slots", NbtType.COMPOUND, slots); + + updateEquipPacket.setTag(builder.build()); + session.sendUpstreamPacket(updateEquipPacket); + + session.setInventoryTranslator(inventoryTranslator); + InventoryUtils.openInventory(session, new Container(entity.getNametag(), packet.getContainerId(), packet.getNumberOfSlots(), null, session.getPlayerInventory())); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaMerchantOffersTranslator.java similarity index 55% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java rename to core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaMerchantOffersTranslator.java index ad422a4cf..9fe1218ed 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/inventory/JavaMerchantOffersTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,71 +23,80 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.java.world; +package org.geysermc.geyser.translator.protocol.java.inventory; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade; -import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerTradeListPacket; +import com.github.steveice10.mc.protocol.data.game.inventory.VillagerTrade; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.ClientboundMerchantOffersPacket; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.UpdateTradePacket; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.item.ItemTranslator; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.inventory.Inventory; +import org.geysermc.geyser.inventory.MerchantContainer; +import org.geysermc.geyser.registry.type.ItemMapping; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.inventory.item.ItemTranslator; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; import java.util.ArrayList; import java.util.List; -@Translator(packet = ServerTradeListPacket.class) -public class JavaTradeListTranslator extends PacketTranslator { +@Translator(packet = ClientboundMerchantOffersPacket.class) +public class JavaMerchantOffersTranslator extends PacketTranslator { @Override - public void translate(ServerTradeListPacket packet, GeyserSession session) { - Entity villager = session.getPlayerEntity(); - session.setVillagerTrades(packet.getTrades()); - villager.getMetadata().put(EntityData.TRADE_TIER, packet.getVillagerLevel() - 1); - villager.getMetadata().put(EntityData.MAX_TRADE_TIER, 4); - villager.getMetadata().put(EntityData.TRADE_XP, packet.getExperience()); - villager.updateBedrockMetadata(session); + public void translate(GeyserSession session, ClientboundMerchantOffersPacket packet) { + Inventory openInventory = session.getOpenInventory(); + if (!(openInventory instanceof MerchantContainer merchantInventory && openInventory.getId() == packet.getContainerId())) { + return; + } + // No previous inventory was closed -> no need of queuing the merchant inventory + if (!openInventory.isPending()) { + openMerchant(session, packet, merchantInventory); + return; + } + + // The inventory is declared as pending due to previous closing inventory -> leads to an incorrect order of execution + // Handled in BedrockContainerCloseTranslator + merchantInventory.setPendingOffersPacket(packet); + } + + public static void openMerchant(GeyserSession session, ClientboundMerchantOffersPacket packet, MerchantContainer merchantInventory) { + // Retrieve the fake villager involved in the trade, and update its metadata to match with the window information + merchantInventory.setVillagerTrades(packet.getTrades()); + Entity villager = merchantInventory.getVillager(); + villager.getDirtyMetadata().put(EntityData.TRADE_TIER, packet.getVillagerLevel() - 1); + villager.getDirtyMetadata().put(EntityData.MAX_TRADE_TIER, 4); + villager.getDirtyMetadata().put(EntityData.TRADE_XP, packet.getExperience()); + villager.updateBedrockMetadata(); + + // Construct the packet that opens the trading window UpdateTradePacket updateTradePacket = new UpdateTradePacket(); updateTradePacket.setTradeTier(packet.getVillagerLevel() - 1); - updateTradePacket.setContainerId((short) packet.getWindowId()); + updateTradePacket.setContainerId((short) packet.getContainerId()); updateTradePacket.setContainerType(ContainerType.TRADE); - Inventory openInv = session.getInventoryCache().getOpenInventory(); - String displayName; - if (openInv != null && openInv.getId() == packet.getWindowId()) { - displayName = openInv.getTitle(); - } else { - Entity realVillager = session.getEntityCache().getEntityByGeyserId(session.getLastInteractedVillagerEid()); - if (realVillager != null && realVillager.getMetadata().containsKey(EntityData.NAMETAG) && realVillager.getMetadata().getString(EntityData.NAMETAG) != null) { - displayName = realVillager.getMetadata().getString(EntityData.NAMETAG); - } else { - displayName = realVillager != null && - realVillager.getEntityType() == EntityType.WANDERING_TRADER ? "Wandering Trader" : "Villager"; - } - } - updateTradePacket.setDisplayName(displayName); + updateTradePacket.setDisplayName(session.getOpenInventory().getTitle()); updateTradePacket.setSize(0); updateTradePacket.setNewTradingUi(true); updateTradePacket.setUsingEconomyTrade(true); updateTradePacket.setPlayerUniqueEntityId(session.getPlayerEntity().getGeyserId()); - updateTradePacket.setTraderUniqueEntityId(session.getPlayerEntity().getGeyserId()); + updateTradePacket.setTraderUniqueEntityId(villager.getGeyserId()); + NbtMapBuilder builder = NbtMap.builder(); - List tags = new ArrayList<>(); - for (VillagerTrade trade : packet.getTrades()) { + boolean addExtraTrade = packet.isRegularVillager() && packet.getVillagerLevel() < 5; + List tags = new ArrayList<>(addExtraTrade ? packet.getTrades().length + 1 : packet.getTrades().length); + for (int i = 0; i < packet.getTrades().length; i++) { + VillagerTrade trade = packet.getTrades()[i]; NbtMapBuilder recipe = NbtMap.builder(); - recipe.putInt("maxUses", trade.getMaxUses()); + recipe.putInt("netId", i + 1); + recipe.putInt("maxUses", trade.isTradeDisabled() ? 0 : trade.getMaxUses()); recipe.putInt("traderExp", trade.getXp()); recipe.putFloat("priceMultiplierA", trade.getPriceMultiplier()); recipe.put("sell", getItemTag(session, trade.getOutput(), 0)); @@ -106,7 +115,7 @@ public class JavaTradeListTranslator extends PacketTranslator