diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 75a76cd05..46653e624 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -11,4 +11,4 @@ assignees: ''
Add a description
**Alternatives?**
-Any alternatives you have tryed
+List any alternatives you might have tried
diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml
index 9cb0726ca..78a3ce299 100644
--- a/.github/workflows/pullrequest.yml
+++ b/.github/workflows/pullrequest.yml
@@ -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()
diff --git a/.gitignore b/.gitignore
index c4c878af4..e3c3b0a56 100644
--- a/.gitignore
+++ b/.gitignore
@@ -241,4 +241,5 @@ config.yml
logs/
public-key.pem
locales/
-cache/
\ No newline at end of file
+/cache/
+/packs/
\ No newline at end of file
diff --git a/README.md b/README.md
index 64ff22522..ba3e6fc2d 100644
--- a/README.md
+++ b/README.md
@@ -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.x and Minecraft Java v1.16.2.
+### 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.
@@ -35,13 +35,14 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set
## What's Left to be Added/Fixed
- The Following Inventories
- - [ ] Enchantment Table
+ - [ ] Enchantment Table (as a proper GUI)
- [ ] Beacon
- [ ] Cartography Table
- [ ] Stonecutter
- - [ ] Command Block
- [ ] Structure Block
- [ ] Horse Inventory
+ - [ ] Loom
+ - [ ] Smithing Table
- Some Entity Flags
## Compiling
diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml
index 8497b9684..44b28e931 100644
--- a/bootstrap/bungeecord/pom.xml
+++ b/bootstrap/bungeecord/pom.xml
@@ -61,14 +61,34 @@
net.md_5.bungee.jni
org.geysermc.platform.bungeecord.shaded.jni
+
+ com.fasterxml.jackson
+ org.geysermc.platform.bungeecord.shaded.jackson
+
io.netty
org.geysermc.platform.bungeecord.shaded.netty
- org.reflections.reflections
+ org.reflections
org.geysermc.platform.bungeecord.shaded.reflections
+
+ com.google.common
+ org.geysermc.platform.bungeecord.shaded.google.common
+
+
+ com.google.guava
+ org.geysermc.platform.bungeecord.shaded.google.guava
+
+
+ org.dom4j
+ org.geysermc.platform.bungeecord.shaded.dom4j
+
+
+ net.kyori.adventure
+ org.geysermc.platform.bungeecord.shaded.adventure
+
diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java
index 500f342a6..0d7d6e06b 100644
--- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java
+++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java
@@ -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");
- 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());
}
}
diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeLogger.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeLogger.java
index cd07b333d..e40f404c1 100644
--- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeLogger.java
+++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeLogger.java
@@ -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;
+ }
}
}
diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java
index a600ebcd3..9d8e05ac7 100644
--- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java
+++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java
@@ -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();
@@ -83,6 +78,11 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
// 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());
}
@@ -97,9 +97,13 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
if (geyserConfig.getRemote().getAuthType().equals("floodgate") && getProxy().getPluginManager().getPlugin("floodgate") == 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);
diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml
index 422b2769b..d4dc33260 100644
--- a/bootstrap/spigot/pom.xml
+++ b/bootstrap/spigot/pom.xml
@@ -20,13 +20,13 @@
org.spigotmc
spigot-api
- 1.14-R0.1-SNAPSHOT
+ 1.15.2-R0.1-SNAPSHOT
provided
us.myles
viaversion
- 3.1.0
+ 3.1.1
provided
@@ -76,9 +76,25 @@
org.geysermc.platform.spigot.shaded.jackson
- org.reflections.reflections
+ org.reflections
org.geysermc.platform.spigot.shaded.reflections
+
+ com.google.common
+ org.geysermc.platform.spigot.shaded.google.common
+
+
+ com.google.guava
+ org.geysermc.platform.spigot.shaded.google.guava
+
+
+ org.dom4j
+ org.geysermc.platform.spigot.shaded.dom4j
+
+
+ net.kyori.adventure
+ org.geysermc.platform.spigot.shaded.adventure
+
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java
index de4e58c3b..209a21061 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java
@@ -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");
- 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
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotLogger.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotLogger.java
index 252d6bbed..b462f1f1c 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotLogger.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotLogger.java
@@ -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;
+ }
}
}
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java
index dab63c4ea..a8ae50d69 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java
@@ -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;
@@ -83,6 +82,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
// 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());
}
@@ -97,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);
@@ -115,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);
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java
index 63cb3b4ab..cb59e202b 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java
@@ -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);
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java
index c43d9eaba..c6443bd05 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java
@@ -25,34 +25,85 @@
package org.geysermc.platform.spigot.world;
-import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
-import lombok.AllArgsConstructor;
+import com.fasterxml.jackson.databind.JsonNode;
+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.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.GeyserWorldManager;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
+import org.geysermc.connector.utils.FileUtils;
import org.geysermc.connector.utils.GameRule;
+import org.geysermc.connector.utils.LanguageUtils;
import us.myles.ViaVersion.protocols.protocol1_13_1to1_13.Protocol1_13_1To1_13;
import us.myles.ViaVersion.protocols.protocol1_16_2to1_16_1.data.MappingData;
-@AllArgsConstructor
+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) {
return BlockTranslator.AIR;
}
+ if (Bukkit.getPlayer(session.getPlayerEntity().getUsername()) == null) {
+ return BlockTranslator.AIR;
+ }
if (isLegacy) {
return getLegacyBlock(session, x, y, z, isViaVersion);
}
- 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(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getBlockAt(x, y, z).getBlockData().getAsString(), 0);
}
@SuppressWarnings("deprecation")
@@ -74,6 +125,43 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager {
}
@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()));
}
diff --git a/bootstrap/spigot/src/main/resources/biomes.json b/bootstrap/spigot/src/main/resources/biomes.json
new file mode 100644
index 000000000..56520e914
--- /dev/null
+++ b/bootstrap/spigot/src/main/resources/biomes.json
@@ -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
+}
diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml
index f3c898084..132f38173 100644
--- a/bootstrap/sponge/pom.xml
+++ b/bootstrap/sponge/pom.xml
@@ -70,9 +70,25 @@
org.geysermc.platform.sponge.shaded.fastutil
- org.reflections.reflections
+ org.reflections
org.geysermc.platform.sponge.shaded.reflections
+
+ com.google.common
+ org.geysermc.platform.sponge.shaded.google.common
+
+
+ com.google.guava
+ org.geysermc.platform.sponge.shaded.google.guava
+
+
+ org.dom4j
+ org.geysermc.platform.sponge.shaded.dom4j
+
+
+ net.kyori.adventure
+ org.geysermc.platform.sponge.shaded.adventure
+
diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java
index 7f08a0950..2d5eefebd 100644
--- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java
+++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java
@@ -25,256 +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 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 userAuths = new ArrayList(((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 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 isPassthroughProtocolName() {
- return node.getNode("passthrough-protocol-name").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 boolean isEnableProxyConnections() {
- return node.getNode("enable-proxy-connections").getBoolean(false);
- }
-
- @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
}
}
diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeLogger.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeLogger.java
index fb7cb54bb..bdbd25311 100644
--- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeLogger.java
+++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeLogger.java
@@ -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;
+ }
}
}
diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java
index e57e383b2..106d2b155 100644
--- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java
+++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java
@@ -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,33 +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 (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) {
- serverPort.setValue(javaAddr.getPort());
+ this.geyserConfig.setAutoconfiguredRemote(true);
+ geyserConfig.getRemote().setPort(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());
diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java
index e4b70133f..123a9a600 100644
--- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java
+++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java
@@ -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,9 +146,10 @@ 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) {
diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneConfiguration.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneConfiguration.java
index 29e18d08f..5707863c2 100644
--- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneConfiguration.java
+++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneConfiguration.java
@@ -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());
}
}
diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java
index 0d9ced951..f0f7156f9 100644
--- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java
+++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java
@@ -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();
}
diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml
index b08e5cbc5..ee445b6e7 100644
--- a/bootstrap/velocity/pom.xml
+++ b/bootstrap/velocity/pom.xml
@@ -57,14 +57,34 @@
+
+ com.fasterxml.jackson
+ org.geysermc.platform.velocity.shaded.jackson
+
it.unimi.dsi.fastutil
org.geysermc.platform.velocity.shaded.fastutil
- org.reflections.reflections
+ org.reflections
org.geysermc.platform.velocity.shaded.reflections
+
+ com.google.common
+ org.geysermc.platform.velocity.shaded.google.common
+
+
+ com.google.guava
+ org.geysermc.platform.velocity.shaded.google.guava
+
+
+ org.dom4j
+ org.geysermc.platform.velocity.shaded.dom4j
+
+
+ net.kyori.adventure
+ org.geysermc.platform.velocity.shaded.adventure
+
diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java
index 075804437..7dc6746b0 100644
--- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java
+++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java
@@ -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 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());
}
}
diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityLogger.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityLogger.java
index a935d786c..cb98411c8 100644
--- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityLogger.java
+++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityLogger.java
@@ -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;
+ }
}
}
\ No newline at end of file
diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java
index 6d8032343..291d7a001 100644
--- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java
+++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java
@@ -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;
@@ -94,6 +92,11 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
// 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());
}
@@ -107,6 +110,10 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
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());
diff --git a/connector/pom.xml b/connector/pom.xml
index a3748044a..f52713cea 100644
--- a/connector/pom.xml
+++ b/connector/pom.xml
@@ -31,9 +31,9 @@
compile
- com.nukkitx.protocol
- bedrock-v409
- 2.6.0-SNAPSHOT
+ com.github.CloudburstMC.Protocol
+ bedrock-v408
+ 250beb2a94
compile
@@ -110,8 +110,8 @@
com.github.GeyserMC
- MCProtocolLib
- f37c98dc70
+ mcprotocollib
+ e4a3aa636a
compile
@@ -129,24 +129,29 @@
org.reflections
reflections
- 0.9.12
+ 0.9.11
- net.kyori
+ org.dom4j
+ dom4j
+ 2.1.3
+
+
+ com.github.kyoripowered.adventure
adventure-api
- 4.0.0-SNAPSHOT
+ 557865caef
compile
- net.kyori
+ com.github.kyoripowered.adventure
adventure-text-serializer-gson
- 4.0.0-SNAPSHOT
+ 557865caef
compile
- net.kyori
+ com.github.kyoripowered.adventure
adventure-text-serializer-legacy
- 4.0.0-SNAPSHOT
+ 557865caef
compile
@@ -232,6 +237,52 @@
+
+ org.codehaus.gmavenplus
+ gmavenplus-plugin
+ 1.9.1
+
+
+ process-classes
+
+ execute
+
+
+
+
+
+
+
+
+
+
+ org.reflections
+ reflections
+ 0.9.11
+
+
+ org.dom4j
+ dom4j
+ 2.1.3
+
+
+ org.codehaus.groovy
+ groovy-all
+ 3.0.5
+ runtime
+ pom
+
+
+
diff --git a/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java b/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java
index a30a5f4d4..ec5dd349a 100644
--- a/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java
+++ b/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java
@@ -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;
diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java
index 9b6045be2..de4d2c2bf 100644
--- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java
+++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java
@@ -25,6 +25,7 @@
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;
@@ -47,6 +48,7 @@ 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;
@@ -55,6 +57,7 @@ import org.geysermc.connector.network.translators.world.block.entity.BlockEntity
import org.geysermc.connector.utils.DimensionUtils;
import org.geysermc.connector.utils.LanguageUtils;
import org.geysermc.connector.utils.LocaleUtils;
+import org.geysermc.connector.utils.ResourcePack;
import org.geysermc.floodgate.crypto.AesCipher;
import org.geysermc.floodgate.crypto.AesKeyProducer;
import org.geysermc.floodgate.crypto.Base64Topping;
@@ -77,7 +80,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 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
@@ -138,9 +141,12 @@ public class GeyserConnector {
ItemTranslator.init();
LocaleUtils.init();
PotionMixRegistry.init();
+ RecipeRegistry.init();
SoundRegistry.init();
SoundHandlerRegistry.init();
+ ResourcePack.loadPacks();
+
if (platformType != PlatformType.STANDALONE && config.getRemote().getAddress().equals("auto")) {
// Set the remote address to localhost since that is where we are always connecting
try {
@@ -168,7 +174,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
@@ -202,7 +208,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();
@@ -234,6 +240,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() {
@@ -319,6 +329,18 @@ public class GeyserConnector {
return bootstrap.getWorldManager();
}
+ /**
+ * Get the production status of the current runtime.
+ * Will return true if the version number is not 'DEV'.
+ * Should only happen in compiled jars.
+ *
+ * @return If we are in a production build/environment
+ */
+ public boolean isProduction() {
+ //noinspection ConstantConditions
+ return !"DEV".equals(GeyserConnector.VERSION);
+ }
+
public static GeyserConnector getInstance() {
return instance;
}
diff --git a/connector/src/main/java/org/geysermc/connector/GeyserLogger.java b/connector/src/main/java/org/geysermc/connector/GeyserLogger.java
index 0ba2a689f..138285eb1 100644
--- a/connector/src/main/java/org/geysermc/connector/GeyserLogger.java
+++ b/connector/src/main/java/org/geysermc/connector/GeyserLogger.java
@@ -84,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();
}
diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java
index 636058a02..c69a8705f 100644
--- a/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java
+++ b/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java
@@ -49,10 +49,6 @@ public class StopCommand extends GeyserCommand {
return;
}
- connector.shutdown();
-
- if (connector.getPlatformType() == PlatformType.STANDALONE) {
- System.exit(0);
- }
+ connector.getBootstrap().onDisable();
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java
index 681474a98..f7f62e597 100644
--- a/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java
+++ b/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java
@@ -26,6 +26,7 @@
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;
@@ -38,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 {
@@ -51,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, BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()));
+ String bedrockVersions;
+ List supportedCodecs = BedrockProtocol.SUPPORTED_BEDROCK_CODECS;
+ if (supportedCodecs.size() > 1) {
+ bedrockVersions = supportedCodecs.get(0).getMinecraftVersion() + " - " + supportedCodecs.get(supportedCodecs.size() - 1).getMinecraftVersion();
+ } else {
+ bedrockVersions = BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion();
+ }
+
+ sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.version", GeyserConnector.NAME, GeyserConnector.VERSION, MinecraftConstants.GAME_VERSION, bedrockVersions));
// Disable update checking in dev mode
//noinspection ConstantConditions - changes in production
diff --git a/connector/src/main/java/org/geysermc/connector/common/PlatformType.java b/connector/src/main/java/org/geysermc/connector/common/PlatformType.java
index 7c245c9bf..4daa5d37d 100644
--- a/connector/src/main/java/org/geysermc/connector/common/PlatformType.java
+++ b/connector/src/main/java/org/geysermc/connector/common/PlatformType.java
@@ -32,6 +32,7 @@ import lombok.Getter;
@AllArgsConstructor
public enum PlatformType {
+ ANDROID("Android"),
BUNGEECORD("BungeeCord"),
SPIGOT("Spigot"),
SPONGE("Sponge"),
diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java
index 2cf9a181e..c1cc4d036 100644
--- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java
+++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java
@@ -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;
@@ -74,12 +73,14 @@ public interface GeyserConfiguration {
String getDefaultLocale();
- Path getFloodgateKeyFile();
+ Path getFloodgateKeyPath();
boolean isAboveBedrockNetherBuilding();
boolean isCacheChunks();
+ boolean isForceResourcePacks();
+
int getCacheImages();
IMetricsInfo getMetrics();
@@ -95,6 +96,8 @@ public interface GeyserConfiguration {
String getMotd1();
String getMotd2();
+
+ String getServerName();
}
interface IRemoteConfiguration {
@@ -123,6 +126,8 @@ public interface GeyserConfiguration {
String getUniqueId();
}
+ int getScoreboardPacketThreshold();
+
// if u have offline mode enabled pls be safe
boolean isEnableProxyConnections();
diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java
index f322b4363..99a3a7a5a 100644
--- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java
+++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java
@@ -29,6 +29,7 @@ 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;
@@ -38,94 +39,105 @@ import java.util.Map;
@JsonIgnoreProperties(ignoreUnknown = true)
public abstract class GeyserJacksonConfiguration implements GeyserConfiguration {
+ /**
+ * If the config was originally 'auto' before the values changed
+ */
+ @Setter
+ private boolean autoconfiguredRemote = false;
+
private BedrockConfiguration bedrock;
private RemoteConfiguration remote;
@JsonProperty("floodgate-key-file")
- private String floodgateKeyFile;
+ private String floodgateKeyFile = "public-key.pem";
- public abstract Path getFloodgateKeyFile();
+ public abstract Path getFloodgateKeyPath();
private Map 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;
+ 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;
+
+ @JsonProperty("force-resource-packs")
+ private boolean forceResourcePacks = true;
private MetricsInfo metrics;
@Getter
public static class BedrockConfiguration implements IBedrockConfiguration {
-
@AsteriskSerializer.Asterisk(sensitive = true)
- private String address;
+ 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;
+ private String address = "auto";
@Setter
- private int port;
+ private int port = 25565;
+ @Setter
@JsonProperty("auth-type")
- private String authType;
+ private String authType = "online";
}
@Getter
@@ -139,13 +151,15 @@ 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 = "generateuuid";
}
+ @JsonProperty("scoreboard-packet-threshold")
+ private int scoreboardPacketThreshold = 10;
+
@JsonProperty("enable-proxy-connections")
private boolean enableProxyConnections = false;
@@ -153,5 +167,5 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
private int mtu = 1400;
@JsonProperty("config-version")
- private int configVersion;
+ private int configVersion = 0;
}
diff --git a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java b/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java
index 5aed562ec..b229e1670 100644
--- a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java
+++ b/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java
@@ -31,6 +31,7 @@ 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;
@@ -112,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();
diff --git a/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java
new file mode 100644
index 000000000..8cabba645
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java
@@ -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);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java
index 3ca170436..ede3b78bb 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java
+++ b/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java
@@ -72,6 +72,10 @@ public class FireworkEntity extends Entity {
}
CompoundTag fireworks = tag.get("Fireworks");
+ if (fireworks == null) {
+ // Thank you Mineplex very cool
+ return;
+ }
NbtMapBuilder fireworksBuilder = NbtMap.builder();
if (fireworks.get("Flight") != null) {
diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java
index 392cec24c..f9d2ace46 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java
+++ b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java
@@ -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) {
diff --git a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java
index 52b273513..390110d18 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java
+++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java
@@ -27,20 +27,24 @@ 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;
-import com.nukkitx.protocol.bedrock.data.AdventureSetting;
import com.nukkitx.protocol.bedrock.data.AttributeData;
import com.nukkitx.protocol.bedrock.data.PlayerPermission;
import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.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;
@@ -48,7 +52,10 @@ import org.geysermc.connector.scoreboard.Team;
import org.geysermc.connector.utils.AttributeUtils;
import org.geysermc.connector.utils.MessageUtils;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Getter @Setter
@@ -60,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);
@@ -73,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;
@@ -111,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) {
@@ -184,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
@@ -197,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
@@ -209,11 +228,6 @@ 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.getWorldCache().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) {
@@ -221,8 +235,18 @@ public class PlayerEntity extends LivingEntity {
}
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());
+ }
}
}
@@ -238,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());
diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java
index 87f4c8b50..500e135ed 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java
+++ b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java
@@ -135,7 +135,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"),
diff --git a/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java b/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java
index 432ca8270..225335a97 100644
--- a/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java
+++ b/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java
@@ -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;
diff --git a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java
index 904f70a0a..5d4462b45 100644
--- a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java
+++ b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java
@@ -28,28 +28,27 @@ 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 com.nukkitx.protocol.bedrock.v409.Bedrock_v409;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
+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 and as the version shown in /geyser version
+ * 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 Set SUPPORTED_BEDROCK_CODECS = ConcurrentHashMap.newKeySet();
+ public static final List SUPPORTED_BEDROCK_CODECS = new ArrayList<>();
static {
- SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC);
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v407.V407_CODEC);
- SUPPORTED_BEDROCK_CODECS.add(Bedrock_v409.V409_CODEC);
+ SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC);
}
/**
diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java
index f65b3ef1a..7e97d4298 100644
--- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java
+++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java
@@ -26,17 +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.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 {
public UpstreamPacketHandler(GeyserConnector connector, GeyserSession session) {
@@ -70,6 +77,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;
}
@@ -81,13 +93,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.setExperimental(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;
@@ -149,4 +190,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;
+ }
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java
index 9a2ebe56d..42c2f2437 100644
--- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java
+++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java
@@ -70,6 +70,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.*;
@@ -155,8 +156,6 @@ public class GeyserSession implements CommandSender {
@Setter
private Vector3i lastInteractionPosition;
- @Setter
- private boolean switchingDimension = false;
private boolean manyDimPackets = false;
private ServerRespawnPacket lastDimPacket = null;
@@ -174,6 +173,11 @@ public class GeyserSession implements CommandSender {
@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.
*/
@@ -186,8 +190,6 @@ public class GeyserSession implements CommandSender {
@Setter
private long lastHitTime;
- private MinecraftProtocol protocol;
-
private boolean reducedDebugInfo = false;
@Setter
@@ -235,6 +237,17 @@ public class GeyserSession implements CommandSender {
@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) {
this.connector = connector;
this.upstream = new UpstreamSession(bedrockServerSession);
@@ -294,10 +307,13 @@ public class GeyserSession implements CommandSender {
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
- GameRulesChangedPacket gamerulePacket = new GameRulesChangedPacket();
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);
}
@@ -578,8 +594,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);
@@ -587,7 +605,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);
}
@@ -612,7 +630,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");
@@ -625,7 +643,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");
@@ -684,8 +702,12 @@ public class GeyserSession implements CommandSender {
public void sendAdventureSettings() {
AdventureSettingsPacket adventureSettingsPacket = new AdventureSettingsPacket();
adventureSettingsPacket.setUniqueEntityId(playerEntity.getGeyserId());
- adventureSettingsPacket.setCommandPermission(CommandPermission.NORMAL);
- adventureSettingsPacket.setPlayerPermission(PlayerPermission.MEMBER);
+ // 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);
Set flags = new HashSet<>();
if (canFly) {
diff --git a/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java b/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java
index 09870eef0..393ebfa82 100644
--- a/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java
+++ b/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java
@@ -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) {
diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java
index 9601a2981..2cc9ea134 100644
--- a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java
+++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java
@@ -42,7 +42,7 @@ public class ChunkCache {
private final boolean cache;
@Getter
- private Map chunks = new HashMap<>();
+ private final Map chunks = new HashMap<>();
public ChunkCache(GeyserSession session) {
if (session.getConnector().getWorldManager().getClass() == GeyserBootstrap.DEFAULT_CHUNK_MANAGER.getClass()) {
@@ -57,6 +57,15 @@ public class ChunkCache {
return;
}
ChunkPosition position = new ChunkPosition(chunk.getX(), chunk.getZ());
+ if (chunk.getBiomeData() == null && chunks.containsKey(position)) {
+ Column newColumn = chunk;
+ chunk = chunks.get(position);
+ for (int i = 0; i < newColumn.getChunks().length; i++) {
+ if (newColumn.getChunks()[i] != null) {
+ chunk.getChunks()[i] = newColumn.getChunks()[i];
+ }
+ }
+ }
chunks.put(position, chunk);
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java
index 0bc51ac7a..4b54c9434 100644
--- a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java
+++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java
@@ -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;
diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java
index 310e5f9d7..ce49d2a09 100644
--- a/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java
+++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java
@@ -31,37 +31,40 @@ 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 WorldCache {
-
- private GeyserSession session;
-
+ private final GeyserSession session;
@Setter
private Difficulty difficulty = Difficulty.EASY;
-
private boolean showCoordinates = true;
private Scoreboard scoreboard;
+ private final ScoreboardUpdater scoreboardUpdater;
public WorldCache(GeyserSession session) {
this.session = session;
this.scoreboard = new Scoreboard(session);
+ scoreboardUpdater = new ScoreboardUpdater(this);
+ scoreboardUpdater.start();
}
public void removeScoreboard() {
if (scoreboard != null) {
- Collection 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
*
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java
index 92d2e9102..c43864816 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java
@@ -33,6 +33,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 +49,7 @@ public class PacketTranslatorRegistry {
private static final ObjectArrayList> IGNORED_PACKETS = new ObjectArrayList<>();
static {
- Reflections ref = new Reflections("org.geysermc.connector.network.translators");
+ Reflections ref = GeyserConnector.getInstance().isProduction() ? 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();
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java
index 38b940399..3522b4d56 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java
@@ -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 {
- // 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 lastMessages = new HashMap<>();
-
@Override
public void translate(BlockEntityDataPacket packet, GeyserSession session) {
NbtMap tag = packet.getData();
@@ -50,9 +45,8 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator 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);
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestPacketTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestPacketTranslator.java
deleted file mode 100644
index 39cfceb10..000000000
--- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestPacketTranslator.java
+++ /dev/null
@@ -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 {
-
- @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;
- }
- }
-}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java
new file mode 100644
index 000000000..023a83afe
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java
@@ -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 {
+
+ @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);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandBlockUpdateTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandBlockUpdateTranslator.java
new file mode 100644
index 000000000..b1e732ffe
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandBlockUpdateTranslator.java
@@ -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 {
+
+ @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);
+ }
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEntityPickRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEntityPickRequestTranslator.java
new file mode 100644
index 000000000..4aa044ee4
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEntityPickRequestTranslator.java
@@ -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 {
+
+ @Override
+ public void translate(EntityPickRequestPacket packet, GeyserSession session) {
+ if (session.getGameMode() != GameMode.CREATIVE) return; // Apparently Java behavior
+ Entity entity = session.getEntityCache().getEntityByGeyserId(packet.getRuntimeEntityId());
+ if (entity == null) return;
+
+ // Get the corresponding item
+ String itemName;
+ switch (entity.getEntityType()) {
+ case BOAT:
+ // Include type of boat in the name
+ int variant = entity.getMetadata().getInt(EntityData.VARIANT);
+ String typeOfBoat;
+ switch (variant) {
+ case 1:
+ typeOfBoat = "spruce";
+ break;
+ case 2:
+ typeOfBoat = "birch";
+ break;
+ case 3:
+ typeOfBoat = "jungle";
+ break;
+ case 4:
+ typeOfBoat = "acacia";
+ break;
+ case 5:
+ typeOfBoat = "dark_oak";
+ break;
+ default:
+ typeOfBoat = "oak";
+ break;
+ }
+ itemName = typeOfBoat + "_boat";
+ break;
+ case LEASH_KNOT:
+ itemName = "lead";
+ break;
+ case MINECART_CHEST:
+ case MINECART_COMMAND_BLOCK:
+ case MINECART_FURNACE:
+ case MINECART_HOPPER:
+ case MINECART_TNT:
+ // Move MINECART to the end of the name
+ itemName = entity.getEntityType().toString().toLowerCase().replace("minecart_", "") + "_minecart";
+ break;
+ case MINECART_SPAWNER:
+ // Turns into a normal minecart
+ itemName = "minecart";
+ break;
+ case ARMOR_STAND:
+ case END_CRYSTAL:
+ case ITEM_FRAME:
+ case MINECART:
+ case PAINTING:
+ // No spawn egg, just an item
+ itemName = entity.getEntityType().toString().toLowerCase();
+ break;
+ default:
+ itemName = entity.getEntityType().toString().toLowerCase() + "_spawn_egg";
+ break;
+ }
+
+ String fullItemName = "minecraft:" + itemName;
+ ItemEntry entry = ItemRegistry.getItemEntry(fullItemName);
+ // Verify it is, indeed, an item
+ if (entry == null) return;
+
+ InventoryUtils.findOrCreatePickedBlock(session, fullItemName);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java
index f8e4eb6d0..95e669572 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java
@@ -39,8 +39,11 @@ 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.ContainerType;
+import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket;
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;
@@ -98,11 +101,31 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator= 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 = packet.getBlockPosition();
// TODO: Find a better way to do this?
switch (packet.getBlockFace()) {
@@ -195,6 +218,18 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator {
@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);
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java
index be918ba74..8809941b9 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java
@@ -86,6 +86,13 @@ public class BedrockMovePlayerTranslator extends PacketTranslator 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());
+ }
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java
index 7d06aed14..e44e4bd0b 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java
@@ -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));
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java
index 4828fbf27..1f3af019f 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java
@@ -173,21 +173,8 @@ public class ItemRegistry {
int netId = 1;
List 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]);
}
@@ -233,4 +220,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);
+ }
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java
index 37dd066ed..864e9fe03 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java
@@ -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().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.item") : new Reflections("org.geysermc.connector.network.translators.item");
Map loadedNbtItemTranslators = new HashMap<>();
for (Class> clazz : ref.getTypesAnnotatedWith(ItemRemapper.class)) {
@@ -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 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
*
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/NbtItemStackTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/NbtItemStackTranslator.java
index 3c0b75b75..89d41e98d 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/NbtItemStackTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/NbtItemStackTranslator.java
@@ -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;
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java
new file mode 100644
index 000000000..191b285c6
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.connector.network.translators.item;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.nukkitx.protocol.bedrock.data.inventory.CraftingData;
+import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
+import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+import org.geysermc.connector.GeyserConnector;
+import org.geysermc.connector.utils.FileUtils;
+import org.geysermc.connector.utils.LanguageUtils;
+
+import java.io.InputStream;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Manages any recipe-related storing
+ */
+public class RecipeRegistry {
+
+ /**
+ * A list of all possible leather armor dyeing recipes.
+ * Created manually.
+ */
+ public static List LEATHER_DYEING_RECIPES = new ObjectArrayList<>();
+ /**
+ * A list of all possible firework rocket recipes, including the base rocket.
+ * Obtained from a ProxyPass dump of protocol v407
+ */
+ public static List FIREWORK_ROCKET_RECIPES = new ObjectArrayList<>(21);
+ /**
+ * A list of all possible firework star recipes.
+ * Obtained from a ProxyPass dump of protocol v407
+ */
+ public static List FIREWORK_STAR_RECIPES = new ObjectArrayList<>(40);
+ /**
+ * A list of all possible shulker box dyeing options.
+ * Obtained from a ProxyPass dump of protocol v407
+ */
+ public static List SHULKER_BOX_DYEING_RECIPES = new ObjectArrayList<>();
+
+ static {
+ // Get all recipes that are not directly sent from a Java server
+ InputStream stream = FileUtils.getResource("mappings/recipes.json");
+
+ JsonNode items;
+ try {
+ items = GeyserConnector.JSON_MAPPER.readTree(stream);
+ } catch (Exception e) {
+ throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e);
+ }
+
+ for (JsonNode entry: items.get("leather_armor")) {
+ // This won't be perfect, as we can't possibly send every leather input for every kind of color
+ // But it does display the correct output from a base leather armor, and besides visuals everything works fine
+ LEATHER_DYEING_RECIPES.add(getCraftingDataFromJsonNode(entry));
+ }
+ for (JsonNode entry : items.get("firework_rockets")) {
+ FIREWORK_ROCKET_RECIPES.add(getCraftingDataFromJsonNode(entry));
+ }
+ for (JsonNode entry : items.get("firework_stars")) {
+ FIREWORK_STAR_RECIPES.add(getCraftingDataFromJsonNode(entry));
+ }
+ for (JsonNode entry : items.get("shulker_boxes")) {
+ SHULKER_BOX_DYEING_RECIPES.add(getCraftingDataFromJsonNode(entry));
+ }
+ }
+
+ /**
+ * Computes a Bedrock crafting recipe from the given JSON data.
+ * @param node the JSON data to compute
+ * @return the {@link CraftingData} to send to the Bedrock client.
+ */
+ private static CraftingData getCraftingDataFromJsonNode(JsonNode node) {
+ ItemData output = ItemRegistry.getBedrockItemFromJson(node.get("output").get(0));
+ List inputs = new ObjectArrayList<>();
+ for (JsonNode entry : node.get("input")) {
+ inputs.add(ItemRegistry.getBedrockItemFromJson(entry));
+ }
+ UUID uuid = UUID.randomUUID();
+ if (node.get("type").asInt() == 5) {
+ // Shulker box
+ return CraftingData.fromShulkerBox(uuid.toString(),
+ inputs.toArray(new ItemData[0]), new ItemData[]{output}, uuid, "crafting_table", 0);
+ }
+ return CraftingData.fromShapeless(uuid.toString(),
+ inputs.toArray(new ItemData[0]), new ItemData[]{output}, uuid, "crafting_table", 0);
+ }
+
+ public static void init() {
+ // no-op
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java
index b30031ebc..e45566264 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java
@@ -33,6 +33,7 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
+import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
@@ -45,7 +46,7 @@ import java.util.List;
public class BasicItemTranslator extends NbtItemStackTranslator {
@Override
- public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) {
+ public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) {
if (!itemTag.contains("display")) {
return;
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java
index e802f0174..41ee4fbca 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java
@@ -29,6 +29,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
+import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
import org.geysermc.connector.network.translators.item.ItemEntry;
@@ -41,7 +42,7 @@ import java.util.List;
public class BookPagesTranslator extends NbtItemStackTranslator {
@Override
- public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) {
+ public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) {
if (!itemTag.contains("pages")) {
return;
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java
index 3c5271ebf..979c5a205 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java
@@ -29,6 +29,7 @@ import com.github.steveice10.opennbt.tag.builtin.ByteTag;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
+import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
import org.geysermc.connector.network.translators.item.ItemEntry;
@@ -37,7 +38,7 @@ import org.geysermc.connector.network.translators.item.ItemEntry;
public class CrossbowTranslator extends NbtItemStackTranslator {
@Override
- public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) {
+ public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) {
if (itemTag.get("ChargedProjectiles") != null) {
ListTag chargedProjectiles = itemTag.get("ChargedProjectiles");
if (!chargedProjectiles.getValue().isEmpty()) {
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantedBookTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantedBookTranslator.java
index 63ac6c510..990d5a7ad 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantedBookTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantedBookTranslator.java
@@ -28,6 +28,7 @@ package org.geysermc.connector.network.translators.item.translators.nbt;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
+import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
import org.geysermc.connector.network.translators.item.ItemEntry;
@@ -36,7 +37,7 @@ import org.geysermc.connector.network.translators.item.ItemEntry;
public class EnchantedBookTranslator extends NbtItemStackTranslator {
@Override
- public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) {
+ public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) {
if (!itemTag.contains("StoredEnchantments")) {
return;
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantmentTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantmentTranslator.java
index 6bd9cc77e..6884c00ba 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantmentTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantmentTranslator.java
@@ -27,6 +27,7 @@ package org.geysermc.connector.network.translators.item.translators.nbt;
import com.github.steveice10.opennbt.tag.builtin.*;
import org.geysermc.connector.GeyserConnector;
+import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
import org.geysermc.connector.network.translators.item.Enchantment;
@@ -40,7 +41,7 @@ import java.util.Map;
public class EnchantmentTranslator extends NbtItemStackTranslator {
@Override
- public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) {
+ public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) {
List newTags = new ArrayList<>();
if (itemTag.contains("Enchantments")) {
ListTag enchantmentTag = itemTag.get("Enchantments");
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java
index 0e23169f8..3b453ea18 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java
@@ -26,6 +26,7 @@
package org.geysermc.connector.network.translators.item.translators.nbt;
import com.github.steveice10.opennbt.tag.builtin.*;
+import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
@@ -36,7 +37,7 @@ import org.geysermc.connector.utils.MathUtils;
public class FireworkTranslator extends NbtItemStackTranslator {
@Override
- public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) {
+ public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) {
if (!itemTag.contains("Fireworks")) {
return;
}
@@ -106,6 +107,9 @@ public class FireworkTranslator extends NbtItemStackTranslator {
fireworks.put(new ByteTag("Flight", MathUtils.convertByte(fireworks.get("Flight").getValue())));
}
+ if (!itemTag.contains("Explosions")) {
+ return;
+ }
ListTag explosions = fireworks.get("Explosions");
for (Tag effect : explosions.getValue()) {
CompoundTag effectData = (CompoundTag) effect;
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/LeatherArmorTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/LeatherArmorTranslator.java
index 9f864ccf4..93af3e709 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/LeatherArmorTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/LeatherArmorTranslator.java
@@ -27,6 +27,7 @@ package org.geysermc.connector.network.translators.item.translators.nbt;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.IntTag;
+import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
import org.geysermc.connector.network.translators.item.ItemEntry;
@@ -37,7 +38,7 @@ public class LeatherArmorTranslator extends NbtItemStackTranslator {
private static final String[] ITEMS = new String[]{"minecraft:leather_helmet", "minecraft:leather_chestplate", "minecraft:leather_leggings", "minecraft:leather_boots"};
@Override
- public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) {
+ public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) {
if (!itemTag.contains("display")) {
return;
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java
index 4a36880c9..c9b49efd4 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java
@@ -29,6 +29,7 @@ import com.github.steveice10.opennbt.tag.builtin.ByteTag;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.IntTag;
import com.github.steveice10.opennbt.tag.builtin.LongTag;
+import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
@@ -37,7 +38,7 @@ import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
public class MapItemTranslator extends NbtItemStackTranslator {
@Override
- public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) {
+ public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) {
IntTag mapId = itemTag.get("map");
if (mapId != null) {
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java
new file mode 100644
index 000000000..a9930f698
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.connector.network.translators.item.translators.nbt;
+
+import com.github.steveice10.opennbt.tag.builtin.*;
+import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.ItemRemapper;
+import org.geysermc.connector.network.translators.item.ItemEntry;
+import org.geysermc.connector.network.translators.item.ItemRegistry;
+import org.geysermc.connector.network.translators.item.ItemTranslator;
+import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
+
+@ItemRemapper
+public class ShulkerBoxItemTranslator extends NbtItemStackTranslator {
+
+ @Override
+ public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) {
+ if (!itemTag.contains("BlockEntityTag")) return; // Empty shulker box
+
+ CompoundTag blockEntityTag = itemTag.get("BlockEntityTag");
+ if (blockEntityTag.get("Items") == null) return;
+ ListTag itemsList = new ListTag("Items");
+ for (Tag item : (ListTag) blockEntityTag.get("Items")) {
+ CompoundTag itemData = (CompoundTag) item; // Information about the item
+ CompoundTag boxItemTag = new CompoundTag(""); // Final item tag to add to the list
+ boxItemTag.put(new ByteTag("Slot", ((ByteTag) itemData.get("Slot")).getValue()));
+ boxItemTag.put(new ByteTag("WasPickedUp", (byte) 0)); // ???
+
+ ItemEntry boxItemEntry = ItemRegistry.getItemEntry(((StringTag) itemData.get("id")).getValue());
+ String blockName = ItemRegistry.getBedrockIdentifer(boxItemEntry);
+
+ boxItemTag.put(new StringTag("Name", blockName));
+ boxItemTag.put(new ShortTag("Damage", (short) boxItemEntry.getBedrockData()));
+ boxItemTag.put(new ByteTag("Count", ((ByteTag) itemData.get("Count")).getValue()));
+ if (itemData.contains("tag")) {
+ // Only the display name is what we have interest in, so just translate that if relevant
+ CompoundTag displayTag = itemData.get("tag");
+ ItemTranslator.translateDisplayProperties(session, displayTag);
+ boxItemTag.put(displayTag);
+ }
+
+ itemsList.add(boxItemTag);
+ }
+ itemTag.put(itemsList);
+ // Don't actually bother with removing the block entity tag. Too risky to translate
+ // if the user is on creative and messing with a shulker box
+ //itemTag.remove("BlockEntityTag");
+ }
+
+ @Override
+ public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) {
+ if (itemTag.contains("Items")) { // Remove any extraneous Bedrock tag and don't touch the Java one
+ itemTag.remove("Items");
+ }
+ }
+
+ @Override
+ public boolean acceptItem(ItemEntry itemEntry) {
+ return itemEntry.getJavaIdentifier().contains("shulker_box");
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java
index 75ccc0a5a..9ffb4f0d9 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java
@@ -41,10 +41,7 @@ import lombok.EqualsAndHashCode;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
-import org.geysermc.connector.network.translators.item.ItemEntry;
-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.*;
import java.util.*;
import java.util.stream.Collectors;
@@ -83,6 +80,24 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator {
+
+ @Override
+ public void translate(ServerDisconnectPacket packet, GeyserSession session) {
+ session.disconnect(MessageUtils.getTranslatedBedrockMessage(packet.getReason(), session.getClientData().getLanguageCode(), true));
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerListEntryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerListEntryTranslator.java
index 10b2ba9ad..6a9ef4dc6 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerListEntryTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerListEntryTranslator.java
@@ -88,18 +88,13 @@ public class JavaPlayerListEntryTranslator extends PacketTranslator {
+ private static final GeyserLogger LOGGER = GeyserConnector.getInstance().getLogger();
@Override
public void translate(ServerTeamPacket packet, GeyserSession session) {
- GeyserConnector.getInstance().getLogger().debug("Team packet " + packet.getTeamName() + " " + packet.getAction() + " " + Arrays.toString(packet.getPlayers()));
+ if (LOGGER.isDebug()) {
+ LOGGER.debug("Team packet " + packet.getTeamName() + " " + packet.getAction() + " " + Arrays.toString(packet.getPlayers()));
+ }
+
+ int pps = session.getWorldCache().increaseAndGetScoreboardPacketsPerSecond();
Scoreboard scoreboard = session.getWorldCache().getScoreboard();
Team team = scoreboard.getTeam(packet.getTeamName());
@@ -54,42 +61,59 @@ public class JavaTeamTranslator extends PacketTranslator {
scoreboard.registerNewTeam(packet.getTeamName(), toPlayerSet(packet.getPlayers()))
.setName(MessageUtils.getBedrockMessage(packet.getDisplayName()))
.setColor(packet.getColor())
+ .setNameTagVisibility(packet.getNameTagVisibility())
.setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getClientData().getLanguageCode()))
.setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode()));
break;
case UPDATE:
- if (team != null) {
- team.setName(MessageUtils.getBedrockMessage(packet.getDisplayName()))
- .setColor(packet.getColor())
- .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getClientData().getLanguageCode()))
- .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode()))
- .setUpdateType(UpdateType.UPDATE);
- } else {
- GeyserConnector.getInstance().getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_not_registered", packet.getAction(), packet.getTeamName()));
+ if (team == null) {
+ LOGGER.debug(LanguageUtils.getLocaleStringLog(
+ "geyser.network.translator.team.failed_not_registered",
+ packet.getAction(), packet.getTeamName()
+ ));
+ return;
}
+
+ team.setName(MessageUtils.getBedrockMessage(packet.getDisplayName()))
+ .setColor(packet.getColor())
+ .setNameTagVisibility(packet.getNameTagVisibility())
+ .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getClientData().getLanguageCode()))
+ .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode()))
+ .setUpdateType(UpdateType.UPDATE);
break;
case ADD_PLAYER:
- if (team != null) {
- team.addEntities(packet.getPlayers());
- } else {
- GeyserConnector.getInstance().getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_not_registered", packet.getAction(), packet.getTeamName()));
+ if (team == null) {
+ LOGGER.debug(LanguageUtils.getLocaleStringLog(
+ "geyser.network.translator.team.failed_not_registered",
+ packet.getAction(), packet.getTeamName()
+ ));
+ return;
}
+ team.addEntities(packet.getPlayers());
break;
case REMOVE_PLAYER:
- if (team != null) {
- team.removeEntities(packet.getPlayers());
- } else {
- GeyserConnector.getInstance().getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_not_registered", packet.getAction(), packet.getTeamName()));
+ if (team == null) {
+ LOGGER.debug(LanguageUtils.getLocaleStringLog(
+ "geyser.network.translator.team.failed_not_registered",
+ packet.getAction(), packet.getTeamName()
+ ));
+ return;
}
+ team.removeEntities(packet.getPlayers());
break;
case REMOVE:
scoreboard.removeTeam(packet.getTeamName());
break;
}
- scoreboard.onUpdate();
+
+ // ScoreboardUpdater will handle it for us if the packets per second
+ // (for score and team packets) is higher then the first threshold
+ if (pps < ScoreboardUpdater.FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) {
+ scoreboard.onUpdate();
+ }
}
private Set toPlayerSet(String[] players) {
- return new ObjectOpenHashSet<>(Arrays.asList(players));
+ return new ObjectOpenHashSet<>(players);
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java
index 8d7d59a89..35033ca52 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java
@@ -25,48 +25,58 @@
package org.geysermc.connector.network.translators.java.scoreboard;
+import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardAction;
+import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerUpdateScorePacket;
import org.geysermc.connector.GeyserConnector;
+import org.geysermc.connector.GeyserLogger;
import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.session.cache.WorldCache;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.scoreboard.Objective;
import org.geysermc.connector.scoreboard.Scoreboard;
-
-import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardAction;
-import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerUpdateScorePacket;
+import org.geysermc.connector.scoreboard.ScoreboardUpdater;
import org.geysermc.connector.utils.LanguageUtils;
@Translator(packet = ServerUpdateScorePacket.class)
public class JavaUpdateScoreTranslator extends PacketTranslator {
+ private final GeyserLogger logger;
+
+ public JavaUpdateScoreTranslator() {
+ logger = GeyserConnector.getInstance().getLogger();
+ }
@Override
public void translate(ServerUpdateScorePacket packet, GeyserSession session) {
- try {
- Scoreboard scoreboard = session.getWorldCache().getScoreboard();
+ WorldCache worldCache = session.getWorldCache();
+ Scoreboard scoreboard = worldCache.getScoreboard();
+ int pps = worldCache.increaseAndGetScoreboardPacketsPerSecond();
- Objective objective = scoreboard.getObjective(packet.getObjective());
- if (objective == null && packet.getAction() != ScoreboardAction.REMOVE) {
- GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.translator.score.failed_objective", packet.getObjective()));
- return;
- }
+ Objective objective = scoreboard.getObjective(packet.getObjective());
+ if (objective == null && packet.getAction() != ScoreboardAction.REMOVE) {
+ logger.info(LanguageUtils.getLocaleStringLog("geyser.network.translator.score.failed_objective", packet.getObjective()));
+ return;
+ }
- switch (packet.getAction()) {
- case ADD_OR_UPDATE:
- objective.setScore(packet.getEntry(), packet.getValue());
- break;
- case REMOVE:
- if (objective != null) {
- objective.resetScore(packet.getEntry());
- } else {
- for (Objective objective1 : scoreboard.getObjectives().values()) {
- objective1.resetScore(packet.getEntry());
- }
+ switch (packet.getAction()) {
+ case ADD_OR_UPDATE:
+ objective.setScore(packet.getEntry(), packet.getValue());
+ break;
+ case REMOVE:
+ if (objective != null) {
+ objective.removeScore(packet.getEntry());
+ } else {
+ for (Objective objective1 : scoreboard.getObjectives().values()) {
+ objective1.removeScore(packet.getEntry());
}
- break;
- }
+ }
+ break;
+ }
+
+ // ScoreboardUpdater will handle it for us if the packets per second
+ // (for score and team packets) is higher then the first threshold
+ if (pps < ScoreboardUpdater.FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) {
scoreboard.onUpdate();
- } catch (Exception ex) {
- ex.printStackTrace();
}
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java
index 556d0eab3..6dae9b4d6 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java
@@ -25,41 +25,53 @@
package org.geysermc.connector.network.translators.java.world;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerChunkDataPacket;
import com.nukkitx.nbt.NBTOutputStream;
import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtUtils;
import com.nukkitx.network.VarInts;
import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket;
-
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.Unpooled;
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.BiomeTranslator;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
-import org.geysermc.connector.utils.ChunkUtils;
import org.geysermc.connector.network.translators.world.chunk.ChunkSection;
+import org.geysermc.connector.utils.ChunkUtils;
@Translator(packet = ServerChunkDataPacket.class)
public class JavaChunkDataTranslator extends PacketTranslator {
+ /**
+ * Determines if we should process non-full chunks
+ */
+ private final boolean isCacheChunks;
+
+ public JavaChunkDataTranslator() {
+ isCacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks();
+ }
+
@Override
public void translate(ServerChunkDataPacket packet, GeyserSession session) {
if (session.isSpawned()) {
ChunkUtils.updateChunkPosition(session, session.getPlayerEntity().getPosition().toInt());
}
- if (packet.getColumn().getBiomeData() == null) //Non-full chunk
+ if (packet.getColumn().getBiomeData() == null && !isCacheChunks) {
+ // Non-full chunk without chunk caching
+ session.getConnector().getLogger().debug("Not sending non-full chunk because chunk caching is off.");
return;
+ }
+
+ // Non-full chunks don't have all the chunk data, and Bedrock won't accept that
+ final boolean isNonFullChunk = (packet.getColumn().getBiomeData() == null);
GeyserConnector.getInstance().getGeneralThreadPool().execute(() -> {
try {
- ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(packet.getColumn());
+ ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(session, packet.getColumn(), isNonFullChunk);
ByteBuf byteBuf = Unpooled.buffer(32);
ChunkSection[] sections = chunkData.sections;
@@ -74,7 +86,12 @@ public class JavaChunkDataTranslator extends PacketTranslator blockEntityEntry : chunkData.getLoadBlockEntitiesLater().object2IntEntrySet()) {
- int x = blockEntityEntry.getKey().getInt("x");
- int y = blockEntityEntry.getKey().getInt("y");
- int z = blockEntityEntry.getKey().getInt("z");
- ChunkUtils.updateBlock(session, blockEntityEntry.getIntValue(), new Position(x, y, z));
- }
- chunkData.getLoadBlockEntitiesLater().clear();
session.getChunkCache().addToCache(packet.getColumn());
} catch (Exception ex) {
ex.printStackTrace();
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaCollectItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaCollectItemTranslator.java
index 31379bd2d..a90c7016b 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaCollectItemTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaCollectItemTranslator.java
@@ -41,6 +41,7 @@ public class JavaCollectItemTranslator extends PacketTranslator {
@@ -141,6 +142,11 @@ public class JavaNotifyClientTranslator extends PacketTranslator {
@@ -41,6 +42,7 @@ public class JavaSpawnPositionTranslator extends PacketTranslator= 2 &&
+ session.getGameMode() == GameMode.CREATIVE && packet.getNbt().size() > 5) {
+ ContainerOpenPacket openPacket = new ContainerOpenPacket();
+ openPacket.setBlockPosition(Vector3i.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ()));
+ openPacket.setId((byte) 1);
+ openPacket.setType(ContainerType.COMMAND_BLOCK);
+ openPacket.setUniqueEntityId(-1);
+ session.sendUpstreamPacket(openPacket);
+ }
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandlerRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandlerRegistry.java
index 893975e5e..163c451cb 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandlerRegistry.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandlerRegistry.java
@@ -25,6 +25,8 @@
package org.geysermc.connector.network.translators.sound;
+import org.geysermc.connector.GeyserConnector;
+import org.geysermc.connector.utils.FileUtils;
import org.reflections.Reflections;
import java.util.HashMap;
@@ -38,7 +40,7 @@ public class SoundHandlerRegistry {
static final Map> INTERACTION_HANDLERS = new HashMap<>();
static {
- Reflections ref = new Reflections("org.geysermc.connector.network.translators.sound");
+ Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.sound") : new Reflections("org.geysermc.connector.network.translators.sound");
for (Class> clazz : ref.getTypesAnnotatedWith(SoundHandler.class)) {
try {
SoundInteractionHandler> interactionHandler = (SoundInteractionHandler>) clazz.newInstance();
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java
index 83f1a7783..46aa6f7f6 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java
@@ -32,6 +32,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.world.chunk.ChunkPosition;
import org.geysermc.connector.utils.GameRule;
public class GeyserWorldManager extends WorldManager {
@@ -43,6 +44,13 @@ public class GeyserWorldManager extends WorldManager {
return session.getChunkCache().getBlockAt(new Position(x, y, z));
}
+ @Override
+ public int[] getBiomeDataAt(GeyserSession session, int x, int z) {
+ if (!session.getConnector().getConfig().isCacheChunks())
+ return new int[1024];
+ return session.getChunkCache().getChunks().get(new ChunkPosition(x, z)).getBiomeData();
+ }
+
@Override
public void setGameRule(GeyserSession session, String name, Object value) {
session.sendDownstreamPacket(new ClientChatPacket("/gamerule " + name + " " + value));
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java
index 326012277..ba78b8f6f 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java
@@ -74,6 +74,16 @@ public abstract class WorldManager {
*/
public abstract int getBlockAt(GeyserSession session, int x, int y, int z);
+ /**
+ * Gets the biome data for the specified chunk.
+ *
+ * @param session the session of the player
+ * @param x the chunk's X coordinate
+ * @param z the chunk's Z coordinate
+ * @return the biome data for the specified region with a length of 1024.
+ */
+ public abstract int[] getBiomeDataAt(GeyserSession session, int x, int z);
+
/**
* Updates a gamerule value on the Java server
*
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java
index 53607317a..305118e6f 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java
@@ -39,6 +39,7 @@ public class BlockStateValues {
private static final Int2IntMap BANNER_COLORS = new Int2IntOpenHashMap();
private static final Int2ByteMap BED_COLORS = new Int2ByteOpenHashMap();
+ private static final Int2ByteMap COMMAND_BLOCK_VALUES = new Int2ByteOpenHashMap();
private static final Int2ObjectMap DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>();
private static final Map FLOWER_POT_BLOCKS = new HashMap<>();
@@ -67,6 +68,11 @@ public class BlockStateValues {
return;
}
+ if (entry.getKey().contains("command_block")) {
+ COMMAND_BLOCK_VALUES.put(javaBlockState, entry.getKey().contains("conditional=true") ? (byte) 1 : (byte) 0);
+ return;
+ }
+
if (entry.getValue().get("double_chest_position") != null) {
boolean isX = (entry.getValue().get("x") != null);
boolean isDirectionPositive = ((entry.getValue().get("x") != null && entry.getValue().get("x").asBoolean()) ||
@@ -138,6 +144,16 @@ public class BlockStateValues {
return -1;
}
+ /**
+ * The block state in Java and Bedrock both contain the conditional bit, however command block block entity tags
+ * in Bedrock need the conditional information.
+ *
+ * @return the list of all command blocks and if they are conditional (1 or 0)
+ */
+ public static Int2ByteMap getCommandBlockValues() {
+ return COMMAND_BLOCK_VALUES;
+ }
+
/**
* All double chest values are part of the block state in Java and part of the block entity tag in Bedrock.
* This gives the DoubleChestValue that can be calculated into the final tag.
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java
index e627b8454..fc7c852cb 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java
@@ -28,15 +28,12 @@ package org.geysermc.connector.network.translators.world.block;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
-import com.nukkitx.nbt.NBTInputStream;
-import com.nukkitx.nbt.NbtList;
-import com.nukkitx.nbt.NbtMap;
-import com.nukkitx.nbt.NbtMapBuilder;
-import com.nukkitx.nbt.NbtType;
-import com.nukkitx.nbt.NbtUtils;
+import com.nukkitx.nbt.*;
import it.unimi.dsi.fastutil.ints.*;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.translators.world.block.entity.BlockEntity;
import org.geysermc.connector.utils.FileUtils;
@@ -52,6 +49,11 @@ public class BlockTranslator {
private static final Int2IntMap JAVA_TO_BEDROCK_BLOCK_MAP = new Int2IntOpenHashMap();
private static final Int2IntMap BEDROCK_TO_JAVA_BLOCK_MAP = new Int2IntOpenHashMap();
+ /**
+ * Stores a list of differences in block identifiers.
+ * Items will not be added to this list if the key and value is the same.
+ */
+ private static final Object2ObjectMap JAVA_TO_BEDROCK_IDENTIFIERS = new Object2ObjectOpenHashMap<>();
private static final BiMap JAVA_ID_BLOCK_MAP = HashBiMap.create();
private static final IntSet WATERLOGGED = new IntOpenHashSet();
private static final Object2IntMap ITEM_FRAMES = new Object2IntOpenHashMap<>();
@@ -65,6 +67,11 @@ public class BlockTranslator {
public static final Int2BooleanMap JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND = new Int2BooleanOpenHashMap();
public static final Int2ObjectMap JAVA_RUNTIME_ID_TO_TOOL_TYPE = new Int2ObjectOpenHashMap<>();
+ /**
+ * Runtime command block ID, used for fixing command block minecart appearances
+ */
+ public static final int BEDROCK_RUNTIME_COMMAND_BLOCK_ID;
+
// For block breaking animation math
public static final IntSet JAVA_RUNTIME_WOOL_IDS = new IntOpenHashSet();
public static final int JAVA_RUNTIME_COBWEB_ID;
@@ -106,13 +113,14 @@ public class BlockTranslator {
addedStatesMap.defaultReturnValue(-1);
List paletteList = new ArrayList<>();
- Reflections ref = new Reflections("org.geysermc.connector.network.translators.world.block.entity");
+ Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") : new Reflections("org.geysermc.connector.network.translators.world.block.entity");
ref.getTypesAnnotatedWith(BlockEntity.class);
int waterRuntimeId = -1;
int javaRuntimeId = -1;
int bedrockRuntimeId = 0;
int cobwebRuntimeId = -1;
+ int commandBlockRuntimeId = -1;
int furnaceRuntimeId = -1;
int furnaceLitRuntimeId = -1;
int spawnerRuntimeId = -1;
@@ -140,23 +148,15 @@ public class BlockTranslator {
JAVA_RUNTIME_ID_TO_TOOL_TYPE.put(javaRuntimeId, toolTypeNode.textValue());
}
- if (javaId.contains("wool")) {
- JAVA_RUNTIME_WOOL_IDS.add(javaRuntimeId);
- }
-
- if (javaId.contains("cobweb")) {
- cobwebRuntimeId = javaRuntimeId;
- }
-
JAVA_ID_BLOCK_MAP.put(javaId, javaRuntimeId);
// Used for adding all "special" Java block states to block state map
String identifier;
- String bedrock_identifer = entry.getValue().get("bedrock_identifier").asText();
+ String bedrockIdentifier = entry.getValue().get("bedrock_identifier").asText();
for (Class> clazz : ref.getTypesAnnotatedWith(BlockEntity.class)) {
identifier = clazz.getAnnotation(BlockEntity.class).regex();
// Endswith, or else the block bedrock gets picked up for bed
- if (bedrock_identifer.endsWith(identifier) && !identifier.equals("")) {
+ if (bedrockIdentifier.endsWith(identifier) && !identifier.equals("")) {
JAVA_ID_TO_BLOCK_ENTITY_MAP.put(javaRuntimeId, clazz.getAnnotation(BlockEntity.class).name());
break;
}
@@ -164,9 +164,15 @@ public class BlockTranslator {
BlockStateValues.storeBlockStateValues(entry, javaRuntimeId);
+ String cleanJavaIdentifier = entry.getKey().split("\\[")[0];
+
+ if (!cleanJavaIdentifier.equals(bedrockIdentifier)) {
+ JAVA_TO_BEDROCK_IDENTIFIERS.put(cleanJavaIdentifier, bedrockIdentifier);
+ }
+
// Get the tag needed for non-empty flower pots
if (entry.getValue().get("pottable") != null) {
- BlockStateValues.getFlowerPotBlocks().put(entry.getKey().split("\\[")[0], buildBedrockState(entry.getValue()));
+ BlockStateValues.getFlowerPotBlocks().put(cleanJavaIdentifier, buildBedrockState(entry.getValue()));
}
if ("minecraft:water[level=0]".equals(javaId)) {
@@ -197,15 +203,23 @@ public class BlockTranslator {
}
JAVA_TO_BEDROCK_BLOCK_MAP.put(javaRuntimeId, bedrockRuntimeId);
- if (javaId.startsWith("minecraft:furnace[facing=north")) {
+ if (javaId.contains("wool")) {
+ JAVA_RUNTIME_WOOL_IDS.add(javaRuntimeId);
+
+ } else if (javaId.contains("cobweb")) {
+ cobwebRuntimeId = javaRuntimeId;
+
+ } else if (javaId.equals("minecraft:command_block[conditional=false,facing=north]")) {
+ commandBlockRuntimeId = bedrockRuntimeId;
+
+ } else if (javaId.startsWith("minecraft:furnace[facing=north")) {
if (javaId.contains("lit=true")) {
furnaceLitRuntimeId = javaRuntimeId;
} else {
furnaceRuntimeId = javaRuntimeId;
}
- }
- if (javaId.startsWith("minecraft:spawner")) {
+ } else if (javaId.startsWith("minecraft:spawner")) {
spawnerRuntimeId = javaRuntimeId;
}
@@ -217,6 +231,11 @@ public class BlockTranslator {
}
JAVA_RUNTIME_COBWEB_ID = cobwebRuntimeId;
+ if (commandBlockRuntimeId == -1) {
+ throw new AssertionError("Unable to find command block in palette");
+ }
+ BEDROCK_RUNTIME_COMMAND_BLOCK_ID = commandBlockRuntimeId;
+
if (furnaceRuntimeId == -1) {
throw new AssertionError("Unable to find furnace in palette");
}
@@ -297,6 +316,14 @@ public class BlockTranslator {
return BEDROCK_TO_JAVA_BLOCK_MAP.get(bedrockId);
}
+ /**
+ * @param javaIdentifier the Java identifier of the block to search for
+ * @return the Bedrock identifier if different, or else the Java identifier
+ */
+ public static String getBedrockBlockIdentifier(String javaIdentifier) {
+ return JAVA_TO_BEDROCK_IDENTIFIERS.getOrDefault(javaIdentifier, javaIdentifier);
+ }
+
public static int getItemFrame(NbtMap tag) {
return ITEM_FRAMES.getOrDefault(tag, -1);
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java
index 9e86cb4cf..57393a6c5 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java
@@ -27,12 +27,9 @@ package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
-import com.nukkitx.nbt.NbtMap;
-import com.nukkitx.nbt.NbtType;
import org.geysermc.connector.network.translators.item.translators.BannerTranslator;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@@ -65,17 +62,4 @@ public class BannerBlockEntityTranslator extends BlockEntityTranslator implement
return tags;
}
- @Override
- public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) {
- CompoundTag tag = getConstantJavaTag(javaId, x, y, z);
- tag.put(new ListTag("Patterns"));
- return tag;
- }
-
- @Override
- public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) {
- return getConstantBedrockTag(bedrockId, x, y, z).toBuilder()
- .putList("Patterns", NbtType.COMPOUND, new ArrayList<>())
- .build();
- }
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java
index b84aad984..080bdc3b2 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java
@@ -26,7 +26,6 @@
package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
-import com.nukkitx.nbt.NbtMap;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
import java.util.HashMap;
@@ -50,15 +49,4 @@ public class BedBlockEntityTranslator extends BlockEntityTranslator implements R
return tags;
}
- @Override
- public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) {
- return null;
- }
-
- @Override
- public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) {
- return getConstantBedrockTag(bedrockId, x, y, z).toBuilder()
- .putByte("color", (byte) 0)
- .build();
- }
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java
index c4401c4c8..54f593a61 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java
@@ -28,12 +28,13 @@ package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.IntTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
+import com.github.steveice10.opennbt.tag.builtin.Tag;
import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtMapBuilder;
-
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.utils.BlockEntityUtils;
+import org.geysermc.connector.utils.FileUtils;
import org.geysermc.connector.utils.LanguageUtils;
import org.reflections.Reflections;
@@ -52,6 +53,7 @@ public abstract class BlockEntityTranslator {
{
// Bedrock/Java differences
put("minecraft:enchanting_table", "EnchantTable");
+ put("minecraft:jigsaw", "JigsawBlock");
put("minecraft:piston_head", "PistonArm");
put("minecraft:trapped_chest", "Chest");
// There are some legacy IDs sent but as far as I can tell they are not needed for things to work properly
@@ -66,7 +68,7 @@ public abstract class BlockEntityTranslator {
}
static {
- Reflections ref = new Reflections("org.geysermc.connector.network.translators.world.block.entity");
+ Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") : new Reflections("org.geysermc.connector.network.translators.world.block.entity");
for (Class> clazz : ref.getTypesAnnotatedWith(BlockEntity.class)) {
GeyserConnector.getInstance().getLogger().debug("Found annotated block entity: " + clazz.getCanonicalName());
@@ -89,10 +91,6 @@ public abstract class BlockEntityTranslator {
public abstract Map translateTag(CompoundTag tag, int blockState);
- public abstract CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z);
-
- public abstract NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z);
-
public NbtMap getBlockEntityTag(String id, CompoundTag tag, int blockState) {
int x = Integer.parseInt(String.valueOf(tag.getValue().get("x").getValue()));
int y = Integer.parseInt(String.valueOf(tag.getValue().get("y").getValue()));
@@ -123,7 +121,7 @@ public abstract class BlockEntityTranslator {
}
@SuppressWarnings("unchecked")
- protected T getOrDefault(com.github.steveice10.opennbt.tag.builtin.Tag tag, T defaultValue) {
+ protected T getOrDefault(Tag tag, T defaultValue) {
return (tag != null && tag.getValue() != null) ? (T) tag.getValue() : defaultValue;
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java
index e3d2c9f5e..d6ac0281b 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java
@@ -50,18 +50,6 @@ public class CampfireBlockEntityTranslator extends BlockEntityTranslator {
return tags;
}
- @Override
- public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) {
- CompoundTag tag = getConstantJavaTag(javaId, x, y, z);
- tag.put(new ListTag("Items"));
- return tag;
- }
-
- @Override
- public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) {
- return getConstantBedrockTag(bedrockId, x, y, z);
- }
-
protected NbtMap getItem(CompoundTag tag) {
ItemEntry entry = ItemRegistry.getItemEntry((String) tag.get("id").getValue());
NbtMapBuilder tagBuilder = NbtMap.builder()
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java
new file mode 100644
index 000000000..6bc940adb
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java
@@ -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.translators.world.block.entity;
+
+import com.github.steveice10.opennbt.tag.builtin.*;
+import org.geysermc.connector.network.translators.world.block.BlockStateValues;
+import org.geysermc.connector.utils.MessageUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@BlockEntity(name = "CommandBlock", regex = "command_block")
+public class CommandBlockBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState {
+
+ @Override
+ public Map translateTag(CompoundTag tag, int blockState) {
+ Map map = new HashMap<>();
+ if (tag.size() < 5) {
+ return map; // These values aren't here
+ }
+ // Java infers from the block state, but Bedrock needs it in the tag
+ map.put("conditionalMode", BlockStateValues.getCommandBlockValues().getOrDefault(blockState, (byte) 0));
+ // Java and Bedrock values
+ map.put("conditionMet", ((ByteTag) tag.get("conditionMet")).getValue());
+ map.put("auto", ((ByteTag) tag.get("auto")).getValue());
+ map.put("CustomName", MessageUtils.getBedrockMessage(((StringTag) tag.get("CustomName")).getValue()));
+ map.put("powered", ((ByteTag) tag.get("powered")).getValue());
+ map.put("Command", ((StringTag) tag.get("Command")).getValue());
+ map.put("SuccessCount", ((IntTag) tag.get("SuccessCount")).getValue());
+ map.put("TrackOutput", ((ByteTag) tag.get("TrackOutput")).getValue());
+ map.put("UpdateLastExecution", ((ByteTag) tag.get("UpdateLastExecution")).getValue());
+ if (tag.get("LastExecution") != null) {
+ map.put("LastExecution", ((LongTag) tag.get("LastExecution")).getValue());
+ } else {
+ map.put("LastExecution", (long) 0);
+ }
+ return map;
+ }
+
+ @Override
+ public boolean isBlock(int blockState) {
+ return BlockStateValues.getCommandBlockValues().containsKey(blockState);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java
index fa8bab3b0..5b59420e0 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java
@@ -27,7 +27,6 @@ package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.math.vector.Vector3i;
-import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtMapBuilder;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
@@ -92,13 +91,4 @@ public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator impl
return tags;
}
- @Override
- public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) {
- return null;
- }
-
- @Override
- public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) {
- return null;
- }
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java
index 6de136119..e9715bd32 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java
@@ -26,7 +26,6 @@
package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
-import com.nukkitx.nbt.NbtMap;
import java.util.HashMap;
import java.util.Map;
@@ -39,13 +38,4 @@ public class EmptyBlockEntityTranslator extends BlockEntityTranslator {
return new HashMap<>();
}
- @Override
- public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) {
- return getConstantJavaTag(javaId, x, y, z);
- }
-
- @Override
- public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) {
- return getConstantBedrockTag(bedrockId, x, y, z);
- }
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java
index 784afed5b..af94c560d 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java
@@ -26,14 +26,12 @@
package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
-import com.github.steveice10.opennbt.tag.builtin.LongTag;
+import com.github.steveice10.opennbt.tag.builtin.IntTag;
import com.nukkitx.nbt.NbtList;
-import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtType;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -56,25 +54,11 @@ public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator {
return tags;
}
- @Override
- public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) {
- CompoundTag tag = getConstantJavaTag(javaId, x, y, z);
- tag.put(new LongTag("Age"));
- return tag;
- }
-
- @Override
- public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) {
- return getConstantBedrockTag(bedrockId, x, y, z).toBuilder()
- .putList("ExitPortal", NbtType.INT, Arrays.asList(0, 0, 0))
- .build();
- }
-
private int getExitPortalCoordinate(CompoundTag tag, String axis) {
// Return 0 if it doesn't exist, otherwise give proper value
if (tag.get("ExitPortal") != null) {
LinkedHashMap, ?> compoundTag = (LinkedHashMap, ?>) tag.get("ExitPortal").getValue();
- com.github.steveice10.opennbt.tag.builtin.IntTag intTag = (com.github.steveice10.opennbt.tag.builtin.IntTag) compoundTag.get(axis);
+ IntTag intTag = (IntTag) compoundTag.get(axis);
return intTag.getValue();
} return 0;
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java
new file mode 100644
index 000000000..43ac1a96f
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.connector.network.translators.world.block.entity;
+
+import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
+import com.github.steveice10.opennbt.tag.builtin.StringTag;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@BlockEntity(name = "JigsawBlock", regex = "jigsaw")
+public class JigsawBlockBlockEntityTranslator extends BlockEntityTranslator {
+
+ @Override
+ public Map translateTag(CompoundTag tag, int blockState) {
+ Map map = new HashMap<>();
+ map.put("joint", ((StringTag) tag.get("joint")).getValue());
+ map.put("name", ((StringTag) tag.get("name")).getValue());
+ map.put("target_pool", ((StringTag) tag.get("pool")).getValue());
+ map.put("final_state", ((StringTag) tag.get("final_state")).getValue());
+ map.put("target", ((StringTag) tag.get("target")).getValue());
+ return map;
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java
index 08a7ae187..08e3abaab 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java
@@ -26,7 +26,6 @@
package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
-import com.nukkitx.nbt.NbtMap;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
import java.util.HashMap;
@@ -46,15 +45,4 @@ public class ShulkerBoxBlockEntityTranslator extends BlockEntityTranslator {
return tags;
}
- @Override
- public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) {
- return null;
- }
-
- @Override
- public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) {
- return getConstantBedrockTag(bedrockId, x, y, z).toBuilder()
- .putByte("facing", (byte) 1)
- .build();
- }
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java
index a95c853e7..b40ed42c9 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java
@@ -27,8 +27,8 @@ package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.mc.protocol.data.message.MessageSerializer;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
-import com.nukkitx.nbt.NbtMap;
import org.geysermc.connector.utils.MessageUtils;
+import org.geysermc.connector.utils.SignUtils;
import java.util.HashMap;
import java.util.Map;
@@ -46,12 +46,25 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator {
String signLine = getOrDefault(tag.getValue().get("Text" + currentLine), "");
signLine = MessageUtils.getBedrockMessage(MessageSerializer.fromString(signLine));
- //Java allows up to 16+ characters on certain symbols.
- if(signLine.length() >= 15 && (signLine.contains("-") || signLine.contains("="))) {
- signLine = signLine.substring(0, 14);
+ // Check the character width on the sign to ensure there is no overflow that is usually hidden
+ // to Java Edition clients but will appear to Bedrock clients
+ int signWidth = 0;
+ StringBuilder finalSignLine = new StringBuilder();
+ for (char c : signLine.toCharArray()) {
+ signWidth += SignUtils.getCharacterWidth(c);
+ if (signWidth <= SignUtils.BEDROCK_CHARACTER_WIDTH_MAX) {
+ finalSignLine.append(c);
+ } else {
+ break;
+ }
}
- signText.append(signLine);
+ // Java Edition 1.14 added the ability to change the text color of the whole sign using dye
+ if (tag.contains("Color")) {
+ signText.append(getBedrockSignColor(tag.get("Color").getValue().toString()));
+ }
+
+ signText.append(finalSignLine.toString());
signText.append("\n");
}
@@ -59,20 +72,65 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator {
return tags;
}
- @Override
- public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) {
- CompoundTag tag = getConstantJavaTag(javaId, x, y, z);
- tag.put(new com.github.steveice10.opennbt.tag.builtin.StringTag("Text1", "{\"text\":\"\"}"));
- tag.put(new com.github.steveice10.opennbt.tag.builtin.StringTag("Text2", "{\"text\":\"\"}"));
- tag.put(new com.github.steveice10.opennbt.tag.builtin.StringTag("Text3", "{\"text\":\"\"}"));
- tag.put(new com.github.steveice10.opennbt.tag.builtin.StringTag("Text4", "{\"text\":\"\"}"));
- return tag;
+ /**
+ * Maps a color stored in a sign's Color tag to a Bedrock Edition formatting code.
+ *
+ * The color names correspond to dye names, because of this we can't use {@link MessageUtils#getColor(String)}.
+ *
+ * @param javaColor The dye color stored in the sign's Color tag.
+ * @return A Bedrock Edition formatting code for valid dye colors, otherwise an empty string.
+ */
+ private static String getBedrockSignColor(String javaColor) {
+ String base = "\u00a7";
+ switch (javaColor) {
+ case "white":
+ base += 'f';
+ break;
+ case "orange":
+ base += '6';
+ break;
+ case "magenta":
+ case "purple":
+ base += '5';
+ break;
+ case "light_blue":
+ base += 'b';
+ break;
+ case "yellow":
+ base += 'e';
+ break;
+ case "lime":
+ base += 'a';
+ break;
+ case "pink":
+ base += 'd';
+ break;
+ case "gray":
+ base += '8';
+ break;
+ case "light_gray":
+ base += '7';
+ break;
+ case "cyan":
+ base += '3';
+ break;
+ case "blue":
+ base += '9';
+ break;
+ case "brown": // Brown does not have a bedrock counterpart.
+ case "red": // In Java Edition light red (&c) can only be applied using commands. Red dye gives &4.
+ base += '4';
+ break;
+ case "green":
+ base += '2';
+ break;
+ case "black":
+ base += '0';
+ break;
+ default:
+ return "";
+ }
+ return base;
}
- @Override
- public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) {
- return getConstantBedrockTag(bedrockId, x, y, z).toBuilder()
- .putString("Text", "")
- .build();
- }
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java
index 9547ba2ff..6d350c0cc 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java
@@ -25,7 +25,6 @@
package org.geysermc.connector.network.translators.world.block.entity;
-import com.nukkitx.nbt.NbtMap;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
import java.util.HashMap;
@@ -51,16 +50,4 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements
return tags;
}
- @Override
- public com.github.steveice10.opennbt.tag.builtin.CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) {
- return null;
- }
-
- @Override
- public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) {
- return getConstantBedrockTag(bedrockId, x, y, z).toBuilder()
- .putFloat("Rotation", 0f)
- .putByte("SkullType", (byte) 0)
- .build();
- }
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java
index 3c443eeeb..2601e3de9 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java
@@ -26,7 +26,6 @@
package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
-import com.nukkitx.nbt.NbtMap;
import org.geysermc.connector.entity.type.EntityType;
import java.util.HashMap;
@@ -86,16 +85,4 @@ public class SpawnerBlockEntityTranslator extends BlockEntityTranslator {
return tags;
}
- @Override
- public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) {
- return null;
- }
-
- @Override
- public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) {
- return getConstantBedrockTag(bedrockId, x, y, z).toBuilder()
- .putByte("isMovable", (byte) 1)
- .putString("id", "MobSpawner")
- .build();
- }
}
diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java
index c3e6c863c..92a1add34 100644
--- a/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java
+++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java
@@ -29,23 +29,23 @@ import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition
import lombok.Getter;
import lombok.Setter;
-import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
@Getter
public class Objective {
- private Scoreboard scoreboard;
- private long id;
- private boolean temp;
+ private final Scoreboard scoreboard;
+ private final long id;
+ private boolean active = true;
@Setter
private UpdateType updateType = UpdateType.ADD;
private String objectiveName;
- private String displaySlot;
+ private String displaySlotName;
private String displayName = "unknown";
private int type = 0; // 0 = integer, 1 = heart
- private Map scores = new HashMap<>();
+ private Map scores = new ConcurrentHashMap<>();
private Objective(Scoreboard scoreboard) {
this.id = scoreboard.getNextId().getAndIncrement();
@@ -54,23 +54,20 @@ public class Objective {
/**
* /!\ This method is made for temporary objectives until the real objective is received
- * @param scoreboard the scoreboard
+ *
+ * @param scoreboard the scoreboard
* @param objectiveName the name of the objective
*/
public Objective(Scoreboard scoreboard, String objectiveName) {
this(scoreboard);
this.objectiveName = objectiveName;
- this.temp = true;
+ this.active = false;
}
public Objective(Scoreboard scoreboard, String objectiveName, ScoreboardPosition displaySlot, String displayName, int type) {
- this(scoreboard, objectiveName, displaySlot.name().toLowerCase(), displayName, type);
- }
-
- public Objective(Scoreboard scoreboard, String objectiveName, String displaySlot, String displayName, int type) {
this(scoreboard);
this.objectiveName = objectiveName;
- this.displaySlot = displaySlot;
+ this.displaySlotName = translateDisplaySlot(displaySlot);
this.displayName = displayName;
this.type = type;
}
@@ -87,21 +84,9 @@ public class Objective {
public void setScore(String id, int score) {
if (scores.containsKey(id)) {
scores.get(id).setScore(score).setUpdateType(UpdateType.ADD);
- } else {
- registerScore(id, score);
+ return;
}
- }
-
- public void setScoreText(String oldText, String newText) {
- if (!scores.containsKey(oldText) || oldText.equals(newText)) return;
- Score oldScore = scores.get(oldText);
-
- Score newScore = new Score(this, newText)
- .setScore(oldScore.getScore())
- .setTeam(scoreboard.getTeamFor(newText));
-
- scores.put(newText, newScore);
- oldScore.setUpdateType(UpdateType.REMOVE);
+ registerScore(id, score);
}
public int getScore(String id) {
@@ -113,37 +98,61 @@ public class Objective {
public Score getScore(int line) {
for (Score score : scores.values()) {
- if (score.getScore() == line) return score;
+ if (score.getScore() == line) {
+ return score;
+ }
}
return null;
}
- public void resetScore(String id) {
+ public void removeScore(String id) {
if (scores.containsKey(id)) {
scores.get(id).setUpdateType(UpdateType.REMOVE);
}
}
- public void removeScore(String id) {
+ /**
+ * Used internally to remove a score from the score map
+ */
+ public void removeScore0(String id) {
scores.remove(id);
}
public Objective setDisplayName(String displayName) {
this.displayName = displayName;
- if (updateType == UpdateType.NOTHING) updateType = UpdateType.UPDATE;
+ if (updateType == UpdateType.NOTHING) {
+ updateType = UpdateType.UPDATE;
+ }
return this;
}
public Objective setType(int type) {
this.type = type;
- if (updateType == UpdateType.NOTHING) updateType = UpdateType.UPDATE;
+ if (updateType == UpdateType.NOTHING) {
+ updateType = UpdateType.UPDATE;
+ }
return this;
}
- public void removeTemp(ScoreboardPosition displaySlot) {
- if (temp) {
- temp = false;
- this.displaySlot = displaySlot.name().toLowerCase();
+ public void setActive(ScoreboardPosition displaySlot) {
+ if (!active) {
+ active = true;
+ displaySlotName = translateDisplaySlot(displaySlot);
+ }
+ }
+
+ public void removed() {
+ scores = null;
+ }
+
+ private static String translateDisplaySlot(ScoreboardPosition displaySlot) {
+ switch (displaySlot) {
+ case BELOW_NAME:
+ return "belowname";
+ case PLAYER_LIST:
+ return "list";
+ default:
+ return "sidebar";
}
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java
index d5a65e8c0..635bafa3d 100644
--- a/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java
+++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java
@@ -25,40 +25,63 @@
package org.geysermc.connector.scoreboard;
+import com.nukkitx.protocol.bedrock.data.ScoreInfo;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
-@Getter @Setter
+@Getter
@Accessors(chain = true)
public class Score {
- private Objective objective;
- private long id;
+ private final Objective objective;
+ private ScoreInfo cachedInfo;
+ private final long id;
+ @Setter
private UpdateType updateType = UpdateType.ADD;
- private String name;
+ private final String name;
private Team team;
private int score;
+ @Setter
private int oldScore = Integer.MIN_VALUE;
public Score(Objective objective, String name) {
this.id = objective.getScoreboard().getNextId().getAndIncrement();
this.objective = objective;
this.name = name;
+ update();
}
public String getDisplayName() {
- if (team != null && team.getUpdateType() != UpdateType.REMOVE) {
+ if (team != null) {
return team.getPrefix() + name + team.getSuffix();
}
return name;
}
public Score setScore(int score) {
- if (oldScore == Integer.MIN_VALUE) {
- this.oldScore = score;
- }
this.score = score;
+ updateType = UpdateType.UPDATE;
return this;
}
+
+ public Score setTeam(Team team) {
+ if (this.team != null && team != null) {
+ if (!this.team.equals(team)) {
+ this.team = team;
+ updateType = UpdateType.UPDATE;
+ }
+ return this;
+ }
+ // simplified from (this.team != null && team == null) || (this.team == null && team != null)
+ if (this.team != null || team != null) {
+ this.team = team;
+ updateType = UpdateType.UPDATE;
+ }
+ return this;
+ }
+
+ public void update() {
+ cachedInfo = new ScoreInfo(id, objective.getObjectiveName(), score, getDisplayName());
+ }
}
diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java
index 5fdda617f..9f89d9d2b 100644
--- a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java
+++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java
@@ -26,73 +26,72 @@
package org.geysermc.connector.scoreboard;
import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition;
+import com.nukkitx.protocol.bedrock.BedrockPacket;
import com.nukkitx.protocol.bedrock.data.ScoreInfo;
import com.nukkitx.protocol.bedrock.packet.RemoveObjectivePacket;
import com.nukkitx.protocol.bedrock.packet.SetDisplayObjectivePacket;
import com.nukkitx.protocol.bedrock.packet.SetScorePacket;
-import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import lombok.Getter;
-
+import org.geysermc.connector.GeyserConnector;
+import org.geysermc.connector.GeyserLogger;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.utils.LanguageUtils;
import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import static org.geysermc.connector.scoreboard.UpdateType.*;
@Getter
public class Scoreboard {
- private GeyserSession session;
- private AtomicLong nextId = new AtomicLong(0);
+ private final GeyserSession session;
+ private final GeyserLogger logger;
+ private final AtomicLong nextId = new AtomicLong(0);
- private Map objectives = new HashMap<>();
- private Map teams = new HashMap<>();
+ private final Map objectives = new ConcurrentHashMap<>();
+ private final Map teams = new HashMap<>();
+
+ private int lastScoreCount = 0;
public Scoreboard(GeyserSession session) {
this.session = session;
+ this.logger = GeyserConnector.getInstance().getLogger();
}
- public Objective registerNewObjective(String objectiveId, boolean temp) {
- if (!temp || objectives.containsKey(objectiveId)) return objectives.get(objectiveId);
+ public Objective registerNewObjective(String objectiveId, boolean active) {
+ if (active || objectives.containsKey(objectiveId)) {
+ return objectives.get(objectiveId);
+ }
Objective objective = new Objective(this, objectiveId);
objectives.put(objectiveId, objective);
return objective;
}
public Objective registerNewObjective(String objectiveId, ScoreboardPosition displaySlot) {
- Objective objective = null;
- if (objectives.containsKey(objectiveId)) {
- objective = objectives.get(objectiveId);
- if (objective.isTemp()) objective.removeTemp(displaySlot);
- else {
- despawnObjective(objective);
- objective = null;
+ Objective objective = objectives.get(objectiveId);
+ if (objective != null) {
+ if (!objective.isActive()) {
+ objective.setActive(displaySlot);
+ return objective;
}
+ despawnObjective(objective);
}
- if (objective == null) {
- objective = new Objective(this, objectiveId, displaySlot, "unknown", 0);
- objectives.put(objectiveId, objective);
- }
+
+ objective = new Objective(this, objectiveId, displaySlot, "unknown", 0);
+ objectives.put(objectiveId, objective);
return objective;
}
public Team registerNewTeam(String teamName, Set players) {
- if (teams.containsKey(teamName)) {
- session.getConnector().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_overrides", teamName));
- return getTeam(teamName);
+ Team team = teams.get(teamName);
+ if (team != null) {
+ logger.info(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_overrides", teamName));
+ return team;
}
- Team team = new Team(this, teamName).setEntities(players);
+ team = new Team(this, teamName).setEntities(players);
teams.put(teamName, team);
-
- for (Objective objective : objectives.values()) {
- for (Score score : objective.getScores().values()) {
- if (players.contains(score.getName())) {
- score.setTeam(team);
- }
- }
- }
return team;
}
@@ -106,102 +105,119 @@ public class Scoreboard {
public void unregisterObjective(String objectiveName) {
Objective objective = getObjective(objectiveName);
- if (objective != null) objective.setUpdateType(REMOVE);
+ if (objective != null) {
+ objective.setUpdateType(REMOVE);
+ }
}
public void removeTeam(String teamName) {
Team remove = teams.remove(teamName);
- if (remove != null) remove.setUpdateType(REMOVE);
+ if (remove != null) {
+ remove.setUpdateType(REMOVE);
+ }
}
public void onUpdate() {
- Set changedObjectives = new ObjectOpenHashSet<>();
- List addScores = new ArrayList<>();
- List removeScores = new ArrayList<>();
+ List addScores = new ArrayList<>(getLastScoreCount());
+ List removeScores = new ArrayList<>(getLastScoreCount());
- for (String objectiveId : new ArrayList<>(objectives.keySet())) {
- Objective objective = objectives.get(objectiveId);
- if (objective.isTemp()) {
- session.getConnector().getLogger().debug("Ignoring temp Scoreboard Objective '"+ objectiveId +'\'');
+ for (Objective objective : objectives.values()) {
+ if (!objective.isActive()) {
+ logger.debug("Ignoring non-active Scoreboard Objective '"+ objective.getObjectiveName() +'\'');
continue;
}
- if (objective.getUpdateType() != NOTHING) changedObjectives.add(objective);
+ // hearts can't hold teams, so we treat them differently
+ if (objective.getType() == 1) {
+ for (Score score : objective.getScores().values()) {
+ if (score.getUpdateType() == NOTHING) {
+ continue;
+ }
+
+ boolean update = score.getUpdateType() == UPDATE;
+ if (update) {
+ score.update();
+ }
+
+ if (score.getUpdateType() == ADD || update) {
+ addScores.add(score.getCachedInfo());
+ }
+ if (score.getUpdateType() == REMOVE || update) {
+ removeScores.add(score.getCachedInfo());
+ }
+ }
+ continue;
+ }
boolean globalUpdate = objective.getUpdateType() == UPDATE;
- boolean globalAdd = objective.getUpdateType() == ADD || globalUpdate;
- boolean globalRemove = objective.getUpdateType() == REMOVE || globalUpdate;
+ boolean globalAdd = objective.getUpdateType() == ADD;
+ boolean globalRemove = objective.getUpdateType() == REMOVE;
- boolean hasUpdate = globalUpdate;
-
- List handledScores = new ArrayList<>();
- for (String identifier : new ObjectOpenHashSet<>(objective.getScores().keySet())) {
- Score score = objective.getScores().get(identifier);
+ for (Score score : objective.getScores().values()) {
Team team = score.getTeam();
- boolean inTeam = team != null && team.getEntities().contains(score.getName());
+ boolean add = globalAdd || globalUpdate;
+ boolean remove = globalRemove;
+ boolean teamChanged = false;
+ if (team != null) {
+ if (team.getUpdateType() == REMOVE || !team.hasEntity(score.getName())) {
+ score.setTeam(null);
+ teamChanged = true;
+ }
- boolean teamAdd = team != null && (team.getUpdateType() == ADD || team.getUpdateType() == UPDATE);
- boolean teamRemove = team != null && (team.getUpdateType() == REMOVE || team.getUpdateType() == UPDATE);
+ teamChanged |= team.getUpdateType() == UPDATE;
- if (team != null && (team.getUpdateType() == REMOVE || !inTeam)) score.setTeam(null);
-
- boolean add = (hasUpdate || globalAdd || teamAdd || teamRemove || score.getUpdateType() == ADD || score.getUpdateType() == UPDATE) && (score.getUpdateType() != REMOVE);
- boolean remove = hasUpdate || globalRemove || teamAdd || teamRemove || score.getUpdateType() == REMOVE || score.getUpdateType() == UPDATE;
-
- boolean updated = false;
- if (!hasUpdate) {
- updated = hasUpdate = add;
+ add |= team.getUpdateType() == ADD || team.getUpdateType() == UPDATE;
+ remove |= team.getUpdateType() == REMOVE;
}
- if (updated) {
- for (Score score1 : handledScores) {
- ScoreInfo scoreInfo = new ScoreInfo(score1.getId(), score1.getObjective().getObjectiveName(), score1.getScore(), score1.getDisplayName());
- addScores.add(scoreInfo);
- removeScores.add(scoreInfo);
- }
+ add |= score.getUpdateType() == ADD || score.getUpdateType() == UPDATE;
+ remove |= score.getUpdateType() == REMOVE;
+ if (score.getUpdateType() == REMOVE) {
+ add = false;
+ }
+
+ if (score.getUpdateType() == UPDATE || teamChanged) {
+ score.update();
}
if (add) {
- addScores.add(new ScoreInfo(score.getId(), score.getObjective().getObjectiveName(), score.getScore(), score.getDisplayName()));
+ addScores.add(score.getCachedInfo());
}
if (remove) {
- removeScores.add(new ScoreInfo(score.getId(), score.getObjective().getObjectiveName(), score.getOldScore(), score.getDisplayName()));
+ removeScores.add(score.getCachedInfo());
}
+ // score is pending to be updated, so we use the current score as the old score
score.setOldScore(score.getScore());
+ // score is pending to be removed, so we can remove it from the objective
if (score.getUpdateType() == REMOVE) {
- objective.removeScore(score.getName());
+ objective.removeScore0(score.getName());
}
- if (add || remove) {
- changedObjectives.add(objective);
- } else { // stays the same like before
- handledScores.add(score);
- }
score.setUpdateType(NOTHING);
}
- }
- for (Objective objective : changedObjectives) {
- boolean update = objective.getUpdateType() == NOTHING || objective.getUpdateType() == UPDATE;
- if (objective.getUpdateType() == REMOVE || update) {
+ if (globalRemove || globalUpdate) {
RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket();
removeObjectivePacket.setObjectiveId(objective.getObjectiveName());
session.sendUpstreamPacket(removeObjectivePacket);
if (objective.getUpdateType() == REMOVE) {
objectives.remove(objective.getObjectiveName()); // now we can deregister
+ objective.removed();
}
}
- if (objective.getUpdateType() == ADD || update) {
+
+ if (globalAdd || globalUpdate) {
SetDisplayObjectivePacket displayObjectivePacket = new SetDisplayObjectivePacket();
displayObjectivePacket.setObjectiveId(objective.getObjectiveName());
displayObjectivePacket.setDisplayName(objective.getDisplayName());
displayObjectivePacket.setCriteria("dummy");
- displayObjectivePacket.setDisplaySlot(objective.getDisplaySlot());
+ displayObjectivePacket.setDisplaySlot(objective.getDisplaySlotName());
displayObjectivePacket.setSortOrder(1); // ??
session.sendUpstreamPacket(displayObjectivePacket);
}
+
objective.setUpdateType(NOTHING);
}
@@ -218,6 +234,8 @@ public class Scoreboard {
setScorePacket.setInfos(addScores);
session.sendUpstreamPacket(setScorePacket);
}
+
+ lastScoreCount = addScores.size();
}
public void despawnObjective(Objective objective) {
@@ -234,6 +252,8 @@ public class Scoreboard {
0, ""
));
}
+
+ objective.removed();
if (!toRemove.isEmpty()) {
SetScorePacket setScorePacket = new SetScorePacket();
diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java b/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java
new file mode 100644
index 000000000..3812fb141
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.connector.scoreboard;
+
+import org.geysermc.connector.GeyserConnector;
+import org.geysermc.connector.configuration.GeyserConfiguration;
+import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.session.cache.WorldCache;
+import org.geysermc.connector.utils.LanguageUtils;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class ScoreboardUpdater extends Thread {
+ public static final int FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD;
+ public static final int SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD = 250;
+
+ private static final int FIRST_MILLIS_BETWEEN_UPDATES = 250; // 4 updates per second
+ private static final int SECOND_MILLIS_BETWEEN_UPDATES = 1000 * 3; // 1 update per 3 seconds
+
+ private static final boolean DEBUG_ENABLED;
+
+ private final WorldCache worldCache;
+ private final GeyserSession session;
+
+ private int millisBetweenUpdates = FIRST_MILLIS_BETWEEN_UPDATES;
+ private long lastUpdate = System.currentTimeMillis();
+ private long lastLog = -1;
+
+ private long lastPacketsPerSecondUpdate = System.currentTimeMillis();
+ private final AtomicInteger packetsPerSecond = new AtomicInteger(0);
+ private final AtomicInteger pendingPacketsPerSecond = new AtomicInteger(0);
+
+ public ScoreboardUpdater(WorldCache worldCache) {
+ super("Scoreboard Updater");
+ this.worldCache = worldCache;
+ session = worldCache.getSession();
+ }
+
+ @Override
+ public void run() {
+ if (!session.isClosed()) {
+ long currentTime = System.currentTimeMillis();
+
+ // reset score-packets per second every second
+ if (currentTime - lastPacketsPerSecondUpdate > 1000) {
+ lastPacketsPerSecondUpdate = currentTime;
+ packetsPerSecond.set(pendingPacketsPerSecond.get());
+ pendingPacketsPerSecond.set(0);
+ }
+
+ if (currentTime - lastUpdate > millisBetweenUpdates) {
+ lastUpdate = currentTime;
+
+ int pps = packetsPerSecond.get();
+ if (pps >= FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) {
+ boolean reachedSecondThreshold = pps >= SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD;
+ if (reachedSecondThreshold) {
+ millisBetweenUpdates = SECOND_MILLIS_BETWEEN_UPDATES;
+ } else {
+ millisBetweenUpdates = FIRST_MILLIS_BETWEEN_UPDATES;
+ }
+
+ worldCache.getScoreboard().onUpdate();
+
+ if (DEBUG_ENABLED && (currentTime - lastLog > 60000)) { // one minute
+ int threshold = reachedSecondThreshold ?
+ SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD :
+ FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD;
+
+ GeyserConnector.getInstance().getLogger().info(
+ LanguageUtils.getLocaleStringLog("geyser.scoreboard.updater.threshold_reached.log", session.getName(), threshold, pps) +
+ LanguageUtils.getLocaleStringLog("geyser.scoreboard.updater.threshold_reached", (millisBetweenUpdates / 1000.0))
+ );
+
+ lastLog = currentTime;
+ }
+ }
+ }
+
+ session.getConnector().getGeneralThreadPool().schedule(this, 50, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ public int getPacketsPerSecond() {
+ return packetsPerSecond.get();
+ }
+
+ /**
+ * Increase the Scoreboard Packets Per Second and return the updated value
+ */
+ public int incrementAndGetPacketsPerSecond() {
+ return pendingPacketsPerSecond.incrementAndGet();
+ }
+
+ static {
+ GeyserConfiguration config = GeyserConnector.getInstance().getConfig();
+ FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD = Math.min(config.getScoreboardPacketThreshold(), SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD);
+ DEBUG_ENABLED = config.isDebugMode();
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java
index c2fcc02c8..a073e2e99 100644
--- a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java
+++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java
@@ -25,6 +25,7 @@
package org.geysermc.connector.scoreboard;
+import com.github.steveice10.mc.protocol.data.game.scoreboard.NameTagVisibility;
import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import lombok.Getter;
@@ -35,8 +36,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Set;
-@Getter
-@Setter
+@Getter @Setter
@Accessors(chain = true)
public class Team {
private final Scoreboard scoreboard;
@@ -44,6 +44,8 @@ public class Team {
private UpdateType updateType = UpdateType.ADD;
private String name;
+
+ private NameTagVisibility nameTagVisibility;
private String prefix;
private TeamColor color;
private String suffix;
@@ -57,8 +59,7 @@ public class Team {
public void addEntities(String... names) {
List added = new ArrayList<>();
for (String name : names) {
- if (!entities.contains(name)) {
- entities.add(name);
+ if (entities.add(name)) {
added.add(name);
}
}
@@ -78,4 +79,35 @@ public class Team {
}
setUpdateType(UpdateType.UPDATE);
}
+
+ public boolean hasEntity(String name) {
+ return entities.contains(name);
+ }
+
+ public Team setPrefix(String prefix) {
+ // replace "null" to an empty string,
+ // we do this here to improve the performance of Score#getDisplayName
+ if (prefix.length() == 4 && "null".equals(prefix)) {
+ this.prefix = "";
+ return this;
+ }
+ this.prefix = prefix;
+ return this;
+ }
+
+ public Team setSuffix(String suffix) {
+ // replace "null" to an empty string,
+ // we do this here to improve the performance of Score#getDisplayName
+ if (suffix.length() == 4 && "null".equals(suffix)) {
+ this.suffix = "";
+ return this;
+ }
+ this.suffix = suffix;
+ return this;
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
}
diff --git a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java
index 06b400908..9413bbba5 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java
@@ -81,7 +81,7 @@ public class ChunkUtils {
}
}
- public static ChunkData translateToBedrock(Column column) {
+ public static ChunkData translateToBedrock(GeyserSession session, Column column, boolean isNonFullChunk) {
ChunkData chunkData = new ChunkData();
Chunk[] chunks = column.getChunks();
chunkData.sections = new ChunkSection[chunks.length];
@@ -97,14 +97,26 @@ public class ChunkUtils {
chunkData.sections[chunkY] = new ChunkSection();
Chunk chunk = chunks[chunkY];
- if (chunk == null || chunk.isEmpty())
+ // Chunk is null and caching chunks is off or this isn't a non-full chunk
+ if (chunk == null && (!session.getConnector().getConfig().isCacheChunks() || !isNonFullChunk))
+ continue;
+
+ // If chunk is empty then no need to process
+ if (chunk != null && chunk.isEmpty())
continue;
ChunkSection section = chunkData.sections[chunkY];
for (int x = 0; x < 16; x++) {
for (int y = 0; y < 16; y++) {
for (int z = 0; z < 16; z++) {
- int blockState = chunk.get(x, y, z);
+ int blockState;
+ // If a non-full chunk, then grab the block that should be here to create a 'full' chunk
+ if (chunk == null) {
+ Position pos = new ChunkPosition(column.getX(), column.getZ()).getBlock(x, (chunkY << 4) + y, z);
+ blockState = session.getConnector().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ());
+ } else {
+ blockState = chunk.get(x, y, z);
+ }
int id = BlockTranslator.getBedrockBlockId(blockState);
// Check to see if the name is in BlockTranslator.getBlockEntityString, and therefore must be handled differently
@@ -271,7 +283,5 @@ public class ChunkUtils {
@Getter
private NbtMap[] blockEntities = new NbtMap[0];
- @Getter
- private Object2IntMap loadBlockEntitiesLater = new Object2IntOpenHashMap<>();
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java
index de09ed8c6..7b283e9cb 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java
@@ -85,6 +85,10 @@ public class DimensionUtils {
stopSoundPacket.setStoppingAllSound(true);
stopSoundPacket.setSoundName("");
session.sendUpstreamPacket(stopSoundPacket);
+
+ // TODO - fix this hack of a fix by sending the final dimension switching logic after chunks have been sent.
+ // The client wants chunks sent to it before it can successfully respawn.
+ ChunkUtils.sendEmptyChunks(session, player.getPosition().toInt(), 3, true);
}
/**
diff --git a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java
index 38369d6c8..63255cfa0 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java
@@ -25,14 +25,22 @@
package org.geysermc.connector.utils;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import org.geysermc.connector.GeyserConnector;
+import org.reflections.Reflections;
+import org.reflections.serializers.XmlSerializer;
+import org.reflections.util.ConfigurationBuilder;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.Files;
+import java.security.MessageDigest;
import java.util.function.Function;
public class FileUtils {
@@ -51,6 +59,15 @@ public class FileUtils {
return objectMapper.readValue(src, valueType);
}
+ public static T loadYaml(InputStream src, Class valueType) throws IOException {
+ ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()).enable(JsonParser.Feature.IGNORE_UNDEFINED).disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+ return objectMapper.readValue(src, valueType);
+ }
+
+ public static T loadJson(InputStream src, Class valueType) throws IOException {
+ return GeyserConnector.JSON_MAPPER.readValue(src, valueType);
+ }
+
/**
* Open the specified file or copy if from resources
*
@@ -140,4 +157,38 @@ public class FileUtils {
}
return stream;
}
+
+ /**
+ * Calculate the SHA256 hash of the resource pack file
+ * @param file File to calculate the hash for
+ * @return A byte[] representation of the hash
+ */
+ public static byte[] calculateSHA256(File file) {
+ byte[] sha256;
+
+ try {
+ sha256 = MessageDigest.getInstance("SHA-256").digest(Files.readAllBytes(file.toPath()));
+ } catch (Exception e) {
+ throw new RuntimeException("Could not calculate pack hash", e);
+ }
+
+ return sha256;
+ }
+
+ /**
+ * Get the stored reflection data for a given path
+ *
+ * @param path The path to get the reflection data for
+ * @return The created Reflections object
+ */
+ public static Reflections getReflections(String path) {
+ Reflections reflections = new Reflections(new ConfigurationBuilder());
+ XmlSerializer serializer = new XmlSerializer();
+ URL resource = FileUtils.class.getClassLoader().getResource("META-INF/reflections/" + path + "-reflections.xml");
+ try (InputStream inputStream = resource.openConnection().getInputStream()) {
+ reflections.merge(serializer.read(inputStream));
+ } catch (IOException e) { }
+
+ return reflections;
+ }
}
diff --git a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java
index cb51e2f3b..c1224e6e2 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java
@@ -26,6 +26,9 @@
package org.geysermc.connector.utils;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
+import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
+import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCreativeInventoryActionPacket;
+import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientMoveItemToHotbarPacket;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtMapBuilder;
@@ -33,12 +36,14 @@ import com.nukkitx.nbt.NbtType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket;
-import org.geysermc.connector.common.ChatColor;
+import com.nukkitx.protocol.bedrock.packet.PlayerHotbarPacket;
import org.geysermc.connector.GeyserConnector;
+import org.geysermc.connector.common.ChatColor;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.DoubleChestInventoryTranslator;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
+import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.item.ItemTranslator;
@@ -151,4 +156,90 @@ public class InventoryUtils {
root.put("display", display.build());
return ItemData.of(ItemRegistry.ITEM_ENTRIES.get(ItemRegistry.BARRIER_INDEX).getBedrockId(), (short) 0, 1, root.build());
}
+
+ /**
+ * Attempt to find the specified item name in the session's inventory.
+ * If it is found and in the hotbar, set the user's held item to that slot.
+ * If it is found in another part of the inventory, move it.
+ * If it is not found and the user is in creative mode, create the item,
+ * overriding the current item slot if no other hotbar slots are empty, or otherwise selecting the empty slot.
+ *
+ * This attempts to mimic Java Edition behavior as best as it can.
+ * @param session the Bedrock client's session
+ * @param itemName the Java identifier of the item to search/select
+ */
+ public static void findOrCreatePickedBlock(GeyserSession session, String itemName) {
+ // Get the inventory to choose a slot to pick
+ Inventory inventory = session.getInventoryCache().getOpenInventory();
+ if (inventory == null) {
+ inventory = session.getInventory();
+ }
+
+ // Check hotbar for item
+ for (int i = 36; i < 45; i++) {
+ if (inventory.getItem(i) == null) {
+ continue;
+ }
+ ItemEntry item = ItemRegistry.getItem(inventory.getItem(i));
+ // If this isn't the item we're looking for
+ if (!item.getJavaIdentifier().equals(itemName)) {
+ continue;
+ }
+
+ setHotbarItem(session, i);
+ // Don't check inventory if item was in hotbar
+ return;
+ }
+
+ // Check inventory for item
+ for (int i = 9; i < 36; i++) {
+ if (inventory.getItem(i) == null) {
+ continue;
+ }
+ ItemEntry item = ItemRegistry.getItem(inventory.getItem(i));
+ // If this isn't the item we're looking for
+ if (!item.getJavaIdentifier().equals(itemName)) {
+ continue;
+ }
+
+ ClientMoveItemToHotbarPacket packetToSend = new ClientMoveItemToHotbarPacket(i); // https://wiki.vg/Protocol#Pick_Item
+ session.sendDownstreamPacket(packetToSend);
+ return;
+ }
+
+ // If we still have not found the item, and we're in creative, ask for the item from the server.
+ if (session.getGameMode() == GameMode.CREATIVE) {
+ int slot = session.getInventory().getHeldItemSlot() + 36;
+ if (session.getInventory().getItemInHand() != null) { // Otherwise we should just use the current slot
+ for (int i = 36; i < 45; i++) {
+ if (inventory.getItem(i) == null) {
+ slot = i;
+ break;
+ }
+ }
+ }
+
+ ClientCreativeInventoryActionPacket actionPacket = new ClientCreativeInventoryActionPacket(slot,
+ new ItemStack(ItemRegistry.getItemEntry(itemName).getJavaId()));
+ if ((slot - 36) != session.getInventory().getHeldItemSlot()) {
+ setHotbarItem(session, slot);
+ }
+ session.sendDownstreamPacket(actionPacket);
+ }
+ }
+
+ /**
+ * Changes the held item slot to the specified slot
+ * @param session GeyserSession
+ * @param slot inventory slot to be selected
+ */
+ private static void setHotbarItem(GeyserSession session, int slot) {
+ PlayerHotbarPacket hotbarPacket = new PlayerHotbarPacket();
+ hotbarPacket.setContainerId(0);
+ // Java inventory slot to hotbar slot ID
+ hotbarPacket.setSelectedHotbarSlot(slot - 36);
+ hotbarPacket.setSelectHotbarSlot(true);
+ session.sendUpstreamPacket(hotbarPacket);
+ // No need to send a Java packet as Bedrock sends a confirmation packet back that we translate
+ }
}
diff --git a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java
index 285846a97..d1d59490f 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java
@@ -133,7 +133,7 @@ public class LocaleUtils {
* @param locale Locale to download
*/
private static void downloadLocale(String locale) {
- File localeFile = Paths.get(GeyserConnector.getInstance().getBootstrap().getConfigFolder().toString(),"locales",locale + ".json").toFile();
+ File localeFile = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("locales/" + locale + ".json").toFile();
// Check if we have already downloaded the locale file
if (localeFile.exists()) {
@@ -150,7 +150,7 @@ public class LocaleUtils {
// Get the hash and download the locale
String hash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash();
- WebUtils.downloadFile("http://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, localeFile.toString());
+ WebUtils.downloadFile("https://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, localeFile.toString());
}
/**
@@ -246,6 +246,11 @@ public class LocaleUtils {
Map localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(locale.toLowerCase());
if (localeStrings == null)
localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(LanguageUtils.getDefaultLocale());
+ if (localeStrings == null) {
+ // Don't cause a NPE if the locale is STILL missing
+ GeyserConnector.getInstance().getLogger().debug("MISSING DEFAULT LOCALE: " + LanguageUtils.getDefaultLocale());
+ return messageText;
+ }
return localeStrings.getOrDefault(messageText, messageText);
}
diff --git a/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java b/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java
index 487024922..29dd2cc23 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java
@@ -40,6 +40,26 @@ public class MathUtils {
return floatNumber > truncated ? truncated + 1 : truncated;
}
+ /**
+ * If number is greater than the max, set it to max, and if number is lower than low, set it to low.
+ * @param num number to calculate
+ * @param min the lowest value the number can be
+ * @param max the greatest value the number can be
+ * @return - min if num is lower than min
+ * - max if num is greater than max
+ * - num otherwise
+ */
+ public static double constrain(double num, double min, double max) {
+ if (num > max) {
+ num = max;
+ }
+ if (num < min) {
+ num = min;
+ }
+
+ return num;
+ }
+
/**
* Converts the given object from an int or byte to byte.
* This is used for NBT data that might be either an int
diff --git a/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java b/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java
new file mode 100644
index 000000000..3e9848dbe
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.connector.utils;
+
+import org.geysermc.connector.GeyserConnector;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.zip.ZipFile;
+
+/**
+ * This represents a resource pack and all the data relevant to it
+ */
+public class ResourcePack {
+ /**
+ * The list of loaded resource packs
+ */
+ public static final Map PACKS = new HashMap<>();
+
+ /**
+ * The size of each chunk to use when sending the resource packs to clients in bytes
+ */
+ public static final int CHUNK_SIZE = 102400;
+
+ private byte[] sha256;
+ private File file;
+ private ResourcePackManifest manifest;
+ private ResourcePackManifest.Version version;
+
+ /**
+ * Loop through the packs directory and locate valid resource pack files
+ */
+ public static void loadPacks() {
+ File directory = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("packs").toFile();
+
+ if (!directory.exists()) {
+ directory.mkdir();
+
+ // As we just created the directory it will be empty
+ return;
+ }
+
+ for (File file : directory.listFiles()) {
+ if (file.getName().endsWith(".zip") || file.getName().endsWith(".mcpack")) {
+ ResourcePack pack = new ResourcePack();
+
+ pack.sha256 = FileUtils.calculateSHA256(file);
+
+ try {
+ ZipFile zip = new ZipFile(file);
+
+ zip.stream().forEach((x) -> {
+ if (x.getName().contains("manifest.json")) {
+ try {
+ ResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), ResourcePackManifest.class);
+
+ pack.file = file;
+ pack.manifest = manifest;
+ pack.version = ResourcePackManifest.Version.fromArray(manifest.getHeader().getVersion());
+
+ PACKS.put(pack.getManifest().getHeader().getUuid().toString(), pack);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ } catch (Exception e) {
+ GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.resource_pack.broken", file.getName()));
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ public byte[] getSha256() {
+ return sha256;
+ }
+
+ public File getFile() {
+ return file;
+ }
+
+ public ResourcePackManifest getManifest() {
+ return manifest;
+ }
+
+ public ResourcePackManifest.Version getVersion() {
+ return version;
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/utils/ResourcePackManifest.java b/connector/src/main/java/org/geysermc/connector/utils/ResourcePackManifest.java
new file mode 100644
index 000000000..6a08c4dbc
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/utils/ResourcePackManifest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.connector.utils;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import lombok.Value;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.UUID;
+
+/**
+ * author: NukkitX
+ * Nukkit Project
+ */
+@Getter
+@EqualsAndHashCode
+public class ResourcePackManifest {
+ @JsonProperty("format_version")
+ private Integer formatVersion;
+ private Header header;
+ private Collection modules;
+ protected Collection dependencies;
+
+ public Collection getModules() {
+ return Collections.unmodifiableCollection(modules);
+ }
+
+ @Getter
+ @ToString
+ public static class Header {
+ private String description;
+ private String name;
+ private UUID uuid;
+ private int[] version;
+ @JsonProperty("min_engine_version")
+ private int[] minimumSupportedMinecraftVersion;
+
+ public String getVersionString() {
+ return version[0] + "." + version[1] + "." + version[2];
+ }
+ }
+
+ @Getter
+ @ToString
+ public static class Module {
+ private String description;
+ private String name;
+ private UUID uuid;
+ private int[] version;
+ }
+
+ @Getter
+ @ToString
+ public static class Dependency {
+ private UUID uuid;
+ private int[] version;
+ }
+
+ @Value
+ public static class Version {
+ private final int major;
+ private final int minor;
+ private final int patch;
+
+ public static Version fromString(String ver) {
+ String[] split = ver.replace(']', ' ')
+ .replace('[', ' ')
+ .replaceAll(" ", "").split(",");
+
+ return new Version(Integer.parseInt(split[0]), Integer.parseInt(split[1]), Integer.parseInt(split[2]));
+ }
+
+ public static Version fromArray(int[] ver) {
+ return new Version(ver[0], ver[1], ver[2]);
+ }
+
+ private Version(int major, int minor, int patch) {
+ this.major = major;
+ this.minor = minor;
+ this.patch = patch;
+ }
+
+
+ @Override
+ public String toString() {
+ return major + "." + minor + "." + patch;
+ }
+ }
+}
+
diff --git a/connector/src/main/java/org/geysermc/connector/utils/SignUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SignUtils.java
new file mode 100644
index 000000000..06406b55e
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/utils/SignUtils.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.connector.utils;
+
+/**
+ * Provides utilities for interacting with signs. Mainly, it deals with the widths of each character.
+ * Since Bedrock auto-wraps signs and Java does not, we have to take this into account when translating signs.
+ */
+public class SignUtils {
+
+ // TODO: If we send the Java font via resource pack, does width change?
+ /**
+ * The maximum character width that a sign can hold in Bedrock
+ */
+ public static final int BEDROCK_CHARACTER_WIDTH_MAX = 88;
+
+ /**
+ * The maximum character width that a sign can hold in Java
+ */
+ public static final int JAVA_CHARACTER_WIDTH_MAX = 90;
+
+ /**
+ * Gets the Minecraft width of a character
+ * @param c character to determine
+ * @return width of the character
+ */
+ public static int getCharacterWidth(char c) {
+ switch (c) {
+ case '!':
+ case ',':
+ case '.':
+ case ':':
+ case ';':
+ case 'i':
+ case '|':
+ case '¡':
+ return 2;
+
+ case '\'':
+ case 'l':
+ case 'ì':
+ case 'í':
+ return 3;
+
+ case ' ':
+ case 'I':
+ case '[':
+ case ']':
+ case 't':
+ case '×':
+ case 'ï':
+ return 4;
+
+ case '"':
+ case '(':
+ case ')':
+ case '*':
+ case '<':
+ case '>':
+ case 'f':
+ case 'k':
+ case '{':
+ case '}':
+ return 5;
+
+ case '@':
+ case '~':
+ case '®':
+ return 7;
+
+ default:
+ return 6;
+ }
+ }
+
+}
diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java
index 9b3d737e8..13e6dcbb3 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java
@@ -119,7 +119,7 @@ public class SkinProvider {
// Schedule Daily Image Expiry if we are caching them
if (GeyserConnector.getInstance().getConfig().getCacheImages() > 0) {
GeyserConnector.getInstance().getGeneralThreadPool().scheduleAtFixedRate(() -> {
- File cacheFolder = Paths.get("cache", "images").toFile();
+ File cacheFolder = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("images").toFile();
if (!cacheFolder.exists()) {
return;
}
@@ -395,7 +395,7 @@ public class SkinProvider {
BufferedImage image = null;
// First see if we have a cached file. We also update the modification stamp so we know when the file was last used
- File imageFile = Paths.get("cache", "images", UUID.nameUUIDFromBytes(imageUrl.getBytes()).toString() + ".png").toFile();
+ File imageFile = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("images").resolve(UUID.nameUUIDFromBytes(imageUrl.getBytes()).toString() + ".png").toFile();
if (imageFile.exists()) {
try {
GeyserConnector.getInstance().getLogger().debug("Reading cached image from file " + imageFile.getPath() + " for " + imageUrl);
@@ -600,7 +600,7 @@ public class SkinProvider {
@Getter
public enum CapeProvider {
MINECRAFT,
- OPTIFINE("http://s.optifine.net/capes/%s.png", CapeUrlType.USERNAME),
+ OPTIFINE("https://optifine.net/capes/%s.png", CapeUrlType.USERNAME),
LABYMOD("https://www.labymod.net/page/php/getCapeTexture.php?uuid=%s", CapeUrlType.UUID_DASHED),
FIVEZIG("https://textures.5zigreborn.eu/profile/%s", CapeUrlType.UUID_DASHED),
MINECRAFTCAPES("https://minecraftcapes.net/profile/%s/cape", CapeUrlType.UUID);
diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java
index f0b88c435..fe2a8aa96 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java
@@ -150,14 +150,14 @@ public class SkinUtils {
JsonNode textures = skinObject.get("textures");
JsonNode skinTexture = textures.get("SKIN");
- String skinUrl = skinTexture.get("url").asText();
+ String skinUrl = skinTexture.get("url").asText().replace("http://", "https://");
isAlex = skinTexture.has("metadata");
String capeUrl = null;
if (textures.has("CAPE")) {
JsonNode capeTexture = textures.get("CAPE");
- capeUrl = capeTexture.get("url").asText();
+ capeUrl = capeTexture.get("url").asText().replace("http://", "https://");
}
return new GameProfileData(skinUrl, capeUrl, isAlex);
@@ -270,7 +270,7 @@ public class SkinUtils {
GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.skin.bedrock.register", playerEntity.getUsername(), playerEntity.getUuid()));
try {
- byte[] skinBytes = com.github.steveice10.mc.auth.util.Base64.decode(clientData.getSkinData().getBytes("UTF-8"));
+ byte[] skinBytes = Base64.getDecoder().decode(clientData.getSkinData().getBytes("UTF-8"));
byte[] capeBytes = clientData.getCapeData();
byte[] geometryNameBytes = Base64.getDecoder().decode(clientData.getGeometryName().getBytes("UTF-8"));
diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml
index f59939b6a..0602bb546 100644
--- a/connector/src/main/resources/config.yml
+++ b/connector/src/main/resources/config.yml
@@ -13,13 +13,15 @@ bedrock:
address: 0.0.0.0
# The port that will listen for connections
port: 19132
- # Some hosting services change your Java port everytime you open the server, and require the same port to be used for Bedrock.
+ # Some hosting services change your Java port everytime you start the server and require the same port to be used for Bedrock.
# This option makes the Bedrock port the same as the Java port every time you start the server.
# This option is for the plugin version only.
clone-remote-port: false
- # The MOTD that will be broadcasted to Minecraft: Bedrock Edition clients. Irrelevant if "passthrough-motd" is set to true
+ # The MOTD that will be broadcasted to Minecraft: Bedrock Edition clients. This is irrelevant if "passthrough-motd" is set to true
motd1: "GeyserMC"
motd2: "Another GeyserMC forced host."
+ # The Server Name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu.
+ server-name: "Geyser"
remote:
# The IP address of the remote (Java Edition) server
# If it is "auto", for standalone version the remote address will be set to 127.0.0.1,
@@ -53,7 +55,7 @@ floodgate-key-file: public-key.pem
# Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients.
command-suggestions: true
-# The following three options enable "ping passthrough" -the MOTD, player count and/or protocol name gets retrieved from the Java server.
+# The following three options enable "ping passthrough" - the MOTD, player count and/or protocol name gets retrieved from the Java server.
# Relay the MOTD from the remote server to Bedrock players.
passthrough-motd: false
# Relay the protocol name (e.g. BungeeCord [X.X], Paper 1.X) - only really useful when using a custom protocol name!
@@ -110,6 +112,11 @@ cache-images: 0
# the end sky in the nether, but ultimately it's the only way for this feature to work.
above-bedrock-nether-building: false
+# Force clients to load all resource packs if there are any.
+# If set to false it allows the user to disconnect from the server if they don't
+# want to download the resource packs
+force-resource-packs: true
+
# bStats is a stat tracker that is entirely anonymous and tracks only basic information
# about Geyser, such as how many people are online, how many servers are using Geyser,
# what OS is being used, etc. You can learn more about bStats here: https://bstats.org/.
@@ -122,6 +129,12 @@ metrics:
# ADVANCED OPTIONS - DO NOT TOUCH UNLESS YOU KNOW WHAT YOU ARE DOING!
+# Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle
+# a lot of scoreboard packets per second can cause serious lag.
+# This option allows you to specify after how many Scoreboard packets per seconds
+# the Scoreboard updates will be limited to four updates per second.
+scoreboard-packet-threshold: 20
+
# Allow connections from ProxyPass and Waterdog.
# See https://www.spigotmc.org/wiki/firewall-guide/ for assistance - use UDP instead of TCP.
enable-proxy-connections: false
diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages
index 2641db5aa..93b2caed3 160000
--- a/connector/src/main/resources/languages
+++ b/connector/src/main/resources/languages
@@ -1 +1 @@
-Subproject commit 2641db5aa9100cdbe21b4493489e9be19092a600
+Subproject commit 93b2caed3c4ecd94b3c77a87f1b2304a7bf4f062
diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings
index 88678e69b..28a22d2ba 160000
--- a/connector/src/main/resources/mappings
+++ b/connector/src/main/resources/mappings
@@ -1 +1 @@
-Subproject commit 88678e69bf358cd562bd410a2459384aeb7ba482
+Subproject commit 28a22d2baad680f511bffc36d90d06bf626f0527