forked from GeyserMC/Geyser
		
	Merge branch 'master' of https://github.com/GeyserMC/Geyser into feature/addons
This commit is contained in:
		
						commit
						269e72c943
					
				
					 194 changed files with 5867 additions and 1975 deletions
				
			
		
							
								
								
									
										2
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -11,4 +11,4 @@ assignees: ''
 | 
			
		|||
Add a description
 | 
			
		||||
 | 
			
		||||
**Alternatives?**
 | 
			
		||||
Any alternatives you have tryed
 | 
			
		||||
List any alternatives you might have tried
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2
									
								
								.github/workflows/pullrequest.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/pullrequest.yml
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -22,7 +22,7 @@ jobs:
 | 
			
		|||
      - name: submodules-init
 | 
			
		||||
        uses: snickerbockers/submodules-init@v4
 | 
			
		||||
      - name: Build with Maven
 | 
			
		||||
        run: mvn -B package
 | 
			
		||||
        run: mvn -B package -T 2C
 | 
			
		||||
      - name: Archive artifacts (Geyser Standalone)
 | 
			
		||||
        uses: actions/upload-artifact@v2
 | 
			
		||||
        if: success()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -241,4 +241,5 @@ config.yml
 | 
			
		|||
logs/
 | 
			
		||||
public-key.pem
 | 
			
		||||
locales/
 | 
			
		||||
cache/
 | 
			
		||||
/cache/
 | 
			
		||||
/packs/
 | 
			
		||||
							
								
								
									
										35
									
								
								Jenkinsfile
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										35
									
								
								Jenkinsfile
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -5,7 +5,7 @@ pipeline {
 | 
			
		|||
        jdk 'Java 8'
 | 
			
		||||
    }
 | 
			
		||||
    options {
 | 
			
		||||
        buildDiscarder(logRotator(artifactNumToKeepStr: '5'))
 | 
			
		||||
        buildDiscarder(logRotator(artifactNumToKeepStr: '20'))
 | 
			
		||||
    }
 | 
			
		||||
    stages {
 | 
			
		||||
        stage ('Build') {
 | 
			
		||||
| 
						 | 
				
			
			@ -32,9 +32,40 @@ pipeline {
 | 
			
		|||
 | 
			
		||||
    post {
 | 
			
		||||
        always {
 | 
			
		||||
            script {
 | 
			
		||||
                def changeLogSets = currentBuild.changeSets
 | 
			
		||||
                def message = "**Changes:**"
 | 
			
		||||
 | 
			
		||||
                if (changeLogSets.size() == 0) {
 | 
			
		||||
                    message += "\n*No changes.*"
 | 
			
		||||
                } else {
 | 
			
		||||
                    def repositoryUrl = scm.userRemoteConfigs[0].url.replace(".git", "")
 | 
			
		||||
                    def count = 0;
 | 
			
		||||
                    def extra = 0;
 | 
			
		||||
                    for (int i = 0; i < changeLogSets.size(); i++) {
 | 
			
		||||
                        def entries = changeLogSets[i].items
 | 
			
		||||
                        for (int j = 0; j < entries.length; j++) {
 | 
			
		||||
                            if (count <= 10) {
 | 
			
		||||
                                def entry = entries[j]
 | 
			
		||||
                                def commitId = entry.commitId.substring(0, 6)
 | 
			
		||||
                                message += "\n   - [`${commitId}`](${repositoryUrl}/commit/${entry.commitId}) ${entry.msg}"
 | 
			
		||||
                                count++
 | 
			
		||||
                            } else {
 | 
			
		||||
                                extra++;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    if (extra != 0) {
 | 
			
		||||
                        message += "\n   - ${extra} more commits"
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                env.changes = message
 | 
			
		||||
            }
 | 
			
		||||
            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\n[**Artifacts on Jenkins**](https://ci.nukkitx.com/job/Geyser)", footer: 'NukkitX 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.nukkitx.com/job/Geyser)", footer: 'Cloudburst Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										12
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										12
									
								
								README.md
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -3,7 +3,7 @@
 | 
			
		|||
[](https://java.com/)
 | 
			
		||||
 | 
			
		||||
[](LICENSE)
 | 
			
		||||
[](https://ci.nukkitx.com/job/Geyser/job/master/)
 | 
			
		||||
[](https://ci.nukkitx.com/job/GeyserMC/job/Geyser/job/master/)
 | 
			
		||||
[](http://discord.geysermc.org/)
 | 
			
		||||
[](http://hits.dwyl.io/Geyser/GeyserMC)
 | 
			
		||||
[](https://translate.geysermc.org/)
 | 
			
		||||
| 
						 | 
				
			
			@ -18,7 +18,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
 | 
			
		|||
 | 
			
		||||
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!
 | 
			
		||||
 | 
			
		||||
### Currently supporting Minecraft Bedrock v1.16.0/1 and Minecraft Java v1.16.1.
 | 
			
		||||
### Currently supporting Minecraft Bedrock v1.16.x and Minecraft Java v1.16.3.
 | 
			
		||||
 | 
			
		||||
## Setting Up
 | 
			
		||||
Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser.
 | 
			
		||||
| 
						 | 
				
			
			@ -31,14 +31,18 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set
 | 
			
		|||
- Download: http://ci.geysermc.org
 | 
			
		||||
- Discord: http://discord.geysermc.org/
 | 
			
		||||
- ~~Donate: https://patreon.com/GeyserMC~~ Currently disabled.
 | 
			
		||||
- Test Server: test.geysermc.org port 25565 for Java and 19132 for Bedrock
 | 
			
		||||
- 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
 | 
			
		||||
  - [ ] Enchantment Table (as a proper GUI)
 | 
			
		||||
  - [ ] Beacon
 | 
			
		||||
  - [ ] Cartography Table
 | 
			
		||||
  - [ ] Stonecutter
 | 
			
		||||
  - [ ] Structure Block
 | 
			
		||||
  - [ ] Horse Inventory
 | 
			
		||||
  - [ ] Loom
 | 
			
		||||
  - [ ] Smithing Table
 | 
			
		||||
- Some Entity Flags
 | 
			
		||||
 | 
			
		||||
## Compiling
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,7 @@
 | 
			
		|||
    <parent>
 | 
			
		||||
        <groupId>org.geysermc</groupId>
 | 
			
		||||
        <artifactId>bootstrap-parent</artifactId>
 | 
			
		||||
        <version>1.0.0</version>
 | 
			
		||||
        <version>1.1.0</version>
 | 
			
		||||
        <relativePath>../</relativePath>
 | 
			
		||||
    </parent>
 | 
			
		||||
    <artifactId>bootstrap-bungeecord</artifactId>
 | 
			
		||||
| 
						 | 
				
			
			@ -14,7 +14,7 @@
 | 
			
		|||
        <dependency>
 | 
			
		||||
            <groupId>org.geysermc</groupId>
 | 
			
		||||
            <artifactId>connector</artifactId>
 | 
			
		||||
            <version>1.0.0</version>
 | 
			
		||||
            <version>1.1.0</version>
 | 
			
		||||
            <scope>compile</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
| 
						 | 
				
			
			@ -61,14 +61,34 @@
 | 
			
		|||
                                    <pattern>net.md_5.bungee.jni</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.bungeecord.shaded.jni</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                                <relocation>
 | 
			
		||||
                                    <pattern>com.fasterxml.jackson</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.bungeecord.shaded.jackson</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                                <relocation>
 | 
			
		||||
                                    <pattern>io.netty</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.bungeecord.shaded.netty</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                                <relocation>
 | 
			
		||||
                                    <pattern>org.reflections.reflections</pattern>
 | 
			
		||||
                                    <pattern>org.reflections</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.bungeecord.shaded.reflections</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                                <relocation>
 | 
			
		||||
                                    <pattern>com.google.common</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.bungeecord.shaded.google.common</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                                <relocation>
 | 
			
		||||
                                    <pattern>com.google.guava</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.bungeecord.shaded.google.guava</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                                <relocation>
 | 
			
		||||
                                    <pattern>org.dom4j</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.bungeecord.shaded.dom4j</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                                <relocation>
 | 
			
		||||
                                    <pattern>net.kyori.adventure</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.bungeecord.shaded.adventure</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                            </relocations>
 | 
			
		||||
                        </configuration>
 | 
			
		||||
                    </execution>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,27 +29,22 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
 | 
			
		|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import net.md_5.bungee.api.plugin.Plugin;
 | 
			
		||||
import net.md_5.bungee.config.Configuration;
 | 
			
		||||
import org.geysermc.connector.FloodgateKeyLoader;
 | 
			
		||||
import org.geysermc.connector.configuration.GeyserJacksonConfiguration;
 | 
			
		||||
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.nio.file.Paths;
 | 
			
		||||
 | 
			
		||||
@Getter
 | 
			
		||||
@JsonIgnoreProperties(ignoreUnknown = true)
 | 
			
		||||
public class GeyserBungeeConfiguration extends GeyserJacksonConfiguration {
 | 
			
		||||
 | 
			
		||||
public final class GeyserBungeeConfiguration extends GeyserJacksonConfiguration {
 | 
			
		||||
    @JsonIgnore
 | 
			
		||||
    private Path floodgateKey;
 | 
			
		||||
    private Path floodgateKeyPath;
 | 
			
		||||
 | 
			
		||||
    public void loadFloodgate(GeyserBungeePlugin plugin, Configuration configuration) {
 | 
			
		||||
    public void loadFloodgate(GeyserBungeePlugin plugin) {
 | 
			
		||||
        Plugin floodgate = plugin.getProxy().getPluginManager().getPlugin("floodgate-bungee");
 | 
			
		||||
        floodgateKey = FloodgateKeyLoader.getKey(plugin.getGeyserLogger(), this, Paths.get(plugin.getDataFolder().toString(), configuration.getString("floodgate-key-file"), "public-key.pem"), floodgate, floodgate != null ? floodgate.getDataFolder().toPath() : null);
 | 
			
		||||
    }
 | 
			
		||||
        Path geyserDataFolder = plugin.getDataFolder().toPath();
 | 
			
		||||
        Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Path getFloodgateKeyFile() {
 | 
			
		||||
        return floodgateKey;
 | 
			
		||||
        floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgate, floodgateDataFolder, geyserDataFolder, plugin.getGeyserLogger());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,6 +28,7 @@ package org.geysermc.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 java.util.ArrayList;
 | 
			
		||||
| 
						 | 
				
			
			@ -52,7 +53,13 @@ public class GeyserBungeeDumpInfo extends BootstrapDumpInfo {
 | 
			
		|||
        this.plugins = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
        for (net.md_5.bungee.api.config.ListenerInfo listener : proxy.getConfig().getListeners()) {
 | 
			
		||||
            this.listeners.add(new ListenerInfo(listener.getHost().getHostString(), listener.getHost().getPort()));
 | 
			
		||||
            String hostname;
 | 
			
		||||
            if (AsteriskSerializer.showSensitive || (listener.getHost().getHostString().equals("") || listener.getHost().getHostString().equals("0.0.0.0"))) {
 | 
			
		||||
                hostname = listener.getHost().getHostString();
 | 
			
		||||
            } else {
 | 
			
		||||
                hostname = "***";
 | 
			
		||||
            }
 | 
			
		||||
            this.listeners.add(new ListenerInfo(hostname, listener.getHost().getPort()));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (Plugin plugin : proxy.getPluginManager().getPlugins()) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,19 +25,21 @@
 | 
			
		|||
 | 
			
		||||
package org.geysermc.platform.bungeecord;
 | 
			
		||||
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import lombok.Setter;
 | 
			
		||||
import org.geysermc.connector.GeyserLogger;
 | 
			
		||||
 | 
			
		||||
import java.util.logging.Level;
 | 
			
		||||
import java.util.logging.Logger;
 | 
			
		||||
 | 
			
		||||
public class GeyserBungeeLogger implements GeyserLogger {
 | 
			
		||||
    private final Logger logger;
 | 
			
		||||
    @Getter @Setter
 | 
			
		||||
    private boolean debug;
 | 
			
		||||
 | 
			
		||||
    private Logger logger;
 | 
			
		||||
    private boolean debugMode;
 | 
			
		||||
 | 
			
		||||
    public GeyserBungeeLogger(Logger logger, boolean debugMode) {
 | 
			
		||||
    public GeyserBungeeLogger(Logger logger, boolean debug) {
 | 
			
		||||
        this.logger = logger;
 | 
			
		||||
        this.debugMode = debugMode;
 | 
			
		||||
        this.debug = debug;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			@ -72,12 +74,8 @@ public class GeyserBungeeLogger implements GeyserLogger {
 | 
			
		|||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void debug(String message) {
 | 
			
		||||
        if (debugMode)
 | 
			
		||||
        if (debug) {
 | 
			
		||||
            info(message);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setDebug(boolean debug) {
 | 
			
		||||
        debugMode = debug;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,13 +27,10 @@ package org.geysermc.platform.bungeecord;
 | 
			
		|||
 | 
			
		||||
import net.md_5.bungee.api.config.ListenerInfo;
 | 
			
		||||
import net.md_5.bungee.api.plugin.Plugin;
 | 
			
		||||
import net.md_5.bungee.config.Configuration;
 | 
			
		||||
import net.md_5.bungee.config.ConfigurationProvider;
 | 
			
		||||
import net.md_5.bungee.config.YamlConfiguration;
 | 
			
		||||
import org.geysermc.connector.common.PlatformType;
 | 
			
		||||
import org.geysermc.connector.GeyserConnector;
 | 
			
		||||
import org.geysermc.connector.bootstrap.GeyserBootstrap;
 | 
			
		||||
import org.geysermc.connector.command.CommandManager;
 | 
			
		||||
import org.geysermc.connector.common.PlatformType;
 | 
			
		||||
import org.geysermc.connector.configuration.GeyserConfiguration;
 | 
			
		||||
import org.geysermc.connector.dump.BootstrapDumpInfo;
 | 
			
		||||
import org.geysermc.connector.ping.GeyserLegacyPingPassthrough;
 | 
			
		||||
| 
						 | 
				
			
			@ -64,13 +61,11 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
 | 
			
		|||
        if (!getDataFolder().exists())
 | 
			
		||||
            getDataFolder().mkdir();
 | 
			
		||||
 | 
			
		||||
        Configuration configuration = null;
 | 
			
		||||
        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.geyserConfig = FileUtils.loadConfig(configFile, GeyserBungeeConfiguration.class);
 | 
			
		||||
            configuration = ConfigurationProvider.getProvider(YamlConfiguration.class).load(new File(getDataFolder(), "config.yml"));
 | 
			
		||||
        } catch (IOException ex) {
 | 
			
		||||
            getLogger().log(Level.WARNING, LanguageUtils.getLocaleStringLog("geyser.config.failed"), ex);
 | 
			
		||||
            ex.printStackTrace();
 | 
			
		||||
| 
						 | 
				
			
			@ -81,17 +76,19 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
 | 
			
		|||
 | 
			
		||||
            InetSocketAddress javaAddr = listener.getHost();
 | 
			
		||||
 | 
			
		||||
            // Don't change the ip if its listening on all interfaces
 | 
			
		||||
            // By default this should be 127.0.0.1 but may need to be changed in some circumstances
 | 
			
		||||
            if (!javaAddr.getHostString().equals("0.0.0.0") && !javaAddr.getHostString().equals("")) {
 | 
			
		||||
                this.geyserConfig.getRemote().setAddress(javaAddr.getHostString());
 | 
			
		||||
            // By default this should be localhost but may need to be changed in some circumstances
 | 
			
		||||
            if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) {
 | 
			
		||||
                this.geyserConfig.setAutoconfiguredRemote(true);
 | 
			
		||||
                // Don't use localhost if not listening on all interfaces
 | 
			
		||||
                if (!javaAddr.getHostString().equals("0.0.0.0") && !javaAddr.getHostString().equals("")) {
 | 
			
		||||
                    this.geyserConfig.getRemote().setAddress(javaAddr.getHostString());
 | 
			
		||||
                }
 | 
			
		||||
                this.geyserConfig.getRemote().setPort(javaAddr.getPort());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (geyserConfig.getBedrock().isCloneRemotePort()) {
 | 
			
		||||
                geyserConfig.getBedrock().setPort(javaAddr.getPort());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.geyserConfig.getRemote().setPort(javaAddr.getPort());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode());
 | 
			
		||||
| 
						 | 
				
			
			@ -100,9 +97,13 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
 | 
			
		|||
        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"));
 | 
			
		||||
            return;
 | 
			
		||||
        } else if (geyserConfig.isAutoconfiguredRemote() && getProxy().getPluginManager().getPlugin("floodgate-bungee") != null) {
 | 
			
		||||
            // Floodgate installed means that the user wants Floodgate authentication
 | 
			
		||||
            geyserLogger.debug("Auto-setting to Floodgate authentication.");
 | 
			
		||||
            geyserConfig.getRemote().setAuthType("floodgate");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        geyserConfig.loadFloodgate(this, configuration);
 | 
			
		||||
        geyserConfig.loadFloodgate(this);
 | 
			
		||||
 | 
			
		||||
        this.connector = GeyserConnector.start(PlatformType.BUNGEECORD, this);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -64,10 +64,10 @@ public class GeyserBungeeCommandExecutor extends Command implements TabExecutor
 | 
			
		|||
                    sender.sendMessage(TextComponent.fromLegacyText(ChatColor.RED + message));
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                getCommand(args[0]).execute(new BungeeCommandSender(sender), args);
 | 
			
		||||
                getCommand(args[0]).execute(new BungeeCommandSender(sender), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            getCommand("help").execute(new BungeeCommandSender(sender), args);
 | 
			
		||||
            getCommand("help").execute(new BungeeCommandSender(sender), new String[0]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,7 +10,7 @@
 | 
			
		|||
        <relativePath>../</relativePath>
 | 
			
		||||
    </parent>
 | 
			
		||||
    <artifactId>bootstrap-parent</artifactId>
 | 
			
		||||
    <version>1.0.0</version>
 | 
			
		||||
    <version>1.1.0</version>
 | 
			
		||||
    <packaging>pom</packaging>
 | 
			
		||||
    <repositories>
 | 
			
		||||
        <repository>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,7 @@
 | 
			
		|||
    <parent>
 | 
			
		||||
        <groupId>org.geysermc</groupId>
 | 
			
		||||
        <artifactId>bootstrap-parent</artifactId>
 | 
			
		||||
        <version>1.0.0</version>
 | 
			
		||||
        <version>1.1.0</version>
 | 
			
		||||
        <relativePath>../</relativePath>
 | 
			
		||||
    </parent>
 | 
			
		||||
    <artifactId>bootstrap-spigot</artifactId>
 | 
			
		||||
| 
						 | 
				
			
			@ -14,19 +14,19 @@
 | 
			
		|||
        <dependency>
 | 
			
		||||
            <groupId>org.geysermc</groupId>
 | 
			
		||||
            <artifactId>connector</artifactId>
 | 
			
		||||
            <version>1.0.0</version>
 | 
			
		||||
            <version>1.1.0</version>
 | 
			
		||||
            <scope>compile</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.spigotmc</groupId>
 | 
			
		||||
            <artifactId>spigot-api</artifactId>
 | 
			
		||||
            <version>1.14-R0.1-SNAPSHOT</version>
 | 
			
		||||
            <version>1.15.2-R0.1-SNAPSHOT</version>
 | 
			
		||||
            <scope>provided</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>us.myles</groupId>
 | 
			
		||||
            <artifactId>viaversion</artifactId>
 | 
			
		||||
            <version>3.0.1</version>
 | 
			
		||||
            <version>3.1.1</version>
 | 
			
		||||
            <scope>provided</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
| 
						 | 
				
			
			@ -76,9 +76,25 @@
 | 
			
		|||
                                    <shadedPattern>org.geysermc.platform.spigot.shaded.jackson</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                                <relocation>
 | 
			
		||||
                                    <pattern>org.reflections.reflections</pattern>
 | 
			
		||||
                                    <pattern>org.reflections</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.spigot.shaded.reflections</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                                <relocation>
 | 
			
		||||
                                    <pattern>com.google.common</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.spigot.shaded.google.common</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                                <relocation>
 | 
			
		||||
                                    <pattern>com.google.guava</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.spigot.shaded.google.guava</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                                <relocation>
 | 
			
		||||
                                    <pattern>org.dom4j</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.spigot.shaded.dom4j</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                                <relocation>
 | 
			
		||||
                                    <pattern>net.kyori.adventure</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.spigot.shaded.adventure</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                            </relocations>
 | 
			
		||||
                        </configuration>
 | 
			
		||||
                    </execution>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,7 +27,6 @@ package org.geysermc.platform.spigot;
 | 
			
		|||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonIgnore;
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonProperty;
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import org.bukkit.Bukkit;
 | 
			
		||||
import org.bukkit.plugin.Plugin;
 | 
			
		||||
| 
						 | 
				
			
			@ -35,26 +34,19 @@ import org.geysermc.connector.FloodgateKeyLoader;
 | 
			
		|||
import org.geysermc.connector.configuration.GeyserJacksonConfiguration;
 | 
			
		||||
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.nio.file.Paths;
 | 
			
		||||
 | 
			
		||||
@Getter
 | 
			
		||||
@JsonIgnoreProperties(ignoreUnknown = true)
 | 
			
		||||
public class GeyserSpigotConfiguration extends GeyserJacksonConfiguration {
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("floodgate-key-file")
 | 
			
		||||
    private String floodgateKeyFile;
 | 
			
		||||
 | 
			
		||||
public final class GeyserSpigotConfiguration extends GeyserJacksonConfiguration {
 | 
			
		||||
    @JsonIgnore
 | 
			
		||||
    private Path floodgateKey;
 | 
			
		||||
    private Path floodgateKeyPath;
 | 
			
		||||
 | 
			
		||||
    public void loadFloodgate(GeyserSpigotPlugin plugin) {
 | 
			
		||||
        Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate-bukkit");
 | 
			
		||||
        floodgateKey = FloodgateKeyLoader.getKey(plugin.getGeyserLogger(), this, Paths.get(plugin.getDataFolder().toString(), plugin.getConfig().getString("floodgate-key-file", "public-key.pem")), floodgate, floodgate != null ? floodgate.getDataFolder().toPath() : null);
 | 
			
		||||
    }
 | 
			
		||||
        Path geyserDataFolder = plugin.getDataFolder().toPath();
 | 
			
		||||
        Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Path getFloodgateKeyFile() {
 | 
			
		||||
        return floodgateKey;
 | 
			
		||||
        floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgate, floodgateDataFolder, geyserDataFolder, plugin.getGeyserLogger());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,6 +28,7 @@ package org.geysermc.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 java.util.ArrayList;
 | 
			
		||||
| 
						 | 
				
			
			@ -50,7 +51,11 @@ public class GeyserSpigotDumpInfo extends BootstrapDumpInfo {
 | 
			
		|||
        this.platformVersion = Bukkit.getVersion();
 | 
			
		||||
        this.platformAPIVersion = Bukkit.getBukkitVersion();
 | 
			
		||||
        this.onlineMode = Bukkit.getOnlineMode();
 | 
			
		||||
        this.serverIP = Bukkit.getIp();
 | 
			
		||||
        if (AsteriskSerializer.showSensitive || (Bukkit.getIp().equals("") || Bukkit.getIp().equals("0.0.0.0"))) {
 | 
			
		||||
            this.serverIP = Bukkit.getIp();
 | 
			
		||||
        } else {
 | 
			
		||||
            this.serverIP = "***";
 | 
			
		||||
        }
 | 
			
		||||
        this.serverPort = Bukkit.getPort();
 | 
			
		||||
        this.plugins = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,7 +26,8 @@
 | 
			
		|||
package org.geysermc.platform.spigot;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import lombok.Setter;
 | 
			
		||||
import org.geysermc.connector.GeyserLogger;
 | 
			
		||||
 | 
			
		||||
import java.util.logging.Level;
 | 
			
		||||
| 
						 | 
				
			
			@ -34,9 +35,9 @@ import java.util.logging.Logger;
 | 
			
		|||
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class GeyserSpigotLogger implements GeyserLogger {
 | 
			
		||||
 | 
			
		||||
    private Logger logger;
 | 
			
		||||
    private boolean debugMode;
 | 
			
		||||
    private final Logger logger;
 | 
			
		||||
    @Getter @Setter
 | 
			
		||||
    private boolean debug;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void severe(String message) {
 | 
			
		||||
| 
						 | 
				
			
			@ -70,12 +71,8 @@ public class GeyserSpigotLogger implements GeyserLogger {
 | 
			
		|||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void debug(String message) {
 | 
			
		||||
        if (debugMode)
 | 
			
		||||
        if (debug) {
 | 
			
		||||
            info(message);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setDebug(boolean debug) {
 | 
			
		||||
        debugMode = debug;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,10 +27,10 @@ package org.geysermc.platform.spigot;
 | 
			
		|||
 | 
			
		||||
import org.bukkit.Bukkit;
 | 
			
		||||
import org.bukkit.plugin.java.JavaPlugin;
 | 
			
		||||
import org.geysermc.connector.common.PlatformType;
 | 
			
		||||
import org.geysermc.connector.GeyserConnector;
 | 
			
		||||
import org.geysermc.connector.bootstrap.GeyserBootstrap;
 | 
			
		||||
import org.geysermc.connector.command.CommandManager;
 | 
			
		||||
import org.geysermc.connector.common.PlatformType;
 | 
			
		||||
import org.geysermc.connector.configuration.GeyserConfiguration;
 | 
			
		||||
import org.geysermc.connector.dump.BootstrapDumpInfo;
 | 
			
		||||
import org.geysermc.connector.network.translators.world.WorldManager;
 | 
			
		||||
| 
						 | 
				
			
			@ -56,7 +56,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
 | 
			
		|||
    private GeyserSpigotConfiguration geyserConfig;
 | 
			
		||||
    private GeyserSpigotLogger geyserLogger;
 | 
			
		||||
    private IGeyserPingPassthrough geyserSpigotPingPassthrough;
 | 
			
		||||
    private GeyserSpigotBlockPlaceListener blockPlaceListener;
 | 
			
		||||
    private GeyserSpigotWorldManager geyserWorldManager;
 | 
			
		||||
 | 
			
		||||
    private GeyserConnector connector;
 | 
			
		||||
| 
						 | 
				
			
			@ -81,18 +80,20 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
 | 
			
		|||
            ex.printStackTrace();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Don't change the ip if its listening on all interfaces
 | 
			
		||||
        // By default this should be 127.0.0.1 but may need to be changed in some circumstances
 | 
			
		||||
        if (!Bukkit.getIp().equals("0.0.0.0") && !Bukkit.getIp().equals("")) {
 | 
			
		||||
            geyserConfig.getRemote().setAddress(Bukkit.getIp());
 | 
			
		||||
        // 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());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        geyserConfig.getRemote().setPort(Bukkit.getPort());
 | 
			
		||||
 | 
			
		||||
        this.geyserLogger = new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode());
 | 
			
		||||
        GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -100,6 +101,10 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
 | 
			
		|||
            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);
 | 
			
		||||
| 
						 | 
				
			
			@ -118,10 +123,15 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
 | 
			
		|||
        // 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.");
 | 
			
		||||
            geyserLogger.debug("Legacy version of Minecraft (1.12.2 or older) detected; falling back to ViaVersion for block state retrieval.");
 | 
			
		||||
 | 
			
		||||
        this.geyserWorldManager = new GeyserSpigotWorldManager(isLegacy, isViaVersion);
 | 
			
		||||
        this.blockPlaceListener = new GeyserSpigotBlockPlaceListener(connector, isLegacy, isViaVersion);
 | 
			
		||||
        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.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.geyserWorldManager = new GeyserSpigotWorldManager(isLegacy, use3dBiomes, isViaVersion);
 | 
			
		||||
        GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(connector, isLegacy, isViaVersion);
 | 
			
		||||
 | 
			
		||||
        Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,11 +59,11 @@ public class GeyserSpigotCommandExecutor implements TabExecutor {
 | 
			
		|||
                    sender.sendMessage(ChatColor.RED + message);
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
                getCommand(args[0]).execute(new SpigotCommandSender(sender), args);
 | 
			
		||||
                getCommand(args[0]).execute(new SpigotCommandSender(sender), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]);
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            getCommand("help").execute(new SpigotCommandSender(sender), args);
 | 
			
		||||
            getCommand("help").execute(new SpigotCommandSender(sender), new String[0]);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,7 +59,7 @@ public class GeyserSpigotBlockPlaceListener implements Listener {
 | 
			
		|||
                } else {
 | 
			
		||||
                    javaBlockId = event.getBlockPlaced().getBlockData().getAsString();
 | 
			
		||||
                }
 | 
			
		||||
                placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaIdBlockMap().get(javaBlockId)));
 | 
			
		||||
                placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaIdBlockMap().getOrDefault(javaBlockId, 0)));
 | 
			
		||||
                placeBlockSoundPacket.setIdentifier(":");
 | 
			
		||||
                session.sendUpstreamPacket(placeBlockSoundPacket);
 | 
			
		||||
                session.setLastBlockPlacePosition(null);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,48 +25,201 @@
 | 
			
		|||
 | 
			
		||||
package org.geysermc.platform.spigot.world;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import com.fasterxml.jackson.databind.JsonNode;
 | 
			
		||||
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.WorldManager;
 | 
			
		||||
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.protocols.protocol1_13_1to1_13.Protocol1_13_1To1_13;
 | 
			
		||||
import us.myles.ViaVersion.protocols.protocol1_16to1_15_2.data.MappingData;
 | 
			
		||||
import us.myles.ViaVersion.protocols.protocol1_16_2to1_16_1.data.MappingData;
 | 
			
		||||
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class GeyserSpigotWorldManager extends WorldManager {
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
 | 
			
		||||
public class GeyserSpigotWorldManager extends GeyserWorldManager {
 | 
			
		||||
 | 
			
		||||
    private final boolean isLegacy;
 | 
			
		||||
    // 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 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()) {
 | 
			
		||||
            if (biomes.has(enumBiome.toString())) {
 | 
			
		||||
                biomeToIdMap.put(enumBiome.ordinal(), biomes.get(enumBiome.toString()).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) {
 | 
			
		||||
        if (session.getPlayerEntity() == null) {
 | 
			
		||||
        Player bukkitPlayer;
 | 
			
		||||
        if ((this.isLegacy && !this.isViaVersion)
 | 
			
		||||
            || session.getPlayerEntity() == null
 | 
			
		||||
            || (bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
 | 
			
		||||
            return BlockTranslator.AIR;
 | 
			
		||||
        }
 | 
			
		||||
        World world = bukkitPlayer.getWorld();
 | 
			
		||||
        if (isLegacy) {
 | 
			
		||||
            return getLegacyBlock(session, x, y, z, isViaVersion);
 | 
			
		||||
            return getLegacyBlock(session, x, y, z, true);
 | 
			
		||||
        }
 | 
			
		||||
        return BlockTranslator.getJavaIdBlockMap().get(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getBlockAt(x, y, z).getBlockData().getAsString());
 | 
			
		||||
        //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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("deprecation")
 | 
			
		||||
    public static int getLegacyBlock(GeyserSession session, int x, int y, int z, boolean isViaVersion) {
 | 
			
		||||
        if (isViaVersion) {
 | 
			
		||||
            Block block = Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getBlockAt(x, y, z);
 | 
			
		||||
            // Black magic that gets the old block state ID
 | 
			
		||||
            int oldBlockId = (block.getType().getId() << 4) | (block.getData() & 0xF);
 | 
			
		||||
            // Convert block state from old version -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16
 | 
			
		||||
            int thirteenBlockId = us.myles.ViaVersion.protocols.protocol1_13to1_12_2.data.MappingData.blockMappings.getNewId(oldBlockId);
 | 
			
		||||
            int thirteenPointOneBlockId = Protocol1_13_1To1_13.getNewBlockStateId(thirteenBlockId);
 | 
			
		||||
            int fourteenBlockId = us.myles.ViaVersion.protocols.protocol1_14to1_13_2.data.MappingData.blockStateMappings.getNewId(thirteenPointOneBlockId);
 | 
			
		||||
            int fifteenBlockId = us.myles.ViaVersion.protocols.protocol1_15to1_14_4.data.MappingData.blockStateMappings.getNewId(fourteenBlockId);
 | 
			
		||||
            return MappingData.blockStateMappings.getNewId(fifteenBlockId);
 | 
			
		||||
            return getLegacyBlock(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld(), x, y, z, true);
 | 
			
		||||
        } else {
 | 
			
		||||
            return BlockTranslator.AIR;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("deprecation")
 | 
			
		||||
    public static int getLegacyBlock(World world, int x, int y, int z, boolean isViaVersion) {
 | 
			
		||||
        if (isViaVersion) {
 | 
			
		||||
            Block block = world.getBlockAt(x, y, z);
 | 
			
		||||
            // Black magic that gets the old block state ID
 | 
			
		||||
            int oldBlockId = (block.getType().getId() << 4) | (block.getData() & 0xF);
 | 
			
		||||
            // Convert block state from old version -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2
 | 
			
		||||
            int thirteenBlockId = us.myles.ViaVersion.protocols.protocol1_13to1_12_2.data.MappingData.blockMappings.getNewId(oldBlockId);
 | 
			
		||||
            int thirteenPointOneBlockId = Protocol1_13_1To1_13.getNewBlockStateId(thirteenBlockId);
 | 
			
		||||
            int fourteenBlockId = us.myles.ViaVersion.protocols.protocol1_14to1_13_2.data.MappingData.blockStateMappings.getNewId(thirteenPointOneBlockId);
 | 
			
		||||
            int fifteenBlockId = us.myles.ViaVersion.protocols.protocol1_15to1_14_4.data.MappingData.blockStateMappings.getNewId(fourteenBlockId);
 | 
			
		||||
            int sixteenBlockId = us.myles.ViaVersion.protocols.protocol1_16to1_15_2.data.MappingData.blockStateMappings.getNewId(fifteenBlockId);
 | 
			
		||||
            return MappingData.blockStateMappings.getNewId(sixteenBlockId);
 | 
			
		||||
        } else {
 | 
			
		||||
            return BlockTranslator.AIR;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @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)  {
 | 
			
		||||
            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(world, (x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ, true));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } 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(), 0);
 | 
			
		||||
                        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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										155
									
								
								bootstrap/spigot/src/main/resources/biomes.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								bootstrap/spigot/src/main/resources/biomes.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,155 @@
 | 
			
		|||
{
 | 
			
		||||
  "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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -6,7 +6,7 @@
 | 
			
		|||
    <parent>
 | 
			
		||||
        <groupId>org.geysermc</groupId>
 | 
			
		||||
        <artifactId>bootstrap-parent</artifactId>
 | 
			
		||||
        <version>1.0.0</version>
 | 
			
		||||
        <version>1.1.0</version>
 | 
			
		||||
        <relativePath>../</relativePath>
 | 
			
		||||
    </parent>
 | 
			
		||||
    <artifactId>bootstrap-sponge</artifactId>
 | 
			
		||||
| 
						 | 
				
			
			@ -14,7 +14,7 @@
 | 
			
		|||
        <dependency>
 | 
			
		||||
            <groupId>org.geysermc</groupId>
 | 
			
		||||
            <artifactId>connector</artifactId>
 | 
			
		||||
            <version>1.0.0</version>
 | 
			
		||||
            <version>1.1.0</version>
 | 
			
		||||
            <scope>compile</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
| 
						 | 
				
			
			@ -70,9 +70,25 @@
 | 
			
		|||
                                    <shadedPattern>org.geysermc.platform.sponge.shaded.fastutil</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                                <relocation>
 | 
			
		||||
                                    <pattern>org.reflections.reflections</pattern>
 | 
			
		||||
                                    <pattern>org.reflections</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.sponge.shaded.reflections</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                                <relocation>
 | 
			
		||||
                                    <pattern>com.google.common</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.sponge.shaded.google.common</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                                <relocation>
 | 
			
		||||
                                    <pattern>com.google.guava</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.sponge.shaded.google.guava</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                                <relocation>
 | 
			
		||||
                                    <pattern>org.dom4j</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.sponge.shaded.dom4j</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                                <relocation>
 | 
			
		||||
                                    <pattern>net.kyori.adventure</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.sponge.shaded.adventure</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                            </relocations>
 | 
			
		||||
                        </configuration>
 | 
			
		||||
                    </execution>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,246 +25,13 @@
 | 
			
		|||
 | 
			
		||||
package org.geysermc.platform.sponge;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import org.geysermc.connector.configuration.GeyserJacksonConfiguration;
 | 
			
		||||
 | 
			
		||||
import ninja.leaping.configurate.ConfigurationNode;
 | 
			
		||||
 | 
			
		||||
import org.geysermc.connector.configuration.GeyserConfiguration;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.nio.file.Paths;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
 | 
			
		||||
public class GeyserSpongeConfiguration implements GeyserConfiguration {
 | 
			
		||||
 | 
			
		||||
    private File dataFolder;
 | 
			
		||||
    private ConfigurationNode node;
 | 
			
		||||
 | 
			
		||||
    private SpongeBedrockConfiguration bedrockConfig;
 | 
			
		||||
    private SpongeRemoteConfiguration remoteConfig;
 | 
			
		||||
    private SpongeMetricsInfo metricsInfo;
 | 
			
		||||
 | 
			
		||||
    private Map<String, SpongeUserAuthenticationInfo> userAuthInfo = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
    public GeyserSpongeConfiguration(File dataFolder, ConfigurationNode node) {
 | 
			
		||||
        this.dataFolder = dataFolder;
 | 
			
		||||
        this.node = node;
 | 
			
		||||
 | 
			
		||||
        this.bedrockConfig = new SpongeBedrockConfiguration(node.getNode("bedrock"));
 | 
			
		||||
        this.remoteConfig = new SpongeRemoteConfiguration(node.getNode("remote"));
 | 
			
		||||
        this.metricsInfo = new SpongeMetricsInfo();
 | 
			
		||||
 | 
			
		||||
        if (node.getNode("userAuths").getValue() == null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        List<String> userAuths = new ArrayList<String>(((LinkedHashMap)node.getNode("userAuths").getValue()).keySet());
 | 
			
		||||
        for (String key : userAuths) {
 | 
			
		||||
            userAuthInfo.put(key, new SpongeUserAuthenticationInfo(key));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
public final class GeyserSpongeConfiguration extends GeyserJacksonConfiguration {
 | 
			
		||||
    @Override
 | 
			
		||||
    public SpongeBedrockConfiguration getBedrock() {
 | 
			
		||||
        return bedrockConfig;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public SpongeRemoteConfiguration getRemote() {
 | 
			
		||||
        return remoteConfig;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Map<String, SpongeUserAuthenticationInfo> getUserAuths() {
 | 
			
		||||
        return userAuthInfo;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isCommandSuggestions() {
 | 
			
		||||
        return node.getNode("command-suggestions").getBoolean(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isPassthroughMotd() {
 | 
			
		||||
        return node.getNode("passthrough-motd").getBoolean(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isPassthroughPlayerCounts() {
 | 
			
		||||
        return node.getNode("passthrough-player-counts").getBoolean(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isLegacyPingPassthrough() {
 | 
			
		||||
        return node.getNode("legacy-ping-passthrough").getBoolean(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getPingPassthroughInterval() {
 | 
			
		||||
        return node.getNode("ping-passthrough-interval").getInt(3);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getMaxPlayers() {
 | 
			
		||||
        return node.getNode("max-players").getInt(100);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isDebugMode() {
 | 
			
		||||
        return node.getNode("debug-mode").getBoolean(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getGeneralThreadPool() {
 | 
			
		||||
        return node.getNode("genereal-thread-pool").getInt(32);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isAllowThirdPartyCapes() {
 | 
			
		||||
        return node.getNode("allow-third-party-capes").getBoolean(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isAllowThirdPartyEars() {
 | 
			
		||||
        return node.getNode("allow-third-party-ears").getBoolean(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isShowCooldown() {
 | 
			
		||||
        return node.getNode("show-cooldown").getBoolean(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getDefaultLocale() {
 | 
			
		||||
        return node.getNode("default-locale").getString("en_us");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Path getFloodgateKeyFile() {
 | 
			
		||||
        return Paths.get(dataFolder.toString(), node.getNode("floodgate-key-file").getString("public-key.pem"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isCacheChunks() {
 | 
			
		||||
        return node.getNode("cache-chunks").getBoolean(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getCacheImages() {
 | 
			
		||||
        return node.getNode("cache-skins").getInt(0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isAboveBedrockNetherBuilding() {
 | 
			
		||||
        return node.getNode("above-bedrock-nether-building").getBoolean(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public SpongeMetricsInfo getMetrics() {
 | 
			
		||||
        return metricsInfo;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @AllArgsConstructor
 | 
			
		||||
    public class SpongeBedrockConfiguration implements IBedrockConfiguration {
 | 
			
		||||
 | 
			
		||||
        private ConfigurationNode node;
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public String getAddress() {
 | 
			
		||||
            return node.getNode("address").getString("0.0.0.0");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public int getPort() {
 | 
			
		||||
            return node.getNode("port").getInt(19132);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public boolean isCloneRemotePort() {
 | 
			
		||||
            return node.getNode("clone-remote-port").getBoolean(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public String getMotd1() {
 | 
			
		||||
            return node.getNode("motd1").getString("GeyserMC");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public String getMotd2() {
 | 
			
		||||
            return node.getNode("motd2").getString("GeyserMC");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @AllArgsConstructor
 | 
			
		||||
    public class SpongeRemoteConfiguration implements IRemoteConfiguration {
 | 
			
		||||
 | 
			
		||||
        private ConfigurationNode node;
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public String getAddress() {
 | 
			
		||||
            return node.getNode("address").getString("127.0.0.1");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void setAddress(String address) {
 | 
			
		||||
            node.getNode("address").setValue(address);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public int getPort() {
 | 
			
		||||
            return node.getNode("port").getInt(25565);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void setPort(int port) {
 | 
			
		||||
            node.getNode("port").setValue(port);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public String getAuthType() {
 | 
			
		||||
            return node.getNode("auth-type").getString("online");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class SpongeUserAuthenticationInfo implements IUserAuthenticationInfo {
 | 
			
		||||
 | 
			
		||||
        private String key;
 | 
			
		||||
 | 
			
		||||
        public SpongeUserAuthenticationInfo(String key) {
 | 
			
		||||
            this.key = key;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public String getEmail() {
 | 
			
		||||
            return node.getNode("userAuths").getNode(key).getNode("email").getString();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public String getPassword() {
 | 
			
		||||
            return node.getNode("userAuths").getNode(key).getNode("password").getString();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class SpongeMetricsInfo implements IMetricsInfo {
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public boolean isEnabled() {
 | 
			
		||||
            return node.getNode("metrics").getNode("enabled").getBoolean(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public String getUniqueId() {
 | 
			
		||||
            return node.getNode("metrics").getNode("uuid").getString("generateduuid");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getMtu() {
 | 
			
		||||
        return node.getNode("mtu").getInt(1400);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getConfigVersion() {
 | 
			
		||||
        return node.getNode("config-version").getInt(0);
 | 
			
		||||
    public Path getFloodgateKeyPath() {
 | 
			
		||||
        return null; //floodgate isn't available for Sponge
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,15 +26,16 @@
 | 
			
		|||
package org.geysermc.platform.sponge;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import lombok.Setter;
 | 
			
		||||
import org.geysermc.connector.GeyserLogger;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class GeyserSpongeLogger implements GeyserLogger {
 | 
			
		||||
 | 
			
		||||
    private Logger logger;
 | 
			
		||||
    private boolean debugMode;
 | 
			
		||||
    private final Logger logger;
 | 
			
		||||
    @Getter @Setter
 | 
			
		||||
    private boolean debug;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void severe(String message) {
 | 
			
		||||
| 
						 | 
				
			
			@ -68,12 +69,8 @@ public class GeyserSpongeLogger implements GeyserLogger {
 | 
			
		|||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void debug(String message) {
 | 
			
		||||
        if (debugMode)
 | 
			
		||||
        if (debug) {
 | 
			
		||||
            info(message);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setDebug(boolean debugMode) {
 | 
			
		||||
        this.debugMode = debugMode;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,14 +26,11 @@
 | 
			
		|||
package org.geysermc.platform.sponge;
 | 
			
		||||
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import ninja.leaping.configurate.ConfigurationNode;
 | 
			
		||||
import ninja.leaping.configurate.loader.ConfigurationLoader;
 | 
			
		||||
import ninja.leaping.configurate.yaml.YAMLConfigurationLoader;
 | 
			
		||||
import org.geysermc.connector.common.PlatformType;
 | 
			
		||||
import org.geysermc.connector.configuration.GeyserConfiguration;
 | 
			
		||||
import org.geysermc.connector.GeyserConnector;
 | 
			
		||||
import org.geysermc.connector.bootstrap.GeyserBootstrap;
 | 
			
		||||
import org.geysermc.connector.command.CommandManager;
 | 
			
		||||
import org.geysermc.connector.common.PlatformType;
 | 
			
		||||
import org.geysermc.connector.configuration.GeyserConfiguration;
 | 
			
		||||
import org.geysermc.connector.dump.BootstrapDumpInfo;
 | 
			
		||||
import org.geysermc.connector.ping.GeyserLegacyPingPassthrough;
 | 
			
		||||
import org.geysermc.connector.ping.IGeyserPingPassthrough;
 | 
			
		||||
| 
						 | 
				
			
			@ -85,35 +82,27 @@ public class GeyserSpongePlugin implements GeyserBootstrap {
 | 
			
		|||
            ex.printStackTrace();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ConfigurationLoader loader = YAMLConfigurationLoader.builder().setPath(configFile.toPath()).build();
 | 
			
		||||
        ConfigurationNode config;
 | 
			
		||||
        try {
 | 
			
		||||
            config = loader.load();
 | 
			
		||||
            this.geyserConfig = new GeyserSpongeConfiguration(configDir, config);
 | 
			
		||||
            this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpongeConfiguration.class);
 | 
			
		||||
        } catch (IOException ex) {
 | 
			
		||||
            logger.warn(LanguageUtils.getLocaleStringLog("geyser.config.failed"));
 | 
			
		||||
            ex.printStackTrace();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ConfigurationNode serverIP = config.getNode("remote").getNode("address");
 | 
			
		||||
        ConfigurationNode serverPort = config.getNode("remote").getNode("port");
 | 
			
		||||
 | 
			
		||||
        if (Sponge.getServer().getBoundAddress().isPresent()) {
 | 
			
		||||
            InetSocketAddress javaAddr = Sponge.getServer().getBoundAddress().get();
 | 
			
		||||
 | 
			
		||||
            // Don't change the ip if its listening on all interfaces
 | 
			
		||||
            // By default this should be 127.0.0.1 but may need to be changed in some circumstances
 | 
			
		||||
            if (!javaAddr.getHostString().equals("0.0.0.0") && !javaAddr.getHostString().equals("")) {
 | 
			
		||||
                serverIP.setValue("127.0.0.1");
 | 
			
		||||
            if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) {
 | 
			
		||||
                this.geyserConfig.setAutoconfiguredRemote(true);
 | 
			
		||||
                geyserConfig.getRemote().setPort(javaAddr.getPort());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            serverPort.setValue(javaAddr.getPort());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ConfigurationNode bedrockPort = config.getNode("bedrock").getNode("port");
 | 
			
		||||
        if (geyserConfig.getBedrock().isCloneRemotePort()){
 | 
			
		||||
            bedrockPort.setValue(serverPort.getValue());
 | 
			
		||||
            geyserConfig.getBedrock().setPort(geyserConfig.getRemote().getPort());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.geyserLogger = new GeyserSpongeLogger(logger, geyserConfig.isDebugMode());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,10 +59,10 @@ public class GeyserSpongeCommandExecutor implements CommandCallable {
 | 
			
		|||
                    source.sendMessage(Text.of(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail")));
 | 
			
		||||
                    return CommandResult.success();
 | 
			
		||||
                }
 | 
			
		||||
                getCommand(args[0]).execute(new SpongeCommandSender(source), args);
 | 
			
		||||
                getCommand(args[0]).execute(new SpongeCommandSender(source), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            getCommand("help").execute(new SpongeCommandSender(source), args);
 | 
			
		||||
            getCommand("help").execute(new SpongeCommandSender(source), new String[0]);
 | 
			
		||||
        }
 | 
			
		||||
        return CommandResult.success();
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,7 @@
 | 
			
		|||
    <parent>
 | 
			
		||||
        <groupId>org.geysermc</groupId>
 | 
			
		||||
        <artifactId>bootstrap-parent</artifactId>
 | 
			
		||||
        <version>1.0.0</version>
 | 
			
		||||
        <version>1.1.0</version>
 | 
			
		||||
        <relativePath>../</relativePath>
 | 
			
		||||
    </parent>
 | 
			
		||||
    <artifactId>bootstrap-standalone</artifactId>
 | 
			
		||||
| 
						 | 
				
			
			@ -14,7 +14,7 @@
 | 
			
		|||
        <dependency>
 | 
			
		||||
            <groupId>org.geysermc</groupId>
 | 
			
		||||
            <artifactId>connector</artifactId>
 | 
			
		||||
            <version>1.0.0</version>
 | 
			
		||||
            <version>1.1.0</version>
 | 
			
		||||
            <scope>compile</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,8 +33,8 @@ import org.apache.logging.log4j.core.Logger;
 | 
			
		|||
import org.apache.logging.log4j.core.appender.ConsoleAppender;
 | 
			
		||||
import org.geysermc.connector.GeyserConnector;
 | 
			
		||||
import org.geysermc.connector.bootstrap.GeyserBootstrap;
 | 
			
		||||
import org.geysermc.connector.common.PlatformType;
 | 
			
		||||
import org.geysermc.connector.command.CommandManager;
 | 
			
		||||
import org.geysermc.connector.common.PlatformType;
 | 
			
		||||
import org.geysermc.connector.configuration.GeyserConfiguration;
 | 
			
		||||
import org.geysermc.connector.dump.BootstrapDumpInfo;
 | 
			
		||||
import org.geysermc.connector.ping.GeyserLegacyPingPassthrough;
 | 
			
		||||
| 
						 | 
				
			
			@ -49,6 +49,7 @@ import java.io.IOException;
 | 
			
		|||
import java.lang.reflect.Method;
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.nio.file.Paths;
 | 
			
		||||
import java.text.MessageFormat;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
public class GeyserStandaloneBootstrap implements GeyserBootstrap {
 | 
			
		||||
| 
						 | 
				
			
			@ -62,22 +63,61 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
 | 
			
		|||
 | 
			
		||||
    @Getter
 | 
			
		||||
    private boolean useGui = System.console() == null && !isHeadless();
 | 
			
		||||
    private String configFilename = "config.yml";
 | 
			
		||||
 | 
			
		||||
    private GeyserConnector connector;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        for (String arg : args) {
 | 
			
		||||
        GeyserStandaloneBootstrap bootstrap = new GeyserStandaloneBootstrap();
 | 
			
		||||
        // Set defaults
 | 
			
		||||
        boolean useGuiOpts = bootstrap.useGui;
 | 
			
		||||
        String configFilenameOpt = bootstrap.configFilename;
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < args.length; i++) {
 | 
			
		||||
            // By default, standalone Geyser will check if it should open the GUI based on if the GUI is null
 | 
			
		||||
            // Optionally, you can force the use of a GUI or no GUI by specifying args
 | 
			
		||||
            if (arg.equals("gui")) {
 | 
			
		||||
                new GeyserStandaloneBootstrap().onEnable(true);
 | 
			
		||||
                return;
 | 
			
		||||
            } else if (arg.equals("nogui")) {
 | 
			
		||||
                new GeyserStandaloneBootstrap().onEnable(false);
 | 
			
		||||
                return;
 | 
			
		||||
            // 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":
 | 
			
		||||
                    if (i >= args.length - 1) {
 | 
			
		||||
                        System.err.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.confignotspecified"), "-c"));
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    configFilenameOpt = args[i+1]; i++;
 | 
			
		||||
                    System.out.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.configspecified"), 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"));
 | 
			
		||||
                    return;
 | 
			
		||||
                default:
 | 
			
		||||
                    String badArgMsg = LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.unrecognised");
 | 
			
		||||
                    System.err.println(MessageFormat.format(badArgMsg, arg));
 | 
			
		||||
                    return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        new GeyserStandaloneBootstrap().onEnable();
 | 
			
		||||
        bootstrap.onEnable(useGuiOpts, configFilenameOpt);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void onEnable(boolean useGui, String configFilename) {
 | 
			
		||||
        this.configFilename = configFilename;
 | 
			
		||||
        this.useGui = useGui;
 | 
			
		||||
        this.onEnable();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void onEnable(boolean useGui) {
 | 
			
		||||
| 
						 | 
				
			
			@ -106,8 +146,12 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
 | 
			
		|||
        LoopbackUtil.checkLoopback(geyserLogger);
 | 
			
		||||
        
 | 
			
		||||
        try {
 | 
			
		||||
            File configFile = FileUtils.fileOrCopiedFromResource("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()));
 | 
			
		||||
            geyserConfig = FileUtils.loadConfig(configFile, GeyserStandaloneConfiguration.class);
 | 
			
		||||
            if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) {
 | 
			
		||||
                geyserConfig.setAutoconfiguredRemote(true); // Doesn't really need to be set but /shrug
 | 
			
		||||
                geyserConfig.getRemote().setAddress("127.0.0.1");
 | 
			
		||||
            }
 | 
			
		||||
        } catch (IOException ex) {
 | 
			
		||||
            geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.config.failed"), ex);
 | 
			
		||||
            System.exit(0);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,7 +26,6 @@
 | 
			
		|||
package org.geysermc.platform.standalone;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonProperty;
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import org.geysermc.connector.configuration.GeyserJacksonConfiguration;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -35,13 +34,9 @@ import java.nio.file.Paths;
 | 
			
		|||
 | 
			
		||||
@Getter
 | 
			
		||||
@JsonIgnoreProperties(ignoreUnknown = true)
 | 
			
		||||
public class GeyserStandaloneConfiguration extends GeyserJacksonConfiguration {
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("floodgate-key-file")
 | 
			
		||||
    private String floodgateKeyFile;
 | 
			
		||||
 | 
			
		||||
public final class GeyserStandaloneConfiguration extends GeyserJacksonConfiguration {
 | 
			
		||||
    @Override
 | 
			
		||||
    public Path getFloodgateKeyFile() {
 | 
			
		||||
        return Paths.get(floodgateKeyFile);
 | 
			
		||||
    public Path getFloodgateKeyPath() {
 | 
			
		||||
        return Paths.get(getFloodgateKeyFile());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,18 +26,16 @@
 | 
			
		|||
package org.geysermc.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.common.ChatColor;
 | 
			
		||||
import org.geysermc.connector.GeyserConnector;
 | 
			
		||||
import org.geysermc.connector.GeyserLogger;
 | 
			
		||||
import org.geysermc.connector.command.CommandSender;
 | 
			
		||||
import org.geysermc.connector.common.ChatColor;
 | 
			
		||||
 | 
			
		||||
@Log4j2
 | 
			
		||||
public class GeyserStandaloneLogger extends SimpleTerminalConsole implements org.geysermc.connector.GeyserLogger, CommandSender {
 | 
			
		||||
 | 
			
		||||
public class GeyserStandaloneLogger extends SimpleTerminalConsole implements GeyserLogger, CommandSender {
 | 
			
		||||
    private boolean colored = true;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			@ -99,10 +97,6 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements org
 | 
			
		|||
        Configurator.setLevel(log.getName(), debug ? Level.DEBUG : Level.INFO);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Used for setting debug mode in GUI mode
 | 
			
		||||
     * @return if debug is enabled
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isDebug() {
 | 
			
		||||
        return log.isDebugEnabled();
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,7 @@
 | 
			
		|||
    <parent>
 | 
			
		||||
        <groupId>org.geysermc</groupId>
 | 
			
		||||
        <artifactId>bootstrap-parent</artifactId>
 | 
			
		||||
        <version>1.0.0</version>
 | 
			
		||||
        <version>1.1.0</version>
 | 
			
		||||
        <relativePath>../</relativePath>
 | 
			
		||||
    </parent>
 | 
			
		||||
    <artifactId>bootstrap-velocity</artifactId>
 | 
			
		||||
| 
						 | 
				
			
			@ -14,7 +14,7 @@
 | 
			
		|||
        <dependency>
 | 
			
		||||
            <groupId>org.geysermc</groupId>
 | 
			
		||||
            <artifactId>connector</artifactId>
 | 
			
		||||
            <version>1.0.0</version>
 | 
			
		||||
            <version>1.1.0</version>
 | 
			
		||||
            <scope>compile</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
| 
						 | 
				
			
			@ -57,14 +57,34 @@
 | 
			
		|||
                        </goals>
 | 
			
		||||
                        <configuration>
 | 
			
		||||
                            <relocations>
 | 
			
		||||
                                <relocation>
 | 
			
		||||
                                    <pattern>com.fasterxml.jackson</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.velocity.shaded.jackson</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                                <relocation>
 | 
			
		||||
                                    <pattern>it.unimi.dsi.fastutil</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.velocity.shaded.fastutil</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                                <relocation>
 | 
			
		||||
                                    <pattern>org.reflections.reflections</pattern>
 | 
			
		||||
                                    <pattern>org.reflections</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.velocity.shaded.reflections</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                                <relocation>
 | 
			
		||||
                                    <pattern>com.google.common</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.velocity.shaded.google.common</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                                <relocation>
 | 
			
		||||
                                    <pattern>com.google.guava</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.velocity.shaded.google.guava</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                                <relocation>
 | 
			
		||||
                                    <pattern>org.dom4j</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.velocity.shaded.dom4j</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                                <relocation>
 | 
			
		||||
                                    <pattern>net.kyori.adventure</pattern>
 | 
			
		||||
                                    <shadedPattern>org.geysermc.platform.velocity.shaded.adventure</shadedPattern>
 | 
			
		||||
                                </relocation>
 | 
			
		||||
                            </relocations>
 | 
			
		||||
                        </configuration>
 | 
			
		||||
                    </execution>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,7 +27,6 @@ package org.geysermc.platform.velocity;
 | 
			
		|||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonIgnore;
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonProperty;
 | 
			
		||||
import com.velocitypowered.api.plugin.PluginContainer;
 | 
			
		||||
import com.velocitypowered.api.proxy.ProxyServer;
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
| 
						 | 
				
			
			@ -37,25 +36,15 @@ import org.geysermc.connector.configuration.GeyserJacksonConfiguration;
 | 
			
		|||
import java.io.File;
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.nio.file.Paths;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
 | 
			
		||||
@Getter
 | 
			
		||||
@JsonIgnoreProperties(ignoreUnknown = true)
 | 
			
		||||
public class GeyserVelocityConfiguration extends GeyserJacksonConfiguration {
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("floodgate-key-file")
 | 
			
		||||
    private String floodgateKeyFile;
 | 
			
		||||
 | 
			
		||||
public final class GeyserVelocityConfiguration extends GeyserJacksonConfiguration {
 | 
			
		||||
    @JsonIgnore
 | 
			
		||||
    private Path floodgateKey;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Path getFloodgateKeyFile() {
 | 
			
		||||
        return floodgateKey;
 | 
			
		||||
    }
 | 
			
		||||
    private Path floodgateKeyPath;
 | 
			
		||||
 | 
			
		||||
    public void loadFloodgate(GeyserVelocityPlugin plugin, ProxyServer proxyServer, File dataFolder) {
 | 
			
		||||
        Optional<PluginContainer> floodgate = proxyServer.getPluginManager().getPlugin("floodgate");
 | 
			
		||||
        floodgate.ifPresent(it -> floodgateKey = FloodgateKeyLoader.getKey(plugin.getGeyserLogger(), this, Paths.get(dataFolder.toString(), floodgateKeyFile.isEmpty() ? floodgateKeyFile : "public-key.pem"), it, Paths.get("plugins/floodgate/")));
 | 
			
		||||
        PluginContainer floodgate = proxyServer.getPluginManager().getPlugin("floodgate").orElse(null);
 | 
			
		||||
        floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgate, Paths.get("plugins/floodgate/"), dataFolder.toPath(), plugin.getGeyserLogger());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,6 +28,7 @@ package org.geysermc.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 java.util.ArrayList;
 | 
			
		||||
| 
						 | 
				
			
			@ -50,7 +51,11 @@ public class GeyserVelocityDumpInfo extends BootstrapDumpInfo {
 | 
			
		|||
        this.platformVersion = proxy.getVersion().getVersion();
 | 
			
		||||
        this.platformVendor = proxy.getVersion().getVendor();
 | 
			
		||||
        this.onlineMode = proxy.getConfiguration().isOnlineMode();
 | 
			
		||||
        this.serverIP = proxy.getBoundAddress().getHostString();
 | 
			
		||||
        if (AsteriskSerializer.showSensitive || (proxy.getBoundAddress().getHostString().equals("") || proxy.getBoundAddress().getHostString().equals("0.0.0.0"))) {
 | 
			
		||||
            this.serverIP = proxy.getBoundAddress().getHostString();
 | 
			
		||||
        } else {
 | 
			
		||||
            this.serverIP = "***";
 | 
			
		||||
        }
 | 
			
		||||
        this.serverPort = proxy.getBoundAddress().getPort();
 | 
			
		||||
        this.plugins = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,15 +26,16 @@
 | 
			
		|||
package org.geysermc.platform.velocity;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import lombok.Setter;
 | 
			
		||||
import org.geysermc.connector.GeyserLogger;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class GeyserVelocityLogger implements GeyserLogger {
 | 
			
		||||
 | 
			
		||||
    private Logger logger;
 | 
			
		||||
    private boolean debugMode;
 | 
			
		||||
    private final Logger logger;
 | 
			
		||||
    @Getter @Setter
 | 
			
		||||
    private boolean debug;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void severe(String message) {
 | 
			
		||||
| 
						 | 
				
			
			@ -68,12 +69,8 @@ public class GeyserVelocityLogger implements GeyserLogger {
 | 
			
		|||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void debug(String message) {
 | 
			
		||||
        if (debugMode)
 | 
			
		||||
        if (debug) {
 | 
			
		||||
            info(message);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setDebug(boolean debugMode) {
 | 
			
		||||
        this.debugMode = debugMode;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -26,19 +26,17 @@
 | 
			
		|||
package org.geysermc.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.ProxyInitializeEvent;
 | 
			
		||||
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
 | 
			
		||||
import com.velocitypowered.api.plugin.Plugin;
 | 
			
		||||
 | 
			
		||||
import com.velocitypowered.api.proxy.ProxyServer;
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import org.geysermc.connector.common.PlatformType;
 | 
			
		||||
import org.geysermc.connector.configuration.GeyserConfiguration;
 | 
			
		||||
import org.geysermc.connector.GeyserConnector;
 | 
			
		||||
import org.geysermc.connector.bootstrap.GeyserBootstrap;
 | 
			
		||||
import org.geysermc.connector.common.PlatformType;
 | 
			
		||||
import org.geysermc.connector.configuration.GeyserConfiguration;
 | 
			
		||||
import org.geysermc.connector.dump.BootstrapDumpInfo;
 | 
			
		||||
import org.geysermc.connector.ping.GeyserLegacyPingPassthrough;
 | 
			
		||||
import org.geysermc.connector.ping.IGeyserPingPassthrough;
 | 
			
		||||
| 
						 | 
				
			
			@ -92,24 +90,30 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
 | 
			
		|||
 | 
			
		||||
        InetSocketAddress javaAddr = proxyServer.getBoundAddress();
 | 
			
		||||
 | 
			
		||||
        // Don't change the ip if its listening on all interfaces
 | 
			
		||||
        // By default this should be 127.0.0.1 but may need to be changed in some circumstances
 | 
			
		||||
        if (!javaAddr.getHostString().equals("0.0.0.0") && !javaAddr.getHostString().equals("")) {
 | 
			
		||||
            geyserConfig.getRemote().setAddress(javaAddr.getHostString());
 | 
			
		||||
        // By default this should be localhost but may need to be changed in some circumstances
 | 
			
		||||
        if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) {
 | 
			
		||||
            this.geyserConfig.setAutoconfiguredRemote(true);
 | 
			
		||||
            // Don't use localhost if not listening on all interfaces
 | 
			
		||||
            if (!javaAddr.getHostString().equals("0.0.0.0") && !javaAddr.getHostString().equals("")) {
 | 
			
		||||
                this.geyserConfig.getRemote().setAddress(javaAddr.getHostString());
 | 
			
		||||
            }
 | 
			
		||||
            geyserConfig.getRemote().setPort(javaAddr.getPort());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (geyserConfig.getBedrock().isCloneRemotePort()) {
 | 
			
		||||
            geyserConfig.getBedrock().setPort(javaAddr.getPort());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        geyserConfig.getRemote().setPort(javaAddr.getPort());
 | 
			
		||||
 | 
			
		||||
        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"));
 | 
			
		||||
            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.loadFloodgate(this, proxyServer, configFolder.toFile());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,6 +37,8 @@ import org.geysermc.connector.GeyserConnector;
 | 
			
		|||
import org.geysermc.connector.command.GeyserCommand;
 | 
			
		||||
import org.geysermc.connector.utils.LanguageUtils;
 | 
			
		||||
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class GeyserVelocityCommandExecutor implements Command {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -51,10 +53,10 @@ public class GeyserVelocityCommandExecutor implements Command {
 | 
			
		|||
                    source.sendMessage(TextComponent.of(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail")));
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                getCommand(args[0]).execute(new VelocityCommandSender(source), args);
 | 
			
		||||
                getCommand(args[0]).execute(new VelocityCommandSender(source), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            getCommand("help").execute(new VelocityCommandSender(source), args);
 | 
			
		||||
            getCommand("help").execute(new VelocityCommandSender(source), new String[0]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,7 +10,7 @@
 | 
			
		|||
        <relativePath>../</relativePath>
 | 
			
		||||
    </parent>
 | 
			
		||||
    <artifactId>common</artifactId>
 | 
			
		||||
    <version>1.0.0</version>
 | 
			
		||||
    <version>1.1.0</version>
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>com.google.code.gson</groupId>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,12 +10,12 @@
 | 
			
		|||
        <relativePath>../</relativePath>
 | 
			
		||||
    </parent>
 | 
			
		||||
    <artifactId>connector</artifactId>
 | 
			
		||||
    <version>1.0.0</version>
 | 
			
		||||
    <version>1.1.0</version>
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.geysermc</groupId>
 | 
			
		||||
            <artifactId>common</artifactId>
 | 
			
		||||
            <version>1.0.0</version>
 | 
			
		||||
            <version>1.1.0</version>
 | 
			
		||||
            <scope>compile</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
| 
						 | 
				
			
			@ -31,9 +31,9 @@
 | 
			
		|||
            <scope>compile</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>com.nukkitx.protocol</groupId>
 | 
			
		||||
            <artifactId>bedrock-v407</artifactId>
 | 
			
		||||
            <version>2.6.0-SNAPSHOT</version>
 | 
			
		||||
            <groupId>com.github.CloudburstMC.Protocol</groupId>
 | 
			
		||||
            <artifactId>bedrock-v408</artifactId>
 | 
			
		||||
            <version>02f46a8700</version>
 | 
			
		||||
            <scope>compile</scope>
 | 
			
		||||
            <exclusions>
 | 
			
		||||
                <exclusion>
 | 
			
		||||
| 
						 | 
				
			
			@ -96,6 +96,12 @@
 | 
			
		|||
            <version>8.3.1</version>
 | 
			
		||||
            <scope>compile</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>com.nukkitx.fastutil</groupId>
 | 
			
		||||
            <artifactId>fastutil-object-object-maps</artifactId>
 | 
			
		||||
            <version>8.3.1</version>
 | 
			
		||||
            <scope>compile</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>com.google.guava</groupId>
 | 
			
		||||
            <artifactId>guava</artifactId>
 | 
			
		||||
| 
						 | 
				
			
			@ -105,7 +111,7 @@
 | 
			
		|||
        <dependency>
 | 
			
		||||
            <groupId>com.github.steveice10</groupId>
 | 
			
		||||
            <artifactId>mcprotocollib</artifactId>
 | 
			
		||||
            <version>f03b176e18</version>
 | 
			
		||||
            <version>1b01b1ffef</version>
 | 
			
		||||
            <scope>compile</scope>
 | 
			
		||||
            <exclusions>
 | 
			
		||||
                <exclusion>
 | 
			
		||||
| 
						 | 
				
			
			@ -123,24 +129,29 @@
 | 
			
		|||
        <dependency>
 | 
			
		||||
            <groupId>org.reflections</groupId>
 | 
			
		||||
            <artifactId>reflections</artifactId>
 | 
			
		||||
            <version>0.9.12</version>
 | 
			
		||||
            <version>0.9.11</version> <!-- This isn't the latest version to get round https://github.com/ronmamo/reflections/issues/273 -->
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.dom4j</groupId>
 | 
			
		||||
            <artifactId>dom4j</artifactId>
 | 
			
		||||
            <version>2.1.3</version>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>net.kyori</groupId>
 | 
			
		||||
            <artifactId>adventure-api</artifactId>
 | 
			
		||||
            <version>4.0.0-SNAPSHOT</version>
 | 
			
		||||
            <version>4.1.1</version>
 | 
			
		||||
            <scope>compile</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>net.kyori</groupId>
 | 
			
		||||
            <artifactId>adventure-text-serializer-gson</artifactId>
 | 
			
		||||
            <version>4.0.0-SNAPSHOT</version>
 | 
			
		||||
            <version>4.1.1</version>
 | 
			
		||||
            <scope>compile</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>net.kyori</groupId>
 | 
			
		||||
            <artifactId>adventure-text-serializer-legacy</artifactId>
 | 
			
		||||
            <version>4.0.0-SNAPSHOT</version>
 | 
			
		||||
            <version>4.1.1</version>
 | 
			
		||||
            <scope>compile</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
| 
						 | 
				
			
			@ -226,6 +237,52 @@
 | 
			
		|||
                    </execution>
 | 
			
		||||
                </executions>
 | 
			
		||||
            </plugin>
 | 
			
		||||
            <plugin>
 | 
			
		||||
                <groupId>org.codehaus.gmavenplus</groupId>
 | 
			
		||||
                <artifactId>gmavenplus-plugin</artifactId>
 | 
			
		||||
                <version>1.9.1</version>
 | 
			
		||||
                <executions>
 | 
			
		||||
                    <execution>
 | 
			
		||||
                        <phase>process-classes</phase>
 | 
			
		||||
                        <goals>
 | 
			
		||||
                            <goal>execute</goal>
 | 
			
		||||
                        </goals>
 | 
			
		||||
                        <configuration>
 | 
			
		||||
                            <scripts>
 | 
			
		||||
                                <script><![CDATA[
 | 
			
		||||
                                    new org.reflections.Reflections("org.geysermc.connector.network.translators")
 | 
			
		||||
                                        .save("${project.artifactId}/target/classes/META-INF/reflections/org.geysermc.connector.network.translators-reflections.xml")
 | 
			
		||||
                                    new org.reflections.Reflections("org.geysermc.connector.network.translators.item")
 | 
			
		||||
                                        .save("${project.artifactId}/target/classes/META-INF/reflections/org.geysermc.connector.network.translators.item-reflections.xml")
 | 
			
		||||
                                    new org.reflections.Reflections("org.geysermc.connector.network.translators.sound")
 | 
			
		||||
                                        .save("${project.artifactId}/target/classes/META-INF/reflections/org.geysermc.connector.network.translators.sound-reflections.xml")
 | 
			
		||||
                                    new org.reflections.Reflections("org.geysermc.connector.network.translators.world.block.entity")
 | 
			
		||||
                                        .save("${project.artifactId}/target/classes/META-INF/reflections/org.geysermc.connector.network.translators.world.block.entity-reflections.xml")
 | 
			
		||||
                                ]]></script>
 | 
			
		||||
                            </scripts>
 | 
			
		||||
                        </configuration>
 | 
			
		||||
                    </execution>
 | 
			
		||||
                </executions>
 | 
			
		||||
                <dependencies>
 | 
			
		||||
                    <dependency>
 | 
			
		||||
                        <groupId>org.reflections</groupId>
 | 
			
		||||
                        <artifactId>reflections</artifactId>
 | 
			
		||||
                        <version>0.9.11</version>
 | 
			
		||||
                    </dependency>
 | 
			
		||||
                    <dependency>
 | 
			
		||||
                        <groupId>org.dom4j</groupId>
 | 
			
		||||
                        <artifactId>dom4j</artifactId>
 | 
			
		||||
                        <version>2.1.3</version>
 | 
			
		||||
                    </dependency>
 | 
			
		||||
                    <dependency>
 | 
			
		||||
                        <groupId>org.codehaus.groovy</groupId>
 | 
			
		||||
                        <artifactId>groovy-all</artifactId>
 | 
			
		||||
                        <version>3.0.5</version>
 | 
			
		||||
                        <scope>runtime</scope>
 | 
			
		||||
                        <type>pom</type>
 | 
			
		||||
                    </dependency>
 | 
			
		||||
                </dependencies>
 | 
			
		||||
            </plugin>
 | 
			
		||||
        </plugins>
 | 
			
		||||
    </build>
 | 
			
		||||
</project>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,17 +25,19 @@
 | 
			
		|||
 | 
			
		||||
package org.geysermc.connector;
 | 
			
		||||
 | 
			
		||||
import org.geysermc.connector.configuration.GeyserJacksonConfiguration;
 | 
			
		||||
import org.geysermc.connector.utils.LanguageUtils;
 | 
			
		||||
import org.geysermc.connector.configuration.GeyserConfiguration;
 | 
			
		||||
 | 
			
		||||
import java.nio.file.Files;
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
 | 
			
		||||
public class FloodgateKeyLoader {
 | 
			
		||||
    public static Path getKey(GeyserLogger logger, GeyserConfiguration config, Path floodgateKey, Object floodgate, Path floodgateFolder) {
 | 
			
		||||
    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 = floodgateFolder.resolve("public-key.pem");
 | 
			
		||||
                Path autoKey = floodgateDataFolder.resolve("public-key.pem");
 | 
			
		||||
                if (Files.exists(autoKey)) {
 | 
			
		||||
                    logger.info(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.auto_loaded"));
 | 
			
		||||
                    floodgateKey = autoKey;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,12 +25,11 @@
 | 
			
		|||
 | 
			
		||||
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.BedrockPacketCodec;
 | 
			
		||||
import com.nukkitx.protocol.bedrock.BedrockServer;
 | 
			
		||||
import com.nukkitx.protocol.bedrock.v407.Bedrock_v407;
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import lombok.Setter;
 | 
			
		||||
import org.geysermc.connector.bootstrap.GeyserBootstrap;
 | 
			
		||||
| 
						 | 
				
			
			@ -50,22 +49,27 @@ 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.DockerCheck;
 | 
			
		||||
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;
 | 
			
		||||
| 
						 | 
				
			
			@ -74,9 +78,7 @@ import java.util.concurrent.TimeUnit;
 | 
			
		|||
@Getter
 | 
			
		||||
public class GeyserConnector {
 | 
			
		||||
 | 
			
		||||
    public static final ObjectMapper JSON_MAPPER = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES);
 | 
			
		||||
 | 
			
		||||
    public static final BedrockPacketCodec BEDROCK_PACKET_CODEC = Bedrock_v407.V407_CODEC;
 | 
			
		||||
    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
 | 
			
		||||
| 
						 | 
				
			
			@ -135,12 +137,23 @@ public class GeyserConnector {
 | 
			
		|||
        ItemTranslator.init();
 | 
			
		||||
        LocaleUtils.init();
 | 
			
		||||
        PotionMixRegistry.init();
 | 
			
		||||
        RecipeRegistry.init();
 | 
			
		||||
        SoundRegistry.init();
 | 
			
		||||
        SoundHandlerRegistry.init();
 | 
			
		||||
        AddonListenerRegistry.init();
 | 
			
		||||
 | 
			
		||||
        if (platformType != PlatformType.STANDALONE) {
 | 
			
		||||
            DockerCheck.check(bootstrap);
 | 
			
		||||
        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();
 | 
			
		||||
| 
						 | 
				
			
			@ -158,7 +171,7 @@ public class GeyserConnector {
 | 
			
		|||
                    config.getRemote().setPort(remotePort = Integer.parseInt(record[2]));
 | 
			
		||||
                    logger.debug("Found SRV record \"" + remoteAddress + ":" + remotePort + "\"");
 | 
			
		||||
                }
 | 
			
		||||
            } catch (Exception ex) {
 | 
			
		||||
            } 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
 | 
			
		||||
| 
						 | 
				
			
			@ -181,7 +194,7 @@ public class GeyserConnector {
 | 
			
		|||
            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(), config.getBedrock().getPort()));
 | 
			
		||||
                logger.severe(LanguageUtils.getLocaleStringLog("geyser.core.fail", config.getBedrock().getAddress(), String.valueOf(config.getBedrock().getPort())));
 | 
			
		||||
                throwable.printStackTrace();
 | 
			
		||||
            }
 | 
			
		||||
        }).join();
 | 
			
		||||
| 
						 | 
				
			
			@ -190,8 +203,39 @@ public class GeyserConnector {
 | 
			
		|||
            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));
 | 
			
		||||
            metrics.addCustomChart(new Metrics.SimplePie("authMode", authType.name()::toLowerCase));
 | 
			
		||||
            // 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<String, Integer> 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<String, Integer> 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;
 | 
			
		||||
| 
						 | 
				
			
			@ -213,6 +257,10 @@ public class GeyserConnector {
 | 
			
		|||
            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() {
 | 
			
		||||
| 
						 | 
				
			
			@ -298,6 +346,19 @@ public class GeyserConnector {
 | 
			
		|||
        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;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,6 +36,9 @@ public interface GeyserLogger {
 | 
			
		|||
 | 
			
		||||
    /**
 | 
			
		||||
     * Logs a severe message and an exception to console
 | 
			
		||||
     *
 | 
			
		||||
     * @param message the message to log
 | 
			
		||||
     * @param error the error to throw
 | 
			
		||||
     */
 | 
			
		||||
    void severe(String message, Throwable error);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -48,6 +51,9 @@ public interface GeyserLogger {
 | 
			
		|||
 | 
			
		||||
    /**
 | 
			
		||||
     * Logs an error message and an exception to console
 | 
			
		||||
     *
 | 
			
		||||
     * @param message the message to log
 | 
			
		||||
     * @param error the error to throw
 | 
			
		||||
     */
 | 
			
		||||
    void error(String message, Throwable error);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -78,4 +84,9 @@ public interface GeyserLogger {
 | 
			
		|||
     * @param debug if the logger should print debug messages
 | 
			
		||||
     */
 | 
			
		||||
    void setDebug(boolean debug);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * If debug is enabled for this logger
 | 
			
		||||
     */
 | 
			
		||||
    boolean isDebug();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,14 +30,14 @@ 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.CachedChunkManager;
 | 
			
		||||
import org.geysermc.connector.network.translators.world.GeyserWorldManager;
 | 
			
		||||
import org.geysermc.connector.network.translators.world.WorldManager;
 | 
			
		||||
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
 | 
			
		||||
public interface GeyserBootstrap {
 | 
			
		||||
 | 
			
		||||
    CachedChunkManager DEFAULT_CHUNK_MANAGER = new CachedChunkManager();
 | 
			
		||||
    GeyserWorldManager DEFAULT_CHUNK_MANAGER = new GeyserWorldManager();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the GeyserBootstrap is enabled
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,10 +33,12 @@ import org.geysermc.connector.common.ChatColor;
 | 
			
		|||
import org.geysermc.connector.GeyserConnector;
 | 
			
		||||
import org.geysermc.connector.command.CommandSender;
 | 
			
		||||
import org.geysermc.connector.command.GeyserCommand;
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
public class DumpCommand extends GeyserCommand {
 | 
			
		||||
| 
						 | 
				
			
			@ -49,43 +51,80 @@ public class DumpCommand extends GeyserCommand {
 | 
			
		|||
        super(name, description, permission);
 | 
			
		||||
 | 
			
		||||
        this.connector = connector;
 | 
			
		||||
 | 
			
		||||
        final SimpleFilterProvider filter = new SimpleFilterProvider();
 | 
			
		||||
        filter.addFilter("dump_user_auth", SimpleBeanPropertyFilter.serializeAllExcept(new String[] {"password"}));
 | 
			
		||||
 | 
			
		||||
        MAPPER.setFilterProvider(filter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @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.getLocaleStringLog("geyser.commands.dump.collecting"));
 | 
			
		||||
        String dumpData = "";
 | 
			
		||||
        try {
 | 
			
		||||
            dumpData = MAPPER.writeValueAsString(new DumpInfo());
 | 
			
		||||
            if (offlineDump) {
 | 
			
		||||
                dumpData = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(new DumpInfo());
 | 
			
		||||
            } else {
 | 
			
		||||
                dumpData = MAPPER.writeValueAsString(new DumpInfo());
 | 
			
		||||
            }
 | 
			
		||||
        } catch (IOException e) {
 | 
			
		||||
            sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.collect_error"));
 | 
			
		||||
            connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.collect_error_short"), e);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.uploading"));
 | 
			
		||||
        String response;
 | 
			
		||||
        JsonNode responseNode;
 | 
			
		||||
        try {
 | 
			
		||||
            response = WebUtils.post(DUMP_URL + "documents", dumpData);
 | 
			
		||||
            responseNode = MAPPER.readTree(response);
 | 
			
		||||
        } catch (IOException e) {
 | 
			
		||||
            sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error"));
 | 
			
		||||
            connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error_short"), e);
 | 
			
		||||
            return;
 | 
			
		||||
        String uploadedDumpUrl = "";
 | 
			
		||||
 | 
			
		||||
        if (offlineDump) {
 | 
			
		||||
            sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.writing"));
 | 
			
		||||
 | 
			
		||||
            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.getLocaleStringLog("geyser.commands.dump.write_error"));
 | 
			
		||||
                connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.write_error_short"), e);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            uploadedDumpUrl = "dump.json";
 | 
			
		||||
        } else {
 | 
			
		||||
            sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.uploading"));
 | 
			
		||||
 | 
			
		||||
            String response;
 | 
			
		||||
            JsonNode responseNode;
 | 
			
		||||
            try {
 | 
			
		||||
                response = WebUtils.post(DUMP_URL + "documents", dumpData);
 | 
			
		||||
                responseNode = MAPPER.readTree(response);
 | 
			
		||||
            } catch (IOException e) {
 | 
			
		||||
                sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error"));
 | 
			
		||||
                connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error_short"), e);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!responseNode.has("key")) {
 | 
			
		||||
                sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error_short") + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            uploadedDumpUrl = DUMP_URL + responseNode.get("key").asText();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!responseNode.has("key")) {
 | 
			
		||||
            sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error_short") + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        String uploadedDumpUrl = DUMP_URL + responseNode.get("key").asText();
 | 
			
		||||
        sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.message") + " " + ChatColor.DARK_AQUA + uploadedDumpUrl);
 | 
			
		||||
        if (!sender.isConsole()) {
 | 
			
		||||
            connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.commands.dump.created", sender.getName(), uploadedDumpUrl));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -49,10 +49,6 @@ public class StopCommand extends GeyserCommand {
 | 
			
		|||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        connector.shutdown();
 | 
			
		||||
 | 
			
		||||
        if (connector.getPlatformType() == PlatformType.STANDALONE) {
 | 
			
		||||
            System.exit(0);
 | 
			
		||||
        }
 | 
			
		||||
        connector.getBootstrap().onDisable();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,10 +26,12 @@
 | 
			
		|||
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;
 | 
			
		||||
| 
						 | 
				
			
			@ -37,6 +39,7 @@ 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 {
 | 
			
		||||
| 
						 | 
				
			
			@ -50,7 +53,15 @@ public class VersionCommand extends GeyserCommand {
 | 
			
		|||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void execute(CommandSender sender, String[] args) {
 | 
			
		||||
        sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.version", GeyserConnector.NAME, GeyserConnector.VERSION, MinecraftConstants.GAME_VERSION, GeyserConnector.BEDROCK_PACKET_CODEC.getMinecraftVersion()));
 | 
			
		||||
        String bedrockVersions;
 | 
			
		||||
        List<BedrockPacketCodec> 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.getLocaleStringLog("geyser.commands.version.version", GeyserConnector.NAME, GeyserConnector.VERSION, MinecraftConstants.GAME_VERSION, bedrockVersions));
 | 
			
		||||
 | 
			
		||||
        // Disable update checking in dev mode
 | 
			
		||||
        //noinspection ConstantConditions - changes in production
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,11 +32,13 @@ import lombok.Getter;
 | 
			
		|||
@AllArgsConstructor
 | 
			
		||||
public enum PlatformType {
 | 
			
		||||
 | 
			
		||||
    ANDROID("Android"),
 | 
			
		||||
    BUNGEECORD("BungeeCord"),
 | 
			
		||||
    FABRIC("Fabric"),
 | 
			
		||||
    SPIGOT("Spigot"),
 | 
			
		||||
    SPONGE("Sponge"),
 | 
			
		||||
    STANDALONE("Standalone"),
 | 
			
		||||
    VELOCITY("Velocity");
 | 
			
		||||
 | 
			
		||||
    private String platformName;
 | 
			
		||||
    private final String platformName;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,34 +42,46 @@ import java.lang.annotation.Target;
 | 
			
		|||
import java.util.Optional;
 | 
			
		||||
 | 
			
		||||
public class AsteriskSerializer extends StdSerializer<Object> implements ContextualSerializer {
 | 
			
		||||
 | 
			
		||||
    public static boolean showSensitive = false;
 | 
			
		||||
 | 
			
		||||
    @Target({ElementType.FIELD})
 | 
			
		||||
    @Retention(RetentionPolicy.RUNTIME)
 | 
			
		||||
    @JacksonAnnotationsInside
 | 
			
		||||
    @JsonSerialize(using = AsteriskSerializer.class)
 | 
			
		||||
    public @interface Asterisk {
 | 
			
		||||
        String value() default "***";
 | 
			
		||||
        boolean sensitive() default false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    String asterisk;
 | 
			
		||||
    boolean sensitive;
 | 
			
		||||
 | 
			
		||||
    public AsteriskSerializer() {
 | 
			
		||||
        super(Object.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public AsteriskSerializer(String asterisk) {
 | 
			
		||||
    public AsteriskSerializer(String asterisk, boolean sensitive) {
 | 
			
		||||
        super(Object.class);
 | 
			
		||||
        this.asterisk = asterisk;
 | 
			
		||||
        this.sensitive = sensitive;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty property) {
 | 
			
		||||
        Optional<Asterisk> anno = Optional.ofNullable(property)
 | 
			
		||||
                .map(prop -> prop.getAnnotation(Asterisk.class));
 | 
			
		||||
        return new AsteriskSerializer(anno.map(Asterisk::value).orElse(null));
 | 
			
		||||
 | 
			
		||||
        return new AsteriskSerializer(anno.map(Asterisk::value).orElse(null), anno.map(Asterisk::sensitive).orElse(null));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void serialize(Object obj, JsonGenerator gen, SerializerProvider prov) throws IOException {
 | 
			
		||||
        if (sensitive && showSensitive) {
 | 
			
		||||
            gen.writeObject(obj);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        gen.writeString(asterisk);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,7 +27,6 @@ package org.geysermc.connector.configuration;
 | 
			
		|||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonIgnore;
 | 
			
		||||
import org.geysermc.connector.GeyserLogger;
 | 
			
		||||
 | 
			
		||||
import org.geysermc.connector.utils.LanguageUtils;
 | 
			
		||||
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
| 
						 | 
				
			
			@ -36,7 +35,7 @@ import java.util.Map;
 | 
			
		|||
public interface GeyserConfiguration {
 | 
			
		||||
 | 
			
		||||
    // Modify this when you update the config
 | 
			
		||||
    int CURRENT_CONFIG_VERSION = 3;
 | 
			
		||||
    int CURRENT_CONFIG_VERSION = 4;
 | 
			
		||||
 | 
			
		||||
    IBedrockConfiguration getBedrock();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -49,6 +48,9 @@ public interface GeyserConfiguration {
 | 
			
		|||
    @JsonIgnore
 | 
			
		||||
    boolean isPassthroughMotd();
 | 
			
		||||
 | 
			
		||||
    @JsonIgnore
 | 
			
		||||
    boolean isPassthroughProtocolName();
 | 
			
		||||
 | 
			
		||||
    @JsonIgnore
 | 
			
		||||
    boolean isPassthroughPlayerCounts();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -71,12 +73,14 @@ public interface GeyserConfiguration {
 | 
			
		|||
 | 
			
		||||
    String getDefaultLocale();
 | 
			
		||||
 | 
			
		||||
    Path getFloodgateKeyFile();
 | 
			
		||||
    Path getFloodgateKeyPath();
 | 
			
		||||
 | 
			
		||||
    boolean isAboveBedrockNetherBuilding();
 | 
			
		||||
 | 
			
		||||
    boolean isCacheChunks();
 | 
			
		||||
 | 
			
		||||
    boolean isForceResourcePacks();
 | 
			
		||||
 | 
			
		||||
    int getCacheImages();
 | 
			
		||||
 | 
			
		||||
    IMetricsInfo getMetrics();
 | 
			
		||||
| 
						 | 
				
			
			@ -92,6 +96,8 @@ public interface GeyserConfiguration {
 | 
			
		|||
        String getMotd1();
 | 
			
		||||
 | 
			
		||||
        String getMotd2();
 | 
			
		||||
 | 
			
		||||
        String getServerName();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    interface IRemoteConfiguration {
 | 
			
		||||
| 
						 | 
				
			
			@ -120,6 +126,11 @@ public interface GeyserConfiguration {
 | 
			
		|||
        String getUniqueId();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int getScoreboardPacketThreshold();
 | 
			
		||||
 | 
			
		||||
    // if u have offline mode enabled pls be safe
 | 
			
		||||
    boolean isEnableProxyConnections();
 | 
			
		||||
 | 
			
		||||
    int getMtu();
 | 
			
		||||
 | 
			
		||||
    int getConfigVersion();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,98 +29,116 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 | 
			
		|||
import com.fasterxml.jackson.annotation.JsonProperty;
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import lombok.Setter;
 | 
			
		||||
import org.geysermc.connector.GeyserConnector;
 | 
			
		||||
import org.geysermc.connector.common.serializer.AsteriskSerializer;
 | 
			
		||||
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
@Getter
 | 
			
		||||
@JsonIgnoreProperties(ignoreUnknown = true)
 | 
			
		||||
public abstract class GeyserJacksonConfiguration implements GeyserConfiguration {
 | 
			
		||||
 | 
			
		||||
    private BedrockConfiguration bedrock;
 | 
			
		||||
    private RemoteConfiguration remote;
 | 
			
		||||
    /**
 | 
			
		||||
     * If the config was originally 'auto' before the values changed
 | 
			
		||||
     */
 | 
			
		||||
    @Setter
 | 
			
		||||
    private boolean autoconfiguredRemote = false;
 | 
			
		||||
 | 
			
		||||
    private BedrockConfiguration bedrock = new BedrockConfiguration();
 | 
			
		||||
    private RemoteConfiguration remote = new RemoteConfiguration();
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("floodgate-key-file")
 | 
			
		||||
    private String floodgateKeyFile;
 | 
			
		||||
    private String floodgateKeyFile = "public-key.pem";
 | 
			
		||||
 | 
			
		||||
    public abstract Path getFloodgateKeyFile();
 | 
			
		||||
    public abstract Path getFloodgateKeyPath();
 | 
			
		||||
 | 
			
		||||
    private Map<String, UserAuthenticationInfo> userAuths;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("command-suggestions")
 | 
			
		||||
    private boolean commandSuggestions;
 | 
			
		||||
    private boolean commandSuggestions = true;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("passthrough-motd")
 | 
			
		||||
    private boolean isPassthroughMotd;
 | 
			
		||||
    private boolean isPassthroughMotd = false;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("passthrough-player-counts")
 | 
			
		||||
    private boolean isPassthroughPlayerCounts;
 | 
			
		||||
    private boolean isPassthroughPlayerCounts = false;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("passthrough-protocol-name")
 | 
			
		||||
    private boolean isPassthroughProtocolName = false;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("legacy-ping-passthrough")
 | 
			
		||||
    private boolean isLegacyPingPassthrough;
 | 
			
		||||
    private boolean isLegacyPingPassthrough = false;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("ping-passthrough-interval")
 | 
			
		||||
    private int pingPassthroughInterval;
 | 
			
		||||
    private int pingPassthroughInterval = 3;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("max-players")
 | 
			
		||||
    private int maxPlayers;
 | 
			
		||||
    private int maxPlayers = 100;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("debug-mode")
 | 
			
		||||
    private boolean debugMode;
 | 
			
		||||
    private boolean debugMode = false;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("general-thread-pool")
 | 
			
		||||
    private int generalThreadPool;
 | 
			
		||||
    private int generalThreadPool = 32;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("allow-third-party-capes")
 | 
			
		||||
    private boolean allowThirdPartyCapes;
 | 
			
		||||
    private boolean allowThirdPartyCapes = true;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("show-cooldown")
 | 
			
		||||
    private boolean showCooldown = true;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("allow-third-party-ears")
 | 
			
		||||
    private boolean allowThirdPartyEars;
 | 
			
		||||
    private boolean allowThirdPartyEars = false;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("default-locale")
 | 
			
		||||
    private String defaultLocale;
 | 
			
		||||
    private String defaultLocale = null; // is null by default so system language takes priority
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("cache-chunks")
 | 
			
		||||
    private boolean cacheChunks;
 | 
			
		||||
    private boolean cacheChunks = false;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("cache-images")
 | 
			
		||||
    private int cacheImages = 0;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("above-bedrock-nether-building")
 | 
			
		||||
    private boolean aboveBedrockNetherBuilding;
 | 
			
		||||
    private boolean aboveBedrockNetherBuilding = false;
 | 
			
		||||
 | 
			
		||||
    private MetricsInfo metrics;
 | 
			
		||||
    @JsonProperty("force-resource-packs")
 | 
			
		||||
    private boolean forceResourcePacks = true;
 | 
			
		||||
 | 
			
		||||
    private MetricsInfo metrics = new MetricsInfo();
 | 
			
		||||
 | 
			
		||||
    @Getter
 | 
			
		||||
    public static class BedrockConfiguration implements IBedrockConfiguration {
 | 
			
		||||
 | 
			
		||||
        private String address;
 | 
			
		||||
        @AsteriskSerializer.Asterisk(sensitive = true)
 | 
			
		||||
        private String address = "0.0.0.0";
 | 
			
		||||
 | 
			
		||||
        @Setter
 | 
			
		||||
        private int port;
 | 
			
		||||
        private int port = 19132;
 | 
			
		||||
 | 
			
		||||
        @JsonProperty("clone-remote-port")
 | 
			
		||||
        private boolean cloneRemotePort;
 | 
			
		||||
        private boolean cloneRemotePort = false;
 | 
			
		||||
 | 
			
		||||
        private String motd1;
 | 
			
		||||
        private String motd2;
 | 
			
		||||
        private String motd1 = "GeyserMC";
 | 
			
		||||
        private String motd2 = "Geyser";
 | 
			
		||||
 | 
			
		||||
        @JsonProperty("server-name")
 | 
			
		||||
        private String serverName = GeyserConnector.NAME;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Getter
 | 
			
		||||
    public static class RemoteConfiguration implements IRemoteConfiguration {
 | 
			
		||||
        @Setter
 | 
			
		||||
        @AsteriskSerializer.Asterisk(sensitive = true)
 | 
			
		||||
        private String address = "auto";
 | 
			
		||||
 | 
			
		||||
        @Setter
 | 
			
		||||
        private String address;
 | 
			
		||||
        private int port = 25565;
 | 
			
		||||
 | 
			
		||||
        @Setter
 | 
			
		||||
        private int port;
 | 
			
		||||
 | 
			
		||||
        @JsonProperty("auth-type")
 | 
			
		||||
        private String authType;
 | 
			
		||||
        private String authType = "online";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Getter
 | 
			
		||||
| 
						 | 
				
			
			@ -134,16 +152,21 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
 | 
			
		|||
 | 
			
		||||
    @Getter
 | 
			
		||||
    public static class MetricsInfo implements IMetricsInfo {
 | 
			
		||||
 | 
			
		||||
        private boolean enabled;
 | 
			
		||||
        private boolean enabled = true;
 | 
			
		||||
 | 
			
		||||
        @JsonProperty("uuid")
 | 
			
		||||
        private String uniqueId;
 | 
			
		||||
        private String uniqueId = UUID.randomUUID().toString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("scoreboard-packet-threshold")
 | 
			
		||||
    private int scoreboardPacketThreshold = 10;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("enable-proxy-connections")
 | 
			
		||||
    private boolean enableProxyConnections = false;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("mtu")
 | 
			
		||||
    private int mtu = 1400;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty("config-version")
 | 
			
		||||
    private int configVersion;
 | 
			
		||||
    private int configVersion = 0;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,7 +31,9 @@ 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;
 | 
			
		||||
| 
						 | 
				
			
			@ -111,16 +113,21 @@ public class DumpInfo {
 | 
			
		|||
        private final boolean dockerCheck;
 | 
			
		||||
 | 
			
		||||
        NetworkInfo() {
 | 
			
		||||
            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) {
 | 
			
		||||
            if (AsteriskSerializer.showSensitive) {
 | 
			
		||||
                try {
 | 
			
		||||
                    // Fallback to the normal way of getting the local IP
 | 
			
		||||
                    this.internalIP = InetAddress.getLocalHost().getHostAddress();
 | 
			
		||||
                } catch (UnknownHostException ignored) { }
 | 
			
		||||
                    // 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();
 | 
			
		||||
| 
						 | 
				
			
			@ -136,8 +143,8 @@ public class DumpInfo {
 | 
			
		|||
        private final int javaProtocol;
 | 
			
		||||
 | 
			
		||||
        MCInfo() {
 | 
			
		||||
            this.bedrockVersion = GeyserConnector.BEDROCK_PACKET_CODEC.getMinecraftVersion();
 | 
			
		||||
            this.bedrockProtocol = GeyserConnector.BEDROCK_PACKET_CODEC.getProtocolVersion();
 | 
			
		||||
            this.bedrockVersion = BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion();
 | 
			
		||||
            this.bedrockProtocol = BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion();
 | 
			
		||||
            this.javaVersion = MinecraftConstants.GAME_VERSION;
 | 
			
		||||
            this.javaProtocol = MinecraftConstants.PROTOCOL_VERSION;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,6 +43,8 @@ public class AreaEffectCloudEntity extends Entity {
 | 
			
		|||
 | 
			
		||||
        // 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
 | 
			
		||||
| 
						 | 
				
			
			@ -50,11 +52,14 @@ public class AreaEffectCloudEntity extends Entity {
 | 
			
		|||
        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();
 | 
			
		||||
            metadata.put(EntityData.AREA_EFFECT_CLOUD_PARTICLE_ID, EffectRegistry.getParticleString(particle.getType()));
 | 
			
		||||
        } else if (entityMetadata.getId() == 8) {
 | 
			
		||||
            metadata.put(EntityData.POTION_AUX_VALUE, entityMetadata.getValue());
 | 
			
		||||
            int particleId = EffectRegistry.getParticleId(particle.getType());
 | 
			
		||||
            if (particleId != -1) {
 | 
			
		||||
                metadata.put(EntityData.AREA_EFFECT_CLOUD_PARTICLE_ID, particleId);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        super.updateBedrockMetadata(entityMetadata, session);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,67 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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.message.Message;
 | 
			
		||||
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.utils.MessageUtils;
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @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, MessageUtils.getBedrockMessage((Message) entityMetadata.getValue()));
 | 
			
		||||
        }
 | 
			
		||||
        super.updateBedrockMetadata(entityMetadata, session);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * By default, the command block shown is purple on Bedrock, which does not match Java Edition's orange.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void updateDefaultBlockMetadata() {
 | 
			
		||||
        metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.BEDROCK_RUNTIME_COMMAND_BLOCK_ID);
 | 
			
		||||
        metadata.put(EntityData.DISPLAY_OFFSET, 6);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -32,8 +32,7 @@ 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.TextMessage;
 | 
			
		||||
import com.github.steveice10.mc.protocol.data.message.TranslationMessage;
 | 
			
		||||
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;
 | 
			
		||||
| 
						 | 
				
			
			@ -318,13 +317,10 @@ public class Entity {
 | 
			
		|||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case 2: // custom name
 | 
			
		||||
                if (entityMetadata.getValue() instanceof TextMessage) {
 | 
			
		||||
                    TextMessage name = (TextMessage) entityMetadata.getValue();
 | 
			
		||||
                    if (name != null)
 | 
			
		||||
                        metadata.put(EntityData.NAMETAG, MessageUtils.getBedrockMessage(name));
 | 
			
		||||
                } else if (entityMetadata.getValue() instanceof TranslationMessage) {
 | 
			
		||||
                    TranslationMessage message = (TranslationMessage) entityMetadata.getValue();
 | 
			
		||||
                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, MessageUtils.getTranslatedBedrockMessage(message, session.getClientData().getLanguageCode(), true));
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,6 +40,7 @@ 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;
 | 
			
		||||
| 
						 | 
				
			
			@ -55,13 +56,26 @@ public class FireworkEntity extends Entity {
 | 
			
		|||
    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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,7 +34,6 @@ import com.nukkitx.nbt.NbtMap;
 | 
			
		|||
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.StartGamePacket;
 | 
			
		||||
import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket;
 | 
			
		||||
import org.geysermc.connector.entity.type.EntityType;
 | 
			
		||||
import org.geysermc.connector.network.session.GeyserSession;
 | 
			
		||||
| 
						 | 
				
			
			@ -102,13 +101,7 @@ public class ItemFrameEntity extends Entity {
 | 
			
		|||
            ItemEntry itemEntry = ItemRegistry.getItem((ItemStack) entityMetadata.getValue());
 | 
			
		||||
            NbtMapBuilder builder = NbtMap.builder();
 | 
			
		||||
 | 
			
		||||
            String blockName = "";
 | 
			
		||||
            for (StartGamePacket.ItemEntry startGamePacketItemEntry : ItemRegistry.ITEMS) {
 | 
			
		||||
                if (startGamePacketItemEntry.getId() == (short) itemEntry.getBedrockId()) {
 | 
			
		||||
                    blockName = startGamePacketItemEntry.getIdentifier();
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            String blockName = ItemRegistry.getBedrockIdentifer(itemEntry);
 | 
			
		||||
 | 
			
		||||
            builder.putByte("Count", (byte) itemData.getCount());
 | 
			
		||||
            if (itemData.getTag() != null) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,6 +27,7 @@ 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.game.scoreboard.NameTagVisibility;
 | 
			
		||||
import com.github.steveice10.mc.protocol.data.message.TextMessage;
 | 
			
		||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
 | 
			
		||||
import com.nukkitx.math.vector.Vector3f;
 | 
			
		||||
| 
						 | 
				
			
			@ -35,11 +36,15 @@ 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.EntityLinkData;
 | 
			
		||||
import com.nukkitx.protocol.bedrock.packet.*;
 | 
			
		||||
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;
 | 
			
		||||
| 
						 | 
				
			
			@ -62,8 +67,14 @@ public class PlayerEntity extends LivingEntity {
 | 
			
		|||
    private boolean playerList = true;  // Player is in the player list
 | 
			
		||||
    private final EntityEffectCache effectCache;
 | 
			
		||||
 | 
			
		||||
    private Entity leftParrot;
 | 
			
		||||
    private Entity rightParrot;
 | 
			
		||||
    /**
 | 
			
		||||
     * 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);
 | 
			
		||||
| 
						 | 
				
			
			@ -75,12 +86,6 @@ public class PlayerEntity extends LivingEntity {
 | 
			
		|||
        if (geyserId == 1) valid = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean despawnEntity(GeyserSession session) {
 | 
			
		||||
        super.despawnEntity(session);
 | 
			
		||||
        return !playerList; // don't remove from cache when still on playerlist
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void spawnEntity(GeyserSession session) {
 | 
			
		||||
        if (geyserId == 1) return;
 | 
			
		||||
| 
						 | 
				
			
			@ -95,7 +100,7 @@ public class PlayerEntity extends LivingEntity {
 | 
			
		|||
        addPlayerPacket.setMotion(motion);
 | 
			
		||||
        addPlayerPacket.setHand(hand);
 | 
			
		||||
        addPlayerPacket.getAdventureSettings().setCommandPermission(CommandPermission.NORMAL);
 | 
			
		||||
        addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.VISITOR);
 | 
			
		||||
        addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.MEMBER);
 | 
			
		||||
        addPlayerPacket.setDeviceId("");
 | 
			
		||||
        addPlayerPacket.setPlatformChatId("");
 | 
			
		||||
        addPlayerPacket.getMetadata().putAll(metadata);
 | 
			
		||||
| 
						 | 
				
			
			@ -113,7 +118,7 @@ public class PlayerEntity extends LivingEntity {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    public void sendPlayer(GeyserSession session) {
 | 
			
		||||
        if(session.getEntityCache().getPlayerEntity(uuid) == null)
 | 
			
		||||
        if (session.getEntityCache().getPlayerEntity(uuid) == null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        if (session.getUpstream().isInitialized() && session.getEntityCache().getEntityByGeyserId(geyserId) == null) {
 | 
			
		||||
| 
						 | 
				
			
			@ -186,6 +191,12 @@ public class PlayerEntity extends LivingEntity {
 | 
			
		|||
    @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
 | 
			
		||||
| 
						 | 
				
			
			@ -199,6 +210,12 @@ public class PlayerEntity extends LivingEntity {
 | 
			
		|||
        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
 | 
			
		||||
| 
						 | 
				
			
			@ -211,20 +228,25 @@ public class PlayerEntity extends LivingEntity {
 | 
			
		|||
        super.updateBedrockMetadata(entityMetadata, session);
 | 
			
		||||
 | 
			
		||||
        if (entityMetadata.getId() == 2) {
 | 
			
		||||
            // System.out.println(session.getScoreboardCache().getScoreboard().getObjectives().keySet());
 | 
			
		||||
            for (Team team : session.getScoreboardCache().getScoreboard().getTeams().values()) {
 | 
			
		||||
                // session.getConnector().getLogger().info("team name " + team.getName());
 | 
			
		||||
                // session.getConnector().getLogger().info("team entities " + team.getEntities());
 | 
			
		||||
            }
 | 
			
		||||
            String username = this.username;
 | 
			
		||||
            TextMessage name = (TextMessage) entityMetadata.getValue();
 | 
			
		||||
            if (name != null) {
 | 
			
		||||
                username = MessageUtils.getBedrockMessage(name);
 | 
			
		||||
            }
 | 
			
		||||
            Team team = session.getScoreboardCache().getScoreboard().getTeamFor(username);
 | 
			
		||||
            Team team = session.getWorldCache().getScoreboard().getTeamFor(username);
 | 
			
		||||
            if (team != null) {
 | 
			
		||||
                // session.getConnector().getLogger().info("team name es " + team.getName() + " with prefix " + team.getPrefix() + " and suffix " + team.getSuffix());
 | 
			
		||||
                metadata.put(EntityData.NAMETAG, team.getPrefix() + MessageUtils.toChatColor(team.getColor()) + username + team.getSuffix());
 | 
			
		||||
                // Cover different visibility settings
 | 
			
		||||
                if (team.getNameTagVisibility() == NameTagVisibility.NEVER) {
 | 
			
		||||
                    metadata.put(EntityData.NAMETAG, "");
 | 
			
		||||
                } else if (team.getNameTagVisibility() == NameTagVisibility.HIDE_FOR_OTHER_TEAMS &&
 | 
			
		||||
                        !team.getEntities().contains(session.getPlayerEntity().getUsername())) {
 | 
			
		||||
                    metadata.put(EntityData.NAMETAG, "");
 | 
			
		||||
                } else if (team.getNameTagVisibility() == NameTagVisibility.HIDE_FOR_OWN_TEAM &&
 | 
			
		||||
                        team.getEntities().contains(session.getPlayerEntity().getUsername())) {
 | 
			
		||||
                    metadata.put(EntityData.NAMETAG, "");
 | 
			
		||||
                } else {
 | 
			
		||||
                    metadata.put(EntityData.NAMETAG, team.getPrefix() + MessageUtils.toChatColor(team.getColor()) + username + team.getSuffix());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -240,11 +262,15 @@ public class PlayerEntity extends LivingEntity {
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        // Parrot occupying shoulder
 | 
			
		||||
        if ((entityMetadata.getId() == 18 && leftParrot == null) || (entityMetadata.getId() == 19 && rightParrot == null)) { // null check since this code just creates the parrot
 | 
			
		||||
        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
 | 
			
		||||
                Entity parrot = new Entity(0, session.getEntityCache().getNextEntityId().incrementAndGet(),
 | 
			
		||||
                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());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,12 +25,39 @@
 | 
			
		|||
 | 
			
		||||
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.item.TippedArrowPotion;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @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);
 | 
			
		||||
            } 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);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        super.updateBedrockMetadata(entityMetadata, session);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,6 +34,8 @@ 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);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -57,12 +59,13 @@ public class WolfEntity extends TameableEntity {
 | 
			
		|||
        // 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, (byte) (int) entityMetadata.getValue());
 | 
			
		||||
            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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
package org.geysermc.connector.entity.living.monster;
 | 
			
		||||
 | 
			
		||||
import com.nukkitx.math.vector.Vector3f;
 | 
			
		||||
import org.geysermc.connector.entity.type.EntityType;
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -33,7 +33,7 @@ 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 MonsterEntity {
 | 
			
		||||
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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -53,14 +53,21 @@ public class ShulkerEntity extends GolemEntity {
 | 
			
		|||
                metadata.put(EntityData.SHULKER_ATTACH_POS, Vector3i.from(position.getX(), position.getY(), position.getZ()));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        //TODO Outdated metadata flag SHULKER_PEAK_HEIGHT
 | 
			
		||||
//        if (entityMetadata.getId() == 17) {
 | 
			
		||||
//            int height = (byte) entityMetadata.getValue();
 | 
			
		||||
//            metadata.put(EntityData.SHULKER_PEAK_HEIGHT, height);
 | 
			
		||||
//        }
 | 
			
		||||
 | 
			
		||||
        if (entityMetadata.getId() == 17) {
 | 
			
		||||
            int height = (byte) entityMetadata.getValue();
 | 
			
		||||
            metadata.put(EntityData.SHULKER_PEEK_ID, height);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (entityMetadata.getId() == 18) {
 | 
			
		||||
            int color = Math.abs((byte) entityMetadata.getValue() - 15);
 | 
			
		||||
            metadata.put(EntityData.VARIANT, color);
 | 
			
		||||
            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);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,31 +23,27 @@
 | 
			
		|||
 * @link https://github.com/GeyserMC/Geyser
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package org.geysermc.connector.network.translators.world.chunk;
 | 
			
		||||
package org.geysermc.connector.entity.living.monster.raid;
 | 
			
		||||
 | 
			
		||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import lombok.Setter;
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
@Getter
 | 
			
		||||
@Setter
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
@EqualsAndHashCode
 | 
			
		||||
public class ChunkPosition {
 | 
			
		||||
public class PillagerEntity extends AbstractIllagerEntity {
 | 
			
		||||
 | 
			
		||||
    private int x;
 | 
			
		||||
    private int z;
 | 
			
		||||
 | 
			
		||||
    public Position getBlock(int x, int y, int z) {
 | 
			
		||||
        return new Position((this.x << 4) + x, y, (this.z << 4) + z);
 | 
			
		||||
    public PillagerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
 | 
			
		||||
        super(entityId, geyserId, entityType, position, motion, rotation);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Position getChunkBlock(int x, int y, int z) {
 | 
			
		||||
        int chunkX = x & 15;
 | 
			
		||||
        int chunkY = y & 15;
 | 
			
		||||
        int chunkZ = z & 15;
 | 
			
		||||
        return new Position(chunkX, chunkY, chunkZ);
 | 
			
		||||
    @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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -34,6 +34,7 @@ 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;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -90,7 +91,7 @@ public enum EntityType {
 | 
			
		|||
    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(AbstractIllagerEntity.class, 114, 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),
 | 
			
		||||
| 
						 | 
				
			
			@ -135,7 +136,7 @@ public enum EntityType {
 | 
			
		|||
    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(MinecartEntity.class, 100, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:command_block_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"),
 | 
			
		||||
| 
						 | 
				
			
			@ -155,6 +156,7 @@ public enum EntityType {
 | 
			
		|||
    HOGLIN(AnimalEntity.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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,6 +31,10 @@ import lombok.Setter;
 | 
			
		|||
 | 
			
		||||
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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,67 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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.v407.Bedrock_v407;
 | 
			
		||||
import com.nukkitx.protocol.bedrock.v408.Bedrock_v408;
 | 
			
		||||
 | 
			
		||||
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_v408.V408_CODEC;
 | 
			
		||||
    /**
 | 
			
		||||
     * A list of all supported Bedrock versions that can join Geyser
 | 
			
		||||
     */
 | 
			
		||||
    public static final List<BedrockPacketCodec> SUPPORTED_BEDROCK_CODECS = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
    static {
 | 
			
		||||
        SUPPORTED_BEDROCK_CODECS.add(Bedrock_v407.V407_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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -71,7 +71,7 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
 | 
			
		|||
        pong.setEdition("MCPE");
 | 
			
		||||
        pong.setGameType("Default");
 | 
			
		||||
        pong.setNintendoLimited(false);
 | 
			
		||||
        pong.setProtocolVersion(GeyserConnector.BEDROCK_PACKET_CODEC.getProtocolVersion());
 | 
			
		||||
        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());
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -108,7 +108,8 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
 | 
			
		|||
    public void onSessionCreation(BedrockServerSession bedrockServerSession) {
 | 
			
		||||
        bedrockServerSession.setLogging(true);
 | 
			
		||||
        bedrockServerSession.setPacketHandler(new UpstreamPacketHandler(connector, new GeyserSession(connector, bedrockServerSession)));
 | 
			
		||||
        bedrockServerSession.setPacketCodec(GeyserConnector.BEDROCK_PACKET_CODEC);
 | 
			
		||||
        // Set the packet codec to default just in case we need to send disconnect packets.
 | 
			
		||||
        bedrockServerSession.setPacketCodec(BedrockProtocol.DEFAULT_BEDROCK_CODEC);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -141,6 +141,7 @@ public class QueryPacketHandler {
 | 
			
		|||
        String motd;
 | 
			
		||||
        String currentPlayerCount;
 | 
			
		||||
        String maxPlayerCount;
 | 
			
		||||
        String map;
 | 
			
		||||
 | 
			
		||||
        if (connector.getConfig().isPassthroughMotd() || connector.getConfig().isPassthroughPlayerCounts()) {
 | 
			
		||||
            pingInfo = connector.getBootstrap().getGeyserPingPassthrough().getPingInformation();
 | 
			
		||||
| 
						 | 
				
			
			@ -162,14 +163,21 @@ public class QueryPacketHandler {
 | 
			
		|||
            maxPlayerCount = String.valueOf(connector.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()));
 | 
			
		||||
        } else {
 | 
			
		||||
            map = GeyserConnector.NAME;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Create a hashmap of all game data needed in the query
 | 
			
		||||
        Map<String, String> gameData = new HashMap<String, String>();
 | 
			
		||||
        gameData.put("hostname", motd);
 | 
			
		||||
        gameData.put("gametype", "SMP");
 | 
			
		||||
        gameData.put("game_id", "MINECRAFT");
 | 
			
		||||
        gameData.put("version", GeyserConnector.BEDROCK_PACKET_CODEC.getMinecraftVersion());
 | 
			
		||||
        gameData.put("version", BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion());
 | 
			
		||||
        gameData.put("plugins", "");
 | 
			
		||||
        gameData.put("map", GeyserConnector.NAME);
 | 
			
		||||
        gameData.put("map", map);
 | 
			
		||||
        gameData.put("numplayers", currentPlayerCount);
 | 
			
		||||
        gameData.put("maxplayers", maxPlayerCount);
 | 
			
		||||
        gameData.put("hostport", String.valueOf(connector.getConfig().getBedrock().getPort()));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,15 +26,24 @@
 | 
			
		|||
package org.geysermc.connector.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.packet.*;
 | 
			
		||||
import org.geysermc.connector.GeyserConnector;
 | 
			
		||||
import org.geysermc.connector.common.AuthType;
 | 
			
		||||
import org.geysermc.connector.configuration.GeyserConfiguration;
 | 
			
		||||
import org.geysermc.connector.GeyserConnector;
 | 
			
		||||
import org.geysermc.connector.network.addon.FormAddonListener;
 | 
			
		||||
import org.geysermc.connector.network.session.GeyserSession;
 | 
			
		||||
import org.geysermc.connector.network.translators.PacketTranslatorRegistry;
 | 
			
		||||
import org.geysermc.connector.utils.LoginEncryptionUtils;
 | 
			
		||||
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 java.io.FileInputStream;
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
 | 
			
		||||
public class UpstreamPacketHandler extends LoggingPacketHandler {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -48,15 +57,20 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
 | 
			
		|||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean handle(LoginPacket loginPacket) {
 | 
			
		||||
        if (loginPacket.getProtocolVersion() > GeyserConnector.BEDROCK_PACKET_CODEC.getProtocolVersion()) {
 | 
			
		||||
            // Too early to determine session locale
 | 
			
		||||
            session.disconnect(LanguageUtils.getLocaleStringLog("geyser.network.outdated.server", GeyserConnector.BEDROCK_PACKET_CODEC.getMinecraftVersion()));
 | 
			
		||||
            return true;
 | 
			
		||||
        } else if (loginPacket.getProtocolVersion() < GeyserConnector.BEDROCK_PACKET_CODEC.getProtocolVersion()) {
 | 
			
		||||
            session.disconnect(LanguageUtils.getLocaleStringLog("geyser.network.outdated.client", GeyserConnector.BEDROCK_PACKET_CODEC.getMinecraftVersion()));
 | 
			
		||||
            return true;
 | 
			
		||||
        BedrockPacketCodec packetCodec = BedrockProtocol.getBedrockCodec(loginPacket.getProtocolVersion());
 | 
			
		||||
        if (packetCodec == null) {
 | 
			
		||||
            if (loginPacket.getProtocolVersion() > BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) {
 | 
			
		||||
                // Too early to determine session locale
 | 
			
		||||
                session.disconnect(LanguageUtils.getLocaleStringLog("geyser.network.outdated.server", BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()));
 | 
			
		||||
                return true;
 | 
			
		||||
            } else if (loginPacket.getProtocolVersion() < BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) {
 | 
			
		||||
                session.disconnect(LanguageUtils.getLocaleStringLog("geyser.network.outdated.client", BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()));
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        session.getUpstream().getSession().setPacketCodec(packetCodec);
 | 
			
		||||
 | 
			
		||||
        LoginEncryptionUtils.encryptPlayerConnection(connector, session, loginPacket);
 | 
			
		||||
 | 
			
		||||
        PlayStatusPacket playStatus = new PlayStatusPacket();
 | 
			
		||||
| 
						 | 
				
			
			@ -64,6 +78,11 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
 | 
			
		|||
        session.sendUpstreamPacket(playStatus);
 | 
			
		||||
 | 
			
		||||
        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.setForcedToAccept(GeyserConnector.getInstance().getConfig().isForceResourcePacks());
 | 
			
		||||
        session.sendUpstreamPacket(resourcePacksInfo);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -75,13 +94,42 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
 | 
			
		|||
                session.connect(connector.getRemoteServer());
 | 
			
		||||
                connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.connect", session.getAuthData().getName()));
 | 
			
		||||
                break;
 | 
			
		||||
            case HAVE_ALL_PACKS:
 | 
			
		||||
                ResourcePackStackPacket stack = new ResourcePackStackPacket();
 | 
			
		||||
                stack.setExperimental(false);
 | 
			
		||||
                stack.setForcedToAccept(false);
 | 
			
		||||
                stack.setGameVersion("*");
 | 
			
		||||
                session.sendUpstreamPacket(stack);
 | 
			
		||||
 | 
			
		||||
            case SEND_PACKS:
 | 
			
		||||
                for(String id : packet.getPackIds()) {
 | 
			
		||||
                    ResourcePackDataInfoPacket data = new ResourcePackDataInfoPacket();
 | 
			
		||||
                    String[] packID = id.split("_");
 | 
			
		||||
                    ResourcePack pack = ResourcePack.PACKS.get(packID[0]);
 | 
			
		||||
                    ResourcePackManifest.Header header = pack.getManifest().getHeader();
 | 
			
		||||
 | 
			
		||||
                    data.setPackId(header.getUuid());
 | 
			
		||||
                    int chunkCount = (int) Math.ceil((int) pack.getFile().length() / (double) ResourcePack.CHUNK_SIZE);
 | 
			
		||||
                    data.setChunkCount(chunkCount);
 | 
			
		||||
                    data.setCompressedPackSize(pack.getFile().length());
 | 
			
		||||
                    data.setMaxChunkSize(ResourcePack.CHUNK_SIZE);
 | 
			
		||||
                    data.setHash(pack.getSha256());
 | 
			
		||||
                    data.setPackVersion(packID[1]);
 | 
			
		||||
                    data.setPremium(false);
 | 
			
		||||
                    data.setType(ResourcePackType.RESOURCE);
 | 
			
		||||
 | 
			
		||||
                    session.sendUpstreamPacket(data);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case HAVE_ALL_PACKS:
 | 
			
		||||
                ResourcePackStackPacket stackPacket = new ResourcePackStackPacket();
 | 
			
		||||
                stackPacket.setExperimentsPreviouslyToggled(false);
 | 
			
		||||
                stackPacket.setForcedToAccept(false); // Leaving this as false allows the player to choose to download or not
 | 
			
		||||
                stackPacket.setGameVersion(session.getClientData().getGameVersion());
 | 
			
		||||
 | 
			
		||||
                for (ResourcePack pack : ResourcePack.PACKS.values()) {
 | 
			
		||||
                    ResourcePackManifest.Header header = pack.getManifest().getHeader();
 | 
			
		||||
                    stackPacket.getResourcePacks().add(new ResourcePackStackPacket.Entry(header.getUuid().toString(), header.getVersionString(), ""));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                session.sendUpstreamPacket(stackPacket);
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            default:
 | 
			
		||||
                session.disconnect("disconnectionScreen.resourcePack");
 | 
			
		||||
                break;
 | 
			
		||||
| 
						 | 
				
			
			@ -95,6 +143,9 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
 | 
			
		|||
        if (packet.getFormId() == LoginEncryptionUtils.AUTH_FORM_ID || packet.getFormId() == LoginEncryptionUtils.AUTH_DETAILS_FORM_ID) {
 | 
			
		||||
            return LoginEncryptionUtils.authenticateFromForm(session, connector, packet.getFormId(), packet.getFormData());
 | 
			
		||||
        }
 | 
			
		||||
        if (packet.getFormId() == SettingsUtils.SETTINGS_FORM_ID) {
 | 
			
		||||
            return SettingsUtils.handleSettingsForm(session, packet.getFormData());
 | 
			
		||||
        }
 | 
			
		||||
        FormAddonListener.get().handleResponse(this.session, packet);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -143,4 +194,30 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
 | 
			
		|||
    boolean defaultHandler(BedrockPacket packet) {
 | 
			
		||||
        return translateAndDefault(packet);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean handle(ResourcePackChunkRequestPacket packet) {
 | 
			
		||||
        ResourcePackChunkDataPacket data = new ResourcePackChunkDataPacket();
 | 
			
		||||
        ResourcePack pack = ResourcePack.PACKS.get(packet.getPackId().toString());
 | 
			
		||||
 | 
			
		||||
        data.setChunkIndex(packet.getChunkIndex());
 | 
			
		||||
        data.setProgress(packet.getChunkIndex() * ResourcePack.CHUNK_SIZE);
 | 
			
		||||
        data.setPackVersion(packet.getPackVersion());
 | 
			
		||||
        data.setPackId(packet.getPackId());
 | 
			
		||||
 | 
			
		||||
        int offset = packet.getChunkIndex() * ResourcePack.CHUNK_SIZE;
 | 
			
		||||
        byte[] packData = new byte[(int) MathUtils.constrain(pack.getFile().length() - offset, 0, ResourcePack.CHUNK_SIZE)];
 | 
			
		||||
 | 
			
		||||
        try (InputStream inputStream = new FileInputStream(pack.getFile())) {
 | 
			
		||||
            inputStream.skip(offset);
 | 
			
		||||
            inputStream.read(packData, 0, packData.length);
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            e.printStackTrace();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        data.setData(packData);
 | 
			
		||||
 | 
			
		||||
        session.sendUpstreamPacket(data);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,6 +28,7 @@ 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;
 | 
			
		||||
| 
						 | 
				
			
			@ -46,6 +47,7 @@ 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;
 | 
			
		||||
| 
						 | 
				
			
			@ -54,6 +56,7 @@ import it.unimi.dsi.fastutil.objects.Object2LongMap;
 | 
			
		|||
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
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;
 | 
			
		||||
| 
						 | 
				
			
			@ -68,6 +71,7 @@ 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.network.translators.world.block.BlockTranslator;
 | 
			
		||||
import org.geysermc.connector.utils.*;
 | 
			
		||||
| 
						 | 
				
			
			@ -79,9 +83,8 @@ import java.net.InetSocketAddress;
 | 
			
		|||
import java.security.NoSuchAlgorithmException;
 | 
			
		||||
import java.security.PublicKey;
 | 
			
		||||
import java.security.spec.InvalidKeySpecException;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.concurrent.ScheduledFuture;
 | 
			
		||||
import java.util.concurrent.atomic.AtomicInteger;
 | 
			
		||||
 | 
			
		||||
@Getter
 | 
			
		||||
| 
						 | 
				
			
			@ -102,7 +105,7 @@ public class GeyserSession implements CommandSender {
 | 
			
		|||
    private ChunkCache chunkCache;
 | 
			
		||||
    private EntityCache entityCache;
 | 
			
		||||
    private InventoryCache inventoryCache;
 | 
			
		||||
    private ScoreboardCache scoreboardCache;
 | 
			
		||||
    private WorldCache worldCache;
 | 
			
		||||
    private WindowCache windowCache;
 | 
			
		||||
    @Setter
 | 
			
		||||
    private TeleportCache teleportCache;
 | 
			
		||||
| 
						 | 
				
			
			@ -116,8 +119,6 @@ public class GeyserSession implements CommandSender {
 | 
			
		|||
     */
 | 
			
		||||
    private final Object2LongMap<Vector3i> itemFrameCache = new Object2LongOpenHashMap<>();
 | 
			
		||||
 | 
			
		||||
    private DataCache<Packet> javaPacketCache;
 | 
			
		||||
 | 
			
		||||
    @Setter
 | 
			
		||||
    private Vector2i lastChunkPosition = null;
 | 
			
		||||
    private int renderDistance;
 | 
			
		||||
| 
						 | 
				
			
			@ -155,11 +156,14 @@ public class GeyserSession implements CommandSender {
 | 
			
		|||
    @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). <br>
 | 
			
		||||
     * Initialized as (0, 0, 0) so it is always not-null.
 | 
			
		||||
     */
 | 
			
		||||
    @Setter
 | 
			
		||||
    private Vector3i lastInteractionPosition;
 | 
			
		||||
    private Vector3i lastInteractionPosition = Vector3i.ZERO;
 | 
			
		||||
 | 
			
		||||
    @Setter
 | 
			
		||||
    private boolean switchingDimension = false;
 | 
			
		||||
    private boolean manyDimPackets = false;
 | 
			
		||||
    private ServerRespawnPacket lastDimPacket = null;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -172,16 +176,28 @@ public class GeyserSession implements CommandSender {
 | 
			
		|||
    @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;
 | 
			
		||||
    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.
 | 
			
		||||
| 
						 | 
				
			
			@ -189,6 +205,76 @@ public class GeyserSession implements CommandSender {
 | 
			
		|||
    @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;
 | 
			
		||||
 | 
			
		||||
    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;
 | 
			
		||||
 | 
			
		||||
    private MinecraftProtocol protocol;
 | 
			
		||||
 | 
			
		||||
    public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) {
 | 
			
		||||
| 
						 | 
				
			
			@ -198,14 +284,12 @@ public class GeyserSession implements CommandSender {
 | 
			
		|||
        this.chunkCache = new ChunkCache(this);
 | 
			
		||||
        this.entityCache = new EntityCache(this);
 | 
			
		||||
        this.inventoryCache = new InventoryCache(this);
 | 
			
		||||
        this.scoreboardCache = new ScoreboardCache(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.javaPacketCache = new DataCache<>();
 | 
			
		||||
 | 
			
		||||
        this.spawned = false;
 | 
			
		||||
        this.loggedIn = false;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -249,6 +333,15 @@ public class GeyserSession implements CommandSender {
 | 
			
		|||
        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() {
 | 
			
		||||
| 
						 | 
				
			
			@ -289,7 +382,7 @@ public class GeyserSession implements CommandSender {
 | 
			
		|||
                    PublicKey key = null;
 | 
			
		||||
                    try {
 | 
			
		||||
                        key = EncryptionUtil.getKeyFromFile(
 | 
			
		||||
                                connector.getConfig().getFloodgateKeyFile(),
 | 
			
		||||
                                connector.getConfig().getFloodgateKeyPath(),
 | 
			
		||||
                                PublicKey.class
 | 
			
		||||
                        );
 | 
			
		||||
                    } catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) {
 | 
			
		||||
| 
						 | 
				
			
			@ -303,6 +396,8 @@ public class GeyserSession implements CommandSender {
 | 
			
		|||
                }
 | 
			
		||||
 | 
			
		||||
                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) {
 | 
			
		||||
| 
						 | 
				
			
			@ -434,7 +529,7 @@ public class GeyserSession implements CommandSender {
 | 
			
		|||
 | 
			
		||||
        this.chunkCache = null;
 | 
			
		||||
        this.entityCache = null;
 | 
			
		||||
        this.scoreboardCache = null;
 | 
			
		||||
        this.worldCache = null;
 | 
			
		||||
        this.inventoryCache = null;
 | 
			
		||||
        this.windowCache = null;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -533,8 +628,10 @@ public class GeyserSession implements CommandSender {
 | 
			
		|||
        startGamePacket.setFromWorldTemplate(false);
 | 
			
		||||
        startGamePacket.setWorldTemplateOptionLocked(false);
 | 
			
		||||
 | 
			
		||||
        startGamePacket.setLevelId("world");
 | 
			
		||||
        startGamePacket.setLevelName("world");
 | 
			
		||||
        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);
 | 
			
		||||
| 
						 | 
				
			
			@ -542,7 +639,7 @@ public class GeyserSession implements CommandSender {
 | 
			
		|||
        startGamePacket.setBlockPalette(BlockTranslator.BLOCKS);
 | 
			
		||||
        startGamePacket.setItemEntries(ItemRegistry.ITEMS);
 | 
			
		||||
        startGamePacket.setVanillaVersion("*");
 | 
			
		||||
        // startGamePacket.setMovementServerAuthoritative(true);
 | 
			
		||||
        startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT);
 | 
			
		||||
        upstream.sendPacket(startGamePacket);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -567,7 +664,7 @@ public class GeyserSession implements CommandSender {
 | 
			
		|||
     * @param packet the bedrock packet from the NukkitX protocol lib
 | 
			
		||||
     */
 | 
			
		||||
    public void sendUpstreamPacket(BedrockPacket packet) {
 | 
			
		||||
        if (upstream != null && !upstream.isClosed()) {
 | 
			
		||||
        if (upstream != null) {
 | 
			
		||||
            upstream.sendPacket(packet);
 | 
			
		||||
        } else {
 | 
			
		||||
            connector.getLogger().debug("Tried to send upstream packet " + packet.getClass().getSimpleName() + " but the session was null");
 | 
			
		||||
| 
						 | 
				
			
			@ -580,7 +677,7 @@ public class GeyserSession implements CommandSender {
 | 
			
		|||
     * @param packet the bedrock packet from the NukkitX protocol lib
 | 
			
		||||
     */
 | 
			
		||||
    public void sendUpstreamPacketImmediately(BedrockPacket packet) {
 | 
			
		||||
        if (upstream != null && !upstream.isClosed()) {
 | 
			
		||||
        if (upstream != null) {
 | 
			
		||||
            upstream.sendPacketImmediately(packet);
 | 
			
		||||
        } else {
 | 
			
		||||
            connector.getLogger().debug("Tried to send upstream packet " + packet.getClass().getSimpleName() + " immediately but the session was null");
 | 
			
		||||
| 
						 | 
				
			
			@ -599,4 +696,77 @@ public class GeyserSession implements CommandSender {
 | 
			
		|||
            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<AdventureSetting> 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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -41,17 +41,15 @@ public class UpstreamSession {
 | 
			
		|||
    private boolean initialized = false;
 | 
			
		||||
 | 
			
		||||
    public void sendPacket(@NonNull BedrockPacket packet) {
 | 
			
		||||
        if (isClosed())
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        session.sendPacket(packet);
 | 
			
		||||
        if (!isClosed()) {
 | 
			
		||||
            session.sendPacket(packet);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void sendPacketImmediately(@NonNull BedrockPacket packet) {
 | 
			
		||||
        if (isClosed())
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        session.sendPacketImmediately(packet);
 | 
			
		||||
        if (!isClosed()) {
 | 
			
		||||
            session.sendPacketImmediately(packet);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void disconnect(String reason) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,22 +27,18 @@ 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 com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
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.network.translators.world.chunk.ChunkPosition;
 | 
			
		||||
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import org.geysermc.connector.utils.MathUtils;
 | 
			
		||||
 | 
			
		||||
public class ChunkCache {
 | 
			
		||||
 | 
			
		||||
    private final boolean cache;
 | 
			
		||||
 | 
			
		||||
    @Getter
 | 
			
		||||
    private Map<ChunkPosition, Column> chunks = new HashMap<>();
 | 
			
		||||
    private final Long2ObjectMap<Column> chunks = new Long2ObjectOpenHashMap<>();
 | 
			
		||||
 | 
			
		||||
    public ChunkCache(GeyserSession session) {
 | 
			
		||||
        if (session.getConnector().getWorldManager().getClass() == GeyserBootstrap.DEFAULT_CHUNK_MANAGER.getClass()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -52,52 +48,74 @@ public class ChunkCache {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void addToCache(Column chunk) {
 | 
			
		||||
    public Column addToCache(Column chunk) {
 | 
			
		||||
        if (!cache) {
 | 
			
		||||
            return;
 | 
			
		||||
            return chunk;
 | 
			
		||||
        }
 | 
			
		||||
        ChunkPosition position = new ChunkPosition(chunk.getX(), chunk.getZ());
 | 
			
		||||
        chunks.put(position, chunk);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void updateBlock(Position position, int block) {
 | 
			
		||||
        if (!cache) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        ChunkPosition chunkPosition = new ChunkPosition(position.getX() >> 4, position.getZ() >> 4);
 | 
			
		||||
        if (!chunks.containsKey(chunkPosition))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        Column column = chunks.get(chunkPosition);
 | 
			
		||||
        Chunk chunk = column.getChunks()[position.getY() >> 4];
 | 
			
		||||
        Position blockPosition = chunkPosition.getChunkBlock(position.getX(), position.getY(), position.getZ());
 | 
			
		||||
        if (chunk != null) {
 | 
			
		||||
            chunk.set(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), block);
 | 
			
		||||
        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 int getBlockAt(Position position) {
 | 
			
		||||
    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.AIR;
 | 
			
		||||
        }
 | 
			
		||||
        ChunkPosition chunkPosition = new ChunkPosition(position.getX() >> 4, position.getZ() >> 4);
 | 
			
		||||
        if (!chunks.containsKey(chunkPosition))
 | 
			
		||||
            return BlockTranslator.AIR;
 | 
			
		||||
 | 
			
		||||
        Column column = chunks.get(chunkPosition);
 | 
			
		||||
        Chunk chunk = column.getChunks()[position.getY() >> 4];
 | 
			
		||||
        Position blockPosition = chunkPosition.getChunkBlock(position.getX(), position.getY(), position.getZ());
 | 
			
		||||
        Column column = this.getChunk(x >> 4, z >> 4);
 | 
			
		||||
        if (column == null) {
 | 
			
		||||
            return BlockTranslator.AIR;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Chunk chunk = column.getChunks()[y >> 4];
 | 
			
		||||
        if (chunk != null) {
 | 
			
		||||
            return chunk.get(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
 | 
			
		||||
            return chunk.get(x & 0xF, y & 0xF, z & 0xF);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return BlockTranslator.AIR;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void removeChunk(ChunkPosition position) {
 | 
			
		||||
    public void removeChunk(int chunkX, int chunkZ) {
 | 
			
		||||
        if (!cache) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        chunks.remove(position);
 | 
			
		||||
 | 
			
		||||
        long chunkPosition = MathUtils.chunkPositionToLong(chunkX, chunkZ);
 | 
			
		||||
        chunks.remove(chunkPosition);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -76,9 +76,6 @@ public class EntityCache {
 | 
			
		|||
        if (entity != null && entity.isValid() && (force || entity.despawnEntity(session))) {
 | 
			
		||||
            long geyserId = entityIdTranslations.remove(entity.getEntityId());
 | 
			
		||||
            entities.remove(geyserId);
 | 
			
		||||
            if (entity.is(PlayerEntity.class)) {
 | 
			
		||||
                playerEntities.remove(entity.as(PlayerEntity.class).getUuid());
 | 
			
		||||
            }
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,31 +25,53 @@
 | 
			
		|||
 | 
			
		||||
package org.geysermc.connector.network.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 java.util.Collection;
 | 
			
		||||
import org.geysermc.connector.scoreboard.ScoreboardUpdater;
 | 
			
		||||
 | 
			
		||||
@Getter
 | 
			
		||||
public class ScoreboardCache {
 | 
			
		||||
    private GeyserSession session;
 | 
			
		||||
    private Scoreboard scoreboard;
 | 
			
		||||
public class WorldCache {
 | 
			
		||||
    private final GeyserSession session;
 | 
			
		||||
    @Setter
 | 
			
		||||
    private Difficulty difficulty = Difficulty.EASY;
 | 
			
		||||
    private boolean showCoordinates = true;
 | 
			
		||||
 | 
			
		||||
    public ScoreboardCache(GeyserSession session) {
 | 
			
		||||
    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();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void removeScoreboard() {
 | 
			
		||||
        if (scoreboard != null) {
 | 
			
		||||
            Collection<Objective> objectives = scoreboard.getObjectives().values();
 | 
			
		||||
            scoreboard = new Scoreboard(session);
 | 
			
		||||
 | 
			
		||||
            for (Objective objective : objectives) {
 | 
			
		||||
            for (Objective objective : scoreboard.getObjectives().values()) {
 | 
			
		||||
                scoreboard.despawnObjective(objective);
 | 
			
		||||
            }
 | 
			
		||||
            scoreboard = new Scoreboard(session);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int increaseAndGetScoreboardPacketsPerSecond() {
 | 
			
		||||
        int pendingPps = scoreboardUpdater.incrementAndGetPacketsPerSecond();
 | 
			
		||||
        int pps = scoreboardUpdater.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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -25,7 +25,6 @@
 | 
			
		|||
 | 
			
		||||
package org.geysermc.connector.network.translators;
 | 
			
		||||
 | 
			
		||||
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerKeepAlivePacket;
 | 
			
		||||
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;
 | 
			
		||||
| 
						 | 
				
			
			@ -33,6 +32,7 @@ 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;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -48,7 +48,7 @@ public class PacketTranslatorRegistry<T> {
 | 
			
		|||
    private static final ObjectArrayList<Class<?>> IGNORED_PACKETS = new ObjectArrayList<>();
 | 
			
		||||
 | 
			
		||||
    static {
 | 
			
		||||
        Reflections ref = new Reflections("org.geysermc.connector.network.translators");
 | 
			
		||||
        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();
 | 
			
		||||
| 
						 | 
				
			
			@ -74,7 +74,6 @@ public class PacketTranslatorRegistry<T> {
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        IGNORED_PACKETS.add(ServerKeepAlivePacket.class); // Handled by MCProtocolLib
 | 
			
		||||
        IGNORED_PACKETS.add(ServerUpdateLightPacket.class); // Light is handled on Bedrock for us
 | 
			
		||||
        IGNORED_PACKETS.add(ServerPlayerListDataPacket.class); // Cant be implemented in bedrock
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,7 +25,6 @@
 | 
			
		|||
 | 
			
		||||
package org.geysermc.connector.network.translators.bedrock;
 | 
			
		||||
 | 
			
		||||
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
 | 
			
		||||
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerAbilitiesPacket;
 | 
			
		||||
import com.nukkitx.protocol.bedrock.data.AdventureSetting;
 | 
			
		||||
import com.nukkitx.protocol.bedrock.packet.AdventureSettingsPacket;
 | 
			
		||||
| 
						 | 
				
			
			@ -38,14 +37,8 @@ public class BedrockAdventureSettingsTranslator extends PacketTranslator<Adventu
 | 
			
		|||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void translate(AdventureSettingsPacket packet, GeyserSession session) {
 | 
			
		||||
        // Only canFly and flying are used by the server
 | 
			
		||||
        // https://wiki.vg/Protocol#Player_Abilities_.28serverbound.29
 | 
			
		||||
        boolean canFly = packet.getSettings().contains(AdventureSetting.MAY_FLY);
 | 
			
		||||
        boolean flying = packet.getSettings().contains(AdventureSetting.FLYING);
 | 
			
		||||
        boolean creative = session.getGameMode() == GameMode.CREATIVE;
 | 
			
		||||
        ClientPlayerAbilitiesPacket abilitiesPacket = new ClientPlayerAbilitiesPacket(
 | 
			
		||||
                false, canFly, flying, creative
 | 
			
		||||
        );
 | 
			
		||||
        ClientPlayerAbilitiesPacket abilitiesPacket =
 | 
			
		||||
                new ClientPlayerAbilitiesPacket(packet.getSettings().contains(AdventureSetting.FLYING));
 | 
			
		||||
        session.sendDownstreamPacket(abilitiesPacket);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,23 +26,18 @@
 | 
			
		|||
package org.geysermc.connector.network.translators.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.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 java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import org.geysermc.connector.utils.SignUtils;
 | 
			
		||||
 | 
			
		||||
@Translator(packet = BlockEntityDataPacket.class)
 | 
			
		||||
public class BedrockBlockEntityDataTranslator extends PacketTranslator<BlockEntityDataPacket> {
 | 
			
		||||
 | 
			
		||||
    // In case two people are editing signs at the same time this array holds the temporary messages to be sent
 | 
			
		||||
    // Position -> Message being held
 | 
			
		||||
    protected static Map<Position, String> lastMessages = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void translate(BlockEntityDataPacket packet, GeyserSession session) {
 | 
			
		||||
        NbtMap tag = packet.getData();
 | 
			
		||||
| 
						 | 
				
			
			@ -50,9 +45,8 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator<BlockEnti
 | 
			
		|||
            // This is the reason why this all works - Bedrock sends packets every time you update the sign, Java only wants the final packet
 | 
			
		||||
            // But Bedrock sends one final packet when you're done editing the sign, which should be equal to the last message since there's no edits
 | 
			
		||||
            // So if the latest update does not match the last cached update then it's still being edited
 | 
			
		||||
            Position pos = new Position(tag.getInt("x"), tag.getInt("y"), tag.getInt("z"));
 | 
			
		||||
            if (!tag.getString("Text").equals(lastMessages.get(pos))) {
 | 
			
		||||
                lastMessages.put(pos, tag.getString("Text"));
 | 
			
		||||
            if (!tag.getString("Text").equals(session.getLastSignMessage())) {
 | 
			
		||||
                session.setLastSignMessage(tag.getString("Text"));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            // Otherwise the two messages are identical and we can get to work deconstructing
 | 
			
		||||
| 
						 | 
				
			
			@ -61,29 +55,73 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator<BlockEnti
 | 
			
		|||
            // (Initialized all with empty strings because it complains about null)
 | 
			
		||||
            String[] lines = new String[] {"", "", "", ""};
 | 
			
		||||
            int iterator = 0;
 | 
			
		||||
            // Keep track of the width of each character
 | 
			
		||||
            // If it goes over the maximum, we need to start a new line to match Java
 | 
			
		||||
            int widthCount = 0;
 | 
			
		||||
            // This converts the message into the array'd message Java wants
 | 
			
		||||
            for (char character : tag.getString("Text").toCharArray()) {
 | 
			
		||||
                // If we get a return in Bedrock, that signals to use the next line.
 | 
			
		||||
                if (character == '\n') {
 | 
			
		||||
                widthCount += SignUtils.getCharacterWidth(character);
 | 
			
		||||
                // If we get a return in Bedrock, or go over the character width max, that signals to use the next line.
 | 
			
		||||
                if (character == '\n' || widthCount > SignUtils.JAVA_CHARACTER_WIDTH_MAX) {
 | 
			
		||||
                    // We need to apply some more logic if we went over the character width max
 | 
			
		||||
                    boolean wentOverMax = widthCount > SignUtils.JAVA_CHARACTER_WIDTH_MAX && character != '\n';
 | 
			
		||||
                    widthCount = 0;
 | 
			
		||||
                    // Saves if we're moving a word to the next line
 | 
			
		||||
                    String word = null;
 | 
			
		||||
                    if (wentOverMax && iterator < lines.length - 1) {
 | 
			
		||||
                        // If we went over the max, we want to try to wrap properly like Bedrock does.
 | 
			
		||||
                        // So we look for a space in the Bedrock user's text to imply a word.
 | 
			
		||||
                        int index = newMessage.lastIndexOf(" ");
 | 
			
		||||
                        if (index != -1) {
 | 
			
		||||
                            // There is indeed a space in this line; let's get it
 | 
			
		||||
                            word = newMessage.substring(index + 1);
 | 
			
		||||
                            // 'Delete' that word from the string builder
 | 
			
		||||
                            newMessage.delete(index, newMessage.length());
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    lines[iterator] = newMessage.toString();
 | 
			
		||||
                    iterator++;
 | 
			
		||||
                    // Bedrock, for whatever reason, can hold a message out of bounds
 | 
			
		||||
                    // Bedrock, for whatever reason, can hold a message out of the bounds of the four lines
 | 
			
		||||
                    // We don't care about that so we discard that
 | 
			
		||||
                    if (iterator > lines.length - 1) {
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                    newMessage = new StringBuilder();
 | 
			
		||||
                    if (wentOverMax) {
 | 
			
		||||
                        // Apply the wrapped word to the new line
 | 
			
		||||
                        if (word != null) {
 | 
			
		||||
                            newMessage.append(word);
 | 
			
		||||
                            // And apply the width count
 | 
			
		||||
                            for (char wordCharacter : word.toCharArray()) {
 | 
			
		||||
                                widthCount += SignUtils.getCharacterWidth(wordCharacter);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        // If we went over the max, we want to append the character to the new line.
 | 
			
		||||
                        newMessage.append(character);
 | 
			
		||||
                        widthCount += SignUtils.getCharacterWidth(character);
 | 
			
		||||
                    }
 | 
			
		||||
                } else newMessage.append(character);
 | 
			
		||||
            }
 | 
			
		||||
            // Put the final line on since it isn't done in the for loop
 | 
			
		||||
            if (iterator < lines.length) lines[iterator] = newMessage.toString();
 | 
			
		||||
            Position pos = new Position(tag.getInt("x"), tag.getInt("y"), tag.getInt("z"));
 | 
			
		||||
            ClientUpdateSignPacket clientUpdateSignPacket = new ClientUpdateSignPacket(pos, lines);
 | 
			
		||||
            session.sendDownstreamPacket(clientUpdateSignPacket);
 | 
			
		||||
            //TODO (potentially): originally I was going to update the sign blocks so Bedrock and Java users would match visually
 | 
			
		||||
            // However Java can still store a lot per-line and visuals are still messed up so that doesn't work
 | 
			
		||||
 | 
			
		||||
            // We remove the sign position from map to indicate there is no work-in-progress sign
 | 
			
		||||
            lastMessages.remove(pos);
 | 
			
		||||
            // We set the sign text cached in the session to null to indicate there is no work-in-progress sign
 | 
			
		||||
            session.setLastSignMessage(null);
 | 
			
		||||
 | 
			
		||||
        } else if (tag.getString("id").equals("JigsawBlock")) {
 | 
			
		||||
            // Client has just sent a jigsaw block update
 | 
			
		||||
            Position pos = new Position(tag.getInt("x"), tag.getInt("y"), tag.getInt("z"));
 | 
			
		||||
            String name = tag.getString("name");
 | 
			
		||||
            String target = tag.getString("target");
 | 
			
		||||
            String pool = tag.getString("target_pool");
 | 
			
		||||
            String finalState = tag.getString("final_state");
 | 
			
		||||
            String joint = tag.getString("joint");
 | 
			
		||||
            ClientUpdateJigsawBlockPacket jigsawPacket = new ClientUpdateJigsawBlockPacket(pos, name, target, pool,
 | 
			
		||||
                    finalState, joint);
 | 
			
		||||
            session.sendDownstreamPacket(jigsawPacket);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,99 +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.ClientMoveItemToHotbarPacket;
 | 
			
		||||
import com.nukkitx.math.vector.Vector3i;
 | 
			
		||||
import com.nukkitx.protocol.bedrock.packet.BlockPickRequestPacket;
 | 
			
		||||
import com.nukkitx.protocol.bedrock.packet.PlayerHotbarPacket;
 | 
			
		||||
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.world.block.BlockTranslator;
 | 
			
		||||
 | 
			
		||||
@Translator(packet = BlockPickRequestPacket.class)
 | 
			
		||||
public class BedrockBlockPickRequestPacketTranslator extends PacketTranslator<BlockPickRequestPacket> {
 | 
			
		||||
 | 
			
		||||
    @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 == 0) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Get the inventory to choose a slot to pick
 | 
			
		||||
        Inventory inventory = session.getInventoryCache().getOpenInventory();
 | 
			
		||||
        if (inventory == null) {
 | 
			
		||||
            inventory = session.getInventory();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        String targetIdentifier = BlockTranslator.getJavaIdBlockMap().inverse().get(blockToPick).split("\\[")[0];
 | 
			
		||||
 | 
			
		||||
        // 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(targetIdentifier)) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            PlayerHotbarPacket hotbarPacket = new PlayerHotbarPacket();
 | 
			
		||||
            hotbarPacket.setContainerId(0);
 | 
			
		||||
            // Java inventory slot to hotbar slot ID
 | 
			
		||||
            hotbarPacket.setSelectedHotbarSlot(i - 36);
 | 
			
		||||
            hotbarPacket.setSelectHotbarSlot(true);
 | 
			
		||||
            session.sendUpstreamPacket(hotbarPacket);
 | 
			
		||||
            session.getInventory().setHeldItemSlot(i - 36);
 | 
			
		||||
            // 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(targetIdentifier)) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            ClientMoveItemToHotbarPacket packetToSend = new ClientMoveItemToHotbarPacket(i); // https://wiki.vg/Protocol#Pick_Item
 | 
			
		||||
            session.sendDownstreamPacket(packetToSend);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,52 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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<BlockPickRequestPacket> {
 | 
			
		||||
 | 
			
		||||
    @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 == 0) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        String targetIdentifier = BlockTranslator.getJavaIdBlockMap().inverse().get(blockToPick).split("\\[")[0];
 | 
			
		||||
        InventoryUtils.findOrCreatePickedBlock(session, targetIdentifier);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,71 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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.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.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;
 | 
			
		||||
 | 
			
		||||
@Translator(packet = CommandBlockUpdatePacket.class)
 | 
			
		||||
public class BedrockCommandBlockUpdateTranslator extends PacketTranslator<CommandBlockUpdatePacket> {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void translate(CommandBlockUpdatePacket packet, GeyserSession session) {
 | 
			
		||||
        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;
 | 
			
		||||
            }
 | 
			
		||||
            boolean isConditional = packet.isConditional();
 | 
			
		||||
            boolean automatic = !packet.isRedstoneMode(); // Automatic = Always Active option in Java
 | 
			
		||||
            ClientUpdateCommandBlockPacket commandBlockPacket = new ClientUpdateCommandBlockPacket(
 | 
			
		||||
                    new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()),
 | 
			
		||||
                    command, mode, outputTracked, isConditional, automatic);
 | 
			
		||||
            session.sendDownstreamPacket(commandBlockPacket);
 | 
			
		||||
        } else {
 | 
			
		||||
            ClientUpdateCommandBlockMinecartPacket commandMinecartPacket = new ClientUpdateCommandBlockMinecartPacket(
 | 
			
		||||
                    (int) session.getEntityCache().getEntityByGeyserId(packet.getMinecartRuntimeEntityId()).getEntityId(),
 | 
			
		||||
                    command, outputTracked
 | 
			
		||||
            );
 | 
			
		||||
            session.sendDownstreamPacket(commandMinecartPacket);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,115 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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<EntityPickRequestPacket> {
 | 
			
		||||
 | 
			
		||||
    @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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -39,8 +39,13 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlaye
 | 
			
		|||
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;
 | 
			
		||||
| 
						 | 
				
			
			@ -53,8 +58,11 @@ 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<InventoryTransactionPacket> {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -75,6 +83,17 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
 | 
			
		|||
            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()) &&
 | 
			
		||||
| 
						 | 
				
			
			@ -98,39 +117,50 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
 | 
			
		|||
                        session.sendDownstreamPacket(blockPacket);
 | 
			
		||||
 | 
			
		||||
                        // Otherwise boats will not be able to be placed in survival and buckets wont work on mobile
 | 
			
		||||
                        if (packet.getItemInHand() != null && (packet.getItemInHand().getId() == ItemRegistry.BOAT.getBedrockId() || packet.getItemInHand().getId() == ItemRegistry.BUCKET.getBedrockId())) {
 | 
			
		||||
                           ClientPlayerUseItemPacket itemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND);
 | 
			
		||||
                           session.sendDownstreamPacket(itemPacket);
 | 
			
		||||
                        if (packet.getItemInHand() != null && packet.getItemInHand().getId() == ItemRegistry.BOAT.getBedrockId()) {
 | 
			
		||||
                            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 && packet.getItemInHand().getId() == ItemRegistry.BUCKET.getBedrockId()) {
 | 
			
		||||
                            // 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));
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        Vector3i blockPos = packet.getBlockPosition();
 | 
			
		||||
                        // TODO: Find a better way to do this?
 | 
			
		||||
                        switch (packet.getBlockFace()) {
 | 
			
		||||
                            case 0:
 | 
			
		||||
                                blockPos = blockPos.sub(0, 1, 0);
 | 
			
		||||
                                break;
 | 
			
		||||
                            case 1:
 | 
			
		||||
                                blockPos = blockPos.add(0, 1, 0);
 | 
			
		||||
                                break;
 | 
			
		||||
                            case 2:
 | 
			
		||||
                                blockPos = blockPos.sub(0, 0, 1);
 | 
			
		||||
                                break;
 | 
			
		||||
                            case 3:
 | 
			
		||||
                                blockPos = blockPos.add(0, 0, 1);
 | 
			
		||||
                                break;
 | 
			
		||||
                            case 4:
 | 
			
		||||
                                blockPos = blockPos.sub(1, 0, 0);
 | 
			
		||||
                                break;
 | 
			
		||||
                            case 5:
 | 
			
		||||
                                blockPos = blockPos.add(1, 0, 0);
 | 
			
		||||
                                break;
 | 
			
		||||
                        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.setLastInteractionPosition(packet.getBlockPosition());
 | 
			
		||||
                        session.setInteracting(true);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case 1:
 | 
			
		||||
| 
						 | 
				
			
			@ -140,15 +170,14 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
 | 
			
		|||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        // Handled in ITEM_USE
 | 
			
		||||
                        if (packet.getItemInHand() != null && packet.getItemInHand().getId() == ItemRegistry.BUCKET.getBedrockId()) {
 | 
			
		||||
                        // Handled in ITEM_USE if the item is not milk
 | 
			
		||||
                        if (packet.getItemInHand() != null && packet.getItemInHand().getId() == ItemRegistry.BUCKET.getBedrockId() &&
 | 
			
		||||
                                packet.getItemInHand().getDamage() != 1) {
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        ClientPlayerUseItemPacket useItemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND);
 | 
			
		||||
                        session.sendDownstreamPacket(useItemPacket);
 | 
			
		||||
                        // Used for sleeping in beds
 | 
			
		||||
                        session.setLastInteractionPosition(packet.getBlockPosition());
 | 
			
		||||
                        break;
 | 
			
		||||
                    case 2:
 | 
			
		||||
                        int blockState = session.getConnector().getWorldManager().getBlockAt(session, packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ());
 | 
			
		||||
| 
						 | 
				
			
			@ -195,6 +224,18 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
 | 
			
		|||
                //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());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,7 +40,8 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator<MobEquipment
 | 
			
		|||
    @Override
 | 
			
		||||
    public void translate(MobEquipmentPacket packet, GeyserSession session) {
 | 
			
		||||
        if (!session.isSpawned() || packet.getHotbarSlot() > 8 ||
 | 
			
		||||
                packet.getContainerId() != ContainerId.INVENTORY) {
 | 
			
		||||
                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;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,46 @@
 | 
			
		|||
/*
 | 
			
		||||
 * 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<NetworkStackLatencyPacket> {
 | 
			
		||||
 | 
			
		||||
    @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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +28,10 @@ package org.geysermc.connector.network.translators.bedrock;
 | 
			
		|||
import com.github.steveice10.mc.protocol.data.game.ClientRequest;
 | 
			
		||||
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientRequestPacket;
 | 
			
		||||
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;
 | 
			
		||||
| 
						 | 
				
			
			@ -39,12 +42,30 @@ public class BedrockRespawnTranslator extends PacketTranslator<RespawnPacket> {
 | 
			
		|||
    @Override
 | 
			
		||||
    public void translate(RespawnPacket packet, GeyserSession session) {
 | 
			
		||||
        if (packet.getState() == RespawnPacket.State.CLIENT_READY) {
 | 
			
		||||
            if (!session.isSpawned()) { // Otherwise when immediate respawn is on the client never loads
 | 
			
		||||
                RespawnPacket respawnPacket = new RespawnPacket();
 | 
			
		||||
                respawnPacket.setRuntimeEntityId(0);
 | 
			
		||||
                respawnPacket.setPosition(Vector3f.ZERO);
 | 
			
		||||
                respawnPacket.setState(RespawnPacket.State.SERVER_SEARCHING);
 | 
			
		||||
                session.sendUpstreamPacket(respawnPacket);
 | 
			
		||||
            // 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'
 | 
			
		||||
            // But I assume the new if statement below fixes that problem
 | 
			
		||||
            RespawnPacket respawnPacket = new RespawnPacket();
 | 
			
		||||
            respawnPacket.setRuntimeEntityId(0);
 | 
			
		||||
            respawnPacket.setPosition(Vector3f.ZERO);
 | 
			
		||||
            respawnPacket.setState(RespawnPacket.State.SERVER_READY);
 | 
			
		||||
            session.sendUpstreamPacket(respawnPacket);
 | 
			
		||||
 | 
			
		||||
            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);
 | 
			
		||||
 | 
			
		||||
                MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
 | 
			
		||||
                movePlayerPacket.setRuntimeEntityId(entity.getGeyserId());
 | 
			
		||||
                movePlayerPacket.setPosition(entity.getPosition());
 | 
			
		||||
                movePlayerPacket.setRotation(entity.getBedrockRotation());
 | 
			
		||||
                movePlayerPacket.setMode(MovePlayerPacket.Mode.RESPAWN);
 | 
			
		||||
                session.sendUpstreamPacket(movePlayerPacket);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ClientRequestPacket javaRespawnPacket = new ClientRequestPacket(ClientRequest.RESPAWN);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.connector.network.translators.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;
 | 
			
		||||
 | 
			
		||||
@Translator(packet = ServerSettingsRequestPacket.class)
 | 
			
		||||
public class BedrockServerSettingsRequestTranslator extends PacketTranslator<ServerSettingsRequestPacket> {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void translate(ServerSettingsRequestPacket packet, GeyserSession session) {
 | 
			
		||||
        SettingsUtils.buildForm(session);
 | 
			
		||||
 | 
			
		||||
        ServerSettingsResponsePacket serverSettingsResponsePacket = new ServerSettingsResponsePacket();
 | 
			
		||||
        serverSettingsResponsePacket.setFormData(session.getSettingsForm().getJSONData());
 | 
			
		||||
        serverSettingsResponsePacket.setFormId(SettingsUtils.SETTINGS_FORM_ID);
 | 
			
		||||
        session.sendUpstreamPacket(serverSettingsResponsePacket);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -23,7 +23,7 @@
 | 
			
		|||
 * @link https://github.com/GeyserMC/Geyser
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package org.geysermc.connector.network.translators.bedrock;
 | 
			
		||||
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;
 | 
			
		||||
| 
						 | 
				
			
			@ -23,10 +23,9 @@
 | 
			
		|||
 * @link https://github.com/GeyserMC/Geyser
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package org.geysermc.connector.network.translators.bedrock;
 | 
			
		||||
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.GameMode;
 | 
			
		||||
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;
 | 
			
		||||
| 
						 | 
				
			
			@ -45,6 +44,7 @@ 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;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -79,9 +79,7 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
 | 
			
		|||
                break;
 | 
			
		||||
            case START_GLIDE:
 | 
			
		||||
                // Otherwise gliding will not work in creative
 | 
			
		||||
                ClientPlayerAbilitiesPacket playerAbilitiesPacket = new ClientPlayerAbilitiesPacket(
 | 
			
		||||
                        false, false, false, session.getGameMode() == GameMode.CREATIVE
 | 
			
		||||
                );
 | 
			
		||||
                ClientPlayerAbilitiesPacket playerAbilitiesPacket = new ClientPlayerAbilitiesPacket(false);
 | 
			
		||||
                session.sendDownstreamPacket(playerAbilitiesPacket);
 | 
			
		||||
            case STOP_GLIDE:
 | 
			
		||||
                ClientPlayerStatePacket glidePacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.START_ELYTRA_FLYING);
 | 
			
		||||
| 
						 | 
				
			
			@ -116,9 +114,26 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
 | 
			
		|||
                session.sendDownstreamPacket(stopSleepingPacket);
 | 
			
		||||
                break;
 | 
			
		||||
            case BLOCK_INTERACT:
 | 
			
		||||
                // Handled in BedrockInventoryTransactionTranslator
 | 
			
		||||
                // 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);
 | 
			
		||||
| 
						 | 
				
			
			@ -23,10 +23,10 @@
 | 
			
		|||
 * @link https://github.com/GeyserMC/Geyser
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package org.geysermc.connector.network.translators.bedrock;
 | 
			
		||||
package org.geysermc.connector.network.translators.bedrock.entity.player;
 | 
			
		||||
 | 
			
		||||
import com.nukkitx.protocol.bedrock.packet.EmotePacket;
 | 
			
		||||
import org.geysermc.connector.GeyserConnector;
 | 
			
		||||
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;
 | 
			
		||||
| 
						 | 
				
			
			@ -37,9 +37,12 @@ public class BedrockEmoteTranslator extends PacketTranslator<EmotePacket> {
 | 
			
		|||
    @Override
 | 
			
		||||
    public void translate(EmotePacket packet, GeyserSession session) {
 | 
			
		||||
        long javaId = session.getPlayerEntity().getEntityId();
 | 
			
		||||
        for (GeyserSession otherSession : GeyserConnector.getInstance().getPlayers()) {
 | 
			
		||||
        for (GeyserSession otherSession : session.getConnector().getPlayers()) {
 | 
			
		||||
            if (otherSession != session) {
 | 
			
		||||
                packet.setRuntimeEntityId(otherSession.getEntityCache().getEntityByJavaId(javaId).getGeyserId());
 | 
			
		||||
                if (otherSession.isClosed()) continue;
 | 
			
		||||
                Entity otherEntity = otherSession.getEntityCache().getEntityByJavaId(javaId);
 | 
			
		||||
                if (otherEntity == null) continue;
 | 
			
		||||
                packet.setRuntimeEntityId(otherEntity.getGeyserId());
 | 
			
		||||
                otherSession.sendUpstreamPacket(packet);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -23,7 +23,7 @@
 | 
			
		|||
 * @link https://github.com/GeyserMC/Geyser
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package org.geysermc.connector.network.translators.bedrock;
 | 
			
		||||
package org.geysermc.connector.network.translators.bedrock.entity.player;
 | 
			
		||||
 | 
			
		||||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
 | 
			
		||||
import com.nukkitx.protocol.bedrock.data.entity.EntityDataMap;
 | 
			
		||||
| 
						 | 
				
			
			@ -23,7 +23,7 @@
 | 
			
		|||
 * @link https://github.com/GeyserMC/Geyser
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package org.geysermc.connector.network.translators.bedrock;
 | 
			
		||||
package org.geysermc.connector.network.translators.bedrock.entity.player;
 | 
			
		||||
 | 
			
		||||
import com.nukkitx.math.vector.Vector3d;
 | 
			
		||||
import org.geysermc.connector.common.ChatColor;
 | 
			
		||||
| 
						 | 
				
			
			@ -86,6 +86,13 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
 | 
			
		|||
        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;
 | 
			
		||||
| 
						 | 
				
			
			@ -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.connector.network.translators.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;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * In vanilla Bedrock, if you have operator status, this sets the player's gamemode without confirmation from the server.
 | 
			
		||||
 * Since we have a custom server option to request the gamemode, we just reset the gamemode and ignore this.
 | 
			
		||||
 */
 | 
			
		||||
@Translator(packet = SetPlayerGameTypePacket.class)
 | 
			
		||||
public class BedrockSetPlayerGameTypeTranslator extends PacketTranslator<SetPlayerGameTypePacket> {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void translate(SetPlayerGameTypePacket packet, GeyserSession session) {
 | 
			
		||||
        // no
 | 
			
		||||
        SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket();
 | 
			
		||||
        playerGameTypePacket.setGamemode(session.getGameMode().ordinal());
 | 
			
		||||
        session.sendUpstreamPacket(playerGameTypePacket);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -23,7 +23,7 @@
 | 
			
		|||
 * @link https://github.com/GeyserMC/Geyser
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package org.geysermc.connector.network.translators.bedrock;
 | 
			
		||||
package org.geysermc.connector.network.translators.bedrock.world;
 | 
			
		||||
 | 
			
		||||
import com.nukkitx.protocol.bedrock.data.SoundEvent;
 | 
			
		||||
import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket;
 | 
			
		||||
| 
						 | 
				
			
			@ -32,10 +32,11 @@ 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 org.geysermc.connector.utils.LanguageUtils;
 | 
			
		||||
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
| 
						 | 
				
			
			@ -50,8 +51,19 @@ public class EffectRegistry {
 | 
			
		|||
    public static final Map<SoundEffect, Effect> SOUND_EFFECTS = new HashMap<>();
 | 
			
		||||
    public static final Int2ObjectMap<SoundEvent> RECORDS = new Int2ObjectOpenHashMap<>();
 | 
			
		||||
 | 
			
		||||
    private static Map<ParticleType, LevelEventType> particleTypeMap = new HashMap<>();
 | 
			
		||||
    private static Map<ParticleType, String> particleStringMap = new HashMap<>();
 | 
			
		||||
    /**
 | 
			
		||||
     * Java particle type to Bedrock particle ID
 | 
			
		||||
     * Used for area effect clouds.
 | 
			
		||||
     */
 | 
			
		||||
    private static final Object2IntMap<ParticleType> PARTICLE_TO_ID = new Object2IntOpenHashMap<>();
 | 
			
		||||
    /**
 | 
			
		||||
     * Java particle type to Bedrock level event
 | 
			
		||||
     */
 | 
			
		||||
    private static final Map<ParticleType, LevelEventType> PARTICLE_TO_LEVEL_EVENT = new HashMap<>();
 | 
			
		||||
    /**
 | 
			
		||||
     * Java particle type to Bedrock namespaced string ID
 | 
			
		||||
     */
 | 
			
		||||
    private static final Map<ParticleType, String> PARTICLE_TO_STRING = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
    public static void init() {
 | 
			
		||||
        // no-op
 | 
			
		||||
| 
						 | 
				
			
			@ -68,22 +80,24 @@ public class EffectRegistry {
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        Iterator<Map.Entry<String, JsonNode>> particlesIterator = particleEntries.fields();
 | 
			
		||||
        while (particlesIterator.hasNext()) {
 | 
			
		||||
            Map.Entry<String, JsonNode> entry = particlesIterator.next();
 | 
			
		||||
            try {
 | 
			
		||||
                particleTypeMap.put(ParticleType.valueOf(entry.getKey().toUpperCase()), LevelEventType.valueOf(entry.getValue().asText().toUpperCase()));
 | 
			
		||||
            } catch (IllegalArgumentException e1) {
 | 
			
		||||
                try {
 | 
			
		||||
                    particleStringMap.put(ParticleType.valueOf(entry.getKey().toUpperCase()), entry.getValue().asText());
 | 
			
		||||
                    GeyserConnector.getInstance().getLogger().debug("Force to map particle "
 | 
			
		||||
                            + entry.getKey()
 | 
			
		||||
                            + "=>"
 | 
			
		||||
                            + entry.getValue().asText()
 | 
			
		||||
                            + ", it will take effect.");
 | 
			
		||||
                } catch (IllegalArgumentException e2){
 | 
			
		||||
                    GeyserConnector.getInstance().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.particle.failed_map", entry.getKey(), entry.getValue().asText()));
 | 
			
		||||
        try {
 | 
			
		||||
            while (particlesIterator.hasNext()) {
 | 
			
		||||
                Map.Entry<String, JsonNode> 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 */
 | 
			
		||||
| 
						 | 
				
			
			@ -149,11 +163,27 @@ public class EffectRegistry {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static LevelEventType getParticleLevelEventType(@NonNull ParticleType type) {
 | 
			
		||||
        return particleTypeMap.getOrDefault(type, null);
 | 
			
		||||
    /**
 | 
			
		||||
     * @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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static String getParticleString(@NonNull ParticleType type){
 | 
			
		||||
        return particleStringMap.getOrDefault(type, null);
 | 
			
		||||
    /**
 | 
			
		||||
     * @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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,12 +30,9 @@ 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 com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket;
 | 
			
		||||
import org.geysermc.connector.GeyserConnector;
 | 
			
		||||
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 org.geysermc.connector.utils.InventoryUtils;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
| 
						 | 
				
			
			@ -48,7 +45,7 @@ public class CraftingInventoryTranslator extends BlockInventoryTranslator {
 | 
			
		|||
    @Override
 | 
			
		||||
    public int bedrockSlotToJava(InventoryActionData action) {
 | 
			
		||||
        if (action.getSlot() == 50) {
 | 
			
		||||
            GeyserConnector.getInstance().getLogger().warning("Slot 50 found, please report: " + action);
 | 
			
		||||
            // Slot 50 is used for crafting with a controller.
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,18 +25,243 @@
 | 
			
		|||
 | 
			
		||||
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.ContainerInventoryUpdater;
 | 
			
		||||
import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater;
 | 
			
		||||
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 {
 | 
			
		||||
    public EnchantmentInventoryTranslator() {
 | 
			
		||||
        super(2, "minecraft:enchanting_table", ContainerType.ENCHANTMENT, new ContainerInventoryUpdater());
 | 
			
		||||
 | 
			
		||||
    private static final int DYE_ID = 351;
 | 
			
		||||
    private static final short LAPIS_DAMAGE = 4;
 | 
			
		||||
    private static final int ENCHANTED_BOOK_ID = 403;
 | 
			
		||||
 | 
			
		||||
    public EnchantmentInventoryTranslator(InventoryUpdater updater) {
 | 
			
		||||
        super(2, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER, updater);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void translateActions(GeyserSession session, Inventory inventory, List<InventoryActionData> 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().getDamage() != LAPIS_DAMAGE) && 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.getClientData().getLanguageCode());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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.getClientData().getLanguageCode());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -56,7 +56,6 @@ public abstract class InventoryTranslator {
 | 
			
		|||
            put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator());
 | 
			
		||||
            put(WindowType.MERCHANT, new MerchantInventoryTranslator());
 | 
			
		||||
            put(WindowType.SMITHING, new SmithingInventoryTranslator());
 | 
			
		||||
            //put(WindowType.ENCHANTMENT, new EnchantmentInventoryTranslator()); //TODO
 | 
			
		||||
 | 
			
		||||
            InventoryTranslator furnace = new FurnaceInventoryTranslator();
 | 
			
		||||
            put(WindowType.FURNACE, furnace);
 | 
			
		||||
| 
						 | 
				
			
			@ -64,6 +63,7 @@ public abstract class InventoryTranslator {
 | 
			
		|||
            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));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,9 +35,8 @@ 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.world.block.BlockTranslator;
 | 
			
		||||
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
 | 
			
		||||
import org.geysermc.connector.utils.LocaleUtils;
 | 
			
		||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
 | 
			
		||||
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class BlockInventoryHolder extends InventoryHolder {
 | 
			
		||||
| 
						 | 
				
			
			@ -60,7 +59,7 @@ public class BlockInventoryHolder extends InventoryHolder {
 | 
			
		|||
                .putInt("x", position.getX())
 | 
			
		||||
                .putInt("y", position.getY())
 | 
			
		||||
                .putInt("z", position.getZ())
 | 
			
		||||
                .putString("CustomName", LocaleUtils.getLocaleString(inventory.getTitle(), session.getClientData().getLanguageCode())).build();
 | 
			
		||||
                .putString("CustomName", inventory.getTitle()).build();
 | 
			
		||||
        BlockEntityDataPacket dataPacket = new BlockEntityDataPacket();
 | 
			
		||||
        dataPacket.setData(tag);
 | 
			
		||||
        dataPacket.setBlockPosition(position);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -173,21 +173,8 @@ public class ItemRegistry {
 | 
			
		|||
        int netId = 1;
 | 
			
		||||
        List<ItemData> creativeItems = new ArrayList<>();
 | 
			
		||||
        for (JsonNode itemNode : creativeItemEntries) {
 | 
			
		||||
            try {
 | 
			
		||||
                short damage = 0;
 | 
			
		||||
                NbtMap tag = null;
 | 
			
		||||
                if (itemNode.has("damage")) {
 | 
			
		||||
                    damage = itemNode.get("damage").numberValue().shortValue();
 | 
			
		||||
                }
 | 
			
		||||
                if (itemNode.has("nbt_b64")) {
 | 
			
		||||
                    byte[] bytes = Base64.getDecoder().decode(itemNode.get("nbt_b64").asText());
 | 
			
		||||
                    ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
 | 
			
		||||
                    tag = (NbtMap) NbtUtils.createReaderLE(bais).readTag();
 | 
			
		||||
                }
 | 
			
		||||
                creativeItems.add(ItemData.fromNet(netId++, itemNode.get("id").asInt(), damage, 1, tag));
 | 
			
		||||
            } catch (IOException e) {
 | 
			
		||||
                e.printStackTrace();
 | 
			
		||||
            }
 | 
			
		||||
            ItemData item = getBedrockItemFromJson(itemNode);
 | 
			
		||||
            creativeItems.add(ItemData.fromNet(netId++, item.getId(), item.getDamage(), item.getCount(), item.getTag()));
 | 
			
		||||
        }
 | 
			
		||||
        CREATIVE_ITEMS = creativeItems.toArray(new ItemData[0]);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -210,14 +197,9 @@ public class ItemRegistry {
 | 
			
		|||
     */
 | 
			
		||||
    public static ItemEntry getItem(ItemData data) {
 | 
			
		||||
        for (ItemEntry itemEntry : ITEM_ENTRIES.values()) {
 | 
			
		||||
            if (itemEntry.getBedrockId() == data.getId() && (itemEntry.getBedrockData() == data.getDamage() || itemEntry.getJavaIdentifier().endsWith("potion"))) {
 | 
			
		||||
                return itemEntry;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // If item find was unsuccessful first time, we try again while ignoring damage
 | 
			
		||||
        // Fixes piston, sticky pistons, dispensers and droppers turning into air from creative inventory
 | 
			
		||||
        for (ItemEntry itemEntry : ITEM_ENTRIES.values()) {
 | 
			
		||||
            if (itemEntry.getBedrockId() == data.getId()) {
 | 
			
		||||
            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;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -240,4 +222,48 @@ public class ItemRegistry {
 | 
			
		|||
        return JAVA_IDENTIFIER_MAP.computeIfAbsent(javaIdentifier, key -> ITEM_ENTRIES.values()
 | 
			
		||||
                .stream().filter(itemEntry -> itemEntry.getJavaIdentifier().equals(key)).findFirst().orElse(null));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Finds the Bedrock string identifier of an ItemEntry
 | 
			
		||||
     *
 | 
			
		||||
     * @param entry the ItemEntry to search for
 | 
			
		||||
     * @return the Bedrock identifier
 | 
			
		||||
     */
 | 
			
		||||
    public static String getBedrockIdentifer(ItemEntry entry) {
 | 
			
		||||
        String blockName = "";
 | 
			
		||||
        for (StartGamePacket.ItemEntry startGamePacketItemEntry : ItemRegistry.ITEMS) {
 | 
			
		||||
            if (startGamePacketItemEntry.getId() == (short) entry.getBedrockId()) {
 | 
			
		||||
                blockName = startGamePacketItemEntry.getIdentifier(); // Find the Bedrock string name
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return blockName;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,12 +35,14 @@ 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 net.kyori.adventure.text.TextComponent;
 | 
			
		||||
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.GeyserConnector;
 | 
			
		||||
import org.geysermc.connector.network.session.GeyserSession;
 | 
			
		||||
import org.geysermc.connector.network.translators.ItemRemapper;
 | 
			
		||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
 | 
			
		||||
import org.geysermc.connector.utils.FileUtils;
 | 
			
		||||
import org.geysermc.connector.utils.LanguageUtils;
 | 
			
		||||
import org.geysermc.connector.utils.MessageUtils;
 | 
			
		||||
import org.reflections.Reflections;
 | 
			
		||||
| 
						 | 
				
			
			@ -62,7 +64,7 @@ public abstract class ItemTranslator {
 | 
			
		|||
 | 
			
		||||
    static {
 | 
			
		||||
        /* Load item translators */
 | 
			
		||||
        Reflections ref = new Reflections("org.geysermc.connector.network.translators.item");
 | 
			
		||||
        Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.item") : new Reflections("org.geysermc.connector.network.translators.item");
 | 
			
		||||
 | 
			
		||||
        Map<NbtItemStackTranslator, Integer> loadedNbtItemTranslators = new HashMap<>();
 | 
			
		||||
        for (Class<?> clazz : ref.getTypesAnnotatedWith(ItemRemapper.class)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -138,11 +140,13 @@ public abstract class ItemTranslator {
 | 
			
		|||
        if (nbt != null) {
 | 
			
		||||
            for (NbtItemStackTranslator translator : NBT_TRANSLATORS) {
 | 
			
		||||
                if (translator.acceptItem(bedrockItem)) {
 | 
			
		||||
                    translator.translateToBedrock(nbt, bedrockItem);
 | 
			
		||||
                    translator.translateToBedrock(session, nbt, bedrockItem);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        translateDisplayProperties(session, nbt);
 | 
			
		||||
 | 
			
		||||
        ItemData itemData;
 | 
			
		||||
        ItemTranslator itemStackTranslator = ITEM_STACK_TRANSLATORS.get(bedrockItem.getJavaId());
 | 
			
		||||
        if (itemStackTranslator != null) {
 | 
			
		||||
| 
						 | 
				
			
			@ -151,42 +155,43 @@ public abstract class ItemTranslator {
 | 
			
		|||
            itemData = DEFAULT_TRANSLATOR.translateToBedrock(itemStack, bedrockItem);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // Get the display name of the item
 | 
			
		||||
        NbtMap tag = itemData.getTag();
 | 
			
		||||
        if (tag != null) {
 | 
			
		||||
            NbtMap display = tag.getCompound("display");
 | 
			
		||||
            if (display != null && !display.isEmpty() && display.containsKey("Name")) {
 | 
			
		||||
                String name = display.getString("Name");
 | 
			
		||||
 | 
			
		||||
                // If its not a message convert it
 | 
			
		||||
                if (!MessageUtils.isMessage(name)) {
 | 
			
		||||
                    TextComponent component = LegacyComponentSerializer.legacySection().deserialize(name);
 | 
			
		||||
                    name = GsonComponentSerializer.gson().serialize(component);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Check if its a message to translate
 | 
			
		||||
                if (MessageUtils.isMessage(name)) {
 | 
			
		||||
                    // Get the translated name
 | 
			
		||||
                    name = MessageUtils.getTranslatedBedrockMessage(MessageSerializer.fromString(name), session.getClientData().getLanguageCode());
 | 
			
		||||
 | 
			
		||||
                    // Build the new display tag
 | 
			
		||||
                    NbtMapBuilder displayBuilder = display.toBuilder();
 | 
			
		||||
                    displayBuilder.putString("Name", name);
 | 
			
		||||
 | 
			
		||||
                    // Build the new root tag
 | 
			
		||||
                    NbtMapBuilder builder = tag.toBuilder();
 | 
			
		||||
                    builder.put("display", displayBuilder.build());
 | 
			
		||||
 | 
			
		||||
                    // Create a new item with the original data + updated name
 | 
			
		||||
                    itemData = ItemData.of(itemData.getId(), itemData.getDamage(), itemData.getCount(), builder.build());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        if (nbt != null) {
 | 
			
		||||
            // Translate the canDestroy and canPlaceOn Java NBT
 | 
			
		||||
            ListTag canDestroy = nbt.get("CanDestroy");
 | 
			
		||||
            String[] canBreak = new String[0];
 | 
			
		||||
            ListTag canPlaceOn = nbt.get("CanPlaceOn");
 | 
			
		||||
            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);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return itemData;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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
 | 
			
		||||
     */
 | 
			
		||||
    private static String[] getCanModify(ListTag canModifyJava, String[] canModifyBedrock) {
 | 
			
		||||
        if (canModifyJava != null && canModifyJava.size() > 0) {
 | 
			
		||||
            canModifyBedrock = new String[canModifyJava.size()];
 | 
			
		||||
            for (int i = 0; i < canModifyBedrock.length; i++) {
 | 
			
		||||
                // Get the Java identifier of the block that can be placed
 | 
			
		||||
                String block = ((StringTag) canModifyJava.get(i)).getValue();
 | 
			
		||||
                // Sometimes this is done but it's still valid
 | 
			
		||||
                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:", "");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return canModifyBedrock;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static final ItemTranslator DEFAULT_TRANSLATOR = new ItemTranslator() {
 | 
			
		||||
        @Override
 | 
			
		||||
        public List<ItemEntry> getAppliedItems() {
 | 
			
		||||
| 
						 | 
				
			
			@ -375,6 +380,38 @@ public abstract class ItemTranslator {
 | 
			
		|||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Translates the display name of the item
 | 
			
		||||
     * @param session the Bedrock client's session
 | 
			
		||||
     * @param tag the tag to translate
 | 
			
		||||
     */
 | 
			
		||||
    public static void translateDisplayProperties(GeyserSession session, CompoundTag tag) {
 | 
			
		||||
        if (tag != null) {
 | 
			
		||||
            CompoundTag display = tag.get("display");
 | 
			
		||||
            if (display != null && !display.isEmpty() && display.contains("Name")) {
 | 
			
		||||
                String name = ((StringTag) display.get("Name")).getValue();
 | 
			
		||||
 | 
			
		||||
                // If its not a message convert it
 | 
			
		||||
                if (!MessageUtils.isMessage(name)) {
 | 
			
		||||
                    Component component = LegacyComponentSerializer.legacySection().deserialize(name);
 | 
			
		||||
                    name = GsonComponentSerializer.gson().serialize(component);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Check if its a message to translate
 | 
			
		||||
                if (MessageUtils.isMessage(name)) {
 | 
			
		||||
                    // Get the translated name
 | 
			
		||||
                    name = MessageUtils.getTranslatedBedrockMessage(MessageSerializer.fromString(name), session.getClientData().getLanguageCode());
 | 
			
		||||
 | 
			
		||||
                    // Add the new name tag
 | 
			
		||||
                    display.put(new StringTag("Name", name));
 | 
			
		||||
 | 
			
		||||
                    // Add to the new root tag
 | 
			
		||||
                    tag.put(display);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if an {@link ItemStack} is equal to another item stack
 | 
			
		||||
     *
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,17 +26,33 @@
 | 
			
		|||
package org.geysermc.connector.network.translators.item;
 | 
			
		||||
 | 
			
		||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
 | 
			
		||||
import org.geysermc.connector.network.session.GeyserSession;
 | 
			
		||||
 | 
			
		||||
public class NbtItemStackTranslator {
 | 
			
		||||
 | 
			
		||||
    public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) {
 | 
			
		||||
    /**
 | 
			
		||||
     * 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
 | 
			
		||||
     */
 | 
			
		||||
    public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Translate the item NBT to Java.
 | 
			
		||||
     * @param itemTag the item's CompoundTag
 | 
			
		||||
     * @param itemEntry Geyser's item entry
 | 
			
		||||
     */
 | 
			
		||||
    public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param itemEntry Geyser's item entry
 | 
			
		||||
     * @return if the item should be processed under this class
 | 
			
		||||
     */
 | 
			
		||||
    public boolean acceptItem(ItemEntry itemEntry) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue