diff --git a/.gitignore b/.gitignore index 6e0e8f49..0af6ecd0 100644 --- a/.gitignore +++ b/.gitignore @@ -222,6 +222,7 @@ nbdist/ # End of https://www.gitignore.io/api/git,java,maven,eclipse,netbeans,jetbrains+all ### Geyser ### +run/ config.yml logs/ public-key.pem diff --git a/README.md b/README.md index f11b9bfb..874c79f3 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,14 @@ Geyser is a bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition, closing the gap from those wanting to play true cross-platform. +Geyser is an open collaboration project by [CubeCraft Games](https://cubecraft.net). + ## What is Geyser? Geyser is a proxy, bridging the gap between Minecraft: Bedrock Edition and Minecraft: Java Edition servers. The ultimate goal of this project is to allow Minecraft: Bedrock Edition users to join Minecraft: Java Edition servers as seamlessly as possible. **Please note, this project is still a work in progress and should not be used on production. Expect bugs!** +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.14.6(0) and Minecraft Java v1.15.2. ## Setting Up @@ -34,8 +38,6 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set - [ ] Cartography Table - [ ] Stonecutter - [ ] Villager Trading -- Sounds -- Block Particles - Some Entity Flags ## Compiling diff --git a/bootstrap/bukkit/pom.xml b/bootstrap/bukkit/pom.xml index fd2ecbf0..1f831d67 100644 --- a/bootstrap/bukkit/pom.xml +++ b/bootstrap/bukkit/pom.xml @@ -23,6 +23,12 @@ 1.14-R0.1-SNAPSHOT provided + + us.myles + viaversion + 3.0.0-SNAPSHOT + provided + ${outputName}-Bukkit @@ -33,6 +39,18 @@ + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + + org.geysermc.platform.bukkit.GeyserBukkitMain + + + + org.apache.maven.plugins maven-shade-plugin @@ -58,14 +76,6 @@ - - - *:* - - META-INF/* - - - com.google.code.gson:* diff --git a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitConfiguration.java b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitConfiguration.java index 5b8842b4..df98b408 100644 --- a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitConfiguration.java +++ b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitConfiguration.java @@ -25,8 +25,11 @@ package org.geysermc.platform.bukkit; +import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; -import org.geysermc.common.IGeyserConfiguration; +import org.bukkit.plugin.Plugin; +import org.geysermc.connector.FloodgateKeyLoader; +import org.geysermc.connector.GeyserConfiguration; import java.io.File; import java.nio.file.Path; @@ -34,7 +37,7 @@ import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; -public class GeyserBukkitConfiguration implements IGeyserConfiguration { +public class GeyserBukkitConfiguration implements GeyserConfiguration { private FileConfiguration config; private File dataFolder; @@ -45,6 +48,8 @@ public class GeyserBukkitConfiguration implements IGeyserConfiguration { private Map userAuthInfo = new HashMap<>(); + private Path floodgateKey; + public GeyserBukkitConfiguration(File dataFolder, FileConfiguration config) { this.dataFolder = dataFolder; this.config = config; @@ -61,6 +66,11 @@ public class GeyserBukkitConfiguration implements IGeyserConfiguration { } } + public void loadFloodgate(GeyserBukkitPlugin plugin) { + Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate-bukkit"); + floodgateKey = FloodgateKeyLoader.getKey(plugin.getGeyserLogger(), this, Paths.get(dataFolder.toString(), config.getString("floodgate-key-file", "public-key.pem")), floodgate, floodgate != null ? floodgate.getDataFolder().toPath() : null); + } + @Override public IBedrockConfiguration getBedrock() { return bedrockConfig; @@ -77,8 +87,28 @@ public class GeyserBukkitConfiguration implements IGeyserConfiguration { } @Override - public boolean isPingPassthrough() { - return config.getBoolean("ping-passthrough", false); + public boolean isCommandSuggestions() { + return config.getBoolean("command-suggestions", true); + } + + @Override + public boolean isPassthroughMotd() { + return config.getBoolean("passthrough-motd", false); + } + + @Override + public boolean isPassthroughPlayerCounts() { + return config.getBoolean("passthrough-player-counts", false); + } + + @Override + public boolean isLegacyPingPassthrough() { + return config.getBoolean("legacy-ping-passthrough", false); + } + + @Override + public int getPingPassthroughInterval() { + return config.getInt("ping-passthrough-interval", 3); } @Override @@ -101,6 +131,11 @@ public class GeyserBukkitConfiguration implements IGeyserConfiguration { return config.getBoolean("allow-third-party-capes", true); } + @Override + public boolean isAllowThirdPartyEars() { + return config.getBoolean("allow-third-party-ears", false); + } + @Override public String getDefaultLocale() { return config.getString("default-locale", "en_us"); @@ -108,7 +143,17 @@ public class GeyserBukkitConfiguration implements IGeyserConfiguration { @Override public Path getFloodgateKeyFile() { - return Paths.get(dataFolder.toString(), config.getString("floodgate-key-file", "public-key.pem")); + return floodgateKey; + } + + @Override + public boolean isCacheChunks() { + return true; // We override this as with Bukkit, we have direct access to the server implementation + } + + @Override + public boolean isAboveBedrockNetherBuilding() { + return config.getBoolean("above-bedrock-nether-building", false); } @Override @@ -188,4 +233,9 @@ public class GeyserBukkitConfiguration implements IGeyserConfiguration { return config.getString("metrics.uuid", "generateduuid"); } } + + @Override + public int getConfigVersion() { + return config.getInt("config-version", 0); + } } diff --git a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitLogger.java b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitLogger.java index 454ec9f6..08822568 100644 --- a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitLogger.java +++ b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitLogger.java @@ -27,13 +27,13 @@ package org.geysermc.platform.bukkit; import lombok.AllArgsConstructor; -import org.geysermc.common.logger.IGeyserLogger; +import org.geysermc.connector.GeyserLogger; import java.util.logging.Level; import java.util.logging.Logger; @AllArgsConstructor -public class GeyserBukkitLogger implements IGeyserLogger { +public class GeyserBukkitLogger implements GeyserLogger { private Logger logger; private boolean debugMode; diff --git a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitMain.java b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitMain.java new file mode 100644 index 00000000..b6da66c1 --- /dev/null +++ b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitMain.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.platform.bukkit; + +import org.geysermc.common.main.IGeyserMain; + +public class GeyserBukkitMain extends IGeyserMain { + + public static void main(String[] args) { + new GeyserBukkitMain().displayMessage(); + } + + public String getPluginType() { + return "Spigot or Paper (recommended)"; + } + + public String getPluginFolder() { + return "plugins"; + } +} diff --git a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitPingPassthrough.java b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitPingPassthrough.java new file mode 100644 index 00000000..812467be --- /dev/null +++ b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitPingPassthrough.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.platform.bukkit; + +import lombok.AllArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.server.ServerListPingEvent; +import org.bukkit.util.CachedServerIcon; +import org.geysermc.common.ping.GeyserPingInfo; +import org.geysermc.connector.ping.IGeyserPingPassthrough; + +import java.net.InetAddress; +import java.util.Collections; +import java.util.Iterator; + +@AllArgsConstructor +public class GeyserBukkitPingPassthrough implements IGeyserPingPassthrough { + + private final GeyserBukkitLogger logger; + + @Override + public GeyserPingInfo getPingInformation() { + try { + ServerListPingEvent event = new GeyserPingEvent(InetAddress.getLocalHost(), Bukkit.getMotd(), Bukkit.getOnlinePlayers().size(), Bukkit.getMaxPlayers()); + Bukkit.getPluginManager().callEvent(event); + GeyserPingInfo geyserPingInfo = new GeyserPingInfo(event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()); + Bukkit.getOnlinePlayers().forEach(player -> { + geyserPingInfo.addPlayer(player.getName()); + }); + return geyserPingInfo; + } catch (Exception e) { + logger.debug("Error while getting Bukkit ping passthrough: " + e.toString()); + return new GeyserPingInfo(null, 0, 0); + } + } + + // These methods are unimplemented on spigot api by default so we add stubs so plugins don't complain + private static class GeyserPingEvent extends ServerListPingEvent { + + public GeyserPingEvent(InetAddress address, String motd, int numPlayers, int maxPlayers) { + super(address, motd, numPlayers, maxPlayers); + } + + @Override + public void setServerIcon(CachedServerIcon icon) throws IllegalArgumentException, UnsupportedOperationException { + } + + @Override + public Iterator iterator() throws UnsupportedOperationException { + return Collections.EMPTY_LIST.iterator(); + } + } + +} diff --git a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitPlugin.java b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitPlugin.java index fa58f66c..5f0e967a 100644 --- a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitPlugin.java +++ b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitPlugin.java @@ -28,19 +28,29 @@ package org.geysermc.platform.bukkit; import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; import org.geysermc.common.PlatformType; -import org.geysermc.common.bootstrap.IGeyserBootstrap; +import org.geysermc.connector.GeyserConfiguration; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; +import org.geysermc.connector.network.translators.world.WorldManager; +import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; +import org.geysermc.connector.ping.IGeyserPingPassthrough; import org.geysermc.platform.bukkit.command.GeyserBukkitCommandExecutor; import org.geysermc.platform.bukkit.command.GeyserBukkitCommandManager; +import org.geysermc.platform.bukkit.world.GeyserBukkitBlockPlaceListener; +import org.geysermc.platform.bukkit.world.GeyserBukkitWorldManager; +import us.myles.ViaVersion.api.Via; import java.util.UUID; -public class GeyserBukkitPlugin extends JavaPlugin implements IGeyserBootstrap { +public class GeyserBukkitPlugin extends JavaPlugin implements GeyserBootstrap { private GeyserBukkitCommandManager geyserCommandManager; private GeyserBukkitConfiguration geyserConfig; private GeyserBukkitLogger geyserLogger; + private IGeyserPingPassthrough geyserBukkitPingPassthrough; + private GeyserBukkitBlockPlaceListener blockPlaceListener; + private GeyserBukkitWorldManager geyserWorldManager; private GeyserConnector connector; @@ -56,7 +66,7 @@ public class GeyserBukkitPlugin extends JavaPlugin implements IGeyserBootstrap { // Don't change the ip if its listening on all interfaces // By default this should be 127.0.0.1 but may need to be changed in some circumstances - if (!Bukkit.getIp().equals("0.0.0.0")) { + if (!Bukkit.getIp().equals("0.0.0.0") && !Bukkit.getIp().equals("")) { getConfig().set("remote.address", Bukkit.getIp()); } @@ -64,10 +74,40 @@ public class GeyserBukkitPlugin extends JavaPlugin implements IGeyserBootstrap { saveConfig(); this.geyserLogger = new GeyserBukkitLogger(getLogger(), geyserConfig.isDebugMode()); + GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); + + geyserConfig.loadFloodgate(this); + this.connector = GeyserConnector.start(PlatformType.BUKKIT, this); + if (geyserConfig.isLegacyPingPassthrough()) { + this.geyserBukkitPingPassthrough = GeyserLegacyPingPassthrough.init(connector); + } else { + this.geyserBukkitPingPassthrough = new GeyserBukkitPingPassthrough(geyserLogger); + } + this.geyserCommandManager = new GeyserBukkitCommandManager(this, connector); + boolean isViaVersion = false; + // Used to determine if Block.getBlockData() is present. + boolean isLegacy = !isCompatible(Bukkit.getServer().getVersion(), "1.13.0"); + if (isLegacy) + geyserLogger.debug("Legacy version of Minecraft (1.12.2 or older) detected."); + + if (Bukkit.getPluginManager().getPlugin("ViaVersion") != null) { + // TODO: Update when ViaVersion updates + // API changes between 2.2.3 and 3.0.0-SNAPSHOT require this check + if (!Via.getAPI().getVersion().equals("3.0.0-SNAPSHOT") && isLegacy) { + geyserLogger.info("ViaVersion detected but not ViaVersion-ABSTRACTION. Please update your ViaVersion plugin for compatibility with Geyser."); + } else { + isViaVersion = true; + } + } + + this.geyserWorldManager = new GeyserBukkitWorldManager(isLegacy, isViaVersion); + this.blockPlaceListener = new GeyserBukkitBlockPlaceListener(connector, isLegacy, isViaVersion); + Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this); + this.getCommand("geyser").setExecutor(new GeyserBukkitCommandExecutor(connector)); } @@ -90,4 +130,48 @@ public class GeyserBukkitPlugin extends JavaPlugin implements IGeyserBootstrap { public CommandManager getGeyserCommandManager() { return this.geyserCommandManager; } + + @Override + public IGeyserPingPassthrough getGeyserPingPassthrough() { + return geyserBukkitPingPassthrough; + } + + @Override + public WorldManager getWorldManager() { + return this.geyserWorldManager; + } + + public boolean isCompatible(String version, String whichVersion) { + int[] currentVersion = parseVersion(version); + int[] otherVersion = parseVersion(whichVersion); + int length = Math.max(currentVersion.length, otherVersion.length); + for (int index = 0; index < length; index = index + 1) { + int self = (index < currentVersion.length) ? currentVersion[index] : 0; + int other = (index < otherVersion.length) ? otherVersion[index] : 0; + + if (self != other) { + return (self - other) > 0; + } + } + return true; + } + + private int[] parseVersion(String versionParam) { + versionParam = (versionParam == null) ? "" : versionParam; + if (versionParam.contains("(MC: ")) { + versionParam = versionParam.split("\\(MC: ")[1]; + versionParam = versionParam.split("\\)")[0]; + } + String[] stringArray = versionParam.split("[_.-]"); + int[] temp = new int[stringArray.length]; + for (int index = 0; index <= (stringArray.length - 1); index = index + 1) { + String t = stringArray[index].replaceAll("\\D", ""); + try { + temp[index] = Integer.parseInt(t); + } catch(NumberFormatException ex) { + temp[index] = 0; + } + } + return temp; + } } diff --git a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/world/GeyserBukkitBlockPlaceListener.java b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/world/GeyserBukkitBlockPlaceListener.java new file mode 100644 index 00000000..76d1564e --- /dev/null +++ b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/world/GeyserBukkitBlockPlaceListener.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.platform.bukkit.world; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.SoundEvent; +import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; +import lombok.AllArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockPlaceEvent; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; + +@AllArgsConstructor +public class GeyserBukkitBlockPlaceListener implements Listener { + + private final GeyserConnector connector; + private final boolean isLegacy; + private final boolean isViaVersion; + + @EventHandler + public void place(final BlockPlaceEvent event) { + for (GeyserSession session : connector.getPlayers().values()) { + if (event.getPlayer() == Bukkit.getPlayer(session.getPlayerEntity().getUsername())) { + LevelSoundEventPacket placeBlockSoundPacket = new LevelSoundEventPacket(); + placeBlockSoundPacket.setSound(SoundEvent.PLACE); + placeBlockSoundPacket.setPosition(Vector3f.from(event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ())); + placeBlockSoundPacket.setBabySound(false); + String javaBlockId; + if (isLegacy) { + javaBlockId = BlockTranslator.getJavaIdBlockMap().inverse().get(GeyserBukkitWorldManager.getLegacyBlock(session, + event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ(), isViaVersion)); + } else { + javaBlockId = event.getBlockPlaced().getBlockData().getAsString(); + } + placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaIdBlockMap().get(javaBlockId))); + placeBlockSoundPacket.setIdentifier(":"); + session.sendUpstreamPacket(placeBlockSoundPacket); + session.setLastBlockPlacePosition(null); + session.setLastBlockPlacedId(null); + break; + } + } + } + +} diff --git a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/world/GeyserBukkitWorldManager.java b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/world/GeyserBukkitWorldManager.java new file mode 100644 index 00000000..fbdf2a47 --- /dev/null +++ b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/world/GeyserBukkitWorldManager.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.platform.bukkit.world; + +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.bukkit.Bukkit; +import org.bukkit.block.Block; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.WorldManager; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.platform.bukkit.GeyserBukkitPlugin; +import us.myles.ViaVersion.protocols.protocol1_13_1to1_13.Protocol1_13_1To1_13; +import us.myles.ViaVersion.protocols.protocol1_15to1_14_4.data.MappingData; + +@AllArgsConstructor +public class GeyserBukkitWorldManager extends WorldManager { + + 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 isViaVersion; + + @Override + public BlockState getBlockAt(GeyserSession session, int x, int y, int z) { + if (session.getPlayerEntity() == 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()); + } + + @SuppressWarnings("deprecation") + public static BlockState getLegacyBlock(GeyserSession session, int x, int y, int z, boolean isViaVersion) { + if (isViaVersion) { + Block block = Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getBlockAt(x, y, z); + // Black magic that gets the old block state ID + int oldBlockId = (block.getType().getId() << 4) | (block.getData() & 0xF); + // Convert block state from old version -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 + int thirteenBlockId = us.myles.ViaVersion.protocols.protocol1_13to1_12_2.data.MappingData.blockMappings.getNewId(oldBlockId); + int thirteenPointOneBlockId = Protocol1_13_1To1_13.getNewBlockStateId(thirteenBlockId); + int fourteenBlockId = us.myles.ViaVersion.protocols.protocol1_14to1_13_2.data.MappingData.blockStateMappings.getNewId(thirteenPointOneBlockId); + return new BlockState(MappingData.blockStateMappings.getNewId(fourteenBlockId)); + } else { + return BlockTranslator.AIR; + } + } +} diff --git a/bootstrap/bukkit/src/main/resources/plugin.yml b/bootstrap/bukkit/src/main/resources/plugin.yml index 8e002eb2..89c90789 100644 --- a/bootstrap/bukkit/src/main/resources/plugin.yml +++ b/bootstrap/bukkit/src/main/resources/plugin.yml @@ -3,6 +3,8 @@ name: ${outputName}-Bukkit author: ${project.organization.name} website: ${project.organization.url} version: ${project.version} +softdepend: ["ViaVersion"] +api-version: 1.13 commands: geyser: description: The main command for Geyser. diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 0f6de3fa..dd66db32 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -20,7 +20,7 @@ net.md-5 bungeecord-api - 1.14-SNAPSHOT + 1.15-SNAPSHOT provided @@ -33,6 +33,18 @@ + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + + org.geysermc.platform.bungeecord.GeyserBungeeMain + + + + org.apache.maven.plugins maven-shade-plugin @@ -58,14 +70,6 @@ - - - *:* - - META-INF/* - - - com.google.code.gson:* @@ -76,4 +80,4 @@ - \ No newline at end of file + 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 e0f6a6ef..d983aec1 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 @@ -25,9 +25,10 @@ package org.geysermc.platform.bungeecord; +import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.config.Configuration; - -import org.geysermc.common.IGeyserConfiguration; +import org.geysermc.connector.FloodgateKeyLoader; +import org.geysermc.connector.GeyserConfiguration; import java.io.File; import java.nio.file.Path; @@ -35,7 +36,7 @@ import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; -public class GeyserBungeeConfiguration implements IGeyserConfiguration { +public class GeyserBungeeConfiguration implements GeyserConfiguration { private File dataFolder; private Configuration config; @@ -46,6 +47,8 @@ public class GeyserBungeeConfiguration implements IGeyserConfiguration { private Map userAuthInfo = new HashMap<>(); + private Path floodgateKey; + public GeyserBungeeConfiguration(File dataFolder, Configuration config) { this.dataFolder = dataFolder; this.config = config; @@ -62,6 +65,11 @@ public class GeyserBungeeConfiguration implements IGeyserConfiguration { } } + public void loadFloodgate(GeyserBungeePlugin plugin) { + Plugin floodgate = plugin.getProxy().getPluginManager().getPlugin("floodgate-bungee"); + floodgateKey = FloodgateKeyLoader.getKey(plugin.getGeyserLogger(), this, Paths.get(dataFolder.toString(), config.getString("floodgate-key-file", "public-key.pem")), floodgate, floodgate != null ? floodgate.getDataFolder().toPath() : null); + } + @Override public BungeeBedrockConfiguration getBedrock() { return bedrockConfig; @@ -78,8 +86,28 @@ public class GeyserBungeeConfiguration implements IGeyserConfiguration { } @Override - public boolean isPingPassthrough() { - return config.getBoolean("ping-passthrough", false); + public boolean isCommandSuggestions() { + return config.getBoolean("command-suggestions", true); + } + + @Override + public boolean isPassthroughMotd() { + return config.getBoolean("passthrough-motd", false); + } + + @Override + public boolean isPassthroughPlayerCounts() { + return config.getBoolean("passthrough-player-counts", false); + } + + @Override + public boolean isLegacyPingPassthrough() { + return config.getBoolean("legacy-ping-passthrough", false); + } + + @Override + public int getPingPassthroughInterval() { + return config.getInt("ping-passthrough-interval", 3); } @Override @@ -102,6 +130,11 @@ public class GeyserBungeeConfiguration implements IGeyserConfiguration { return config.getBoolean("allow-third-party-capes", true); } + @Override + public boolean isAllowThirdPartyEars() { + return config.getBoolean("allow-third-party-ears", false); + } + @Override public String getDefaultLocale() { return config.getString("default-locale", "en_us"); @@ -109,7 +142,17 @@ public class GeyserBungeeConfiguration implements IGeyserConfiguration { @Override public Path getFloodgateKeyFile() { - return Paths.get(dataFolder.toString(), config.getString("floodgate-key-file", "public-key.pem")); + return floodgateKey; + } + + @Override + public boolean isCacheChunks() { + return config.getBoolean("cache-chunks", false); + } + + @Override + public boolean isAboveBedrockNetherBuilding() { + return config.getBoolean("above-bedrock-nether-building", false); } @Override @@ -189,4 +232,9 @@ public class GeyserBungeeConfiguration implements IGeyserConfiguration { return config.getString("metrics.uuid", "generateduuid"); } } + + @Override + public int getConfigVersion() { + return config.getInt("config-version", 0); + } } 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 7aba88bc..cd07b333 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,12 +25,12 @@ package org.geysermc.platform.bungeecord; -import org.geysermc.common.logger.IGeyserLogger; +import org.geysermc.connector.GeyserLogger; import java.util.logging.Level; import java.util.logging.Logger; -public class GeyserBungeeLogger implements IGeyserLogger { +public class GeyserBungeeLogger implements GeyserLogger { private Logger logger; private boolean debugMode; diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeMain.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeMain.java new file mode 100644 index 00000000..eabbcc69 --- /dev/null +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeMain.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.platform.bungeecord; + +import org.geysermc.common.main.IGeyserMain; + +public class GeyserBungeeMain extends IGeyserMain { + + public static void main(String[] args) { + new GeyserBungeeMain().displayMessage(); + } + + public String getPluginType() { + return "BungeeCord"; + } + + public String getPluginFolder() { + return "plugins"; + } +} diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePingPassthrough.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePingPassthrough.java new file mode 100644 index 00000000..c7f8f276 --- /dev/null +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePingPassthrough.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.platform.bungeecord; + +import lombok.AllArgsConstructor; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.ServerPing; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.config.ListenerInfo; +import net.md_5.bungee.api.connection.PendingConnection; +import net.md_5.bungee.api.event.ProxyPingEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.protocol.ProtocolConstants; +import org.geysermc.common.ping.GeyserPingInfo; +import org.geysermc.connector.ping.IGeyserPingPassthrough; + +import java.net.Inet4Address; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Arrays; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +@AllArgsConstructor +public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, Listener { + + private static final GeyserPendingConnection PENDING_CONNECTION = new GeyserPendingConnection(); + + private final ProxyServer proxyServer; + + @Override + public GeyserPingInfo getPingInformation() { + CompletableFuture future = new CompletableFuture<>(); + proxyServer.getPluginManager().callEvent(new ProxyPingEvent(PENDING_CONNECTION, getPingInfo(), (event, throwable) -> { + if (throwable != null) future.completeExceptionally(throwable); + else future.complete(event); + })); + ProxyPingEvent event = future.join(); + GeyserPingInfo geyserPingInfo = new GeyserPingInfo( + event.getResponse().getDescription(), + event.getResponse().getPlayers().getOnline(), + event.getResponse().getPlayers().getMax() + ); + if (event.getResponse().getPlayers().getSample() != null) { + Arrays.stream(event.getResponse().getPlayers().getSample()).forEach(proxiedPlayer -> { + geyserPingInfo.addPlayer(proxiedPlayer.getName()); + }); + } + return geyserPingInfo; + } + + // This is static so pending connection can use it + private static ListenerInfo getDefaultListener() { + return ProxyServer.getInstance().getConfig().getListeners().iterator().next(); + } + + private ServerPing getPingInfo() { + return new ServerPing( + new ServerPing.Protocol(proxyServer.getName() + " " + proxyServer.getGameVersion(), ProtocolConstants.SUPPORTED_VERSION_IDS.get(ProtocolConstants.SUPPORTED_VERSION_IDS.size() - 1)), + new ServerPing.Players(getDefaultListener().getMaxPlayers(), proxyServer.getOnlineCount(), null), + getDefaultListener().getMotd(), proxyServer.getConfig().getFaviconObject() + ); + } + + private static class GeyserPendingConnection implements PendingConnection { + + private static final UUID FAKE_UUID = UUID.nameUUIDFromBytes("geyser!internal".getBytes()); + private static final InetSocketAddress FAKE_REMOTE = new InetSocketAddress(Inet4Address.getLoopbackAddress(), 69); + + @Override + public String getName() { + throw new UnsupportedOperationException(); + } + + @Override + public int getVersion() { + return ProtocolConstants.SUPPORTED_VERSION_IDS.get(ProtocolConstants.SUPPORTED_VERSION_IDS.size() - 1); + } + + @Override + public InetSocketAddress getVirtualHost() { + return null; + } + + @Override + public ListenerInfo getListener() { + return getDefaultListener(); + } + + @Override + public String getUUID() { + return FAKE_UUID.toString(); + } + + @Override + public UUID getUniqueId() { + return FAKE_UUID; + } + + @Override + public void setUniqueId(UUID uuid) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isOnlineMode() { + return true; + } + + @Override + public void setOnlineMode(boolean b) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isLegacy() { + return false; + } + + @Override + public InetSocketAddress getAddress() { + return FAKE_REMOTE; + } + + @Override + public SocketAddress getSocketAddress() { + return getAddress(); + } + + @Override + public void disconnect(String s) { + throw new UnsupportedOperationException(); + } + + @Override + public void disconnect(BaseComponent... baseComponents) { + throw new UnsupportedOperationException(); + } + + @Override + public void disconnect(BaseComponent baseComponent) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isConnected() { + return false; + } + + @Override + public Unsafe unsafe() { + throw new UnsupportedOperationException(); + } + } + +} 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 4c595390..525b9b6d 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 @@ -31,9 +31,12 @@ import net.md_5.bungee.config.Configuration; import net.md_5.bungee.config.ConfigurationProvider; import net.md_5.bungee.config.YamlConfiguration; import org.geysermc.common.PlatformType; -import org.geysermc.common.bootstrap.IGeyserBootstrap; +import org.geysermc.connector.GeyserConfiguration; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; +import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; +import org.geysermc.connector.ping.IGeyserPingPassthrough; import org.geysermc.platform.bungeecord.command.GeyserBungeeCommandExecutor; import org.geysermc.platform.bungeecord.command.GeyserBungeeCommandManager; @@ -45,11 +48,12 @@ import java.nio.file.Files; import java.util.UUID; import java.util.logging.Level; -public class GeyserBungeePlugin extends Plugin implements IGeyserBootstrap { +public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { private GeyserBungeeCommandManager geyserCommandManager; private GeyserBungeeConfiguration geyserConfig; private GeyserBungeeLogger geyserLogger; + private IGeyserPingPassthrough geyserBungeePingPassthrough; private GeyserConnector connector; @@ -91,7 +95,7 @@ public class GeyserBungeePlugin extends Plugin implements IGeyserBootstrap { // Don't change the ip if its listening on all interfaces // By default this should be 127.0.0.1 but may need to be changed in some circumstances - if (!javaAddr.getHostString().equals("0.0.0.0")) { + if (!javaAddr.getHostString().equals("0.0.0.0") && !javaAddr.getHostString().equals("")) { configuration.set("remote.address", javaAddr.getHostString()); } @@ -116,10 +120,20 @@ public class GeyserBungeePlugin extends Plugin implements IGeyserBootstrap { } this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode()); + GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); + + geyserConfig.loadFloodgate(this); + this.connector = GeyserConnector.start(PlatformType.BUNGEECORD, this); this.geyserCommandManager = new GeyserBungeeCommandManager(connector); + if (geyserConfig.isLegacyPingPassthrough()) { + this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(connector); + } else { + this.geyserBungeePingPassthrough = new GeyserBungeePingPassthrough(getProxy()); + } + this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor(connector)); } @@ -142,4 +156,9 @@ public class GeyserBungeePlugin extends Plugin implements IGeyserBootstrap { public CommandManager getGeyserCommandManager() { return this.geyserCommandManager; } + + @Override + public IGeyserPingPassthrough getGeyserPingPassthrough() { + return geyserBungeePingPassthrough; + } } diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index 696721d2..c9abbe3e 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -33,6 +33,18 @@ + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + + org.geysermc.platform.sponge.GeyserSpongeMain + + + + org.apache.maven.plugins maven-shade-plugin @@ -62,14 +74,6 @@ - - - *:* - - META-INF/* - - - com.google.code.gson:* 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 e60a5c3e..fc148470 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 @@ -26,15 +26,17 @@ package org.geysermc.platform.sponge; import lombok.AllArgsConstructor; + import ninja.leaping.configurate.ConfigurationNode; -import org.geysermc.common.IGeyserConfiguration; + +import org.geysermc.connector.GeyserConfiguration; import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; -public class GeyserSpongeConfiguration implements IGeyserConfiguration { +public class GeyserSpongeConfiguration implements GeyserConfiguration { private File dataFolder; private ConfigurationNode node; @@ -78,8 +80,28 @@ public class GeyserSpongeConfiguration implements IGeyserConfiguration { } @Override - public boolean isPingPassthrough() { - return node.getNode("ping-passthrough").getBoolean(false); + public boolean isCommandSuggestions() { + return node.getNode("command-suggestions").getBoolean(true); + } + + @Override + public boolean isPassthroughMotd() { + return node.getNode("passthrough-motd").getBoolean(false); + } + + @Override + public boolean isPassthroughPlayerCounts() { + return node.getNode("passthrough-player-counts").getBoolean(false); + } + + @Override + public boolean isLegacyPingPassthrough() { + return node.getNode("legacy-ping-passthrough").getBoolean(false); + } + + @Override + public int getPingPassthroughInterval() { + return node.getNode("ping-passthrough-interval").getInt(3); } @Override @@ -102,6 +124,11 @@ public class GeyserSpongeConfiguration implements IGeyserConfiguration { return node.getNode("allow-third-party-capes").getBoolean(true); } + @Override + public boolean isAllowThirdPartyEars() { + return node.getNode("allow-third-party-ears").getBoolean(false); + } + @Override public String getDefaultLocale() { return node.getNode("default-locale").getString("en_us"); @@ -112,6 +139,16 @@ public class GeyserSpongeConfiguration implements IGeyserConfiguration { 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 boolean isAboveBedrockNetherBuilding() { + return node.getNode("above-bedrock-nether-building").getBoolean(false); + } + @Override public SpongeMetricsInfo getMetrics() { return metricsInfo; @@ -195,4 +232,9 @@ public class GeyserSpongeConfiguration implements IGeyserConfiguration { return node.getNode("metrics").getNode("uuid").getString("generateduuid"); } } + + @Override + public int getConfigVersion() { + return node.getNode("config-version").getInt(0); + } } 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 758ac98d..fb7cb54b 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 @@ -27,11 +27,11 @@ package org.geysermc.platform.sponge; import lombok.AllArgsConstructor; -import org.geysermc.common.logger.IGeyserLogger; +import org.geysermc.connector.GeyserLogger; import org.slf4j.Logger; @AllArgsConstructor -public class GeyserSpongeLogger implements IGeyserLogger { +public class GeyserSpongeLogger implements GeyserLogger { private Logger logger; private boolean debugMode; diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeMain.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeMain.java new file mode 100644 index 00000000..11b9583f --- /dev/null +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeMain.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.platform.sponge; + +import org.geysermc.common.main.IGeyserMain; + +public class GeyserSpongeMain extends IGeyserMain { + + public static void main(String[] args) { + new GeyserSpongeMain().displayMessage(); + } + + public String getPluginType() { + return "Sponge"; + } + + public String getPluginFolder() { + return "mods"; + } +} diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePingPassthrough.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePingPassthrough.java new file mode 100644 index 00000000..31b6dc7f --- /dev/null +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePingPassthrough.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.platform.sponge; + +import org.geysermc.common.ping.GeyserPingInfo; +import org.geysermc.connector.ping.IGeyserPingPassthrough; +import org.spongepowered.api.MinecraftVersion; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.event.SpongeEventFactory; +import org.spongepowered.api.event.cause.Cause; +import org.spongepowered.api.event.cause.EventContext; +import org.spongepowered.api.event.server.ClientPingServerEvent; +import org.spongepowered.api.network.status.StatusClient; + +import java.lang.reflect.Method; +import java.net.Inet4Address; +import java.net.InetSocketAddress; +import java.util.Optional; + +public class GeyserSpongePingPassthrough implements IGeyserPingPassthrough { + + private static final GeyserStatusClient STATUS_CLIENT = new GeyserStatusClient(); + private static final Cause CAUSE = Cause.of(EventContext.empty(), Sponge.getServer()); + + private static Method SpongeStatusResponse_create; + + @SuppressWarnings({"unchecked", "rawtypes"}) + @Override + public GeyserPingInfo getPingInformation() { + // come on Sponge, this is in commons, why not expose it :( + ClientPingServerEvent event; + try { + if(SpongeStatusResponse_create == null) { + Class SpongeStatusResponse = Class.forName("org.spongepowered.common.network.status.SpongeStatusResponse"); + Class MinecraftServer = Class.forName("net.minecraft.server.MinecraftServer"); + SpongeStatusResponse_create = SpongeStatusResponse.getDeclaredMethod("create", MinecraftServer); + } + + Object response = SpongeStatusResponse_create.invoke(null, Sponge.getServer()); + event = SpongeEventFactory.createClientPingServerEvent(CAUSE, STATUS_CLIENT, (ClientPingServerEvent.Response) response); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + Sponge.getEventManager().post(event); + GeyserPingInfo geyserPingInfo = new GeyserPingInfo( + event.getResponse().getDescription().toPlain(), + event.getResponse().getPlayers().orElseThrow(IllegalStateException::new).getOnline(), + event.getResponse().getPlayers().orElseThrow(IllegalStateException::new).getMax()); + event.getResponse().getPlayers().get().getProfiles().forEach(player -> { + geyserPingInfo.addPlayer(player.getName().orElseThrow(IllegalStateException::new)); + }); + return geyserPingInfo; + } + + @SuppressWarnings("NullableProblems") + private static class GeyserStatusClient implements StatusClient { + + private static final InetSocketAddress FAKE_REMOTE = new InetSocketAddress(Inet4Address.getLoopbackAddress(), 69); + + @Override + public InetSocketAddress getAddress() { + return FAKE_REMOTE; + } + + @Override + public MinecraftVersion getVersion() { + return Sponge.getPlatform().getMinecraftVersion(); + } + + @Override + public Optional getVirtualHost() { + return Optional.empty(); + } + } +} 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 cc26abb9..d226add7 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 @@ -30,9 +30,12 @@ import ninja.leaping.configurate.ConfigurationNode; import ninja.leaping.configurate.loader.ConfigurationLoader; import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; import org.geysermc.common.PlatformType; -import org.geysermc.common.bootstrap.IGeyserBootstrap; +import org.geysermc.connector.GeyserConfiguration; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; +import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; +import org.geysermc.connector.ping.IGeyserPingPassthrough; import org.geysermc.connector.utils.FileUtils; import org.geysermc.platform.sponge.command.GeyserSpongeCommandExecutor; import org.geysermc.platform.sponge.command.GeyserSpongeCommandManager; @@ -50,7 +53,7 @@ import java.net.InetSocketAddress; import java.util.UUID; @Plugin(id = "geyser", name = GeyserConnector.NAME + "-Sponge", version = GeyserConnector.VERSION, url = "https://geysermc.org", authors = "GeyserMC") -public class GeyserSpongePlugin implements IGeyserBootstrap { +public class GeyserSpongePlugin implements GeyserBootstrap { @Inject private Logger logger; @@ -62,6 +65,7 @@ public class GeyserSpongePlugin implements IGeyserBootstrap { private GeyserSpongeCommandManager geyserCommandManager; private GeyserSpongeConfiguration geyserConfig; private GeyserSpongeLogger geyserLogger; + private IGeyserPingPassthrough geyserSpongePingPassthrough; private GeyserConnector connector; @@ -97,7 +101,7 @@ public class GeyserSpongePlugin implements IGeyserBootstrap { // Don't change the ip if its listening on all interfaces // By default this should be 127.0.0.1 but may need to be changed in some circumstances - if (!javaAddr.getHostString().equals("0.0.0.0")) { + if (!javaAddr.getHostString().equals("0.0.0.0") && !javaAddr.getHostString().equals("")) { serverIP.setValue("127.0.0.1"); } @@ -105,9 +109,16 @@ public class GeyserSpongePlugin implements IGeyserBootstrap { } this.geyserLogger = new GeyserSpongeLogger(logger, geyserConfig.isDebugMode()); + GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); this.connector = GeyserConnector.start(PlatformType.SPONGE, this); - this.geyserCommandManager = new GeyserSpongeCommandManager(Sponge.getCommandManager(), connector); + if (geyserConfig.isLegacyPingPassthrough()) { + this.geyserSpongePingPassthrough = GeyserLegacyPingPassthrough.init(connector); + } else { + this.geyserSpongePingPassthrough = new GeyserSpongePingPassthrough(); + } + + this.geyserCommandManager = new GeyserSpongeCommandManager(Sponge.getCommandManager(), connector); Sponge.getCommandManager().register(this, new GeyserSpongeCommandExecutor(connector), "geyser"); } @@ -131,6 +142,11 @@ public class GeyserSpongePlugin implements IGeyserBootstrap { return this.geyserCommandManager; } + @Override + public IGeyserPingPassthrough getGeyserPingPassthrough() { + return geyserSpongePingPassthrough; + } + @Listener public void onServerStart(GameStartedServerEvent event) { onEnable(); diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index 0a583fa7..770ca100 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -81,7 +81,7 @@ - org.geysermc.platform.standalone.GeyserBootstrap + org.geysermc.platform.standalone.GeyserStandaloneBootstrap @@ -119,7 +119,7 @@ - org.geysermc.platform.standalone.GeyserBootstrap + org.geysermc.platform.standalone.GeyserStandaloneBootstrap true diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java similarity index 74% rename from bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserBootstrap.java rename to bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java index 20b7f5ea..aa0d2392 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java @@ -26,45 +26,52 @@ package org.geysermc.platform.standalone; import org.geysermc.common.PlatformType; -import org.geysermc.common.bootstrap.IGeyserBootstrap; +import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; +import org.geysermc.connector.GeyserConfiguration; +import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandManager; +import org.geysermc.connector.ping.IGeyserPingPassthrough; import org.geysermc.connector.utils.FileUtils; import org.geysermc.platform.standalone.command.GeyserCommandManager; -import org.geysermc.platform.standalone.console.GeyserLogger; import java.io.File; import java.io.IOException; import java.util.UUID; -public class GeyserBootstrap implements IGeyserBootstrap { +public class GeyserStandaloneBootstrap implements GeyserBootstrap { private GeyserCommandManager geyserCommandManager; - private GeyserConfiguration geyserConfig; - private GeyserLogger geyserLogger; + private GeyserStandaloneConfiguration geyserConfig; + private GeyserStandaloneLogger geyserLogger; + private IGeyserPingPassthrough geyserPingPassthrough; private GeyserConnector connector; public static void main(String[] args) { - new GeyserBootstrap().onEnable(); + new GeyserStandaloneBootstrap().onEnable(); } @Override public void onEnable() { - geyserLogger = new GeyserLogger(); + geyserLogger = new GeyserStandaloneLogger(); LoopbackUtil.checkLoopback(geyserLogger); try { File configFile = FileUtils.fileOrCopiedFromResource("config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); - geyserConfig = FileUtils.loadConfig(configFile, GeyserConfiguration.class); + geyserConfig = FileUtils.loadConfig(configFile, GeyserStandaloneConfiguration.class); } catch (IOException ex) { geyserLogger.severe("Failed to read/create config.yml! Make sure it's up to date and/or readable+writable!", ex); System.exit(0); } + GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); connector = GeyserConnector.start(PlatformType.STANDALONE, this); geyserCommandManager = new GeyserCommandManager(connector); + + geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector); + geyserLogger.start(); } @@ -80,7 +87,7 @@ public class GeyserBootstrap implements IGeyserBootstrap { } @Override - public GeyserLogger getGeyserLogger() { + public GeyserStandaloneLogger getGeyserLogger() { return geyserLogger; } @@ -88,4 +95,9 @@ public class GeyserBootstrap implements IGeyserBootstrap { public CommandManager getGeyserCommandManager() { return geyserCommandManager; } + + @Override + public IGeyserPingPassthrough getGeyserPingPassthrough() { + return geyserPingPassthrough; + } } diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserConfiguration.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneConfiguration.java similarity index 77% rename from bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserConfiguration.java rename to bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneConfiguration.java index afd6179e..bd029204 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserConfiguration.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneConfiguration.java @@ -29,8 +29,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; - -import org.geysermc.common.IGeyserConfiguration; +import org.geysermc.connector.GeyserConfiguration; import java.nio.file.Path; import java.nio.file.Paths; @@ -38,7 +37,7 @@ import java.util.Map; @JsonIgnoreProperties(ignoreUnknown = true) @Getter -public class GeyserConfiguration implements IGeyserConfiguration { +public class GeyserStandaloneConfiguration implements GeyserConfiguration { private BedrockConfiguration bedrock; private RemoteConfiguration remote; @@ -48,8 +47,20 @@ public class GeyserConfiguration implements IGeyserConfiguration { private Map userAuths; - @JsonProperty("ping-passthrough") - private boolean pingPassthrough; + @JsonProperty("command-suggestions") + private boolean isCommandSuggestions; + + @JsonProperty("passthrough-motd") + private boolean isPassthroughMotd; + + @JsonProperty("passthrough-player-counts") + private boolean isPassthroughPlayerCounts; + + @JsonProperty("legacy-ping-passthrough") + private boolean isLegacyPingPassthrough; + + @JsonProperty("ping-passthrough-interval") + private int pingPassthroughInterval; @JsonProperty("max-players") private int maxPlayers; @@ -63,9 +74,18 @@ public class GeyserConfiguration implements IGeyserConfiguration { @JsonProperty("allow-third-party-capes") private boolean allowThirdPartyCapes; + @JsonProperty("allow-third-party-ears") + private boolean allowThirdPartyEars; + @JsonProperty("default-locale") private String defaultLocale; + @JsonProperty("cache-chunks") + private boolean cacheChunks; + + @JsonProperty("above-bedrock-nether-building") + private boolean isAboveBedrockNetherBuilding; + private MetricsInfo metrics; @Override @@ -110,4 +130,7 @@ public class GeyserConfiguration implements IGeyserConfiguration { @JsonProperty("uuid") private String uniqueId; } + + @JsonProperty("config-version") + private int configVersion; } diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/console/GeyserLogger.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java similarity index 64% rename from bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/console/GeyserLogger.java rename to bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java index 631de905..ffb252b2 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/console/GeyserLogger.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java @@ -1,29 +1,30 @@ /* * 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: + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.standalone.console; +package org.geysermc.platform.standalone; import lombok.extern.log4j.Log4j2; @@ -31,12 +32,11 @@ import net.minecrell.terminalconsole.SimpleTerminalConsole; import org.apache.logging.log4j.core.config.Configurator; import org.geysermc.common.ChatColor; -import org.geysermc.common.logger.IGeyserLogger; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; @Log4j2 -public class GeyserLogger extends SimpleTerminalConsole implements IGeyserLogger, CommandSender { +public class GeyserStandaloneLogger extends SimpleTerminalConsole implements org.geysermc.connector.GeyserLogger, CommandSender { private boolean colored = true; diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/LoopbackUtil.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/LoopbackUtil.java index 20a81072..03c49705 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/LoopbackUtil.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/LoopbackUtil.java @@ -6,14 +6,13 @@ import java.nio.file.OpenOption; import java.nio.file.Paths; import org.geysermc.common.ChatColor; -import org.geysermc.platform.standalone.console.GeyserLogger; public class LoopbackUtil { private static final String checkExemption = "powershell -Command \"CheckNetIsolation LoopbackExempt -s\""; // Java's Exec feature runs as CMD, NetIsolation is only accessible from PowerShell. private static final String loopbackCommand = "powershell -Command \"CheckNetIsolation LoopbackExempt -a -n='Microsoft.MinecraftUWP_8wekyb3d8bbwe'\""; private static final String startScript = "powershell -Command \"Start-Process 'cmd' -ArgumentList /c,%temp%/loopback_minecraft.bat -Verb runAs\""; - public static void checkLoopback(GeyserLogger geyserLogger) { + public static void checkLoopback(GeyserStandaloneLogger geyserLogger) { if (System.getProperty("os.name").equalsIgnoreCase("Windows 10")) { try { Process process = Runtime.getRuntime().exec(checkExemption); diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index 075aedc3..fb06767e 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -33,6 +33,18 @@ + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + + org.geysermc.platform.velocity.GeyserVelocityMain + + + + org.apache.maven.plugins maven-shade-plugin @@ -54,14 +66,6 @@ - - - *:* - - META-INF/* - - - com.google.code.gson:* 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 03f7cbee..aef0edaa 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,17 +27,22 @@ package org.geysermc.platform.velocity; 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; import lombok.Setter; -import org.geysermc.common.IGeyserConfiguration; +import org.geysermc.connector.FloodgateKeyLoader; +import org.geysermc.connector.GeyserConfiguration; +import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Map; +import java.util.Optional; @JsonIgnoreProperties(ignoreUnknown = true) @Getter -public class GeyserVelocityConfiguration implements IGeyserConfiguration { +public class GeyserVelocityConfiguration implements GeyserConfiguration { private BedrockConfiguration bedrock; private RemoteConfiguration remote; @@ -47,8 +52,20 @@ public class GeyserVelocityConfiguration implements IGeyserConfiguration { private Map userAuths; - @JsonProperty("ping-passthrough") - private boolean pingPassthrough; + @JsonProperty("command-suggestions") + private boolean commandSuggestions; + + @JsonProperty("passthrough-motd") + private boolean isPassthroughMotd; + + @JsonProperty("passthrough-player-counts") + private boolean isPassthroughPlayerCounts; + + @JsonProperty("legacy-ping-passthrough") + private boolean isLegacyPingPassthrough; + + @JsonProperty("ping-passthrough-interval") + private int pingPassthroughInterval; @JsonProperty("max-players") private int maxPlayers; @@ -62,14 +79,30 @@ public class GeyserVelocityConfiguration implements IGeyserConfiguration { @JsonProperty("allow-third-party-capes") private boolean allowThirdPartyCapes; + @JsonProperty("allow-third-party-ears") + private boolean allowThirdPartyEars; + @JsonProperty("default-locale") private String defaultLocale; + @JsonProperty("cache-chunks") + private boolean cacheChunks; + + @JsonProperty("above-bedrock-nether-building") + private boolean aboveBedrockNetherBuilding; + private MetricsInfo metrics; + private Path floodgateKey; + + 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/"))); + } + @Override public Path getFloodgateKeyFile() { - return Paths.get(floodgateKeyFile); + return floodgateKey; } @Getter @@ -112,4 +145,7 @@ public class GeyserVelocityConfiguration implements IGeyserConfiguration { @JsonProperty("uuid") private String uniqueId; } + + @JsonProperty("config-version") + private int configVersion; } 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 623c6481..a935d786 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 @@ -27,11 +27,11 @@ package org.geysermc.platform.velocity; import lombok.AllArgsConstructor; -import org.geysermc.common.logger.IGeyserLogger; +import org.geysermc.connector.GeyserLogger; import org.slf4j.Logger; @AllArgsConstructor -public class GeyserVelocityLogger implements IGeyserLogger { +public class GeyserVelocityLogger implements GeyserLogger { private Logger logger; private boolean debugMode; diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityMain.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityMain.java new file mode 100644 index 00000000..73eaddf0 --- /dev/null +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityMain.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.platform.velocity; + +import org.geysermc.common.main.IGeyserMain; + +public class GeyserVelocityMain extends IGeyserMain { + + public static void main(String[] args) { + new GeyserVelocityMain().displayMessage(); + } + + public String getPluginType() { + return "Velocity"; + } + + public String getPluginFolder() { + return "plugins"; + } +} diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPingPassthrough.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPingPassthrough.java new file mode 100644 index 00000000..01be949b --- /dev/null +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPingPassthrough.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.platform.velocity; + +import com.velocitypowered.api.event.proxy.ProxyPingEvent; +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.api.proxy.InboundConnection; +import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.server.ServerPing; +import lombok.AllArgsConstructor; +import net.kyori.text.TextComponent; +import net.kyori.text.serializer.legacy.LegacyComponentSerializer; +import org.geysermc.common.ping.GeyserPingInfo; +import org.geysermc.connector.ping.IGeyserPingPassthrough; + +import java.net.Inet4Address; +import java.net.InetSocketAddress; +import java.util.Optional; +import java.util.concurrent.ExecutionException; + +@AllArgsConstructor +public class GeyserVelocityPingPassthrough implements IGeyserPingPassthrough { + + private static final GeyserInboundConnection FAKE_INBOUND_CONNECTION = new GeyserInboundConnection(); + + private final ProxyServer server; + + @Override + public GeyserPingInfo getPingInformation() { + ProxyPingEvent event; + try { + event = server.getEventManager().fire(new ProxyPingEvent(FAKE_INBOUND_CONNECTION, ServerPing.builder() + .description(server.getConfiguration().getMotdComponent()).onlinePlayers(server.getPlayerCount()) + .maximumPlayers(server.getConfiguration().getShowMaxPlayers()).build())).get(); + } catch (ExecutionException | InterruptedException e) { + throw new RuntimeException(e); + } + GeyserPingInfo geyserPingInfo = new GeyserPingInfo( + LegacyComponentSerializer.INSTANCE.serialize(event.getPing().getDescription(), '§'), + event.getPing().getPlayers().orElseThrow(IllegalStateException::new).getOnline(), + event.getPing().getPlayers().orElseThrow(IllegalStateException::new).getMax() + ); + event.getPing().getPlayers().get().getSample().forEach(player -> { + geyserPingInfo.addPlayer(player.getName()); + }); + return geyserPingInfo; + } + + private static class GeyserInboundConnection implements InboundConnection { + + private static final InetSocketAddress FAKE_REMOTE = new InetSocketAddress(Inet4Address.getLoopbackAddress(), 69); + + @Override + public InetSocketAddress getRemoteAddress() { + return FAKE_REMOTE; + } + + @Override + public Optional getVirtualHost() { + return Optional.empty(); + } + + @Override + public boolean isActive() { + return false; + } + + @Override + public ProtocolVersion getProtocolVersion() { + return ProtocolVersion.MAXIMUM_VERSION; + } + } + +} 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 38378f01..e7b44da5 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 @@ -35,8 +35,11 @@ import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.proxy.ProxyServer; import org.geysermc.common.PlatformType; -import org.geysermc.common.bootstrap.IGeyserBootstrap; +import org.geysermc.connector.GeyserConfiguration; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.bootstrap.GeyserBootstrap; +import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; +import org.geysermc.connector.ping.IGeyserPingPassthrough; import org.geysermc.connector.utils.FileUtils; import org.geysermc.platform.velocity.command.GeyserVelocityCommandExecutor; import org.geysermc.platform.velocity.command.GeyserVelocityCommandManager; @@ -48,13 +51,13 @@ import java.net.InetSocketAddress; import java.util.UUID; @Plugin(id = "geyser", name = GeyserConnector.NAME + "-Velocity", version = GeyserConnector.VERSION, url = "https://geysermc.org", authors = "GeyserMC") -public class GeyserVelocityPlugin implements IGeyserBootstrap { +public class GeyserVelocityPlugin implements GeyserBootstrap { @Inject private Logger logger; @Inject - private ProxyServer server; + private ProxyServer proxyServer; @Inject private CommandManager commandManager; @@ -62,13 +65,15 @@ public class GeyserVelocityPlugin implements IGeyserBootstrap { private GeyserVelocityCommandManager geyserCommandManager; private GeyserVelocityConfiguration geyserConfig; private GeyserVelocityLogger geyserLogger; + private IGeyserPingPassthrough geyserPingPassthrough; private GeyserConnector connector; @Override public void onEnable() { + File configDir = new File("plugins/" + GeyserConnector.NAME + "-Velocity/"); + try { - File configDir = new File("plugins/" + GeyserConnector.NAME + "-Velocity/"); if (!configDir.exists()) configDir.mkdir(); File configFile = FileUtils.fileOrCopiedFromResource(new File(configDir, "config.yml"), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); @@ -78,21 +83,30 @@ public class GeyserVelocityPlugin implements IGeyserBootstrap { ex.printStackTrace(); } - InetSocketAddress javaAddr = server.getBoundAddress(); + InetSocketAddress javaAddr = proxyServer.getBoundAddress(); // Don't change the ip if its listening on all interfaces // By default this should be 127.0.0.1 but may need to be changed in some circumstances - if (!javaAddr.getHostString().equals("0.0.0.0")) { + if (!javaAddr.getHostString().equals("0.0.0.0") && !javaAddr.getHostString().equals("")) { geyserConfig.getRemote().setAddress(javaAddr.getHostString()); } geyserConfig.getRemote().setPort(javaAddr.getPort()); this.geyserLogger = new GeyserVelocityLogger(logger, geyserConfig.isDebugMode()); + GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); + + geyserConfig.loadFloodgate(this, proxyServer, configDir); + this.connector = GeyserConnector.start(PlatformType.VELOCITY, this); this.geyserCommandManager = new GeyserVelocityCommandManager(connector); this.commandManager.register(new GeyserVelocityCommandExecutor(connector), "geyser"); + if (geyserConfig.isLegacyPingPassthrough()) { + this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector); + } else { + this.geyserPingPassthrough = new GeyserVelocityPingPassthrough(proxyServer); + } } @Override @@ -115,6 +129,11 @@ public class GeyserVelocityPlugin implements IGeyserBootstrap { return this.geyserCommandManager; } + @Override + public IGeyserPingPassthrough getGeyserPingPassthrough() { + return geyserPingPassthrough; + } + @Subscribe public void onInit(ProxyInitializeEvent event) { onEnable(); diff --git a/common/src/main/java/org/geysermc/common/IGeyserConfiguration.java b/common/src/main/java/org/geysermc/common/IGeyserConfiguration.java deleted file mode 100644 index 774e3394..00000000 --- a/common/src/main/java/org/geysermc/common/IGeyserConfiguration.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common; - -import java.nio.file.Path; -import java.util.Map; - -public interface IGeyserConfiguration { - - IBedrockConfiguration getBedrock(); - - IRemoteConfiguration getRemote(); - - Map getUserAuths(); - - boolean isPingPassthrough(); - - int getMaxPlayers(); - - boolean isDebugMode(); - - int getGeneralThreadPool(); - - boolean isAllowThirdPartyCapes(); - - String getDefaultLocale(); - - Path getFloodgateKeyFile(); - - IMetricsInfo getMetrics(); - - interface IBedrockConfiguration { - - String getAddress(); - - int getPort(); - - String getMotd1(); - - String getMotd2(); - } - - interface IRemoteConfiguration { - - String getAddress(); - - int getPort(); - - String getAuthType(); - } - - interface IUserAuthenticationInfo { - String getEmail(); - - String getPassword(); - } - - interface IMetricsInfo { - - boolean isEnabled(); - - String getUniqueId(); - } -} diff --git a/common/src/main/java/org/geysermc/common/bootstrap/IGeyserBootstrap.java b/common/src/main/java/org/geysermc/common/bootstrap/IGeyserBootstrap.java deleted file mode 100644 index 5df61953..00000000 --- a/common/src/main/java/org/geysermc/common/bootstrap/IGeyserBootstrap.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.bootstrap; - -import org.geysermc.common.IGeyserConfiguration; -import org.geysermc.common.command.ICommandManager; -import org.geysermc.common.logger.IGeyserLogger; - -public interface IGeyserBootstrap { - - /** - * Called when the GeyserBootstrap is enabled - */ - void onEnable(); - - /** - * Called when the GeyserBootstrap is disabled - */ - void onDisable(); - - /** - * Returns the current GeyserConfig - * - * @return The current GeyserConfig - */ - IGeyserConfiguration getGeyserConfig(); - - /** - * Returns the current GeyserLogger - * - * @return The current GeyserLogger - */ - IGeyserLogger getGeyserLogger(); - - /** - * Returns the current GeyserCommandManager - * - * @return The current GeyserCommandManager - */ - ICommandManager getGeyserCommandManager(); -} diff --git a/common/src/main/java/org/geysermc/common/logger/IGeyserLogger.java b/common/src/main/java/org/geysermc/common/logger/IGeyserLogger.java deleted file mode 100644 index ad571ebb..00000000 --- a/common/src/main/java/org/geysermc/common/logger/IGeyserLogger.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.logger; - -public interface IGeyserLogger { - - /** - * Logs a severe message to console - * - * @param message the message to log - */ - void severe(String message); - - /** - * Logs a severe message and an exception to console - */ - void severe(String message, Throwable error); - - /** - * Logs an error message to console - * - * @param message the message to log - */ - void error(String message); - - /** - * Logs an error message and an exception to console - */ - void error(String message, Throwable error); - - /** - * Logs a warning message to console - * - * @param message the message to log - */ - void warning(String message); - - /** - * Logs an info message to console - * - * @param message the message to log - */ - void info(String message); - - /** - * Logs a debug message to console - * - * @param message the message to log - */ - void debug(String message); - - /** - * Sets if the logger should print debug messages - * - * @param debug if the logger should print debug messages - */ - void setDebug(boolean debug); -} diff --git a/common/src/main/java/org/geysermc/common/main/IGeyserMain.java b/common/src/main/java/org/geysermc/common/main/IGeyserMain.java new file mode 100644 index 00000000..75da4e6b --- /dev/null +++ b/common/src/main/java/org/geysermc/common/main/IGeyserMain.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.common.main; + +import javax.swing.*; +import java.io.InputStream; +import java.util.Scanner; + +public class IGeyserMain { + + public void displayMessage() { + String message = createMessage(); + + if (System.console() == null) { + JOptionPane.showMessageDialog(null, message, "GeyserMC Plugin: " + this.getPluginType(), JOptionPane.ERROR_MESSAGE); + } + + printMessage(message); + } + + private String createMessage() { + String message = ""; + + InputStream helpStream = IGeyserMain.class.getClassLoader().getResourceAsStream("help.txt"); + Scanner help = new Scanner(helpStream).useDelimiter("\\Z"); + String line = ""; + while (help.hasNext()) { + line = help.next(); + + line = line.replace("${plugin_type}", this.getPluginType()); + line = line.replace("${plugin_folder}", this.getPluginFolder()); + + message += line + "\n"; + } + + return message; + } + + private void printMessage(String message) { + System.out.print(message); + } + + public String getPluginType() { + return "unknown"; + } + + public String getPluginFolder() { + return "unknown"; + } +} diff --git a/common/src/main/java/org/geysermc/common/ping/GeyserPingInfo.java b/common/src/main/java/org/geysermc/common/ping/GeyserPingInfo.java new file mode 100644 index 00000000..40ef6da6 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/ping/GeyserPingInfo.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.common.ping; + +import lombok.Data; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.Collection; + +@Data +public class GeyserPingInfo { + + public final String motd; + public final int currentPlayerCount; + public final int maxPlayerCount; + + @Getter + private Collection players = new ArrayList<>(); + + public void addPlayer(String username) { + players.add(username); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/util/DeviceOS.java b/common/src/main/java/org/geysermc/floodgate/util/DeviceOS.java index 0705d169..93d3c121 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/DeviceOS.java +++ b/common/src/main/java/org/geysermc/floodgate/util/DeviceOS.java @@ -30,24 +30,39 @@ import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; public enum DeviceOS { @JsonEnumDefaultValue - UNKNOWN, - ANDROID, - IOS, - OSX, - FIREOS, - GEARVR, - HOLOLENS, - WIN10, - WIN32, - DEDICATED, - ORBIS, - NX, - SWITCH, - XBOX_ONE; + UNKNOWN("Unknown"), + ANDROID("Android"), + IOS("iOS"), + OSX("macOS"), + FIREOS("FireOS"), + GEARVR("Gear VR"), + HOLOLENS("Hololens"), + WIN10("Windows 10"), + WIN32("Windows"), + DEDICATED("Dedicated"), + ORBIS("PS4"), + NX("Switch"), + SWITCH("Switch"), + XBOX_ONE("Xbox One"), + WIN_PHONE("Windows Phone"); private static final DeviceOS[] VALUES = values(); + private final String displayName; + + DeviceOS(final String displayName) { + this.displayName = displayName; + } + public static DeviceOS getById(int id) { return id < VALUES.length ? VALUES[id] : VALUES[0]; } + + /** + * @return friendly display name of platform. + */ + @Override + public String toString() { + return displayName; + } } \ No newline at end of file diff --git a/common/src/main/resources/help.txt b/common/src/main/resources/help.txt new file mode 100644 index 00000000..3512ed83 --- /dev/null +++ b/common/src/main/resources/help.txt @@ -0,0 +1,18 @@ + +-------------------------------------------------------------------------------- + + Oops! You attempted to run a plugin version of Geyser directly! + + This jar file is a plugin for ${plugin_type}. You can run this file as a + plugin by dropping the jar file into the "${plugin_folder}" directory. + + There is also a standalone version available that doesn't need to + be installed as a plugin, you can find it on our build server: + + http://ci.geysermc.org/ + + If you need more help, you should check out our discord: + + http://discord.geysermc.org/ + +-------------------------------------------------------------------------------- \ No newline at end of file diff --git a/connector/pom.xml b/connector/pom.xml index 9de533b6..9c503dcc 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -91,27 +91,9 @@ compile - com.github.steveice10 - opennbt - 1.4-SNAPSHOT - compile - - - com.github.steveice10 - packetlib - 1.5-SNAPSHOT - compile - - - io.netty - netty-all - - - - - com.github.steveice10 - mcauthlib - 1.3-SNAPSHOT + com.google.guava + guava + 29.0-jre compile @@ -121,19 +103,17 @@ compile - com.github.steveice10 - opennbt - - - com.github.steveice10 - packetlib - - - com.github.steveice10 - mcauthlib + io.netty + netty-all + + io.netty + netty-resolver-dns + 4.1.43.Final + compile + org.reflections reflections @@ -158,4 +138,83 @@ compile + + + + + pl.project13.maven + git-commit-id-plugin + 4.0.0 + + + get-the-git-infos + + revision + + + + + true + ${project.build.outputDirectory}/git.properties + properties + false + false + false + true + false + + git.user.* + + flat + + true + + + + + com.google.code.maven-replacer-plugin + replacer + 1.5.3 + + + add-version + process-sources + + replace + + + + ${project.basedir}/src/main/java/org/geysermc/connector/GeyserConnector.java + + + + VERSION = ".*" + VERSION = "${project.version} (git-${git.branch}-${git.commit.id.abbrev})" + + + + + + + remove-version + process-classes + + replace + + + + ${project.basedir}/src/main/java/org/geysermc/connector/GeyserConnector.java + + + + VERSION = ".*" + VERSION = "DEV" + + + + + + + + diff --git a/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java b/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java new file mode 100644 index 00000000..0b631b2d --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector; + +import 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) { + if (!Files.exists(floodgateKey) && config.getRemote().getAuthType().equals("floodgate")) { + if (floodgate != null) { + Path autoKey = floodgateFolder.resolve("public-key.pem"); + if (Files.exists(autoKey)) { + logger.info("Auto-loaded floodgate key"); + floodgateKey = autoKey; + } else { + logger.error("Auth-type set to floodgate and the public key is missing!"); + } + } else { + logger.error("Auth-type set to floodgate but floodgate is not installed!"); + } + } + + return floodgateKey; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConfiguration.java b/connector/src/main/java/org/geysermc/connector/GeyserConfiguration.java new file mode 100644 index 00000000..8a39323b --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/GeyserConfiguration.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; + +import java.nio.file.Path; +import java.util.Map; + +public interface GeyserConfiguration { + + // Modify this when you update the config + int CURRENT_CONFIG_VERSION = 3; + + IBedrockConfiguration getBedrock(); + + IRemoteConfiguration getRemote(); + + Map getUserAuths(); + + boolean isCommandSuggestions(); + + boolean isPassthroughMotd(); + + boolean isPassthroughPlayerCounts(); + + boolean isLegacyPingPassthrough(); + + int getPingPassthroughInterval(); + + int getMaxPlayers(); + + boolean isDebugMode(); + + int getGeneralThreadPool(); + + boolean isAllowThirdPartyCapes(); + + boolean isAllowThirdPartyEars(); + + String getDefaultLocale(); + + Path getFloodgateKeyFile(); + + boolean isAboveBedrockNetherBuilding(); + + boolean isCacheChunks(); + + IMetricsInfo getMetrics(); + + interface IBedrockConfiguration { + + String getAddress(); + + int getPort(); + + String getMotd1(); + + String getMotd2(); + } + + interface IRemoteConfiguration { + + String getAddress(); + + int getPort(); + + String getAuthType(); + } + + interface IUserAuthenticationInfo { + String getEmail(); + + String getPassword(); + } + + interface IMetricsInfo { + + boolean isEnabled(); + + String getUniqueId(); + } + + int getConfigVersion(); + + static void checkGeyserConfiguration(GeyserConfiguration geyserConfig, GeyserLogger geyserLogger) { + if (geyserConfig.getConfigVersion() < CURRENT_CONFIG_VERSION) { + geyserLogger.warning("Your Geyser config is out of date! Please regenerate your config when possible."); + } else if (geyserConfig.getConfigVersion() > CURRENT_CONFIG_VERSION) { + geyserLogger.warning("Your Geyser config is too new! Errors may occur."); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 101b66cf..e2f13ea0 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -25,26 +25,35 @@ package org.geysermc.connector; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; import com.nukkitx.protocol.bedrock.BedrockServer; import com.nukkitx.protocol.bedrock.v390.Bedrock_v390; - import lombok.Getter; - import org.geysermc.common.AuthType; -import org.geysermc.common.IGeyserConfiguration; import org.geysermc.common.PlatformType; -import org.geysermc.common.bootstrap.IGeyserBootstrap; -import org.geysermc.common.logger.IGeyserLogger; +import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; import org.geysermc.connector.metrics.Metrics; import org.geysermc.connector.network.ConnectorServerEventHandler; import org.geysermc.connector.network.remote.RemoteServer; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.Translators; -import org.geysermc.connector.thread.PingPassthroughThread; import org.geysermc.connector.utils.ResourcePack; -import org.geysermc.connector.utils.Toolbox; +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.item.ItemRegistry; +import org.geysermc.connector.network.translators.item.ItemTranslator; +import org.geysermc.connector.network.translators.sound.SoundHandlerRegistry; +import org.geysermc.connector.network.translators.world.WorldManager; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.connector.network.translators.effect.EffectRegistry; +import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator; +import org.geysermc.connector.utils.DimensionUtils; +import org.geysermc.connector.utils.DockerCheck; +import org.geysermc.connector.utils.LocaleUtils; +import org.geysermc.connector.network.translators.sound.SoundRegistry; import java.net.InetSocketAddress; import java.text.DecimalFormat; @@ -58,10 +67,12 @@ import java.util.concurrent.TimeUnit; @Getter public class GeyserConnector { + public static final ObjectMapper JSON_MAPPER = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES); + public static final BedrockPacketCodec BEDROCK_PACKET_CODEC = Bedrock_v390.V390_CODEC; public static final String NAME = "Geyser"; - public static final String VERSION = "1.0-SNAPSHOT"; + public static final String VERSION = "DEV"; // A fallback for running in IDEs private final Map players = new HashMap<>(); @@ -73,23 +84,22 @@ public class GeyserConnector { private boolean shuttingDown = false; private final ScheduledExecutorService generalThreadPool; - private PingPassthroughThread passthroughThread; private BedrockServer bedrockServer; private PlatformType platformType; - private IGeyserBootstrap bootstrap; + private GeyserBootstrap bootstrap; private Metrics metrics; - private GeyserConnector(PlatformType platformType, IGeyserBootstrap bootstrap) { + private GeyserConnector(PlatformType platformType, GeyserBootstrap bootstrap) { long startupTime = System.currentTimeMillis(); instance = this; this.bootstrap = bootstrap; - IGeyserLogger logger = bootstrap.getGeyserLogger(); - IGeyserConfiguration config = bootstrap.getGeyserConfig(); + GeyserLogger logger = bootstrap.getGeyserLogger(); + GeyserConfiguration config = bootstrap.getGeyserConfig(); this.platformType = platformType; @@ -103,16 +113,30 @@ public class GeyserConnector { logger.setDebug(config.isDebugMode()); - Translators.start(); - Toolbox.init(); ResourcePack.loadPacks(); + PacketTranslatorRegistry.init(); + + /* Initialize translators and registries */ + BiomeTranslator.init(); + BlockTranslator.init(); + BlockEntityTranslator.init(); + EffectRegistry.init(); + EntityIdentifierRegistry.init(); + ItemRegistry.init(); + ItemTranslator.init(); + LocaleUtils.init(); + SoundRegistry.init(); + SoundHandlerRegistry.init(); + + if (platformType != PlatformType.STANDALONE) { + DockerCheck.check(bootstrap); + } remoteServer = new RemoteServer(config.getRemote().getAddress(), config.getRemote().getPort()); authType = AuthType.getByName(config.getRemote().getAuthType()); - passthroughThread = new PingPassthroughThread(this); - if (config.isPingPassthrough()) - generalThreadPool.scheduleAtFixedRate(passthroughThread, 1, 1, TimeUnit.SECONDS); + if (config.isAboveBedrockNetherBuilding()) + DimensionUtils.changeBedrockNetherId(); // Apply End dimension ID workaround to Nether bedrockServer = new BedrockServer(new InetSocketAddress(config.getBedrock().getAddress(), config.getBedrock().getPort())); bedrockServer.setHandler(new ConnectorServerEventHandler(this)); @@ -193,7 +217,7 @@ public class GeyserConnector { players.remove(player.getSocketAddress()); } - public static GeyserConnector start(PlatformType platformType, IGeyserBootstrap bootstrap) { + public static GeyserConnector start(PlatformType platformType, GeyserBootstrap bootstrap) { return new GeyserConnector(platformType, bootstrap); } @@ -202,16 +226,20 @@ public class GeyserConnector { bootstrap.onEnable(); } - public IGeyserLogger getLogger() { + public GeyserLogger getLogger() { return bootstrap.getGeyserLogger(); } - public IGeyserConfiguration getConfig() { + public GeyserConfiguration getConfig() { return bootstrap.getGeyserConfig(); } public CommandManager getCommandManager() { - return (CommandManager) bootstrap.getGeyserCommandManager(); + return bootstrap.getGeyserCommandManager(); + } + + public WorldManager getWorldManager() { + return bootstrap.getWorldManager(); } public static GeyserConnector getInstance() { diff --git a/connector/src/main/java/org/geysermc/connector/GeyserLogger.java b/connector/src/main/java/org/geysermc/connector/GeyserLogger.java new file mode 100644 index 00000000..4acab222 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/GeyserLogger.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector; + +public interface GeyserLogger { + + /** + * Logs a severe message to console + * + * @param message the message to log + */ + void severe(String message); + + /** + * Logs a severe message and an exception to console + */ + void severe(String message, Throwable error); + + /** + * Logs an error message to console + * + * @param message the message to log + */ + void error(String message); + + /** + * Logs an error message and an exception to console + */ + void error(String message, Throwable error); + + /** + * Logs a warning message to console + * + * @param message the message to log + */ + void warning(String message); + + /** + * Logs an info message to console + * + * @param message the message to log + */ + void info(String message); + + /** + * Logs a debug message to console + * + * @param message the message to log + */ + void debug(String message); + + /** + * Sets if the logger should print debug messages + * + * @param debug if the logger should print debug messages + */ + void setDebug(boolean debug); +} diff --git a/connector/src/main/java/org/geysermc/connector/bootstrap/GeyserBootstrap.java b/connector/src/main/java/org/geysermc/connector/bootstrap/GeyserBootstrap.java new file mode 100644 index 00000000..24ce81cf --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/bootstrap/GeyserBootstrap.java @@ -0,0 +1,86 @@ +/* + * 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.bootstrap; + +import org.geysermc.connector.ping.IGeyserPingPassthrough; +import org.geysermc.connector.GeyserConfiguration; +import org.geysermc.connector.GeyserLogger; +import org.geysermc.connector.command.CommandManager; +import org.geysermc.connector.network.translators.world.CachedChunkManager; +import org.geysermc.connector.network.translators.world.WorldManager; + +public interface GeyserBootstrap { + + CachedChunkManager DEFAULT_CHUNK_MANAGER = new CachedChunkManager(); + + /** + * Called when the GeyserBootstrap is enabled + */ + void onEnable(); + + /** + * Called when the GeyserBootstrap is disabled + */ + void onDisable(); + + /** + * Returns the current GeyserConfiguration + * + * @return The current GeyserConfiguration + */ + GeyserConfiguration getGeyserConfig(); + + /** + * Returns the current GeyserLogger + * + * @return The current GeyserLogger + */ + GeyserLogger getGeyserLogger(); + + /** + * Returns the current CommandManager + * + * @return The current CommandManager + */ + CommandManager getGeyserCommandManager(); + + /** + * Returns the current PingPassthrough manager + * + * @return The current PingPassthrough manager + */ + IGeyserPingPassthrough getGeyserPingPassthrough(); + + /** + * Returns the current WorldManager + * + * @return the current WorldManager + */ + default WorldManager getWorldManager() { + return DEFAULT_CHUNK_MANAGER; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java index 7b1b4d69..88b9e795 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java @@ -26,7 +26,7 @@ package org.geysermc.connector.command; import lombok.Getter; -import org.geysermc.common.command.ICommandManager; + import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.defaults.*; @@ -34,7 +34,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -public abstract class CommandManager implements ICommandManager { +public abstract class CommandManager { @Getter private final Map commands = Collections.synchronizedMap(new HashMap<>()); @@ -87,4 +87,12 @@ public abstract class CommandManager implements ICommandManager { cmd.execute(sender, args); } + + /** + * Returns the description of the given command + * + * @param command Command to get the description for + * @return Command description + */ + public abstract String getDescription(String command); } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java index d181d07e..a49506b0 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java @@ -55,7 +55,17 @@ public class OffhandCommand extends GeyserCommand { GeyserSession session = (GeyserSession) sender; ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.SWAP_HANDS, new Position(0,0,0), BlockFace.DOWN); - session.getDownstream().getSession().send(releaseItemPacket); + session.sendDownstreamPacket(releaseItemPacket); + return; + } + // Needed for Bukkit - sender is not an instance of GeyserSession + for (GeyserSession session : connector.getPlayers().values()) { + if (sender.getName().equals(session.getPlayerEntity().getUsername())) { + ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.SWAP_HANDS, new Position(0,0,0), + BlockFace.DOWN); + session.sendDownstreamPacket(releaseItemPacket); + break; + } } } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java b/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java new file mode 100644 index 00000000..fa089707 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class AbstractArrowEntity extends Entity { + + public AbstractArrowEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 7) { + byte data = (byte) entityMetadata.getValue(); + + metadata.getFlags().setFlag(EntityFlag.CRITICAL, (data & 0x01) == 0x01); + } + + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java b/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java new file mode 100644 index 00000000..79e67f34 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.world.particle.Particle; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityData; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.effect.EffectRegistry; + +public class AreaEffectCloudEntity extends Entity { + + public AreaEffectCloudEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + + // Without this the cloud doesn't appear, + metadata.put(EntityData.AREA_EFFECT_CLOUD_DURATION, 600); + + // This disabled client side shrink of the cloud + metadata.put(EntityData.AREA_EFFECT_CLOUD_RADIUS_PER_TICK, 0.0f); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 7) { + metadata.put(EntityData.AREA_EFFECT_CLOUD_RADIUS, (float) entityMetadata.getValue()); + metadata.put(EntityData.BOUNDING_BOX_WIDTH, 2.0f * (float) entityMetadata.getValue()); + } else if (entityMetadata.getId() == 10) { + Particle particle = (Particle) entityMetadata.getValue(); + metadata.put(EntityData.AREA_EFFECT_CLOUD_PARTICLE_ID, EffectRegistry.getParticleString(particle.getType())); + } else if (entityMetadata.getId() == 8) { + metadata.put(EntityData.POTION_COLOR, entityMetadata.getValue()); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java new file mode 100644 index 00000000..8f79526d --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityData; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +import java.util.concurrent.TimeUnit; + +public class BoatEntity extends Entity { + + private boolean isPaddlingLeft; + private float paddleTimeLeft; + private boolean isPaddlingRight; + private float paddleTimeRight; + + // Looks too fast and too choppy with 0.1f, which is how I believe the Microsoftian client handles it + private final float ROWING_SPEED = 0.05f; + + public BoatEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position.add(0d, entityType.getOffset(), 0d), motion, rotation.add(0, 0, 90)); + } + + @Override + public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { + // Rotation is basically only called when entering/exiting a boat. + // We don't include the rotation (y) as it causes the boat to appear sideways + super.moveAbsolute(session, position.add(0d, this.entityType.getOffset(), 0d), Vector3f.from(0, 0, rotation.getZ() + 90), isOnGround, teleported); + } + + @Override + public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) { + super.moveRelative(session, relX, relY, relZ, Vector3f.from(0, 0, rotation.getZ()), isOnGround); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + + // Time since last hit + if (entityMetadata.getId() == 7) { + metadata.put(EntityData.HURT_TIME, entityMetadata.getValue()); + } + + // Rocking direction + if (entityMetadata.getId() == 8) { + metadata.put(EntityData.HURT_DIRECTION, entityMetadata.getValue()); + } + + // 'Health' in Bedrock, damage taken in Java + if (entityMetadata.getId() == 9) { + // Not exactly health but it makes motion in Bedrock + metadata.put(EntityData.HEALTH, 40 - ((int) (float) entityMetadata.getValue())); + } + + if (entityMetadata.getId() == 10) { + metadata.put(EntityData.VARIANT, entityMetadata.getValue()); + } else if (entityMetadata.getId() == 11) { + isPaddlingLeft = (boolean) entityMetadata.getValue(); + if (!isPaddlingLeft) { + metadata.put(EntityData.PADDLE_TIME_LEFT, 0f); + } + else { + // Java sends simply "true" and "false" (is_paddling_left), Bedrock keeps sending packets as you're rowing + // This is an asynchronous method that emulates Bedrock rowing until "false" is sent. + paddleTimeLeft = 0f; + session.getConnector().getGeneralThreadPool().execute(() -> + updateLeftPaddle(session, entityMetadata) + ); + } + } + else if (entityMetadata.getId() == 12) { + isPaddlingRight = (boolean) entityMetadata.getValue(); + if (!isPaddlingRight) { + metadata.put(EntityData.PADDLE_TIME_RIGHT, 0f); + } else { + paddleTimeRight = 0f; + session.getConnector().getGeneralThreadPool().execute(() -> + updateRightPaddle(session, entityMetadata) + ); + } + } else if (entityMetadata.getId() == 13) { + // Possibly - I don't think this does anything? + metadata.put(EntityData.BOAT_BUBBLE_TIME, entityMetadata.getValue()); + } + + super.updateBedrockMetadata(entityMetadata, session); + } + + public void updateLeftPaddle(GeyserSession session, EntityMetadata entityMetadata) { + if (isPaddlingLeft) { + paddleTimeLeft += ROWING_SPEED; + metadata.put(EntityData.PADDLE_TIME_LEFT, paddleTimeLeft); + super.updateBedrockMetadata(entityMetadata, session); + session.getConnector().getGeneralThreadPool().schedule(() -> + updateLeftPaddle(session, entityMetadata), + 100, + TimeUnit.MILLISECONDS + ); + }} + + public void updateRightPaddle(GeyserSession session, EntityMetadata entityMetadata) { + if (isPaddlingRight) { + paddleTimeRight += ROWING_SPEED; + metadata.put(EntityData.PADDLE_TIME_RIGHT, paddleTimeRight); + super.updateBedrockMetadata(entityMetadata, session); + session.getConnector().getGeneralThreadPool().schedule(() -> + updateRightPaddle(session, entityMetadata), + 100, + TimeUnit.MILLISECONDS + ); + }} +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/EnderCrystalEntity.java b/connector/src/main/java/org/geysermc/connector/entity/EnderCrystalEntity.java index b3ce2278..727df90c 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/EnderCrystalEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/EnderCrystalEntity.java @@ -31,7 +31,6 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.EntityData; import com.nukkitx.protocol.bedrock.data.EntityFlag; -import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -60,23 +59,4 @@ public class EnderCrystalEntity extends Entity { } super.updateBedrockMetadata(entityMetadata, session); } - - @Override - public void spawnEntity(GeyserSession session) { - AddEntityPacket addEntityPacket = new AddEntityPacket(); - // Not end crystal but ender crystal - addEntityPacket.setIdentifier("minecraft:ender_crystal"); - addEntityPacket.setRuntimeEntityId(geyserId); - addEntityPacket.setUniqueEntityId(geyserId); - addEntityPacket.setPosition(position); - addEntityPacket.setMotion(motion); - addEntityPacket.setRotation(getBedrockRotation()); - addEntityPacket.setEntityType(entityType.getType()); - addEntityPacket.getMetadata().putAll(metadata); - - valid = true; - session.getUpstream().sendPacket(addEntityPacket); - - session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); - } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index 5596ca7a..06679934 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -27,32 +27,40 @@ package org.geysermc.connector.entity; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; import com.github.steveice10.mc.protocol.data.message.TextMessage; +import com.github.steveice10.mc.protocol.data.message.TranslationMessage; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerUseItemPacket; import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.*; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.data.EntityData; +import com.nukkitx.protocol.bedrock.data.EntityDataMap; +import com.nukkitx.protocol.bedrock.data.EntityFlag; +import com.nukkitx.protocol.bedrock.data.EntityFlags; import com.nukkitx.protocol.bedrock.packet.*; - import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import it.unimi.dsi.fastutil.longs.LongSet; - import lombok.Getter; import lombok.Setter; - import org.geysermc.connector.entity.attribute.Attribute; import org.geysermc.connector.entity.attribute.AttributeType; +import org.geysermc.connector.entity.living.ArmorStandEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.item.ItemTranslator; +import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.utils.AttributeUtils; +import org.geysermc.connector.utils.ChunkUtils; import org.geysermc.connector.utils.MessageUtils; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; @Getter @Setter @@ -76,7 +84,7 @@ public class Entity { protected boolean valid; - protected LongSet passengers = new LongOpenHashSet(); + protected LongOpenHashSet passengers = new LongOpenHashSet(); protected Map attributes = new HashMap<>(); protected EntityDataMap metadata = new EntityDataMap(); @@ -93,7 +101,8 @@ public class Entity { setPosition(position); metadata.put(EntityData.SCALE, 1f); - metadata.put(EntityData.MAX_AIR, (short) 400); + metadata.put(EntityData.COLOR, 0); + metadata.put(EntityData.MAX_AIR, (short) 300); metadata.put(EntityData.AIR, (short) 0); metadata.put(EntityData.LEAD_HOLDER_EID, -1L); metadata.put(EntityData.BOUNDING_BOX_HEIGHT, entityType.getHeight()); @@ -108,7 +117,7 @@ public class Entity { public void spawnEntity(GeyserSession session) { AddEntityPacket addEntityPacket = new AddEntityPacket(); - addEntityPacket.setIdentifier("minecraft:" + entityType.name().toLowerCase()); + addEntityPacket.setIdentifier(entityType.getIdentifier()); addEntityPacket.setRuntimeEntityId(geyserId); addEntityPacket.setUniqueEntityId(geyserId); addEntityPacket.setPosition(position); @@ -118,7 +127,7 @@ public class Entity { addEntityPacket.getMetadata().putAll(metadata); valid = true; - session.getUpstream().sendPacket(addEntityPacket); + session.sendUpstreamPacket(addEntityPacket); session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); } @@ -134,7 +143,7 @@ public class Entity { RemoveEntityPacket removeEntityPacket = new RemoveEntityPacket(); removeEntityPacket.setUniqueEntityId(geyserId); - session.getUpstream().sendPacket(removeEntityPacket); + session.sendUpstreamPacket(removeEntityPacket); valid = false; return true; @@ -155,14 +164,14 @@ public class Entity { moveEntityPacket.setOnGround(isOnGround); moveEntityPacket.setTeleported(false); - session.getUpstream().sendPacket(moveEntityPacket); + session.sendUpstreamPacket(moveEntityPacket); } - public void moveAbsolute(GeyserSession session, Vector3f position, float yaw, float pitch, boolean isOnGround) { - moveAbsolute(session, position, Vector3f.from(yaw, pitch, yaw), isOnGround); + public void moveAbsolute(GeyserSession session, Vector3f position, float yaw, float pitch, boolean isOnGround, boolean teleported) { + moveAbsolute(session, position, Vector3f.from(yaw, pitch, yaw), isOnGround, teleported); } - public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround) { + public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { setPosition(position); setRotation(rotation); @@ -171,9 +180,9 @@ public class Entity { moveEntityPacket.setPosition(position); moveEntityPacket.setRotation(getBedrockRotation()); moveEntityPacket.setOnGround(isOnGround); - moveEntityPacket.setTeleported(false); + moveEntityPacket.setTeleported(teleported); - session.getUpstream().sendPacket(moveEntityPacket); + session.sendUpstreamPacket(moveEntityPacket); } public void updateBedrockAttributes(GeyserSession session) { @@ -190,7 +199,7 @@ public class Entity { UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket(); updateAttributesPacket.setRuntimeEntityId(geyserId); updateAttributesPacket.setAttributes(attributes); - session.getUpstream().sendPacket(updateAttributesPacket); + session.sendUpstreamPacket(updateAttributesPacket); } public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { @@ -204,38 +213,56 @@ public class Entity { metadata.getFlags().setFlag(EntityFlag.SWIMMING, (xd & 0x10) == 0x10); metadata.getFlags().setFlag(EntityFlag.GLIDING, (xd & 0x80) == 0x80); + if ((xd & 0x20) == 0x20) { + if (this.is(ArmorStandEntity.class)) { + metadata.put(EntityData.SCALE, 0.0f); + } else { + metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true); + } + } else { + metadata.getFlags().setFlag(EntityFlag.INVISIBLE, false); + } + // Shield code if (session.getPlayerEntity().getEntityId() == entityId && metadata.getFlags().getFlag(EntityFlag.SNEAKING)) { - if ((session.getInventory().getItemInHand() != null && session.getInventory().getItemInHand().getId() == ItemTranslator.SHIELD) || - (session.getInventoryCache().getPlayerInventory().getItem(45) != null && session.getInventoryCache().getPlayerInventory().getItem(45).getId() == ItemTranslator.SHIELD)) { + if ((session.getInventory().getItemInHand() != null && session.getInventory().getItemInHand().getId() == ItemRegistry.SHIELD) || + (session.getInventoryCache().getPlayerInventory().getItem(45) != null && session.getInventoryCache().getPlayerInventory().getItem(45).getId() == ItemRegistry.SHIELD)) { ClientPlayerUseItemPacket useItemPacket; metadata.getFlags().setFlag(EntityFlag.BLOCKING, true); - if (session.getInventory().getItemInHand() != null && session.getInventory().getItemInHand().getId() == ItemTranslator.SHIELD) { + if (session.getInventory().getItemInHand() != null && session.getInventory().getItemInHand().getId() == ItemRegistry.SHIELD) { useItemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND); } // Else we just assume it's the offhand, to simplify logic and to assure the packet gets sent else { useItemPacket = new ClientPlayerUseItemPacket(Hand.OFF_HAND); } - session.getDownstream().getSession().send(useItemPacket); + session.sendDownstreamPacket(useItemPacket); } } else if (session.getPlayerEntity().getEntityId() == entityId && !metadata.getFlags().getFlag(EntityFlag.SNEAKING) && metadata.getFlags().getFlag(EntityFlag.BLOCKING)) { - metadata.getFlags().setFlag(EntityFlag.BLOCKING, false); - metadata.getFlags().setFlag(EntityFlag.DISABLE_BLOCKING, true); - ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, new Position(0,0,0), BlockFace.DOWN); - session.getDownstream().getSession().send(releaseItemPacket); - } - // metadata.getFlags().setFlag(EntityFlag.INVISIBLE, (xd & 0x20) == 0x20); - if ((xd & 0x20) == 0x20) - metadata.put(EntityData.SCALE, 0.0f); - else - metadata.put(EntityData.SCALE, scale); + metadata.getFlags().setFlag(EntityFlag.BLOCKING, false); + metadata.getFlags().setFlag(EntityFlag.DISABLE_BLOCKING, true); + ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, new Position(0, 0, 0), BlockFace.DOWN); + session.sendDownstreamPacket(releaseItemPacket); + } + } + break; + case 1: // Air/bubbles + if ((int) entityMetadata.getValue() == 300) { + metadata.put(EntityData.AIR, (short) 0); // Otherwise the bubble counter remains in the UI + } else { + metadata.put(EntityData.AIR, (short) (int) entityMetadata.getValue()); } break; case 2: // custom name - TextMessage name = (TextMessage) entityMetadata.getValue(); - if (name != null) - metadata.put(EntityData.NAMETAG, MessageUtils.getBedrockMessage(name)); + if (entityMetadata.getValue() instanceof TextMessage) { + TextMessage name = (TextMessage) entityMetadata.getValue(); + if (name != null) + metadata.put(EntityData.NAMETAG, MessageUtils.getBedrockMessage(name)); + } else if (entityMetadata.getValue() instanceof TranslationMessage) { + TranslationMessage message = (TranslationMessage) entityMetadata.getValue(); + if (message != null) + metadata.put(EntityData.NAMETAG, MessageUtils.getTranslatedBedrockMessage(message, session.getClientData().getLanguageCode(), true)); + } break; case 3: // is custom name visible if (!this.is(PlayerEntity.class)) @@ -247,6 +274,32 @@ public class Entity { case 5: // no gravity metadata.getFlags().setFlag(EntityFlag.HAS_GRAVITY, !(boolean) entityMetadata.getValue()); break; + case 6: // Pose change + if (entityMetadata.getValue().equals(Pose.SLEEPING)) { + metadata.getFlags().setFlag(EntityFlag.SLEEPING, true); + // Has to be a byte or it does not work + metadata.put(EntityData.CAN_START_SLEEP, (byte) 2); + if (entityId == session.getPlayerEntity().getEntityId()) { + Vector3i lastInteractionPos = session.getLastInteractionPosition(); + metadata.put(EntityData.BED_RESPAWN_POS, lastInteractionPos); + if (session.getConnector().getConfig().isCacheChunks()) { + BlockState bed = session.getConnector().getWorldManager().getBlockAt(session, lastInteractionPos.getX(), + lastInteractionPos.getY(), lastInteractionPos.getZ()); + // Bed has to be updated, or else player is floating in the air + ChunkUtils.updateBlock(session, bed, lastInteractionPos); + } + } else { + metadata.put(EntityData.BED_RESPAWN_POS, Vector3i.from(position.getFloorX(), position.getFloorY() - 2, position.getFloorZ())); + } + metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.2f); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.2f); + } else if (metadata.getFlags().getFlag(EntityFlag.SLEEPING)) { + metadata.getFlags().setFlag(EntityFlag.SLEEPING, false); + metadata.put(EntityData.BOUNDING_BOX_WIDTH, getEntityType().getWidth()); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, getEntityType().getHeight()); + metadata.put(EntityData.CAN_START_SLEEP, (byte) 0); + } + break; case 7: // blocking if (entityMetadata.getType() == MetadataType.BYTE) { byte xd = (byte) entityMetadata.getValue(); @@ -264,11 +317,12 @@ public class Entity { SetEntityDataPacket entityDataPacket = new SetEntityDataPacket(); entityDataPacket.setRuntimeEntityId(geyserId); entityDataPacket.getMetadata().putAll(metadata); - session.getUpstream().sendPacket(entityDataPacket); + session.sendUpstreamPacket(entityDataPacket); } /** * x = Pitch, y = HeadYaw, z = Yaw + * * @return the bedrock rotation */ public Vector3f getBedrockRotation() { diff --git a/connector/src/main/java/org/geysermc/connector/entity/ExpOrbEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ExpOrbEntity.java index 03c9ffe3..bfd3e1ca 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ExpOrbEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ExpOrbEntity.java @@ -26,7 +26,7 @@ package org.geysermc.connector.entity; import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.packet.SpawnExperienceOrbPacket; +import com.nukkitx.protocol.bedrock.data.EntityData; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -42,11 +42,7 @@ public class ExpOrbEntity extends Entity { @Override public void spawnEntity(GeyserSession session) { - SpawnExperienceOrbPacket spawnExpOrbPacket = new SpawnExperienceOrbPacket(); - spawnExpOrbPacket.setPosition(position); - spawnExpOrbPacket.setAmount(amount); - - valid = true; - session.getUpstream().sendPacket(spawnExpOrbPacket); + this.metadata.put(EntityData.EXPERIENCE_VALUE, amount); + super.spawnEntity(session); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/FallingBlockEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FallingBlockEntity.java index 5a0cac8f..59e1d408 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FallingBlockEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FallingBlockEntity.java @@ -28,7 +28,7 @@ package org.geysermc.connector.entity; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.EntityData; import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.translators.block.BlockTranslator; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; public class FallingBlockEntity extends Entity { diff --git a/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java new file mode 100644 index 00000000..1db4f757 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.protocol.bedrock.data.EntityData; +import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.FireworkColor; +import org.geysermc.connector.utils.MathUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.OptionalInt; + +public class FireworkEntity extends Entity { + + public FireworkEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 7) { + ItemStack item = (ItemStack) entityMetadata.getValue(); + CompoundTag tag = item.getNbt(); + + if (tag == null) { + return; + } + + CompoundTag fireworks = tag.get("Fireworks"); + + CompoundTagBuilder fireworksBuilder = CompoundTagBuilder.builder(); + if (fireworks.get("Flight") != null) { + fireworksBuilder.byteTag("Flight", MathUtils.convertByte(fireworks.get("Flight").getValue())); + } + + List explosions = new ArrayList<>(); + if (fireworks.get("Explosions") != null) { + for (Tag effect : ((ListTag) fireworks.get("Explosions")).getValue()) { + CompoundTag effectData = (CompoundTag) effect; + CompoundTagBuilder effectBuilder = CompoundTagBuilder.builder(); + + if (effectData.get("Type") != null) { + effectBuilder.byteTag("FireworkType", MathUtils.convertByte(effectData.get("Type").getValue())); + } + + if (effectData.get("Colors") != null) { + int[] oldColors = (int[]) effectData.get("Colors").getValue(); + byte[] colors = new byte[oldColors.length]; + + int i = 0; + for (int color : oldColors) { + colors[i++] = FireworkColor.fromJavaID(color).getBedrockID(); + } + + effectBuilder.byteArrayTag("FireworkColor", colors); + } + + if (effectData.get("FadeColors") != null) { + int[] oldColors = (int[]) effectData.get("FadeColors").getValue(); + byte[] colors = new byte[oldColors.length]; + + int i = 0; + for (int color : oldColors) { + colors[i++] = FireworkColor.fromJavaID(color).getBedrockID(); + } + + effectBuilder.byteArrayTag("FireworkFade", colors); + } + + if (effectData.get("Trail") != null) { + effectBuilder.byteTag("FireworkTrail", MathUtils.convertByte(effectData.get("Trail").getValue())); + } + + if (effectData.get("Flicker") != null) { + effectBuilder.byteTag("FireworkFlicker", MathUtils.convertByte(effectData.get("Flicker").getValue())); + } + + explosions.add(effectBuilder.buildRootTag()); + } + } + + fireworksBuilder.tag(new com.nukkitx.nbt.tag.ListTag<>("Explosions", com.nukkitx.nbt.tag.CompoundTag.class, explosions)); + + metadata.put(EntityData.DISPLAY_ITEM, CompoundTagBuilder.builder().tag(fireworksBuilder.build("Fireworks")).buildRootTag()); + } else if (entityMetadata.getId() == 8 && !entityMetadata.getValue().equals(OptionalInt.empty()) && ((OptionalInt) entityMetadata.getValue()).getAsInt() == session.getPlayerEntity().getEntityId()) { + //Checks if the firework has an entity ID (used when a player is gliding) and checks to make sure the player that is gliding is the one getting sent the packet or else every player near the gliding player will boost too. + PlayerEntity entity = session.getPlayerEntity(); + float yaw = entity.getRotation().getX(); + float pitch = entity.getRotation().getY(); + //Uses math from NukkitX + entity.setMotion(Vector3f.from( + -Math.sin(Math.toRadians(yaw)) * Math.cos(Math.toRadians(pitch)) * 2, + -Math.sin(Math.toRadians(pitch)) * 2, + Math.cos(Math.toRadians(yaw)) * Math.cos(Math.toRadians(pitch)) * 2)); + //Need to update the EntityMotionPacket or else the player won't boost + SetEntityMotionPacket entityMotionPacket = new SetEntityMotionPacket(); + entityMotionPacket.setRuntimeEntityId(entity.getGeyserId()); + entityMotionPacket.setMotion(entity.getMotion()); + + session.sendUpstreamPacket(entityMotionPacket); + } + + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java index 3a77292f..1b648f7c 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java @@ -25,32 +25,44 @@ package org.geysermc.connector.entity; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.object.ProjectileData; import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; +import com.nukkitx.protocol.bedrock.data.EntityData; +import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; public class FishingHookEntity extends Entity { - public FishingHookEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + public FishingHookEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, ProjectileData data) { super(entityId, geyserId, entityType, position, motion, rotation); + + for (GeyserSession session : GeyserConnector.getInstance().getPlayers().values()) { + Entity entity = session.getEntityCache().getEntityByJavaId(data.getOwnerId()); + if (entity == null && session.getPlayerEntity().getEntityId() == data.getOwnerId()) { + entity = session.getPlayerEntity(); + } + + if (entity != null) { + this.metadata.put(EntityData.OWNER_EID, entity.getGeyserId()); + return; + } + } } @Override - public void spawnEntity(GeyserSession session) { - AddEntityPacket addEntityPacket = new AddEntityPacket(); - // Different ID in Bedrock - addEntityPacket.setIdentifier("minecraft:fishing_hook"); - addEntityPacket.setRuntimeEntityId(geyserId); - addEntityPacket.setUniqueEntityId(geyserId); - addEntityPacket.setPosition(position); - addEntityPacket.setMotion(motion); - addEntityPacket.setRotation(getBedrockRotation()); - addEntityPacket.setEntityType(entityType.getType()); - addEntityPacket.getMetadata().putAll(metadata); + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 7) { + Entity entity = session.getEntityCache().getEntityByJavaId((Integer) entityMetadata.getValue() - 1); + if (entity == null && session.getPlayerEntity().getEntityId() == (Integer) entityMetadata.getValue() - 1) { + entity = session.getPlayerEntity(); + } - valid = true; - session.getUpstream().sendPacket(addEntityPacket); + if (entity != null) { + metadata.put(EntityData.TARGET_EID, entity.getGeyserId()); + } + } - session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); + super.updateBedrockMetadata(entityMetadata, session); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.java new file mode 100644 index 00000000..ad9be77f --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityData; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; + +public class FurnaceMinecartEntity extends MinecartEntity { + + private int customBlock = 0; + private int customBlockOffset = 0; + private boolean showCustomBlock = false; + private boolean hasFuel = false; + + public FurnaceMinecartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + + metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId(BlockTranslator.JAVA_RUNTIME_FURNACE_ID)); + metadata.put(EntityData.HAS_DISPLAY, (byte) 1); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + + // Custom block + if (entityMetadata.getId() == 10) { + customBlock = (int) entityMetadata.getValue(); + } + + // Custom block offset + if (entityMetadata.getId() == 11) { + customBlockOffset = (int) entityMetadata.getValue(); + } + + // If the custom block should be enabled + if (entityMetadata.getId() == 12) { + if ((boolean) entityMetadata.getValue()) { + showCustomBlock = true; + metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId(customBlock)); + metadata.put(EntityData.DISPLAY_OFFSET, customBlockOffset); + } else { + showCustomBlock = false; + updateFurnaceMetadata(); + } + } + + if (entityMetadata.getId() == 13 && !showCustomBlock) { + hasFuel = (boolean) entityMetadata.getValue(); + updateFurnaceMetadata(); + } + + super.updateBedrockMetadata(entityMetadata, session); + } + + private void updateFurnaceMetadata() { + metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId(hasFuel ? BlockTranslator.JAVA_RUNTIME_FURNACE_LIT_ID : BlockTranslator.JAVA_RUNTIME_FURNACE_ID)); + metadata.put(EntityData.DISPLAY_OFFSET, 6); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemEntity.java index a28b563b..41308a0d 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemEntity.java @@ -31,7 +31,7 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.packet.AddItemEntityPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.Translators; +import org.geysermc.connector.network.translators.item.ItemTranslator; public class ItemEntity extends Entity { @@ -49,8 +49,8 @@ public class ItemEntity extends Entity { itemPacket.setUniqueEntityId(geyserId); itemPacket.setFromFishing(false); itemPacket.getMetadata().putAll(metadata); - itemPacket.setItemInHand(Translators.getItemTranslator().translateToBedrock(session, (ItemStack) entityMetadata.getValue())); - session.getUpstream().sendPacket(itemPacket); + itemPacket.setItemInHand(ItemTranslator.translateToBedrock(session, (ItemStack) entityMetadata.getValue())); + session.sendUpstreamPacket(itemPacket); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java new file mode 100644 index 00000000..3680945c --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.entity.object.HangingDirection; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.protocol.bedrock.data.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; +import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.item.ItemTranslator; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; + +import java.util.concurrent.TimeUnit; + +/** + * Item frames are an entity in Java but a block entity in Bedrock. + */ +public class ItemFrameEntity extends Entity { + + /** + * Used for getting the Bedrock block position. + * Blocks deal with integers whereas entities deal with floats. + */ + private final Vector3i bedrockPosition; + /** + * Specific block 'state' we are emulating in Bedrock. + */ + private final int bedrockRuntimeId; + /** + * Rotation of item in frame. + */ + private float rotation = 0.0f; + /** + * Cached item frame's Bedrock compound tag. + */ + private CompoundTag cachedTag; + + public ItemFrameEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, HangingDirection direction) { + super(entityId, geyserId, entityType, position, motion, rotation); + CompoundTagBuilder builder = CompoundTag.builder(); + builder.tag(CompoundTag.builder() + .stringTag("name", "minecraft:frame") + .intTag("version", BlockTranslator.getBlockStateVersion()) + .tag(CompoundTag.builder() + .intTag("facing_direction", direction.ordinal()) + .byteTag("item_frame_map_bit", (byte) 0) + .build("states")) + .build("block")); + builder.shortTag("id", (short) 199); + bedrockRuntimeId = BlockTranslator.getItemFrame(builder.buildRootTag()); + bedrockPosition = Vector3i.from(position.getFloorX(), position.getFloorY(), position.getFloorZ()); + } + + @Override + public void spawnEntity(GeyserSession session) { + session.getItemFrameCache().put(bedrockPosition, entityId); + // Delay is required, or else loading in frames on chunk load is sketchy at best + session.getConnector().getGeneralThreadPool().schedule(() -> { + updateBlock(session); + session.getConnector().getLogger().debug("Spawned item frame at location " + bedrockPosition + " with java id " + entityId); + }, 500, TimeUnit.MILLISECONDS); + valid = true; + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 7 && entityMetadata.getValue() != null) { + ItemData itemData = ItemTranslator.translateToBedrock(session, (ItemStack) entityMetadata.getValue()); + ItemEntry itemEntry = ItemRegistry.getItem((ItemStack) entityMetadata.getValue()); + CompoundTagBuilder builder = CompoundTag.builder(); + + String blockName = ""; + for (StartGamePacket.ItemEntry startGamePacketItemEntry : ItemRegistry.ITEMS) { + if (startGamePacketItemEntry.getId() == (short) itemEntry.getBedrockId()) { + blockName = startGamePacketItemEntry.getIdentifier(); + break; + } + } + + builder.byteTag("Count", (byte) itemData.getCount()); + if (itemData.getTag() != null) { + builder.tag(itemData.getTag().toBuilder().build("tag")); + } + builder.shortTag("Damage", itemData.getDamage()); + builder.stringTag("Name", blockName); + CompoundTagBuilder tag = getDefaultTag().toBuilder(); + tag.tag(builder.build("Item")); + tag.floatTag("ItemDropChance", 1.0f); + tag.floatTag("ItemRotation", rotation); + cachedTag = tag.buildRootTag(); + updateBlock(session); + } + else if (entityMetadata.getId() == 7 && entityMetadata.getValue() == null && cachedTag != null) { + cachedTag = getDefaultTag(); + updateBlock(session); + } + else if (entityMetadata.getId() == 8) { + rotation = ((int) entityMetadata.getValue()) * 45; + if (cachedTag == null) { + updateBlock(session); + return; + } + CompoundTagBuilder builder = cachedTag.toBuilder(); + builder.floatTag("ItemRotation", rotation); + cachedTag = builder.buildRootTag(); + updateBlock(session); + } + else { + updateBlock(session); + } + } + + @Override + public boolean despawnEntity(GeyserSession session) { + UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); + updateBlockPacket.setDataLayer(0); + updateBlockPacket.setBlockPosition(bedrockPosition); + updateBlockPacket.setRuntimeId(0); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NONE); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); + session.sendUpstreamPacket(updateBlockPacket); + session.getItemFrameCache().remove(position, entityId); + valid = false; + return true; + } + + private CompoundTag getDefaultTag() { + CompoundTagBuilder builder = CompoundTag.builder(); + builder.intTag("x", bedrockPosition.getX()); + builder.intTag("y", bedrockPosition.getY()); + builder.intTag("z", bedrockPosition.getZ()); + builder.byteTag("isMovable", (byte) 1); + builder.stringTag("id", "ItemFrame"); + return builder.buildRootTag(); + } + + /** + * Updates the item frame as a block + * @param session GeyserSession. + */ + public void updateBlock(GeyserSession session) { + UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); + updateBlockPacket.setDataLayer(0); + updateBlockPacket.setBlockPosition(bedrockPosition); + updateBlockPacket.setRuntimeId(bedrockRuntimeId); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NONE); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); + session.sendUpstreamPacket(updateBlockPacket); + + BlockEntityDataPacket blockEntityDataPacket = new BlockEntityDataPacket(); + blockEntityDataPacket.setBlockPosition(bedrockPosition); + if (cachedTag != null) { + blockEntityDataPacket.setData(cachedTag); + } else { + blockEntityDataPacket.setData(getDefaultTag()); + } + + session.sendUpstreamPacket(blockEntityDataPacket); + } + + /** + * Finds the Java entity ID of an item frame from its Bedrock position. + * @param position position of item frame in Bedrock. + * @param session GeyserSession. + * @return Java entity ID or -1 if not found. + */ + public static long getItemFrameEntityId(GeyserSession session, Vector3i position) { + return session.getItemFrameCache().getOrDefault(position, -1); + } + + /** + * Determines if the position contains an item frame. + * Does largely the same thing as getItemFrameEntityId, but for speed purposes is implemented separately, + * since every block destroy packet has to check for an item frame. + * @param position position of block. + * @param session GeyserSession. + * @return true if position contains item frame, false if not. + */ + public static boolean positionContainsItemFrame(GeyserSession session, Vector3i position) { + return session.getItemFrameCache().containsKey(position); + } + + /** + * Force-remove from the position-to-ID map so it doesn't cause conflicts. + * @param session GeyserSession. + * @param position position of the removed item frame. + */ + public static void removePosition(GeyserSession session, Vector3i position) { + session.getItemFrameCache().remove(position); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/LeashKnotEntity.java b/connector/src/main/java/org/geysermc/connector/entity/LeashKnotEntity.java new file mode 100644 index 00000000..0bec07b0 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/LeashKnotEntity.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.entity; + +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.connector.entity.type.EntityType; + +public class LeashKnotEntity extends Entity { + + public LeashKnotEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + // Position is incorrect by default + super(entityId, geyserId, entityType, position.add(0.5f, 0.25f, 0.5f), motion, rotation); + } + +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java b/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java index 40b30f70..f5aa4a54 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java @@ -96,8 +96,8 @@ public class LivingEntity extends Entity { offHandPacket.setInventorySlot(0); offHandPacket.setContainerId(ContainerId.OFFHAND); - session.getUpstream().sendPacket(armorEquipmentPacket); - session.getUpstream().sendPacket(handPacket); - session.getUpstream().sendPacket(offHandPacket); + session.sendUpstreamPacket(armorEquipmentPacket); + session.sendUpstreamPacket(handPacket); + session.sendUpstreamPacket(offHandPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java index 58c887ee..cdc3d8f2 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java @@ -25,12 +25,59 @@ package org.geysermc.connector.entity; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityData; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; public class MinecartEntity extends Entity { public MinecartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + super(entityId, geyserId, entityType, position.add(0d, entityType.getOffset(), 0d), motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + + if (entityMetadata.getId() == 7) { + metadata.put(EntityData.HEALTH, entityMetadata.getValue()); + } + + // Direction in which the minecart is shaking + if (entityMetadata.getId() == 8) { + metadata.put(EntityData.HURT_DIRECTION, entityMetadata.getValue()); + } + + // Power in Java, time in Bedrock + if (entityMetadata.getId() == 9) { + metadata.put(EntityData.HURT_TIME, Math.min((int) (float) entityMetadata.getValue(), 15)); + } + + if (!(this instanceof FurnaceMinecartEntity)) { // Handled in FurnaceMinecartEntity.java + // Custom block + if (entityMetadata.getId() == 10) { + metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId((int) entityMetadata.getValue())); + } + + // Custom block offset + if (entityMetadata.getId() == 11) { + metadata.put(EntityData.DISPLAY_OFFSET, entityMetadata.getValue()); + } + + // If the custom block should be enabled + if (entityMetadata.getId() == 12) { + // Needs a byte based off of Java's boolean + metadata.put(EntityData.HAS_DISPLAY, (byte) ((boolean) entityMetadata.getValue() ? 1 : 0)); + } + } + + super.updateBedrockMetadata(entityMetadata, session); + } + + @Override + public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { + super.moveAbsolute(session, position.add(0d, this.entityType.getOffset(), 0d), rotation, isOnGround, teleported); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java index 87ff9a35..1711fd38 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java @@ -55,7 +55,7 @@ public class PaintingEntity extends Entity { addPaintingPacket.setName(paintingName.getBedrockName()); addPaintingPacket.setPosition(fixOffset(true)); addPaintingPacket.setDirection(direction); - session.getUpstream().sendPacket(addPaintingPacket); + session.sendUpstreamPacket(addPaintingPacket); valid = true; @@ -67,7 +67,7 @@ public class PaintingEntity extends Entity { Vector3f position = super.position; position = position.add(0.5, 0.5, 0.5); double widthOffset = paintingName.getWidth() > 1 ? 0.5 : 0; - double heightOffset = paintingName.getHeight() > 1 ? 0.5 : 0; + double heightOffset = paintingName.getHeight() > 1 && paintingName.getHeight() != 3 ? 0.5 : 0; switch (direction) { case 0: return position.add(widthOffset, heightOffset, OFFSET); 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 d0ac2aeb..aa7848da 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -26,15 +26,13 @@ package org.geysermc.connector.entity; import com.github.steveice10.mc.auth.data.GameProfile; +import com.github.steveice10.mc.protocol.data.game.entity.Effect; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.message.TextMessage; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.CommandPermission; -import com.nukkitx.protocol.bedrock.data.EntityData; -import com.nukkitx.protocol.bedrock.data.PlayerPermission; -import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket; -import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; -import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; +import com.nukkitx.protocol.bedrock.data.*; +import com.nukkitx.protocol.bedrock.packet.*; import lombok.Getter; import lombok.Setter; @@ -47,7 +45,10 @@ import org.geysermc.connector.utils.MessageUtils; import org.geysermc.connector.network.session.cache.EntityEffectCache; import org.geysermc.connector.utils.SkinUtils; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; +import java.util.concurrent.TimeUnit; @Getter @Setter public class PlayerEntity extends LivingEntity { @@ -56,8 +57,12 @@ public class PlayerEntity extends LivingEntity { private String username; private long lastSkinUpdate = -1; private boolean playerList = true; + private boolean onGround; private final EntityEffectCache effectCache; + private Entity leftParrot; + private Entity rightParrot; + public PlayerEntity(GameProfile gameProfile, long entityId, long geyserId, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, EntityType.PLAYER, position, motion, rotation); @@ -83,7 +88,7 @@ public class PlayerEntity extends LivingEntity { addPlayerPacket.setUsername(username); addPlayerPacket.setRuntimeEntityId(geyserId); addPlayerPacket.setUniqueEntityId(geyserId); - addPlayerPacket.setPosition(position); + addPlayerPacket.setPosition(position.clone().sub(0, EntityType.PLAYER.getOffset(), 0)); addPlayerPacket.setRotation(getBedrockRotation()); addPlayerPacket.setMotion(motion); addPlayerPacket.setHand(hand); @@ -93,20 +98,27 @@ public class PlayerEntity extends LivingEntity { addPlayerPacket.setPlatformChatId(""); addPlayerPacket.getMetadata().putAll(metadata); + long linkedEntityId = session.getEntityCache().getCachedPlayerEntityLink(entityId); + if (linkedEntityId != -1) { + addPlayerPacket.getEntityLinks().add(new EntityLink(session.getEntityCache().getEntityByJavaId(linkedEntityId).getGeyserId(), geyserId, EntityLink.Type.RIDER, false)); + } + valid = true; - session.getUpstream().sendPacket(addPlayerPacket); + session.sendUpstreamPacket(addPlayerPacket); updateEquipment(session); updateBedrockAttributes(session); } public void sendPlayer(GeyserSession session) { + if(session.getEntityCache().getPlayerEntity(uuid) == null) + return; if (getLastSkinUpdate() == -1) { if (playerList) { PlayerListPacket playerList = new PlayerListPacket(); playerList.setAction(PlayerListPacket.Action.ADD); playerList.getEntries().add(SkinUtils.buildDefaultEntry(profile, geyserId)); - session.getUpstream().sendPacket(playerList); + session.sendUpstreamPacket(playerList); } } @@ -122,24 +134,36 @@ public class PlayerEntity extends LivingEntity { PlayerListPacket playerList = new PlayerListPacket(); playerList.setAction(PlayerListPacket.Action.REMOVE); playerList.getEntries().add(new PlayerListPacket.Entry(uuid)); - session.getUpstream().sendPacket(playerList); + session.sendUpstreamPacket(playerList); }); } } @Override - public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround) { + public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { setPosition(position); setRotation(rotation); + this.onGround = isOnGround; + MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); movePlayerPacket.setRuntimeEntityId(geyserId); movePlayerPacket.setPosition(this.position); movePlayerPacket.setRotation(getBedrockRotation()); movePlayerPacket.setOnGround(isOnGround); - movePlayerPacket.setMode(MovePlayerPacket.Mode.NORMAL); + movePlayerPacket.setMode(teleported ? MovePlayerPacket.Mode.TELEPORT : MovePlayerPacket.Mode.NORMAL); - session.getUpstream().sendPacket(movePlayerPacket); + if (teleported) { + movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.UNKNOWN); + } + + session.sendUpstreamPacket(movePlayerPacket); + if (leftParrot != null) { + leftParrot.moveAbsolute(session, position, rotation, true, teleported); + } + if (rightParrot != null) { + rightParrot.moveAbsolute(session, position, rotation, true, teleported); + } } @Override @@ -147,13 +171,21 @@ public class PlayerEntity extends LivingEntity { setRotation(rotation); this.position = Vector3f.from(position.getX() + relX, position.getY() + relY, position.getZ() + relZ); + this.onGround = isOnGround; + MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); movePlayerPacket.setRuntimeEntityId(geyserId); movePlayerPacket.setPosition(position); movePlayerPacket.setRotation(getBedrockRotation()); movePlayerPacket.setOnGround(isOnGround); movePlayerPacket.setMode(MovePlayerPacket.Mode.NORMAL); - session.getUpstream().sendPacket(movePlayerPacket); + session.sendUpstreamPacket(movePlayerPacket); + if (leftParrot != null) { + leftParrot.moveRelative(session, relX, relY, relZ, rotation, true); + } + if (rightParrot != null) { + rightParrot.moveRelative(session, relX, relY, relZ, rotation, true); + } } @Override @@ -182,5 +214,54 @@ public class PlayerEntity extends LivingEntity { metadata.put(EntityData.NAMETAG, team.getPrefix() + MessageUtils.toChatColor(team.getColor()) + username + team.getSuffix()); } } + + // Extra hearts - is not metadata but an attribute on Bedrock + if (entityMetadata.getId() == 14) { + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); + attributesPacket.setRuntimeEntityId(geyserId); + List attributes = new ArrayList<>(); + // Setting to a higher maximum since plugins/datapacks can probably extend the Bedrock soft limit + attributes.add(new Attribute("minecraft:absorption", 0.0f, 1024f, (float) entityMetadata.getValue(), 0.0f)); + attributesPacket.setAttributes(attributes); + session.sendUpstreamPacket(attributesPacket); + } + + // Parrot occupying shoulder + if (entityMetadata.getId() == 18 || entityMetadata.getId() == 19) { + CompoundTag tag = (CompoundTag) entityMetadata.getValue(); + if (tag != null && !tag.isEmpty()) { + // 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(), + EntityType.PARROT, position, motion, rotation); + parrot.spawnEntity(session); + parrot.getMetadata().put(EntityData.VARIANT, tag.get("Variant").getValue()); + // Different position whether the parrot is left or right + float offset = (entityMetadata.getId() == 18) ? 0.4f : -0.4f; + parrot.getMetadata().put(EntityData.RIDER_SEAT_POSITION, Vector3f.from(offset, -0.22, -0.1)); + parrot.getMetadata().put(EntityData.RIDER_ROTATION_LOCKED, 1); + parrot.updateBedrockMetadata(session); + SetEntityLinkPacket linkPacket = new SetEntityLinkPacket(); + EntityLink.Type type = (entityMetadata.getId() == 18) ? EntityLink.Type.RIDER : EntityLink.Type.PASSENGER; + linkPacket.setEntityLink(new EntityLink(geyserId, parrot.getGeyserId(), type, false)); + // Delay, or else spawned-in players won't get the link + // TODO: Find a better solution. This problem also exists with item frames + session.getConnector().getGeneralThreadPool().schedule(() -> session.sendUpstreamPacket(linkPacket), 500, TimeUnit.MILLISECONDS); + if (entityMetadata.getId() == 18) { + leftParrot = parrot; + } else { + rightParrot = parrot; + } + } else { + Entity parrot = (entityMetadata.getId() == 18 ? leftParrot : rightParrot); + if (parrot != null) { + parrot.despawnEntity(session); + if (entityMetadata.getId() == 18) { + leftParrot = null; + } else { + rightParrot = null; + } + } + } + } } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/TNTEntity.java b/connector/src/main/java/org/geysermc/connector/entity/TNTEntity.java new file mode 100644 index 00000000..629c9e51 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/TNTEntity.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.entity; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityData; +import com.nukkitx.protocol.bedrock.data.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class TNTEntity extends Entity { + + private int currentTick; + + public TNTEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 7) { + currentTick = (int) entityMetadata.getValue(); + metadata.getFlags().setFlag(EntityFlag.IGNITED, true); + metadata.put(EntityData.FUSE_LENGTH, currentTick); + ScheduledFuture future = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> { + if (currentTick % 5 == 0) { + metadata.put(EntityData.FUSE_LENGTH, currentTick); + } + currentTick--; + super.updateBedrockMetadata(entityMetadata, session); + }, 50, 50, TimeUnit.MILLISECONDS); // 5 ticks + session.getConnector().getGeneralThreadPool().schedule(() -> future.cancel(true), (int) entityMetadata.getValue() / 20, TimeUnit.SECONDS); + } + + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/NbtItemStackTranslator.java b/connector/src/main/java/org/geysermc/connector/entity/TippedArrowEntity.java similarity index 72% rename from connector/src/main/java/org/geysermc/connector/network/translators/NbtItemStackTranslator.java rename to connector/src/main/java/org/geysermc/connector/entity/TippedArrowEntity.java index 56c780f4..def5715c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/NbtItemStackTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/entity/TippedArrowEntity.java @@ -23,23 +23,14 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators; +package org.geysermc.connector.entity; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import org.geysermc.connector.network.translators.item.ItemEntry; +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.connector.entity.type.EntityType; -public class NbtItemStackTranslator { - - public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) { +public class TippedArrowEntity extends AbstractArrowEntity { + public TippedArrowEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); } - - public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { - - } - - public boolean acceptItem(ItemEntry itemEntry) { - return true; - } - } diff --git a/connector/src/main/java/org/geysermc/connector/entity/TridentEntity.java b/connector/src/main/java/org/geysermc/connector/entity/TridentEntity.java new file mode 100644 index 00000000..7c2442b0 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/TridentEntity.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class TridentEntity extends AbstractArrowEntity { + + public TridentEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 11) { + metadata.getFlags().setFlag(EntityFlag.ENCHANTED, (boolean) entityMetadata.getValue()); + } + + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/attribute/AttributeType.java b/connector/src/main/java/org/geysermc/connector/entity/attribute/AttributeType.java index 9166fc8a..2061b895 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/attribute/AttributeType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/attribute/AttributeType.java @@ -38,6 +38,7 @@ public enum AttributeType { MOVEMENT_SPEED("generic.movementSpeed", "minecraft:movement", 0f, 1024f, 0.1f), FLYING_SPEED("generic.flyingSpeed", "minecraft:movement", 0.0f, 1024.0f, 0.4000000059604645f), ATTACK_DAMAGE("generic.attackDamage", "minecraft:attack_damage", 0f, 2048f, 1f), + HORSE_JUMP_STRENGTH("horse.jumpStrength", "minecraft:horse.jump_strength", 0.0f, 2.0f, 0.7f), // Java Attributes ARMOR("generic.armor", null, 0f, 30f, 0f), diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/AbstractFishEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/AbstractFishEntity.java index 54443825..de5fa1b5 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/AbstractFishEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/AbstractFishEntity.java @@ -25,28 +25,18 @@ package org.geysermc.connector.entity.living; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.EntityData; import com.nukkitx.protocol.bedrock.data.EntityFlag; import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; public class AbstractFishEntity extends WaterEntity { public AbstractFishEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); - } - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { metadata.getFlags().setFlag(EntityFlag.CAN_SWIM, true); metadata.getFlags().setFlag(EntityFlag.BREATHING, true); metadata.getFlags().setFlag(EntityFlag.CAN_CLIMB, false); metadata.getFlags().setFlag(EntityFlag.HAS_GRAVITY, false); - - metadata.put(EntityData.AIR, (short) 400); - - super.updateBedrockMetadata(entityMetadata, session); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/AgeableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/AgeableEntity.java index 634f0674..b90983f7 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/AgeableEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/AgeableEntity.java @@ -44,6 +44,9 @@ public class AgeableEntity extends CreatureEntity { boolean isBaby = (boolean) entityMetadata.getValue(); metadata.put(EntityData.SCALE, isBaby ? .55f : 1f); metadata.getFlags().setFlag(EntityFlag.BABY, isBaby); + + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, entityType.getHeight() * (isBaby ? 0.55f : 1f)); + metadata.put(EntityData.BOUNDING_BOX_WIDTH, entityType.getWidth() * (isBaby ? 0.55f : 1f)); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java index 1beb8ab8..8d3c8f8f 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java @@ -43,8 +43,26 @@ public class ArmorStandEntity extends LivingEntity { public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getType() == MetadataType.BYTE) { byte xd = (byte) entityMetadata.getValue(); - if ((xd & 0x01) == 0x01 && (metadata.get(EntityData.SCALE) != null && !metadata.get(EntityData.SCALE).equals(0.0f))) { - metadata.put(EntityData.SCALE, .55f); + + // isSmall + if ((xd & 0x01) == 0x01) { + if (metadata.getFloat(EntityData.SCALE) != 0.55f && metadata.getFloat(EntityData.SCALE) != 0.0f) { + metadata.put(EntityData.SCALE, 0.55f); + } + + if (metadata.get(EntityData.BOUNDING_BOX_WIDTH) != null && metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.5f)) { + metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.25f); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.9875f); + } + } else if (metadata.get(EntityData.BOUNDING_BOX_WIDTH) != null && metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.25f)) { + metadata.put(EntityData.BOUNDING_BOX_WIDTH, entityType.getWidth()); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, entityType.getHeight()); + } + + // setMarker + if ((xd & 0x10) == 0x10 && (metadata.get(EntityData.BOUNDING_BOX_WIDTH) != null && !metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.0f))) { + metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.0f); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.0f); } } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/SlimeEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/SlimeEntity.java new file mode 100644 index 00000000..26106f0a --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/SlimeEntity.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.entity.living; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityData; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class SlimeEntity extends InsentientEntity { + + public SlimeEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 15) { + this.metadata.put(EntityData.SCALE, 0.10f + (int) entityMetadata.getValue()); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/ArrowEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/SquidEntity.java similarity index 90% rename from connector/src/main/java/org/geysermc/connector/entity/ArrowEntity.java rename to connector/src/main/java/org/geysermc/connector/entity/living/SquidEntity.java index 961908a2..81c0eeef 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ArrowEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/SquidEntity.java @@ -23,14 +23,14 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity; +package org.geysermc.connector.entity.living; import com.nukkitx.math.vector.Vector3f; import org.geysermc.connector.entity.type.EntityType; -public class ArrowEntity extends Entity { +public class SquidEntity extends WaterEntity { - public ArrowEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + public SquidEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/WaterEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/WaterEntity.java index dc83847a..69afd975 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/WaterEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/WaterEntity.java @@ -26,11 +26,14 @@ package org.geysermc.connector.entity.living; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityData; import org.geysermc.connector.entity.type.EntityType; public class WaterEntity extends CreatureEntity { public WaterEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); + + metadata.put(EntityData.AIR, (short) 400); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/TropicalFishEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/TropicalFishEntity.java index 3ded4cbf..a8866d7e 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/TropicalFishEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/TropicalFishEntity.java @@ -28,7 +28,6 @@ package org.geysermc.connector.entity.living.animal; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.EntityData; -import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; import lombok.AllArgsConstructor; import lombok.Getter; import org.geysermc.connector.entity.living.AbstractFishEntity; @@ -54,24 +53,6 @@ public class TropicalFishEntity extends AbstractFishEntity { super.updateBedrockMetadata(entityMetadata, session); } - @Override - public void spawnEntity(GeyserSession session) { - AddEntityPacket addEntityPacket = new AddEntityPacket(); - addEntityPacket.setIdentifier("minecraft:tropicalfish"); - addEntityPacket.setRuntimeEntityId(geyserId); - addEntityPacket.setUniqueEntityId(geyserId); - addEntityPacket.setPosition(position); - addEntityPacket.setMotion(motion); - addEntityPacket.setRotation(getBedrockRotation()); - addEntityPacket.setEntityType(entityType.getType()); - addEntityPacket.getMetadata().putAll(metadata); - - valid = true; - session.getUpstream().sendPacket(addEntityPacket); - - session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); - } - @Getter @AllArgsConstructor private static class TropicalFishVariant { diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java index 396ad684..3773011a 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java @@ -27,25 +27,69 @@ package org.geysermc.connector.entity.living.animal.horse; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.Attribute; import com.nukkitx.protocol.bedrock.data.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; +import org.geysermc.connector.entity.attribute.AttributeType; import org.geysermc.connector.entity.living.animal.AnimalEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.AttributeUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; public class AbstractHorseEntity extends AnimalEntity { + // For updating the horse visual easier + private float health = 20f; + public AbstractHorseEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + + if (entityMetadata.getId() == 8) { + health = (float) entityMetadata.getValue(); + updateBedrockAttributes(session); + } + if (entityMetadata.getId() == 16) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.TAMED, (xd & 0x02) == 0x02); metadata.getFlags().setFlag(EntityFlag.SADDLED, (xd & 0x04) == 0x04); metadata.getFlags().setFlag(EntityFlag.EATING, (xd & 0x10) == 0x10); + metadata.getFlags().setFlag(EntityFlag.STANDING, (xd & 0x20) == 0x20); } + + // Needed to control horses + metadata.getFlags().setFlag(EntityFlag.CAN_POWER_JUMP, true); + metadata.getFlags().setFlag(EntityFlag.WASD_CONTROLLED, true); + super.updateBedrockMetadata(entityMetadata, session); } + + @Override + public void updateBedrockAttributes(GeyserSession session) { + if (!valid) return; + + float maxHealth = attributes.containsKey(AttributeType.MAX_HEALTH) ? attributes.get(AttributeType.MAX_HEALTH).getValue() : 20f; + + List attributesLocal = new ArrayList<>(); + for (Map.Entry entry : this.attributes.entrySet()) { + if (!entry.getValue().getType().isBedrockAttribute()) + continue; + + attributesLocal.add(AttributeUtils.getBedrockAttribute(entry.getValue())); + } + attributesLocal.add(new Attribute("minecraft:health", 0.0f, maxHealth, health, maxHealth)); + + UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket(); + updateAttributesPacket.setRuntimeEntityId(geyserId); + updateAttributesPacket.setAttributes(attributesLocal); + session.sendUpstreamPacket(updateAttributesPacket); + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java index b6a50520..27f4b83c 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java @@ -28,6 +28,7 @@ package org.geysermc.connector.entity.living.animal.horse; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.EntityData; +import com.nukkitx.protocol.bedrock.data.EntityFlag; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -41,8 +42,9 @@ public class HorseEntity extends AbstractHorseEntity { public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 18) { metadata.put(EntityData.VARIANT, (int) entityMetadata.getValue()); + metadata.put(EntityData.MARK_VARIANT, (((int) entityMetadata.getValue()) >> 8) % 5); } - super.updateBedrockMetadata(entityMetadata, session); } + } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java index 26c13a5c..d4d7b726 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java @@ -32,7 +32,7 @@ import com.nukkitx.protocol.bedrock.data.ItemData; import com.nukkitx.protocol.bedrock.packet.MobArmorEquipmentPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.block.BlockTranslator; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; public class LlamaEntity extends ChestedHorseEntity { @@ -65,7 +65,7 @@ public class LlamaEntity extends ChestedHorseEntity { equipmentPacket.setHelmet(ItemData.AIR); equipmentPacket.setLeggings(ItemData.AIR); - session.getUpstream().sendPacket(equipmentPacket); + session.sendUpstreamPacket(equipmentPacket); } // Color of the llama if (entityMetadata.getId() == 21) { diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/TraderLlamaEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/TraderLlamaEntity.java index 5e591bc7..b9505509 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/TraderLlamaEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/TraderLlamaEntity.java @@ -27,7 +27,6 @@ package org.geysermc.connector.entity.living.animal.horse; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.EntityData; -import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -39,23 +38,7 @@ public class TraderLlamaEntity extends LlamaEntity { @Override public void spawnEntity(GeyserSession session) { - // The trader llama is a separate entity from the llama in Java but a normal llama with extra metadata in Bedrock. - AddEntityPacket addEntityPacket = new AddEntityPacket(); - addEntityPacket.setIdentifier("minecraft:llama"); - addEntityPacket.setRuntimeEntityId(geyserId); - addEntityPacket.setUniqueEntityId(geyserId); - addEntityPacket.setPosition(position); - addEntityPacket.setMotion(motion); - addEntityPacket.setRotation(getBedrockRotation()); - addEntityPacket.setEntityType(entityType.getType()); - addEntityPacket.getMetadata().putAll(metadata); - // Here's the difference - addEntityPacket.getMetadata().put(EntityData.MARK_VARIANT, 1); - - valid = true; - session.getUpstream().sendPacket(addEntityPacket); - - session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); + this.metadata.put(EntityData.MARK_VARIANT, 1); + super.spawnEntity(session); } - } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java index 9ed48b88..895f8cc1 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java @@ -29,7 +29,6 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.EntityData; -import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import org.geysermc.connector.entity.type.EntityType; @@ -85,24 +84,4 @@ public class VillagerEntity extends AbstractMerchantEntity { } super.updateBedrockMetadata(entityMetadata, session); } - - @Override - public void spawnEntity(GeyserSession session) { - AddEntityPacket addEntityPacket = new AddEntityPacket(); - // "v2" or else it's the legacy villager - addEntityPacket.setIdentifier("minecraft:villager_v2"); - addEntityPacket.setRuntimeEntityId(geyserId); - addEntityPacket.setUniqueEntityId(geyserId); - addEntityPacket.setPosition(position); - addEntityPacket.setMotion(motion); - addEntityPacket.setRotation(getBedrockRotation()); - addEntityPacket.setEntityType(entityType.getType()); - addEntityPacket.getMetadata().putAll(metadata); - - valid = true; - session.getUpstream().sendPacket(addEntityPacket); - - session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); - } - } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java index e99e1a63..9b5c3822 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java @@ -39,8 +39,8 @@ public class CreeperEntity extends MonsterEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15 && (int) entityMetadata.getValue() > 0) { - metadata.getFlags().setFlag(EntityFlag.IGNITED, true); + if (entityMetadata.getId() == 15) { + metadata.getFlags().setFlag(EntityFlag.IGNITED, (int) entityMetadata.getValue() == 1); } if (entityMetadata.getId() == 16) { metadata.getFlags().setFlag(EntityFlag.POWERED, (boolean) entityMetadata.getValue()); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ElderGuardianEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ElderGuardianEntity.java new file mode 100644 index 00000000..fedd7980 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ElderGuardianEntity.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.entity.living.monster; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; + +public class ElderGuardianEntity extends GuardianEntity { + + public ElderGuardianEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + // Otherwise it just looks like a normal guardian but bigger + metadata.getFlags().setFlag(EntityFlag.ELDER, true); + } + +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java index b0737738..394be544 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java @@ -53,7 +53,7 @@ public class EnderDragonEntity extends InsentientEntity { entityEventPacket.setType(EntityEventType.DRAGON_FLAMING); entityEventPacket.setRuntimeEntityId(geyserId); entityEventPacket.setData(0); - session.getUpstream().sendPacket(entityEventPacket); + session.sendUpstreamPacket(entityEventPacket); case 6: case 7: metadata.getFlags().setFlag(EntityFlag.SITTING, true); @@ -79,7 +79,7 @@ public class EnderDragonEntity extends InsentientEntity { addEntityPacket.getAttributes().add(new Attribute("minecraft:health", 0.0f, 200f, 200f, 200f)); valid = true; - session.getUpstream().sendPacket(addEntityPacket); + session.sendUpstreamPacket(addEntityPacket); session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java index a423013c..644181ab 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java @@ -32,7 +32,7 @@ import com.nukkitx.protocol.bedrock.data.EntityData; import com.nukkitx.protocol.bedrock.data.EntityFlag; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.block.BlockTranslator; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; public class EndermanEntity extends MonsterEntity { diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/GiantEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/GiantEntity.java new file mode 100644 index 00000000..b9dc9e66 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/GiantEntity.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living.monster; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityData; +import org.geysermc.connector.entity.type.EntityType; + +public class GiantEntity extends MonsterEntity { + + public GiantEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + + metadata.put(EntityData.SCALE, 6f); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/GuardianEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/GuardianEntity.java index 682b02b2..821faa85 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/GuardianEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/GuardianEntity.java @@ -42,8 +42,14 @@ public class GuardianEntity extends MonsterEntity { public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 16) { Entity entity = session.getEntityCache().getEntityByJavaId((int) entityMetadata.getValue()); + if (entity == null && session.getPlayerEntity().getEntityId() == (Integer) entityMetadata.getValue()) { + entity = session.getPlayerEntity(); + } + if (entity != null) { metadata.put(EntityData.TARGET_EID, entity.getGeyserId()); + } else { + metadata.put(EntityData.TARGET_EID, (long) 0); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/WitherEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/WitherEntity.java new file mode 100644 index 00000000..005d0db3 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/WitherEntity.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living.monster; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityData; +import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class WitherEntity extends MonsterEntity { + + public WitherEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + + metadata.put(EntityData.WITHER_AERIAL_ATTACK, (short) 1); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + long targetID = 0; + + if (entityMetadata.getId() >= 15 && entityMetadata.getId() <= 17) { + Entity entity = session.getEntityCache().getEntityByJavaId((int) entityMetadata.getValue()); + if (entity == null && session.getPlayerEntity().getEntityId() == (Integer) entityMetadata.getValue()) { + entity = session.getPlayerEntity(); + } + + if (entity != null) { + targetID = entity.getGeyserId(); + } + } + + if (entityMetadata.getId() == 15) { + metadata.put(EntityData.WITHER_TARGET_1, targetID); + } else if (entityMetadata.getId() == 16) { + metadata.put(EntityData.WITHER_TARGET_2, targetID); + } else if (entityMetadata.getId() == 17) { + metadata.put(EntityData.WITHER_TARGET_3, targetID); + } else if (entityMetadata.getId() == 18) { + metadata.put(EntityData.WITHER_INVULNERABLE_TICKS, (int) entityMetadata.getValue()); + + // Show the shield for the first few seconds of spawning (like Java) + if ((int) entityMetadata.getValue() >= 165) { + metadata.put(EntityData.WITHER_AERIAL_ATTACK, (short) 0); + } else { + metadata.put(EntityData.WITHER_AERIAL_ATTACK, (short) 1); + } + } + + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index 7a7e13c0..2cfe54c6 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 @@ -45,9 +45,9 @@ public enum EntityType { PIG(PigEntity.class, 12, 0.9f), SHEEP(SheepEntity.class, 13, 1.3f, 0.9f), WOLF(WolfEntity.class, 14, 0.85f, 0.6f), - VILLAGER(VillagerEntity.class, 15, 1.8f, 0.6f, 0.6f, 1.62f), + VILLAGER(VillagerEntity.class, 15, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:villager_v2"), MOOSHROOM(AnimalEntity.class, 16, 1.4f, 0.9f), - SQUID(WaterEntity.class, 17, 0.8f), + SQUID(SquidEntity.class, 17, 0.8f), RABBIT(RabbitEntity.class, 18, 0.5f, 0.4f), BAT(AmbientEntity.class, 19, 0.9f, 0.5f), IRON_GOLEM(GolemEntity.class, 20, 2.7f, 1.4f), @@ -60,20 +60,21 @@ public enum EntityType { ZOMBIE_HORSE(AbstractHorseEntity.class, 27, 1.6f, 1.3965f), POLAR_BEAR(PolarBearEntity.class, 28, 1.4f, 1.3f), LLAMA(LlamaEntity.class, 29, 1.87f, 0.9f), - TRADER_LLAMA(TraderLlamaEntity.class, 29, 1.187f, 0.9f), + TRADER_LLAMA(TraderLlamaEntity.class, 29, 1.187f, 0.9f, 0f, 0f, "minecraft:llama"), PARROT(ParrotEntity.class, 30, 0.9f, 0.5f), DOLPHIN(WaterEntity.class, 31, 0.6f, 0.9f), ZOMBIE(ZombieEntity.class, 32, 1.8f, 0.6f, 0.6f, 1.62f), + GIANT(GiantEntity.class, 32, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:zombie"), CREEPER(CreeperEntity.class, 33, 1.7f, 0.6f, 0.6f, 1.62f), SKELETON(AbstractSkeletonEntity.class, 34, 1.8f, 0.6f, 0.6f, 1.62f), SPIDER(SpiderEntity.class, 35, 0.9f, 1.4f, 1.4f, 1f), ZOMBIE_PIGMAN(MonsterEntity.class, 36, 1.8f, 0.6f, 0.6f, 1.62f), - SLIME(InsentientEntity.class, 37, 0.51f), + SLIME(SlimeEntity.class, 37, 0.51f), ENDERMAN(EndermanEntity.class, 38, 2.9f, 0.6f), SILVERFISH(MonsterEntity.class, 39, 0.3f, 0.4f), CAVE_SPIDER(MonsterEntity.class, 40, 0.5f, 0.7f), GHAST(FlyingEntity.class, 41, 4.0f), - MAGMA_CUBE(InsentientEntity.class, 42, 0.51f), + MAGMA_CUBE(SlimeEntity.class, 42, 0.51f), BLAZE(BlazeEntity.class, 43, 1.8f, 0.6f), ZOMBIE_VILLAGER(ZombieEntity.class, 44, 1.8f, 0.6f, 0.6f, 1.62f), WITCH(RaidParticipantEntity.class, 45, 1.8f, 0.6f, 0.6f, 1.62f), @@ -81,9 +82,9 @@ public enum EntityType { HUSK(ZombieEntity.class, 47, 1.8f, 0.6f, 0.6f, 1.62f), WITHER_SKELETON(AbstractSkeletonEntity.class, 48, 2.4f, 0.7f), GUARDIAN(GuardianEntity.class, 49, 0.85f), - ELDER_GUARDIAN(GuardianEntity.class, 50, 1.9975f), + ELDER_GUARDIAN(ElderGuardianEntity.class, 50, 1.9975f), NPC(PlayerEntity.class, 51, 1.8f, 0.6f, 0.6f, 1.62f), - WITHER(MonsterEntity.class, 52, 3.5f, 0.9f), + WITHER(WitherEntity.class, 52, 3.5f, 0.9f), ENDER_DRAGON(EnderDragonEntity.class, 53, 4f, 13f), SHULKER(ShulkerEntity.class, 54, 1f, 1f), ENDERMITE(MonsterEntity.class, 55, 0.3f, 0.4f), @@ -94,60 +95,73 @@ public enum EntityType { PHANTOM(FlyingEntity.class, 58, 0.5f, 0.9f, 0.9f, 0.6f), RAVAGER(RaidParticipantEntity.class, 59, 1.9f, 1.2f), - ARMOR_STAND(ArmorStandEntity.class, 61, 0f), + ARMOR_STAND(ArmorStandEntity.class, 61, 1.975f, 0.5f), TRIPOD_CAMERA(Entity.class, 62, 0f), PLAYER(PlayerEntity.class, 63, 1.8f, 0.6f, 0.6f, 1.62f), ITEM(ItemEntity.class, 64, 0.25f, 0.25f), - TNT(Entity.class, 65, 0.98f, 0.98f), + PRIMED_TNT(TNTEntity.class, 65, 0.98f, 0.98f, 0.98f, 0f, "minecraft:tnt"), FALLING_BLOCK(FallingBlockEntity.class, 66, 0.98f, 0.98f), MOVING_BLOCK(Entity.class, 67, 0f), - EXPERIENCE_BOTTLE(ThrowableEntity.class, 68, 0.25f, 0.25f), - EXPERIENCE_ORB(ExpOrbEntity.class, 69, 0f), - EYE_OF_ENDER(Entity.class, 70, 0f), - END_CRYSTAL(EnderCrystalEntity.class, 71, 0f), - FIREWORK_ROCKET(Entity.class, 72, 0f), - TRIDENT(ArrowEntity.class, 73, 0f), + THROWN_EXP_BOTTLE(ThrowableEntity.class, 68, 0.25f, 0.25f, 0f, 0f, "minecraft:xp_bottle"), + EXPERIENCE_ORB(ExpOrbEntity.class, 69, 0f, 0f, 0f, 0f, "minecraft:xp_orb"), + EYE_OF_ENDER(Entity.class, 70, 0.25f, 0.25f, 0f, 0f, "minecraft:eye_of_ender_signal"), + END_CRYSTAL(EnderCrystalEntity.class, 71, 2.0f, 2.0f, 2.0f, 0f, "minecraft:ender_crystal"), + FIREWORK_ROCKET(FireworkEntity.class, 72, 0.25f, 0.25f, 0.25f, 0f, "minecraft:fireworks_rocket"), + TRIDENT(TridentEntity.class, 73, 0f, 0f, 0f, 0f, "minecraft:thrown_trident"), TURTLE(AnimalEntity.class, 74, 0.4f, 1.2f), CAT(CatEntity.class, 75, 0.35f, 0.3f), - SHULKER_BULLET(Entity.class, 76, 0f), - FISHING_BOBBER(FishingHookEntity.class, 77, 0f), + SHULKER_BULLET(Entity.class, 76, 0.3125f), + FISHING_BOBBER(FishingHookEntity.class, 77, 0f, 0f, 0f, 0f, "minecraft:fishing_hook"), CHALKBOARD(Entity.class, 78, 0f), - DRAGON_FIREBALL(ItemedFireballEntity.class, 79, 0f), - ARROW(ArrowEntity.class, 80, 0.25f, 0.25f), - SNOWBALL(ThrowableEntity.class, 81, 0f), - EGG(ThrowableEntity.class, 82, 0f), + DRAGON_FIREBALL(ItemedFireballEntity.class, 79, 1.0f), + ARROW(TippedArrowEntity.class, 80, 0.25f, 0.25f), + SPECTRAL_ARROW(AbstractArrowEntity.class, 80, 0.25f, 0.25f, 0.25f, 0f, "minecraft:arrow"), + SNOWBALL(ThrowableEntity.class, 81, 0.25f), + THROWN_EGG(ThrowableEntity.class, 82, 0.25f, 0.25f, 0.25f, 0f, "minecraft:egg"), PAINTING(PaintingEntity.class, 83, 0f), - MINECART(MinecartEntity.class, 84, 0f), - FIREBALL(ItemedFireballEntity.class, 85, 0f), - POTION(ThrowableEntity.class, 86, 0f), - ENDER_PEARL(ThrowableEntity.class, 87, 0f), - LEASH_KNOT(Entity.class, 88, 0f), - WITHER_SKULL(Entity.class, 89, 0f), - BOAT(Entity.class, 90, 0.7f, 1.6f, 1.6f, 0.35f), + MINECART(MinecartEntity.class, 84, 0.7f, 0.98f, 0.98f, 0.35f), + FIREBALL(ItemedFireballEntity.class, 85, 1.0f), + THROWN_POTION(ThrowableEntity.class, 86, 0.25f, 0.25f, 0.25f, 0f, "minecraft:splash_potion"), + THROWN_ENDERPEARL(ThrowableEntity.class, 87, 0.25f, 0.25f, 0.25f, 0f, "minecraft:ender_pearl"), + LEASH_KNOT(LeashKnotEntity.class, 88, 0.5f, 0.375f), + WITHER_SKULL(Entity.class, 89, 0.3125f), + BOAT(BoatEntity.class, 90, 0.7f, 1.6f, 1.6f, 0.35f), WITHER_SKULL_DANGEROUS(Entity.class, 91, 0f), LIGHTNING_BOLT(Entity.class, 93, 0f), - SMALL_FIREBALL(ItemedFireballEntity.class, 94, 0f), - AREA_EFFECT_CLOUD(Entity.class, 95, 0f), - HOPPER_MINECART(MinecartEntity.class, 96, 0f), - TNT_MINECART(MinecartEntity.class, 97, 0f), - CHEST_MINECART(MinecartEntity.class, 98, 0f), - - COMMAND_BLOCK_MINECART(MinecartEntity.class, 100, 0f), + SMALL_FIREBALL(ItemedFireballEntity.class, 94, 0.3125f), + AREA_EFFECT_CLOUD(AreaEffectCloudEntity.class, 95, 0.5f, 1.0f), + MINECART_HOPPER(MinecartEntity.class, 96, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:hopper_minecart"), + MINECART_TNT(MinecartEntity.class, 97, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:tnt_minecart"), + MINECART_CHEST(MinecartEntity.class, 98, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:chest_minecart"), + MINECART_FURNACE(FurnaceMinecartEntity.class, 98, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:minecart"), + MINECART_COMMAND_BLOCK(MinecartEntity.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, 0f), - EVOKER_FANGS(Entity.class, 103, 0f), - EVOKER(SpellcasterIllagerEntity.class, 104, 0f), - VEX(MonsterEntity.class, 105, 0f), + LLAMA_SPIT(Entity.class, 102, 0.25f), + EVOKER_FANGS(Entity.class, 103, 0.8f, 0.5f), + EVOKER(SpellcasterIllagerEntity.class, 104, 1.95f, 0.5f), + VEX(MonsterEntity.class, 105, 0.8f, 0.4f), ICE_BOMB(Entity.class, 106, 0f), BALLOON(Entity.class, 107, 0f), //TODO PUFFERFISH(PufferFishEntity.class, 108, 0.7f, 0.7f), SALMON(AbstractFishEntity.class, 109, 0.5f, 0.7f), DROWNED(ZombieEntity.class, 110, 1.95f, 0.6f), - TROPICAL_FISH(TropicalFishEntity.class, 111, 0.6f, 0.6f), + TROPICAL_FISH(TropicalFishEntity.class, 111, 0.6f, 0.6f, 0f, 0f, "minecraft:tropicalfish"), COD(AbstractFishEntity.class, 112, 0.25f, 0.5f), PANDA(PandaEntity.class, 113, 1.25f, 1.125f, 1.825f), FOX(FoxEntity.class, 121, 0.5f, 1.25f), - BEE(BeeEntity.class, 122, 0.6f, 0.6f); + BEE(BeeEntity.class, 122, 0.6f, 0.6f), + + /** + * Item frames are handled differently since they are a block in Bedrock. + */ + ITEM_FRAME(ItemFrameEntity.class, 0, 0, 0), + + /** + * Not an entity in Bedrock, so we replace it with a Pillager + */ + ILLUSIONER(AbstractIllagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:pillager"); + + private static final EntityType[] VALUES = values(); private Class entityClass; private final int type; @@ -155,9 +169,10 @@ public enum EntityType { private final float width; private final float length; private final float offset; + private String identifier; EntityType(Class entityClass, int type, float height) { - this(entityClass, type, height, 0f); + this(entityClass, type, height, height); } EntityType(Class entityClass, int type, float height, float width) { @@ -169,11 +184,28 @@ public enum EntityType { } EntityType(Class entityClass, int type, float height, float width, float length, float offset) { + this(entityClass, type, height, width, length, offset, null); + + this.identifier = "minecraft:" + name().toLowerCase(); + } + + EntityType(Class entityClass, int type, float height, float width, float length, float offset, String identifier) { this.entityClass = entityClass; this.type = type; this.height = height; this.width = width; this.length = length; this.offset = offset + 0.00001f; + this.identifier = identifier; + } + + public static EntityType getFromIdentifier(String identifier) { + for (EntityType type : VALUES) { + if (type.identifier.equals(identifier)) { + return type; + } + } + + return null; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java index 60ad28d4..29ba1567 100644 --- a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java @@ -25,12 +25,13 @@ package org.geysermc.connector.network; -import com.github.steveice10.mc.protocol.data.status.ServerStatusInfo; -import com.nukkitx.protocol.bedrock.BedrockPong; -import com.nukkitx.protocol.bedrock.BedrockServerEventHandler; -import com.nukkitx.protocol.bedrock.BedrockServerSession; - -import org.geysermc.common.IGeyserConfiguration; +import com.github.steveice10.mc.protocol.data.message.Message; +import com.nukkitx.protocol.bedrock.*; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.socket.DatagramPacket; +import org.geysermc.common.ping.GeyserPingInfo; +import org.geysermc.connector.ping.IGeyserPingPassthrough; +import org.geysermc.connector.GeyserConfiguration; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.MessageUtils; @@ -39,7 +40,7 @@ import java.net.InetSocketAddress; public class ConnectorServerEventHandler implements BedrockServerEventHandler { - private GeyserConnector connector; + private final GeyserConnector connector; public ConnectorServerEventHandler(GeyserConnector connector) { this.connector = connector; @@ -55,30 +56,40 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler { public BedrockPong onQuery(InetSocketAddress inetSocketAddress) { connector.getLogger().debug(inetSocketAddress + " has pinged you!"); - IGeyserConfiguration config = connector.getConfig(); - ServerStatusInfo serverInfo = connector.getPassthroughThread().getInfo(); + GeyserConfiguration config = connector.getConfig(); + + GeyserPingInfo pingInfo = null; + if (config.isPassthroughMotd() || config.isPassthroughPlayerCounts()) { + IGeyserPingPassthrough pingPassthrough = connector.getBootstrap().getGeyserPingPassthrough(); + pingInfo = pingPassthrough.getPingInformation(); + } BedrockPong pong = new BedrockPong(); pong.setEdition("MCPE"); pong.setGameType("Default"); pong.setNintendoLimited(false); pong.setProtocolVersion(GeyserConnector.BEDROCK_PACKET_CODEC.getProtocolVersion()); - pong.setVersion(GeyserConnector.BEDROCK_PACKET_CODEC.getMinecraftVersion()); + pong.setVersion(null); // Server tries to connect either way and it looks better pong.setIpv4Port(config.getBedrock().getPort()); - if (connector.getConfig().isPingPassthrough() && serverInfo != null) { - String[] motd = MessageUtils.getBedrockMessage(serverInfo.getDescription()).split("\n"); + + if (config.isPassthroughMotd() && pingInfo != null && pingInfo.motd != null) { + String[] motd = MessageUtils.getBedrockMessage(Message.fromString(pingInfo.motd)).split("\n"); String mainMotd = motd[0]; // First line of the motd. String subMotd = (motd.length != 1) ? motd[1] : ""; // Second line of the motd if present, otherwise blank. pong.setMotd(mainMotd.trim()); pong.setSubMotd(subMotd.trim()); // Trimmed to shift it to the left, prevents the universe from collapsing on us just because we went 2 characters over the text box's limit. - pong.setPlayerCount(serverInfo.getPlayerInfo().getOnlinePlayers()); - pong.setMaximumPlayerCount(serverInfo.getPlayerInfo().getMaxPlayers()); + } else { + pong.setMotd(config.getBedrock().getMotd1()); + pong.setSubMotd(config.getBedrock().getMotd2()); + } + + if (config.isPassthroughPlayerCounts() && pingInfo != null) { + pong.setPlayerCount(pingInfo.currentPlayerCount); + pong.setMaximumPlayerCount(pingInfo.maxPlayerCount); } else { pong.setPlayerCount(connector.getPlayers().size()); pong.setMaximumPlayerCount(config.getMaxPlayers()); - pong.setMotd(config.getBedrock().getMotd1()); - pong.setMotd(config.getBedrock().getMotd2()); } //Bedrock will not even attempt a connection if the client thinks the server is full @@ -101,13 +112,13 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler { if (player != null) { player.disconnect(disconnectReason.name()); connector.removePlayer(player); - - player.getEntityCache().clear(); - player.getInventoryCache().getInventories().clear(); - player.getWindowCache().getWindows().clear(); - player.getScoreboardCache().removeScoreboard(); } }); bedrockServerSession.setPacketCodec(GeyserConnector.BEDROCK_PACKET_CODEC); } + + @Override + public void onUnhandledDatagram(ChannelHandlerContext ctx, DatagramPacket packet) { + new QueryPacketHandler(connector, packet.sender(), packet.content()); + } } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java new file mode 100644 index 00000000..061e0f86 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.network; + +import com.github.steveice10.mc.protocol.data.message.Message; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import org.geysermc.common.ping.GeyserPingInfo; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.utils.MessageUtils; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +public class QueryPacketHandler { + + public static final byte HANDSHAKE = 0x09; + public static final byte STATISTICS = 0x00; + + private GeyserConnector connector; + private InetSocketAddress sender; + private byte type; + private int sessionId; + private byte[] token; + + /** + * The Query packet handler instance + * @param connector Geyser Connector + * @param sender The Sender IP/Port for the Query + * @param buffer The Query data + */ + public QueryPacketHandler(GeyserConnector connector, InetSocketAddress sender, ByteBuf buffer) { + if(!isQueryPacket(buffer)) + return; + + this.connector = connector; + this.sender = sender; + this.type = buffer.readByte(); + this.sessionId = buffer.readInt(); + + regenerateToken(); + handle(); + } + + /** + * Checks the packet is in fact a query packet + * @param buffer Query data + * @return if the packet is a query packet + */ + private boolean isQueryPacket(ByteBuf buffer) { + return (buffer.readableBytes() >= 2) ? buffer.readUnsignedShort() == 65277 : false; + } + + /** + * Handles the query + */ + private void handle() { + switch (type) { + case HANDSHAKE: + sendToken(); + case STATISTICS: + sendQueryData(); + } + } + + /** + * Sends the token to the sender + */ + private void sendToken() { + ByteBuf reply = ByteBufAllocator.DEFAULT.ioBuffer(10); + reply.writeByte(HANDSHAKE); + reply.writeInt(sessionId); + reply.writeBytes(getTokenString(this.token, this.sender.getAddress())); + reply.writeByte(0); + + sendPacket(reply); + } + + /** + * Sends the query data to the sender + */ + private void sendQueryData() { + ByteBuf reply = ByteBufAllocator.DEFAULT.ioBuffer(64); + reply.writeByte(STATISTICS); + reply.writeInt(sessionId); + + // Game Info + reply.writeBytes(getGameData()); + + // Players + reply.writeBytes(getPlayers()); + + sendPacket(reply); + } + + /** + * Gets the game data for the query + * @return the game data for the query + */ + private byte[] getGameData() { + ByteArrayOutputStream query = new ByteArrayOutputStream(); + + GeyserPingInfo pingInfo = null; + String motd; + String currentPlayerCount; + String maxPlayerCount; + + if (connector.getConfig().isPassthroughMotd() || connector.getConfig().isPassthroughPlayerCounts()) { + pingInfo = connector.getBootstrap().getGeyserPingPassthrough().getPingInformation(); + } + + if (connector.getConfig().isPassthroughMotd() && pingInfo != null) { + String[] javaMotd = MessageUtils.getBedrockMessage(Message.fromString(pingInfo.motd)).split("\n"); + motd = javaMotd[0].trim(); // First line of the motd. + } else { + motd = connector.getConfig().getBedrock().getMotd1(); + } + + // If passthrough player counts is enabled lets get players from the server + if (connector.getConfig().isPassthroughPlayerCounts() && pingInfo != null) { + currentPlayerCount = String.valueOf(pingInfo.currentPlayerCount); + maxPlayerCount = String.valueOf(pingInfo.maxPlayerCount); + } else { + currentPlayerCount = String.valueOf(connector.getPlayers().size()); + maxPlayerCount = String.valueOf(connector.getConfig().getMaxPlayers()); + } + + // Create a hashmap of all game data needed in the query + Map gameData = new HashMap(); + gameData.put("hostname", motd); + gameData.put("gametype", "SMP"); + gameData.put("game_id", "MINECRAFT"); + gameData.put("version", GeyserConnector.BEDROCK_PACKET_CODEC.getMinecraftVersion()); + gameData.put("plugins", ""); + gameData.put("map", GeyserConnector.NAME); + gameData.put("numplayers", currentPlayerCount); + gameData.put("maxplayers", maxPlayerCount); + gameData.put("hostport", String.valueOf(connector.getConfig().getBedrock().getPort())); + gameData.put("hostip", connector.getConfig().getBedrock().getAddress()); + + try { + // Blank Buffer Bytes + query.write("GeyserMC".getBytes()); + query.write((byte) 0x00); + query.write((byte) 128); + query.write((byte) 0x00); + + // Fills the game data + for(Map.Entry entry : gameData.entrySet()) { + query.write(entry.getKey().getBytes()); + query.write((byte) 0x00); + query.write(entry.getValue().getBytes()); + query.write((byte) 0x00); + } + + // Final byte to show the end of the game data + query.write(new byte[]{0x00, 0x01}); + return query.toByteArray(); + } catch (IOException e) { + e.printStackTrace(); + return new byte[0]; + } + } + + private byte[] getPlayers() { + ByteArrayOutputStream query = new ByteArrayOutputStream(); + + GeyserPingInfo pingInfo = null; + if (connector.getConfig().isPassthroughMotd() || connector.getConfig().isPassthroughPlayerCounts()) { + pingInfo = connector.getBootstrap().getGeyserPingPassthrough().getPingInformation(); + } + + try { + // Start the player section + query.write("player_".getBytes()); + query.write(new byte[]{0x00, 0x00}); + + // Fill player names + if(pingInfo != null) { + for (String username : pingInfo.getPlayers()) { + query.write(username.getBytes()); + query.write((byte) 0x00); + } + } + + // Final byte to show the end of the player data + query.write((byte) 0x00); + return query.toByteArray(); + } catch (IOException e) { + e.printStackTrace(); + return new byte[0]; + } + } + + /** + * Sends a packet to the sender + * @param data packet data + */ + private void sendPacket(ByteBuf data) { + connector.getBedrockServer().getRakNet().send(sender, data); + } + + /** + * Regenerates a token + */ + public void regenerateToken() { + byte[] token = new byte[16]; + for (int i = 0; i < 16; i++) { + token[i] = (byte) new Random().nextInt(255); + } + + this.token = token; + } + + /** + * Gets an MD5 token for the current IP/Port. + * This should reset every 30 seconds but a new one is generated per instance + * Seems wasteful to code something in to clear it when it has no use. + * @param token the token + * @param address the address + * @return an MD5 token for the current IP/Port + */ + public static byte[] getTokenString(byte[] token, InetAddress address) { + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + digest.update(address.toString().getBytes(StandardCharsets.UTF_8)); + digest.update(token); + return Arrays.copyOf(digest.digest(), 4); + } catch (NoSuchAlgorithmException e) { + return ByteBuffer.allocate(4).putInt(ThreadLocalRandom.current().nextInt()).array(); + } + } +} 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 553f90f9..633535ff 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -28,15 +28,15 @@ package org.geysermc.connector.network; import com.nukkitx.protocol.bedrock.BedrockPacket; import com.nukkitx.protocol.bedrock.packet.*; import org.geysermc.common.AuthType; -import org.geysermc.common.IGeyserConfiguration; +import org.geysermc.connector.GeyserConfiguration; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.Registry; import org.geysermc.connector.utils.*; +import org.geysermc.connector.network.translators.PacketTranslatorRegistry; +import org.geysermc.connector.utils.LoginEncryptionUtils; import java.io.FileInputStream; import java.io.InputStream; -import java.util.UUID; public class UpstreamPacketHandler extends LoggingPacketHandler { @@ -45,7 +45,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { } private boolean translateAndDefault(BedrockPacket packet) { - return Registry.BEDROCK.translate(packet.getClass(), packet, session); + return PacketTranslatorRegistry.BEDROCK_TRANSLATOR.translate(packet.getClass(), packet, session); } @Override @@ -62,7 +62,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { PlayStatusPacket playStatus = new PlayStatusPacket(); playStatus.setStatus(PlayStatusPacket.Status.LOGIN_SUCCESS); - session.getUpstream().sendPacket(playStatus); + session.sendUpstreamPacket(playStatus); ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket(); for(ResourcePack resourcePack : ResourcePack.PACKS.values()) { @@ -71,7 +71,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { resourcePacksInfo.getResourcePackInfos().add(new ResourcePacksInfoPacket.Entry(header.getUuid().toString(), version, resourcePack.getFile().length(), "", "", "", false)); } resourcePacksInfo.setForcedToAccept(true); - session.getUpstream().sendPacket(resourcePacksInfo); + session.sendUpstreamPacket(resourcePacksInfo); return true; } @@ -110,7 +110,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { String version = header.getVersion()[0] + "." + header.getVersion()[1] + "." + header.getVersion()[2]; stackPacket.getResourcePacks().add(new ResourcePackStackPacket.Entry(header.getUuid().toString(), version, "")); } - session.getUpstream().sendPacket(stackPacket); + session.sendUpstreamPacket(stackPacket); break; default: @@ -128,7 +128,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { private boolean couldLoginUserByName(String bedrockUsername) { if (connector.getConfig().getUserAuths() != null) { - IGeyserConfiguration.IUserAuthenticationInfo info = connector.getConfig().getUserAuths().get(bedrockUsername); + GeyserConfiguration.IUserAuthenticationInfo info = connector.getConfig().getUserAuths().get(bedrockUsername); if (info != null) { connector.getLogger().info("using stored credentials for bedrock user " + session.getAuthData().getName()); @@ -144,15 +144,19 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { } @Override - public boolean handle(MovePlayerPacket packet) { + public boolean handle(SetLocalPlayerAsInitializedPacket packet) { if (!session.isLoggedIn() && !session.isLoggingIn() && session.getConnector().getAuthType() == AuthType.ONLINE) { // TODO it is safer to key authentication on something that won't change (UUID, not username) if (!couldLoginUserByName(session.getAuthData().getName())) { LoginEncryptionUtils.showLoginWindow(session); } // else we were able to log the user in - return true; } + return translateAndDefault(packet); + } + + @Override + public boolean handle(MovePlayerPacket packet) { if (session.isLoggingIn()) { session.sendMessage("Please wait until you are logged in..."); } 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 16c7e7fe..dca29762 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 @@ -29,42 +29,50 @@ import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.auth.exception.request.InvalidCredentialsException; import com.github.steveice10.mc.auth.exception.request.RequestException; import com.github.steveice10.mc.protocol.MinecraftProtocol; +import com.github.steveice10.mc.protocol.data.SubProtocol; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket; +import com.github.steveice10.mc.protocol.packet.login.server.LoginSuccessPacket; import com.github.steveice10.packetlib.Client; import com.github.steveice10.packetlib.event.session.*; import com.github.steveice10.packetlib.packet.Packet; import com.github.steveice10.packetlib.tcp.TcpSessionFactory; import com.nukkitx.math.GenericMath; import com.nukkitx.math.TrigMath; -import com.nukkitx.math.vector.Vector2f; -import com.nukkitx.math.vector.Vector2i; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.math.vector.*; +import com.nukkitx.protocol.bedrock.BedrockPacket; import com.nukkitx.protocol.bedrock.BedrockServerSession; import com.nukkitx.protocol.bedrock.data.ContainerId; import com.nukkitx.protocol.bedrock.data.GamePublishSetting; import com.nukkitx.protocol.bedrock.data.GameRuleData; import com.nukkitx.protocol.bedrock.data.PlayerPermission; import com.nukkitx.protocol.bedrock.packet.*; +import it.unimi.dsi.fastutil.objects.Object2LongMap; +import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; import lombok.Getter; import lombok.Setter; import org.geysermc.common.AuthType; import org.geysermc.common.window.FormWindow; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; +import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.remote.RemoteServer; import org.geysermc.connector.network.session.auth.AuthData; import org.geysermc.connector.network.session.auth.BedrockClientData; import org.geysermc.connector.network.session.cache.*; -import org.geysermc.connector.network.translators.Registry; -import org.geysermc.connector.network.translators.block.BlockTranslator; +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.item.ItemRegistry; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.ChunkUtils; import org.geysermc.connector.utils.LocaleUtils; -import org.geysermc.connector.utils.Toolbox; +import org.geysermc.connector.utils.SkinUtils; import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.EncryptionUtil; @@ -83,8 +91,10 @@ public class GeyserSession implements CommandSender { private final UpstreamSession upstream; private RemoteServer remoteServer; private Client downstream; - @Setter private AuthData authData; - @Setter private BedrockClientData clientData; + @Setter + private AuthData authData; + @Setter + private BedrockClientData clientData; private PlayerEntity playerEntity; private PlayerInventory inventory; @@ -94,6 +104,14 @@ public class GeyserSession implements CommandSender { private InventoryCache inventoryCache; private ScoreboardCache scoreboardCache; private WindowCache windowCache; + @Setter + private TeleportCache teleportCache; + + /** + * A map of Vector3i positions to Java entity IDs. + * Used for translating Bedrock block actions to Java entity actions. + */ + private final Object2LongMap itemFrameCache = new Object2LongOpenHashMap<>(); private DataCache javaPacketCache; @@ -112,20 +130,47 @@ public class GeyserSession implements CommandSender { private GameMode gameMode = GameMode.SURVIVAL; private final AtomicInteger pendingDimSwitches = new AtomicInteger(0); + + @Setter + private boolean sneaking; + @Setter private boolean sprinting; @Setter private boolean jumping; + @Setter + private BlockState breakingBlock; + + @Setter + private Vector3i lastBlockPlacePosition; + + @Setter + private String lastBlockPlacedId; + + @Setter + private boolean interacting; + + @Setter + private Vector3i lastInteractionPosition; + @Setter private boolean switchingDimension = false; private boolean manyDimPackets = false; private ServerRespawnPacket lastDimPacket = null; + @Setter + private Entity ridingVehicleEntity; + @Setter private int craftSlot = 0; + @Setter + private long lastWindowCloseTime = 0; + + private MinecraftProtocol protocol; + public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) { this.connector = connector; this.upstream = new UpstreamSession(bedrockServerSession); @@ -154,16 +199,16 @@ public class GeyserSession implements CommandSender { ChunkUtils.sendEmptyChunks(this, playerEntity.getPosition().toInt(), 0, false); BiomeDefinitionListPacket biomeDefinitionListPacket = new BiomeDefinitionListPacket(); - biomeDefinitionListPacket.setTag(Toolbox.BIOMES); + biomeDefinitionListPacket.setTag(BiomeTranslator.BIOMES); upstream.sendPacket(biomeDefinitionListPacket); AvailableEntityIdentifiersPacket entityPacket = new AvailableEntityIdentifiersPacket(); - entityPacket.setTag(Toolbox.ENTITY_IDENTIFIERS); + entityPacket.setTag(EntityIdentifierRegistry.ENTITY_IDENTIFIERS); upstream.sendPacket(entityPacket); InventoryContentPacket creativePacket = new InventoryContentPacket(); creativePacket.setContainerId(ContainerId.CREATIVE); - creativePacket.setContents(Toolbox.CREATIVE_ITEMS); + creativePacket.setContents(ItemRegistry.CREATIVE_ITEMS); upstream.sendPacket(creativePacket); PlayStatusPacket playStatusPacket = new PlayStatusPacket(); @@ -171,6 +216,17 @@ public class GeyserSession implements CommandSender { upstream.sendPacket(playStatusPacket); } + public void fetchOurSkin(PlayerListPacket.Entry entry) { + PlayerSkinPacket playerSkinPacket = new PlayerSkinPacket(); + playerSkinPacket.setUuid(authData.getUUID()); + playerSkinPacket.setSkin(entry.getSkin()); + playerSkinPacket.setOldSkinName("OldName"); + playerSkinPacket.setNewSkinName("NewName"); + playerSkinPacket.setTrustedSkin(true); + upstream.sendPacket(playerSkinPacket); + getConnector().getLogger().debug("Sending skin for " + playerEntity.getUsername() + " " + authData.getUUID()); + } + public void login() { if (connector.getAuthType() != AuthType.ONLINE) { connector.getLogger().info( @@ -197,7 +253,6 @@ public class GeyserSession implements CommandSender { // new thread so clients don't timeout new Thread(() -> { try { - MinecraftProtocol protocol; if (password != null && !password.isEmpty()) { protocol = new MinecraftProtocol(username, password); } else { @@ -295,13 +350,33 @@ public class GeyserSession implements CommandSender { lastDimPacket = event.getPacket(); return; } else if (lastDimPacket != null) { - Registry.JAVA.translate(lastDimPacket.getClass(), lastDimPacket, GeyserSession.this); + PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(lastDimPacket.getClass(), lastDimPacket, GeyserSession.this); lastDimPacket = null; } - Registry.JAVA.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this); + // Required, or else Floodgate players break with Bukkit chunk caching + if (event.getPacket() instanceof LoginSuccessPacket) { + GameProfile profile = ((LoginSuccessPacket) event.getPacket()).getProfile(); + playerEntity.setUsername(profile.getName()); + playerEntity.setUuid(profile.getId()); + + // Check if they are not using a linked account + if (connector.getAuthType() == AuthType.OFFLINE || playerEntity.getUuid().getMostSignificantBits() == 0) { + SkinUtils.handleBedrockSkin(playerEntity, clientData); + } + } + + PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this); } } + + @Override + public void packetError(PacketErrorEvent event) { + connector.getLogger().warning("Downstream packet error! " + event.getCause().getMessage()); + if (connector.getConfig().isDebugMode()) + event.getCause().printStackTrace(); + event.setSuppress(true); + } }); downstream.getSession().connect(); @@ -327,10 +402,11 @@ public class GeyserSession implements CommandSender { } } - this.entityCache.getEntities().clear(); - this.scoreboardCache.removeScoreboard(); - this.inventoryCache.getInventories().clear(); - this.windowCache.getWindows().clear(); + this.chunkCache = null; + this.entityCache = null; + this.scoreboardCache = null; + this.inventoryCache = null; + this.windowCache = null; closed = true; } @@ -434,9 +510,63 @@ public class GeyserSession implements CommandSender { startGamePacket.setEnchantmentSeed(0); startGamePacket.setMultiplayerCorrelationId(""); startGamePacket.setBlockPalette(BlockTranslator.BLOCKS); - startGamePacket.setItemEntries(Toolbox.ITEMS); + startGamePacket.setItemEntries(ItemRegistry.ITEMS); startGamePacket.setVanillaVersion("*"); // startGamePacket.setMovementServerAuthoritative(true); upstream.sendPacketImmediately(startGamePacket); } + + public boolean confirmTeleport(Vector3d position) { + if (teleportCache != null) { + if (!teleportCache.canConfirm(position)) { + GeyserConnector.getInstance().getLogger().debug("Unconfirmed Teleport " + teleportCache.getTeleportConfirmId() + + " Ignore movement " + position + " expected " + teleportCache); + return false; + } + int teleportId = teleportCache.getTeleportConfirmId(); + teleportCache = null; + ClientTeleportConfirmPacket teleportConfirmPacket = new ClientTeleportConfirmPacket(teleportId); + sendDownstreamPacket(teleportConfirmPacket); + } + return true; + } + + /** + * Queue a packet to be sent to player. + * + * @param packet the bedrock packet from the NukkitX protocol lib + */ + public void sendUpstreamPacket(BedrockPacket packet) { + if (upstream != null && !upstream.isClosed()) { + upstream.sendPacket(packet); + } else { + connector.getLogger().debug("Tried to send upstream packet " + packet.getClass().getSimpleName() + " but the session was null"); + } + } + + /** + * Send a packet immediately to the player. + * + * @param packet the bedrock packet from the NukkitX protocol lib + */ + public void sendUpstreamPacketImmediately(BedrockPacket packet) { + if (upstream != null && !upstream.isClosed()) { + upstream.sendPacketImmediately(packet); + } else { + connector.getLogger().debug("Tried to send upstream packet " + packet.getClass().getSimpleName() + " immediately but the session was null"); + } + } + + /** + * Send a packet to the remote server. + * + * @param packet the java edition packet from MCProtocolLib + */ + public void sendDownstreamPacket(Packet packet) { + if (downstream != null && downstream.getSession() != null && protocol.getSubProtocol().equals(SubProtocol.GAME)) { + downstream.getSession().send(packet); + } else { + connector.getLogger().debug("Tried to send downstream packet " + packet.getClass().getSimpleName() + " before connected to the server"); + } + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java index 8fc56acd..6aeebbaa 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java @@ -24,16 +24,26 @@ public class BedrockClientData { private String skinId; @JsonProperty(value = "SkinData") private String skinData; + @JsonProperty(value = "SkinImageHeight") + private int skinImageHeight; + @JsonProperty(value = "SkinImageWidth") + private int skinImageWidth; @JsonProperty(value = "CapeId") private String capeId; @JsonProperty(value = "CapeData") private byte[] capeData; + @JsonProperty(value = "CapeImageHeight") + private int capeImageHeight; + @JsonProperty(value = "CapeImageWidth") + private int capeImageWidth; @JsonProperty(value = "CapeOnClassicSkin") private boolean capeOnClassicSkin; @JsonProperty(value = "SkinResourcePatch") private String geometryName; @JsonProperty(value = "SkinGeometryData") private String geometryData; + @JsonProperty(value = "PersonaSkin") + private boolean personaSkin; @JsonProperty(value = "PremiumSkin") private boolean premiumSkin; @@ -60,6 +70,15 @@ public class BedrockClientData { @JsonProperty(value = "ClientRandomId") private long clientRandomId; + @JsonProperty(value = "ArmSize") + private String armSize; + @JsonProperty(value = "SkinAnimationData") + private String skinAnimationData; + @JsonProperty(value = "SkinColor") + private String skinColor; + @JsonProperty(value = "ThirdPartyNameOnly") + private boolean thirdPartyNameOnly; + public enum UIProfile { @JsonEnumDefaultValue CLASSIC, diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java index abb9016a..267f3cb1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java @@ -62,7 +62,7 @@ public class BossBar { bossEventPacket.setOverlay(overlay); bossEventPacket.setDarkenSky(darkenSky); - session.getUpstream().sendPacket(bossEventPacket); + session.sendUpstreamPacket(bossEventPacket); } public void updateTitle(Message title) { @@ -72,7 +72,7 @@ public class BossBar { bossEventPacket.setAction(BossEventPacket.Action.TITLE); bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getClientData().getLanguageCode())); - session.getUpstream().sendPacket(bossEventPacket); + session.sendUpstreamPacket(bossEventPacket); } public void updateHealth(float health) { @@ -82,7 +82,7 @@ public class BossBar { bossEventPacket.setAction(BossEventPacket.Action.HEALTH_PERCENTAGE); bossEventPacket.setHealthPercentage(health); - session.getUpstream().sendPacket(bossEventPacket); + session.sendUpstreamPacket(bossEventPacket); } public void removeBossBar() { @@ -90,7 +90,7 @@ public class BossBar { bossEventPacket.setBossUniqueEntityId(entityId); bossEventPacket.setAction(BossEventPacket.Action.HIDE); - session.getUpstream().sendPacket(bossEventPacket); + session.sendUpstreamPacket(bossEventPacket); removeBossEntity(); } @@ -104,18 +104,21 @@ public class BossBar { addEntityPacket.setRuntimeEntityId(entityId); addEntityPacket.setIdentifier("minecraft:creeper"); addEntityPacket.setEntityType(33); - addEntityPacket.setPosition(session.getPlayerEntity().getPosition()); + addEntityPacket.setPosition(session.getPlayerEntity().getPosition().sub(0D, -10D, 0D)); addEntityPacket.setRotation(Vector3f.ZERO); addEntityPacket.setMotion(Vector3f.ZERO); - addEntityPacket.getMetadata().put(EntityData.SCALE, 0.01F); // scale = 0 doesn't work? + addEntityPacket.getMetadata() + .putFloat(EntityData.SCALE, 0F) + .putFloat(EntityData.BOUNDING_BOX_WIDTH, 0F) + .putFloat(EntityData.BOUNDING_BOX_HEIGHT, 0F); - session.getUpstream().sendPacket(addEntityPacket); + session.sendUpstreamPacket(addEntityPacket); } private void removeBossEntity() { RemoveEntityPacket removeEntityPacket = new RemoveEntityPacket(); removeEntityPacket.setUniqueEntityId(entityId); - session.getUpstream().sendPacket(removeEntityPacket); + session.sendUpstreamPacket(removeEntityPacket); } } 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 bc88694d..ac7ab06c 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 @@ -29,34 +29,39 @@ import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; import com.github.steveice10.mc.protocol.data.game.chunk.Column; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; -import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; import lombok.Getter; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.Translators; -import org.geysermc.connector.network.translators.block.BlockTranslator; -import org.geysermc.connector.world.chunk.ChunkPosition; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.connector.network.translators.world.chunk.ChunkPosition; import java.util.HashMap; import java.util.Map; public class ChunkCache { - private GeyserSession session; + private boolean cache; + private final GeyserSession session; @Getter - private Map chunks; + private Map chunks = new HashMap<>(); public ChunkCache(GeyserSession session) { this.session = session; - this.chunks = new HashMap<>(); + this.cache = session.getConnector().getConfig().isCacheChunks(); } public void addToCache(Column chunk) { + if (!cache) { + return; + } ChunkPosition position = new ChunkPosition(chunk.getX(), chunk.getZ()); chunks.put(position, chunk); } public void updateBlock(Position position, BlockState block) { + if (!cache) { + return; + } ChunkPosition chunkPosition = new ChunkPosition(position.getX() >> 4, position.getZ() >> 4); if (!chunks.containsKey(chunkPosition)) return; @@ -70,6 +75,9 @@ public class ChunkCache { } public BlockState getBlockAt(Position position) { + if (!cache) { + return BlockTranslator.AIR; + } ChunkPosition chunkPosition = new ChunkPosition(position.getX() >> 4, position.getZ() >> 4); if (!chunks.containsKey(chunkPosition)) return BlockTranslator.AIR; @@ -85,24 +93,9 @@ public class ChunkCache { } public void removeChunk(ChunkPosition position) { - chunks.remove(position); - sendEmptyChunk(position, true); - } - - public void sendEmptyChunk(ChunkPosition position) { - sendEmptyChunk(position, false); - } - - public void sendEmptyChunk(ChunkPosition position, boolean force) { - if (!force && chunks.containsKey(position)) + if (!cache) { return; - - LevelChunkPacket levelChunkPacket = new LevelChunkPacket(); - levelChunkPacket.setChunkX(position.getX()); - levelChunkPacket.setChunkZ(position.getZ()); - levelChunkPacket.setCachingEnabled(false); - levelChunkPacket.setSubChunksLength(0); - levelChunkPacket.setData(Translators.EMPTY_LEVEL_CHUNK_DATA); - session.getUpstream().sendPacket(levelChunkPacket); + } + chunks.remove(position); } } 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 80d10b1a..0bc51ac7 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 @@ -26,8 +26,6 @@ package org.geysermc.connector.network.session.cache; import it.unimi.dsi.fastutil.longs.*; -import it.unimi.dsi.fastutil.objects.Object2LongMap; -import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import lombok.Getter; import org.geysermc.connector.entity.Entity; @@ -49,6 +47,7 @@ public class EntityCache { private Long2LongMap entityIdTranslations = Long2LongMaps.synchronize(new Long2LongOpenHashMap()); private Map playerEntities = Collections.synchronizedMap(new HashMap<>()); private Map bossBars = Collections.synchronizedMap(new HashMap<>()); + private Long2LongMap cachedPlayerEntityLinks = Long2LongMaps.synchronize(new Long2LongOpenHashMap()); @Getter private AtomicLong nextEntityId = new AtomicLong(2L); @@ -148,4 +147,12 @@ public class EntityCache { playerEntities = null; bossBars = null; } + + public long getCachedPlayerEntityLink(long playerId) { + return cachedPlayerEntityLinks.getOrDefault(playerId, -1); + } + + public void addCachedPlayerEntityLink(long playerId, long linkedEntityId) { + cachedPlayerEntityLinks.put(playerId, linkedEntityId); + } } diff --git a/connector/src/main/java/org/geysermc/connector/world/chunk/ChunkPosition.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/TeleportCache.java similarity index 67% rename from connector/src/main/java/org/geysermc/connector/world/chunk/ChunkPosition.java rename to connector/src/main/java/org/geysermc/connector/network/session/cache/TeleportCache.java index c45a7c94..475630db 100644 --- a/connector/src/main/java/org/geysermc/connector/world/chunk/ChunkPosition.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/TeleportCache.java @@ -23,31 +23,26 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.world.chunk; +package org.geysermc.connector.network.session.cache; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.nukkitx.math.vector.Vector3d; +import com.nukkitx.math.vector.Vector3f; import lombok.AllArgsConstructor; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; +import lombok.Data; -@Getter -@Setter @AllArgsConstructor -@EqualsAndHashCode -public class ChunkPosition { +@Data +public class TeleportCache { - private int x; - private int z; + private static final double ERROR = 0.2; + private static final double ERROR_Y = 0.5; - public Position getBlock(int x, int y, int z) { - return new Position((this.x << 4) + x, y, (this.z << 4) + z); - } + private double x, y, z; + private int teleportConfirmId; - public Position getChunkBlock(int x, int y, int z) { - int chunkX = x & 15; - int chunkY = y & 15; - int chunkZ = z & 15; - return new Position(chunkX, chunkY, chunkZ); + public boolean canConfirm(Vector3d position) { + return (Math.abs(this.x - position.getX()) < ERROR && + Math.abs(this.y - position.getY()) < ERROR_Y && + Math.abs(this.z - position.getZ()) < ERROR); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java index 1d10e4db..15b9a770 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java @@ -66,7 +66,7 @@ public class WindowCache { formRequestPacket.setFormId(id); formRequestPacket.setFormData(windows.get(id).getJSONData()); - session.getUpstream().sendPacket(formRequestPacket); + session.sendUpstreamPacket(formRequestPacket); } public void showWindow(FormWindow window, int id) { @@ -74,7 +74,7 @@ public class WindowCache { formRequestPacket.setFormId(id); formRequestPacket.setFormData(window.getJSONData()); - session.getUpstream().sendPacket(formRequestPacket); + session.sendUpstreamPacket(formRequestPacket); addWindow(window, id); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java index 12278841..c12cc513 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/BiomeTranslator.java @@ -1,14 +1,72 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + package org.geysermc.connector.network.translators; + +import com.nukkitx.nbt.NbtUtils; +import com.nukkitx.nbt.stream.NBTInputStream; +import com.nukkitx.nbt.tag.CompoundTag; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.utils.FileUtils; + +import java.io.InputStream; import java.util.Arrays; -//Based off of ProtocolSupport's LegacyBiomeData.java https://github.com/ProtocolSupport/ProtocolSupport/blob/b2cad35977f3fcb65bee57b9e14fc9c975f71d32/src/protocolsupport/protocol/typeremapper/legacy/LegacyBiomeData.java -//Array index formula by https://wiki.vg/Chunk_Format - +// Based off of ProtocolSupport's LegacyBiomeData.java: +// https://github.com/ProtocolSupport/ProtocolSupport/blob/b2cad35977f3fcb65bee57b9e14fc9c975f71d32/src/protocolsupport/protocol/typeremapper/legacy/LegacyBiomeData.java +// Array index formula by https://wiki.vg/Chunk_Format public class BiomeTranslator { + public static final CompoundTag BIOMES; + + private BiomeTranslator() { + } + + public static void init() { + // no-op + } + + static { + /* Load biomes */ + InputStream stream = FileUtils.getResource("bedrock/biome_definitions.dat"); + + CompoundTag biomesTag; + + try (NBTInputStream biomenbtInputStream = NbtUtils.createNetworkReader(stream)){ + biomesTag = (CompoundTag) biomenbtInputStream.readTag(); + BIOMES = biomesTag; + } catch (Exception ex) { + GeyserConnector.getInstance().getLogger().warning("Failed to get biomes from biome definitions, is there something wrong with the file?"); + throw new AssertionError(ex); + } + } + public static byte[] toBedrockBiome(int[] biomeData) { byte[] bedrockData = new byte[256]; - if(biomeData == null) { + if (biomeData == null) { return bedrockData; } @@ -24,12 +82,12 @@ public class BiomeTranslator { return bedrockData; } - protected static void fillArray(int z, int x, byte[] legacyBiomeData, int biomeId) { + private static void fillArray(int z, int x, byte[] legacyBiomeData, int biomeId) { int offset = (z << 4) | x; Arrays.fill(legacyBiomeData, offset, offset + 4, (byte) biomeId); } - protected static byte biomeID(int[] biomeData, int x, int z) { + private static byte biomeID(int[] biomeData, int x, int z) { return (byte) biomeData[((z >> 2) & 3) << 2 | ((x >> 2) & 3)]; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java new file mode 100644 index 00000000..cc9b2cd8 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.network.translators; + +import com.nukkitx.nbt.NbtUtils; +import com.nukkitx.nbt.stream.NBTInputStream; +import com.nukkitx.nbt.tag.CompoundTag; +import org.geysermc.connector.utils.FileUtils; + +import java.io.InputStream; + +/** + * Registry for entity identifiers. + */ +public class EntityIdentifierRegistry { + + public static CompoundTag ENTITY_IDENTIFIERS; + + private EntityIdentifierRegistry() { + } + + public static void init() { + // no-op + } + + static { + /* Load entity identifiers */ + InputStream stream = FileUtils.getResource("bedrock/entity_identifiers.dat"); + + try (NBTInputStream nbtInputStream = NbtUtils.createNetworkReader(stream)) { + ENTITY_IDENTIFIERS = (CompoundTag) nbtInputStream.readTag(); + } catch (Exception e) { + throw new AssertionError("Unable to get entities from entity identifiers", e); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/ItemStackTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/ItemStackTranslator.java deleted file mode 100644 index 356dcf98..00000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/ItemStackTranslator.java +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.data.message.Message; -import com.github.steveice10.opennbt.tag.builtin.*; -import com.nukkitx.nbt.tag.CompoundTag; -import com.nukkitx.nbt.tag.Tag; -import com.nukkitx.protocol.bedrock.data.ItemData; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.utils.MessageUtils; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public abstract class ItemStackTranslator { - - public ItemData translateToBedrock(ItemStack itemStack, ItemEntry itemEntry) { - if (itemStack == null) { - return ItemData.AIR; - } - if (itemStack.getNbt() == null) { - return ItemData.of(itemEntry.getBedrockId(), (short) itemEntry.getBedrockData(), itemStack.getAmount()); - } - return ItemData.of(itemEntry.getBedrockId(), (short) itemEntry.getBedrockData(), itemStack.getAmount(), this.translateNbtToBedrock(itemStack.getNbt())); - } - - public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { - if (itemData == null) return null; - if (itemData.getTag() == null) { - return new ItemStack(itemEntry.getJavaId(), itemData.getCount(), new com.github.steveice10.opennbt.tag.builtin.CompoundTag("")); - } - return new ItemStack(itemEntry.getJavaId(), itemData.getCount(), this.translateToJavaNBT(itemData.getTag())); - } - - public abstract List getAppliedItems(); - - public CompoundTag translateNbtToBedrock(com.github.steveice10.opennbt.tag.builtin.CompoundTag tag) { - Map> javaValue = new HashMap>(); - if (tag.getValue() != null && !tag.getValue().isEmpty()) { - for (String str : tag.getValue().keySet()) { - com.github.steveice10.opennbt.tag.builtin.Tag javaTag = tag.get(str); - com.nukkitx.nbt.tag.Tag translatedTag = translateToBedrockNBT(javaTag); - if (translatedTag == null) - continue; - - javaValue.put(translatedTag.getName(), translatedTag); - } - } - - com.nukkitx.nbt.tag.CompoundTag bedrockTag = new com.nukkitx.nbt.tag.CompoundTag(tag.getName(), javaValue); - return bedrockTag; - } - - private com.nukkitx.nbt.tag.Tag translateToBedrockNBT(com.github.steveice10.opennbt.tag.builtin.Tag tag) { - if (tag instanceof ByteArrayTag) { - ByteArrayTag byteArrayTag = (ByteArrayTag) tag; - return new com.nukkitx.nbt.tag.ByteArrayTag(byteArrayTag.getName(), byteArrayTag.getValue()); - } - - if (tag instanceof ByteTag) { - ByteTag byteTag = (ByteTag) tag; - return new com.nukkitx.nbt.tag.ByteTag(byteTag.getName(), byteTag.getValue()); - } - - if (tag instanceof DoubleTag) { - DoubleTag doubleTag = (DoubleTag) tag; - return new com.nukkitx.nbt.tag.DoubleTag(doubleTag.getName(), doubleTag.getValue()); - } - - if (tag instanceof FloatTag) { - FloatTag floatTag = (FloatTag) tag; - return new com.nukkitx.nbt.tag.FloatTag(floatTag.getName(), floatTag.getValue()); - } - - if (tag instanceof IntArrayTag) { - IntArrayTag intArrayTag = (IntArrayTag) tag; - return new com.nukkitx.nbt.tag.IntArrayTag(intArrayTag.getName(), intArrayTag.getValue()); - } - - if (tag instanceof IntTag) { - IntTag intTag = (IntTag) tag; - return new com.nukkitx.nbt.tag.IntTag(intTag.getName(), intTag.getValue()); - } - - if (tag instanceof LongArrayTag) { - LongArrayTag longArrayTag = (LongArrayTag) tag; - return new com.nukkitx.nbt.tag.LongArrayTag(longArrayTag.getName(), longArrayTag.getValue()); - } - - if (tag instanceof LongTag) { - LongTag longTag = (LongTag) tag; - return new com.nukkitx.nbt.tag.LongTag(longTag.getName(), longTag.getValue()); - } - - if (tag instanceof ShortTag) { - ShortTag shortTag = (ShortTag) tag; - return new com.nukkitx.nbt.tag.ShortTag(shortTag.getName(), shortTag.getValue()); - } - - if (tag instanceof StringTag) { - StringTag stringTag = (StringTag) tag; - return new com.nukkitx.nbt.tag.StringTag(stringTag.getName(), MessageUtils.getBedrockMessage(Message.fromString(stringTag.getValue()))); - } - - if (tag instanceof ListTag) { - ListTag listTag = (ListTag) tag; - - List tagList = new ArrayList<>(); - for (com.github.steveice10.opennbt.tag.builtin.Tag value : listTag) { - tagList.add(translateToBedrockNBT(value)); - } - Class clazz = CompoundTag.class; - if (!tagList.isEmpty()) { - clazz = tagList.get(0).getClass(); - } - return new com.nukkitx.nbt.tag.ListTag(listTag.getName(), clazz, tagList); - } - - if (tag instanceof com.github.steveice10.opennbt.tag.builtin.CompoundTag) { - com.github.steveice10.opennbt.tag.builtin.CompoundTag compoundTag = (com.github.steveice10.opennbt.tag.builtin.CompoundTag) tag; - - return translateNbtToBedrock(compoundTag); - } - - return null; - } - - public com.github.steveice10.opennbt.tag.builtin.CompoundTag translateToJavaNBT(com.nukkitx.nbt.tag.CompoundTag tag) { - com.github.steveice10.opennbt.tag.builtin.CompoundTag javaTag = new com.github.steveice10.opennbt.tag.builtin.CompoundTag(tag.getName()); - Map javaValue = javaTag.getValue(); - if (tag.getValue() != null && !tag.getValue().isEmpty()) { - for (String str : tag.getValue().keySet()) { - com.nukkitx.nbt.tag.Tag bedrockTag = tag.get(str); - com.github.steveice10.opennbt.tag.builtin.Tag translatedTag = translateToJavaNBT(bedrockTag); - if (translatedTag == null) - continue; - - javaValue.put(translatedTag.getName(), translatedTag); - } - } - - javaTag.setValue(javaValue); - return javaTag; - } - - private com.github.steveice10.opennbt.tag.builtin.Tag translateToJavaNBT(com.nukkitx.nbt.tag.Tag tag) { - if (tag instanceof com.nukkitx.nbt.tag.ByteArrayTag) { - com.nukkitx.nbt.tag.ByteArrayTag byteArrayTag = (com.nukkitx.nbt.tag.ByteArrayTag) tag; - return new ByteArrayTag(byteArrayTag.getName(), byteArrayTag.getValue()); - } - - if (tag instanceof com.nukkitx.nbt.tag.ByteTag) { - com.nukkitx.nbt.tag.ByteTag byteTag = (com.nukkitx.nbt.tag.ByteTag) tag; - return new ByteTag(byteTag.getName(), byteTag.getValue()); - } - - if (tag instanceof com.nukkitx.nbt.tag.DoubleTag) { - com.nukkitx.nbt.tag.DoubleTag doubleTag = (com.nukkitx.nbt.tag.DoubleTag) tag; - return new DoubleTag(doubleTag.getName(), doubleTag.getValue()); - } - - if (tag instanceof com.nukkitx.nbt.tag.FloatTag) { - com.nukkitx.nbt.tag.FloatTag floatTag = (com.nukkitx.nbt.tag.FloatTag) tag; - return new FloatTag(floatTag.getName(), floatTag.getValue()); - } - - if (tag instanceof com.nukkitx.nbt.tag.IntArrayTag) { - com.nukkitx.nbt.tag.IntArrayTag intArrayTag = (com.nukkitx.nbt.tag.IntArrayTag) tag; - return new IntArrayTag(intArrayTag.getName(), intArrayTag.getValue()); - } - - if (tag instanceof com.nukkitx.nbt.tag.IntTag) { - com.nukkitx.nbt.tag.IntTag intTag = (com.nukkitx.nbt.tag.IntTag) tag; - return new IntTag(intTag.getName(), intTag.getValue()); - } - - if (tag instanceof com.nukkitx.nbt.tag.LongArrayTag) { - com.nukkitx.nbt.tag.LongArrayTag longArrayTag = (com.nukkitx.nbt.tag.LongArrayTag) tag; - return new LongArrayTag(longArrayTag.getName(), longArrayTag.getValue()); - } - - if (tag instanceof com.nukkitx.nbt.tag.LongTag) { - com.nukkitx.nbt.tag.LongTag longTag = (com.nukkitx.nbt.tag.LongTag) tag; - return new LongTag(longTag.getName(), longTag.getValue()); - } - - if (tag instanceof com.nukkitx.nbt.tag.ShortTag) { - com.nukkitx.nbt.tag.ShortTag shortTag = (com.nukkitx.nbt.tag.ShortTag) tag; - return new ShortTag(shortTag.getName(), shortTag.getValue()); - } - - if (tag instanceof com.nukkitx.nbt.tag.StringTag) { - com.nukkitx.nbt.tag.StringTag stringTag = (com.nukkitx.nbt.tag.StringTag) tag; - return new StringTag(stringTag.getName(), stringTag.getValue()); - } - - if (tag instanceof com.nukkitx.nbt.tag.ListTag) { - com.nukkitx.nbt.tag.ListTag listTag = (com.nukkitx.nbt.tag.ListTag) tag; - - List tags = new ArrayList<>(); - - for (Object value : listTag.getValue()) { - if (!(value instanceof com.nukkitx.nbt.tag.Tag)) - continue; - - com.nukkitx.nbt.tag.Tag tagValue = (com.nukkitx.nbt.tag.Tag) value; - com.github.steveice10.opennbt.tag.builtin.Tag javaTag = translateToJavaNBT(tagValue); - if (javaTag != null) - tags.add(javaTag); - } - return new ListTag(listTag.getName(), tags); - } - - if (tag instanceof com.nukkitx.nbt.tag.CompoundTag) { - com.nukkitx.nbt.tag.CompoundTag compoundTag = (com.nukkitx.nbt.tag.CompoundTag) tag; - return translateToJavaNBT(compoundTag); - } - - return null; - } - - -} 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 new file mode 100644 index 00000000..c3ec8ff2 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators; + +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerKeepAlivePacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdateLightPacket; +import com.github.steveice10.packetlib.packet.Packet; +import com.nukkitx.protocol.bedrock.BedrockPacket; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.session.GeyserSession; +import org.reflections.Reflections; + +import java.util.HashMap; +import java.util.Map; + +public class PacketTranslatorRegistry { + private final Map, PacketTranslator> translators = new HashMap<>(); + + public static final PacketTranslatorRegistry JAVA_TRANSLATOR = new PacketTranslatorRegistry<>(); + public static final PacketTranslatorRegistry BEDROCK_TRANSLATOR = new PacketTranslatorRegistry<>(); + + private static final ObjectArrayList> IGNORED_PACKETS = new ObjectArrayList<>(); + + static { + Reflections ref = new Reflections("org.geysermc.connector.network.translators"); + + for (Class clazz : ref.getTypesAnnotatedWith(Translator.class)) { + Class packet = clazz.getAnnotation(Translator.class).packet(); + + GeyserConnector.getInstance().getLogger().debug("Found annotated translator: " + clazz.getCanonicalName() + " : " + packet.getSimpleName()); + + try { + if (Packet.class.isAssignableFrom(packet)) { + Class targetPacket = (Class) packet; + PacketTranslator translator = (PacketTranslator) clazz.newInstance(); + + JAVA_TRANSLATOR.translators.put(targetPacket, translator); + } else if (BedrockPacket.class.isAssignableFrom(packet)) { + Class targetPacket = (Class) packet; + PacketTranslator translator = (PacketTranslator) clazz.newInstance(); + + BEDROCK_TRANSLATOR.translators.put(targetPacket, translator); + } else { + GeyserConnector.getInstance().getLogger().error("Class " + clazz.getCanonicalName() + " is annotated as a translator but has an invalid target packet."); + } + } catch (InstantiationException | IllegalAccessException e) { + GeyserConnector.getInstance().getLogger().error("Could not instantiate annotated translator " + clazz.getCanonicalName() + "."); + } + } + + IGNORED_PACKETS.add(ServerKeepAlivePacket.class); // Handled by MCProtocolLib + IGNORED_PACKETS.add(ServerUpdateLightPacket.class); // Light is handled on Bedrock for us + } + + private PacketTranslatorRegistry() { + } + + public static void init() { + // no-op + } + + @SuppressWarnings("unchecked") + public

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

) translators.get(clazz)).translate(packet, session); + return true; + } else { + if (!IGNORED_PACKETS.contains(clazz)) + GeyserConnector.getInstance().getLogger().debug("Could not find packet for " + (packet.toString().length() > 25 ? packet.getClass().getSimpleName() : packet)); + } + } catch (Throwable ex) { + GeyserConnector.getInstance().getLogger().error("Could not translate packet " + packet.getClass().getSimpleName(), ex); + ex.printStackTrace(); + } + } + return false; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/Registry.java b/connector/src/main/java/org/geysermc/connector/network/translators/Registry.java deleted file mode 100644 index 70201ba8..00000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/Registry.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators; - -import java.util.HashMap; -import java.util.Map; - -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.network.session.GeyserSession; - -import com.github.steveice10.packetlib.packet.Packet; -import com.nukkitx.protocol.bedrock.BedrockPacket; - -public class Registry { - private final Map, PacketTranslator> MAP = new HashMap<>(); - - public static final Registry JAVA = new Registry<>(); - public static final Registry BEDROCK = new Registry<>(); - - public static void registerJava(Class targetPacket, PacketTranslator translator) { - JAVA.MAP.put(targetPacket, translator); - } - - public static void registerBedrock(Class targetPacket, PacketTranslator translator) { - BEDROCK.MAP.put(targetPacket, translator); - } - - @SuppressWarnings("unchecked") - public

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

) MAP.get(clazz)).translate(packet, session); - return true; - } else { - GeyserConnector.getInstance().getLogger().debug("Could not find packet for " + (packet.toString().length() > 25 ? packet.getClass().getSimpleName() : packet)); - } - } catch (Throwable ex) { - GeyserConnector.getInstance().getLogger().error("Could not translate packet " + packet.getClass().getSimpleName(), ex); - ex.printStackTrace(); - } - } - return false; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/Translators.java b/connector/src/main/java/org/geysermc/connector/network/translators/Translators.java deleted file mode 100644 index f0a3fd28..00000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/Translators.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -import com.github.steveice10.mc.protocol.data.game.window.WindowType; -import com.nukkitx.protocol.bedrock.data.ContainerType; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.network.translators.block.BlockTranslator; -import org.geysermc.connector.network.translators.block.entity.*; -import org.geysermc.connector.network.translators.inventory.*; -import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater; -import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; -import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.reflections.Reflections; - -import com.github.steveice10.packetlib.packet.Packet; -import com.nukkitx.nbt.CompoundTagBuilder; -import com.nukkitx.nbt.NbtUtils; -import com.nukkitx.nbt.stream.NBTOutputStream; -import com.nukkitx.nbt.tag.CompoundTag; -import com.nukkitx.protocol.bedrock.BedrockPacket; - -import lombok.Getter; - -public class Translators { - - @Getter - private static ItemTranslator itemTranslator; - - @Getter - private static Map inventoryTranslators = new HashMap<>(); - - @Getter - private static Map blockEntityTranslators = new HashMap<>(); - - private static final CompoundTag EMPTY_TAG = CompoundTagBuilder.builder().buildRootTag(); - public static final byte[] EMPTY_LEVEL_CHUNK_DATA; - - static { - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - outputStream.write(new byte[258]); // Biomes + Border Size + Extra Data Size - - try (NBTOutputStream stream = NbtUtils.createNetworkWriter(outputStream)) { - stream.write(EMPTY_TAG); - } - - EMPTY_LEVEL_CHUNK_DATA = outputStream.toByteArray(); - }catch (IOException e) { - throw new AssertionError("Unable to generate empty level chunk data"); - } - } - - @SuppressWarnings("unchecked") - public static void start() { - Reflections ref = new Reflections("org.geysermc.connector.network.translators"); - - for (Class clazz : ref.getTypesAnnotatedWith(Translator.class)) { - Class packet = clazz.getAnnotation(Translator.class).packet(); - - GeyserConnector.getInstance().getLogger().debug("Found annotated translator: " + clazz.getCanonicalName() + " : " + packet.getSimpleName()); - - try { - if (Packet.class.isAssignableFrom(packet)) { - Class targetPacket = (Class) packet; - PacketTranslator translator = (PacketTranslator) clazz.newInstance(); - - Registry.registerJava(targetPacket, translator); - - } else if (BedrockPacket.class.isAssignableFrom(packet)) { - Class targetPacket = (Class) packet; - PacketTranslator translator = (PacketTranslator) clazz.newInstance(); - - Registry.registerBedrock(targetPacket, translator); - - } else { - GeyserConnector.getInstance().getLogger().error("Class " + clazz.getCanonicalName() + " is annotated as a translator but has an invalid target packet."); - } - } catch (InstantiationException | IllegalAccessException e) { - GeyserConnector.getInstance().getLogger().error("Could not instantiate annotated translator " + clazz.getCanonicalName() + "."); - } - } - - itemTranslator = new ItemTranslator(); - itemTranslator.init(); - BlockTranslator.init(); - - registerBlockEntityTranslators(); - registerInventoryTranslators(); - } - - private static void registerBlockEntityTranslators() { - Reflections ref = new Reflections("org.geysermc.connector.network.translators.block.entity"); - - for (Class clazz : ref.getTypesAnnotatedWith(BlockEntity.class)) { - - GeyserConnector.getInstance().getLogger().debug("Found annotated block entity: " + clazz.getCanonicalName()); - - try { - blockEntityTranslators.put(clazz.getAnnotation(BlockEntity.class).name(), (BlockEntityTranslator) clazz.newInstance()); - } catch (InstantiationException | IllegalAccessException e) { - GeyserConnector.getInstance().getLogger().error("Could not instantiate annotated block entity " + clazz.getCanonicalName() + "."); - } - } - } - - private static void registerInventoryTranslators() { - inventoryTranslators.put(null, new PlayerInventoryTranslator()); //player inventory - inventoryTranslators.put(WindowType.GENERIC_9X1, new SingleChestInventoryTranslator(9)); - inventoryTranslators.put(WindowType.GENERIC_9X2, new SingleChestInventoryTranslator(18)); - inventoryTranslators.put(WindowType.GENERIC_9X3, new SingleChestInventoryTranslator(27)); - inventoryTranslators.put(WindowType.GENERIC_9X4, new DoubleChestInventoryTranslator(36)); - inventoryTranslators.put(WindowType.GENERIC_9X5, new DoubleChestInventoryTranslator(45)); - inventoryTranslators.put(WindowType.GENERIC_9X6, new DoubleChestInventoryTranslator(54)); - inventoryTranslators.put(WindowType.BREWING_STAND, new BrewingInventoryTranslator()); - inventoryTranslators.put(WindowType.ANVIL, new AnvilInventoryTranslator()); - inventoryTranslators.put(WindowType.CRAFTING, new CraftingInventoryTranslator()); - inventoryTranslators.put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator()); - //inventoryTranslators.put(WindowType.ENCHANTMENT, new EnchantmentInventoryTranslator()); //TODO - - InventoryTranslator furnace = new FurnaceInventoryTranslator(); - inventoryTranslators.put(WindowType.FURNACE, furnace); - inventoryTranslators.put(WindowType.BLAST_FURNACE, furnace); - inventoryTranslators.put(WindowType.SMOKER, furnace); - - InventoryUpdater containerUpdater = new ContainerInventoryUpdater(); - inventoryTranslators.put(WindowType.GENERIC_3X3, new BlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER, containerUpdater)); - inventoryTranslators.put(WindowType.HOPPER, new BlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER, containerUpdater)); - inventoryTranslators.put(WindowType.SHULKER_BOX, new BlockInventoryTranslator(27, "minecraft:shulker_box[facing=north]", ContainerType.CONTAINER, containerUpdater)); - //inventoryTranslators.put(WindowType.BEACON, new BlockInventoryTranslator(1, "minecraft:beacon", ContainerType.BEACON)); //TODO - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java index 7ab71389..2c44e4fd 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java @@ -27,6 +27,8 @@ package org.geysermc.connector.network.translators.bedrock; import java.util.concurrent.TimeUnit; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -41,6 +43,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlaye import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.packet.PlayStatusPacket; import com.nukkitx.protocol.bedrock.packet.PlayerActionPacket; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; @Translator(packet = PlayerActionPacket.class) public class BedrockActionTranslator extends PacketTranslator { @@ -61,42 +64,44 @@ public class BedrockActionTranslator extends PacketTranslator { + + @Override + public void translate(AdventureSettingsPacket packet, GeyserSession session) { + // Only canFly and flying are used by the server + // https://wiki.vg/Protocol#Player_Abilities_.28serverbound.29 + boolean canFly = packet.getFlags().contains(AdventureSettingsPacket.Flag.MAY_FLY); + boolean flying = packet.getFlags().contains(AdventureSettingsPacket.Flag.FLYING); + boolean creative = session.getGameMode() == GameMode.CREATIVE; + ClientPlayerAbilitiesPacket abilitiesPacket = new ClientPlayerAbilitiesPacket( + false, canFly, flying, creative, 0.05f, 0.1f + ); + session.sendDownstreamPacket(abilitiesPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockAnimateTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockAnimateTranslator.java index af41c9e0..012582da 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockAnimateTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockAnimateTranslator.java @@ -31,6 +31,7 @@ import org.geysermc.connector.network.translators.Translator; import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerSwingArmPacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientSteerBoatPacket; import com.nukkitx.protocol.bedrock.packet.AnimatePacket; import java.util.concurrent.TimeUnit; @@ -38,6 +39,9 @@ import java.util.concurrent.TimeUnit; @Translator(packet = AnimatePacket.class) public class BedrockAnimateTranslator extends PacketTranslator { + private boolean isSteeringLeft; + private boolean isSteeringRight; + @Override public void translate(AnimatePacket packet, GeyserSession session) { // Stop the player sending animations before they have fully spawned into the server @@ -49,11 +53,23 @@ public class BedrockAnimateTranslator extends PacketTranslator { case SWING_ARM: // Delay so entity damage can be processed first session.getConnector().getGeneralThreadPool().schedule(() -> - session.getDownstream().getSession().send(new ClientPlayerSwingArmPacket(Hand.MAIN_HAND)), + session.sendDownstreamPacket(new ClientPlayerSwingArmPacket(Hand.MAIN_HAND)), 25, TimeUnit.MILLISECONDS ); break; + // These two might need to be flipped, but my recommendation is getting moving working first + case ROW_LEFT: + // Packet value is a float of how long one has been rowing, so we convert that into a boolean + isSteeringLeft = packet.getRowingTime() > 0.0; + ClientSteerBoatPacket steerLeftPacket = new ClientSteerBoatPacket(isSteeringRight, isSteeringLeft); + session.sendDownstreamPacket(steerLeftPacket); + break; + case ROW_RIGHT: + isSteeringRight = packet.getRowingTime() > 0.0; + ClientSteerBoatPacket steerRightPacket = new ClientSteerBoatPacket(isSteeringRight, isSteeringLeft); + session.sendDownstreamPacket(steerRightPacket); + break; } } } 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 dc076a91..9fe62bb4 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 @@ -79,7 +79,7 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator { + + @Override + public void translate(BlockPickRequestPacket packet, GeyserSession session) { + Vector3i vector = packet.getBlockPosition(); + BlockState blockToPick = session.getConnector().getWorldManager().getBlockAt(session, vector.getX(), vector.getY(), vector.getZ()); + + // Block is air - chunk caching is probably off + if (blockToPick.getId() == 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/BedrockCommandRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java index c8e11741..1f31367c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java @@ -53,7 +53,7 @@ public class BedrockCommandRequestTranslator extends PacketTranslator { + + @Override + public void translate(EntityEventPacket packet, GeyserSession session) { + switch (packet.getType()) { + // Resend the packet so we get the eating sounds + case EATING_ITEM: + session.sendUpstreamPacket(packet); + return; + } + session.getConnector().getLogger().debug("Did not translate incoming EntityEventPacket: " + packet.toString()); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInteractTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInteractTranslator.java index 0d1c08b2..bfd4a90a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInteractTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInteractTranslator.java @@ -25,6 +25,8 @@ package org.geysermc.connector.network.translators.bedrock; +import com.nukkitx.protocol.bedrock.data.EntityData; +import com.nukkitx.protocol.bedrock.data.EntityFlag; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -32,9 +34,11 @@ import org.geysermc.connector.network.translators.Translator; import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.github.steveice10.mc.protocol.data.game.entity.player.InteractAction; +import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerState; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerInteractEntityPacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerStatePacket; import com.nukkitx.protocol.bedrock.packet.InteractPacket; -import org.geysermc.connector.network.translators.item.ItemTranslator; +import org.geysermc.connector.network.translators.item.ItemRegistry; @Translator(packet = InteractPacket.class) public class BedrockInteractTranslator extends PacketTranslator { @@ -47,17 +51,69 @@ public class BedrockInteractTranslator extends PacketTranslator switch (packet.getAction()) { case INTERACT: - if (session.getInventory().getItem(session.getInventory().getHeldItemSlot() + 36).getId() == ItemTranslator.SHIELD) { + if (session.getInventory().getItem(session.getInventory().getHeldItemSlot() + 36).getId() == ItemRegistry.SHIELD) { break; } ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(), InteractAction.INTERACT, Hand.MAIN_HAND); - session.getDownstream().getSession().send(interactPacket); + session.sendDownstreamPacket(interactPacket); break; case DAMAGE: ClientPlayerInteractEntityPacket attackPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(), InteractAction.ATTACK, Hand.MAIN_HAND); - session.getDownstream().getSession().send(attackPacket); + session.sendDownstreamPacket(attackPacket); + break; + case LEAVE_VEHICLE: + ClientPlayerStatePacket sneakPacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.START_SNEAKING); + session.sendDownstreamPacket(sneakPacket); + session.setRidingVehicleEntity(null); + break; + case MOUSEOVER: + // Handle the buttons for mobile - "Mount", etc; and the suggestions for console - "ZL: Mount", etc + if (packet.getRuntimeEntityId() != 0) { + Entity interactEntity = session.getEntityCache().getEntityByGeyserId(packet.getRuntimeEntityId()); + if (interactEntity == null) + return; + + String interactiveTag; + switch (interactEntity.getEntityType()) { + case PIG: + if (interactEntity.getMetadata().getFlags().getFlag(EntityFlag.SADDLED)) { + interactiveTag = "action.interact.mount"; + } else interactiveTag = ""; + break; + case HORSE: + case SKELETON_HORSE: + case ZOMBIE_HORSE: + case DONKEY: + case MULE: + case LLAMA: + case TRADER_LLAMA: + if (interactEntity.getMetadata().getFlags().getFlag(EntityFlag.TAMED)) { + interactiveTag = "action.interact.ride.horse"; + } else { + interactiveTag = "action.interact.mount"; + } + break; + case BOAT: + interactiveTag = "action.interact.ride.boat"; + break; + case MINECART: + interactiveTag = "action.interact.ride.minecart"; + break; + default: + return; // No need to process any further since there is no interactive tag + } + session.getPlayerEntity().getMetadata().put(EntityData.INTERACTIVE_TAG, interactiveTag); + session.getPlayerEntity().updateBedrockMetadata(session); + } else { + if (!(session.getPlayerEntity().getMetadata().get(EntityData.INTERACTIVE_TAG) == null) || + !(session.getPlayerEntity().getMetadata().get(EntityData.INTERACTIVE_TAG) == "")) { + // No interactive tag should be sent + session.getPlayerEntity().getMetadata().remove(EntityData.INTERACTIVE_TAG); + session.getPlayerEntity().updateBedrockMetadata(session); + } + } break; } } 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 3f6eba55..8f96b800 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 @@ -25,17 +25,7 @@ package org.geysermc.connector.network.translators.bedrock; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPlaceBlockPacket; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.Translators; -import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.geysermc.connector.utils.InventoryUtils; - -import com.nukkitx.math.vector.Vector3f; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; @@ -44,9 +34,28 @@ import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerInteractEntityPacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPlaceBlockPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerUseItemPacket; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import com.nukkitx.protocol.bedrock.packet.InventoryTransactionPacket; +import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.ItemFrameEntity; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.sound.EntitySoundInteractionHandler; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.connector.utils.InventoryUtils; + @Translator(packet = InventoryTransactionPacket.class) public class BedrockInventoryTransactionTranslator extends PacketTranslator { @@ -56,37 +65,111 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator { + + @Override + public void translate(ItemFrameDropItemPacket packet, GeyserSession session) { + // I hope that, when we die, God (or whoever is waiting for us) tells us exactly why this code exists + // The packet sends the Y coordinate (and just the Y coordinate) divided by two, and it's negative if it needs to be subtracted by one + int y; + if (packet.getBlockPosition().getY() > 0) { + y = packet.getBlockPosition().getY() * 2; + } else { + y = (packet.getBlockPosition().getY() * -2) - 1; + } + Vector3i position = Vector3i.from(packet.getBlockPosition().getX(), y, packet.getBlockPosition().getZ()); + ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) ItemFrameEntity.getItemFrameEntityId(session, position), + InteractAction.ATTACK, Hand.MAIN_HAND); + session.sendDownstreamPacket(interactPacket); + } + +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLevelSoundEventTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLevelSoundEventTranslator.java new file mode 100644 index 00000000..6395f0a1 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLevelSoundEventTranslator.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.bedrock; + +import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +@Translator(packet = LevelSoundEventPacket.class) +public class BedrockLevelSoundEventTranslator extends PacketTranslator { + + @Override + public void translate(LevelSoundEventPacket packet, GeyserSession session) { + // lol what even :thinking: + session.sendUpstreamPacket(packet); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java index 3427a99f..5fc7f41a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java @@ -46,6 +46,6 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator { + + @Override + public void translate(MoveEntityAbsolutePacket packet, GeyserSession session) { + + ClientVehicleMovePacket clientVehicleMovePacket = new ClientVehicleMovePacket( + packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ(), + packet.getRotation().getY() - 90, packet.getRotation().getX() + ); + session.sendDownstreamPacket(clientVehicleMovePacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMovePlayerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMovePlayerTranslator.java index 0ae3d88a..14d597fd 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMovePlayerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMovePlayerTranslator.java @@ -25,6 +25,7 @@ package org.geysermc.connector.network.translators.bedrock; +import com.nukkitx.math.vector.Vector3d; import org.geysermc.common.ChatColor; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.PlayerEntity; @@ -34,7 +35,6 @@ import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionRotationPacket; -import com.nukkitx.math.GenericMath; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; @@ -55,7 +55,19 @@ public class BedrockMovePlayerTranslator extends PacketTranslator { + + @Override + public void translate(PlayerInputPacket packet, GeyserSession session) { + ClientSteerVehiclePacket clientSteerVehiclePacket = new ClientSteerVehiclePacket( + packet.getInputMotion().getX(), packet.getInputMotion().getY(), packet.isJumping(), packet.isSneaking() + ); + + session.sendDownstreamPacket(clientSteerVehiclePacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockRespawnTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockRespawnTranslator.java index 3c7e85ec..8fc377ab 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockRespawnTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockRespawnTranslator.java @@ -44,10 +44,10 @@ public class BedrockRespawnTranslator extends PacketTranslator { respawnPacket.setRuntimeEntityId(0); respawnPacket.setPosition(Vector3f.ZERO); respawnPacket.setState(RespawnPacket.State.SERVER_SEARCHING); - session.getUpstream().sendPacket(respawnPacket); + session.sendUpstreamPacket(respawnPacket); ClientRequestPacket javaRespawnPacket = new ClientRequestPacket(ClientRequest.RESPAWN); - session.getDownstream().getSession().send(javaRespawnPacket); + session.sendDownstreamPacket(javaRespawnPacket); } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockShowCreditsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockShowCreditsTranslator.java index 161397b6..4f0af78c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockShowCreditsTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockShowCreditsTranslator.java @@ -40,7 +40,7 @@ public class BedrockShowCreditsTranslator extends PacketTranslator { @Override public void translate(TextPacket packet, GeyserSession session) { - if (packet.getMessage().charAt(0) == '.') { - String message = packet.getMessage().replace(".", "/").trim(); - - if (MessageUtils.isTooLong(message, session)) { - return; - } - - ClientChatPacket chatPacket = new ClientChatPacket(message); - session.getDownstream().getSession().send(chatPacket); - return; - } - - String message = packet.getMessage().trim(); + String message = packet.getMessage().replaceAll("^\\.", "/").trim(); if (MessageUtils.isTooLong(message, session)) { return; } ClientChatPacket chatPacket = new ClientChatPacket(message); - session.getDownstream().getSession().send(chatPacket); + session.sendDownstreamPacket(chatPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockStateValues.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockStateValues.java deleted file mode 100644 index 25d6070f..00000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockStateValues.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.block; - -import com.fasterxml.jackson.databind.JsonNode; -import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; -import it.unimi.dsi.fastutil.objects.Object2ByteMap; -import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; - -import java.util.Map; - -/** - * Used for block entities if the Java block state contains Bedrock block information. - */ -public class BlockStateValues { - - private static final Object2IntMap BANNER_COLORS = new Object2IntOpenHashMap<>(); - private static final Object2ByteMap BED_COLORS = new Object2ByteOpenHashMap<>(); - private static final Object2ByteMap SKULL_VARIANTS = new Object2ByteOpenHashMap<>(); - private static final Object2ByteMap SKULL_ROTATIONS = new Object2ByteOpenHashMap<>(); - - /** - * Determines if the block state contains Bedrock block information - * @param entry The String to JsonNode map used in BlockTranslator - * @param javaBlockState the Java Block State of the block - */ - public static void storeBlockStateValues(Map.Entry entry, BlockState javaBlockState) { - JsonNode bannerColor = entry.getValue().get("banner_color"); - if (bannerColor != null) { - BlockStateValues.BANNER_COLORS.put(javaBlockState, (byte) bannerColor.intValue()); - return; // There will never be a banner color and a skull variant - } - - JsonNode bedColor = entry.getValue().get("bed_color"); - if (bedColor != null) { - BlockStateValues.BED_COLORS.put(javaBlockState, (byte) bedColor.intValue()); - return; - } - - JsonNode skullVariation = entry.getValue().get("variation"); - if(skullVariation != null) { - BlockStateValues.SKULL_VARIANTS.put(javaBlockState, (byte) skullVariation.intValue()); - } - - JsonNode skullRotation = entry.getValue().get("skull_rotation"); - if (skullRotation != null) { - BlockStateValues.SKULL_ROTATIONS.put(javaBlockState, (byte) skullRotation.intValue()); - } - } - - /** - * Banner colors are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. - * This gives an integer color that Bedrock can use. - * @param state BlockState of the block - * @return banner color integer or -1 if no color - */ - public static int getBannerColor(BlockState state) { - if (BANNER_COLORS.containsKey(state)) { - return BANNER_COLORS.getInt(state); - } - return -1; - } - - /** - * Bed colors are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. - * This gives a byte color that Bedrock can use - Bedrock needs a byte in the final tag. - * @param state BlockState of the block - * @return bed color byte or -1 if no color - */ - public static byte getBedColor(BlockState state) { - if (BED_COLORS.containsKey(state)) { - return BED_COLORS.getByte(state); - } - return -1; - } - - /** - * Skull variations are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. - * This gives a byte variant ID that Bedrock can use. - * @param state BlockState of the block - * @return skull variant byte or -1 if no variant - */ - public static byte getSkullVariant(BlockState state) { - if (SKULL_VARIANTS.containsKey(state)) { - return SKULL_VARIANTS.getByte(state); - } - return -1; - } - - /** - * - * @param state BlockState of the block - * @return skull rotation value or -1 if no value - */ - public static byte getSkullRotation(BlockState state) { - if (SKULL_ROTATIONS.containsKey(state)) { - return SKULL_ROTATIONS.getByte(state); - } - return -1; - } - -} diff --git a/common/src/main/java/org/geysermc/common/command/ICommandManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/effect/Effect.java similarity index 78% rename from common/src/main/java/org/geysermc/common/command/ICommandManager.java rename to connector/src/main/java/org/geysermc/connector/network/translators/effect/Effect.java index f46dfafc..4c58235a 100644 --- a/common/src/main/java/org/geysermc/common/command/ICommandManager.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/effect/Effect.java @@ -23,16 +23,21 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.command; +package org.geysermc.connector.network.translators.effect; -public interface ICommandManager { +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; - /** - * Returns the description of the given command - * - * @param command Command to get the description for - * - * @return Command description - */ - String getDescription(String command); -} +@Getter +@Setter +@AllArgsConstructor +public class Effect { + + private String javaName; + private String bedrockName; + private String type; + private int data; + private String identifier; + +} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/effect/EffectRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/effect/EffectRegistry.java new file mode 100644 index 00000000..50888090 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/effect/EffectRegistry.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.network.translators.effect; + +import com.fasterxml.jackson.databind.JsonNode; +import com.github.steveice10.mc.protocol.data.game.world.particle.ParticleType; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.data.SoundEvent; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import lombok.NonNull; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.utils.FileUtils; + +import java.io.InputStream; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * Registry for particles and effects. + */ +public class EffectRegistry { + + public static final Map EFFECTS = new HashMap<>(); + public static final Int2ObjectMap RECORDS = new Int2ObjectOpenHashMap<>(); + + private static Map particleTypeMap = new HashMap<>(); + private static Map particleStringMap = new HashMap<>(); + + public static void init() { + // no-op + } + + static { + /* Load particles */ + InputStream particleStream = FileUtils.getResource("mappings/particles.json"); + JsonNode particleEntries; + try { + particleEntries = GeyserConnector.JSON_MAPPER.readTree(particleStream); + } catch (Exception e) { + throw new AssertionError("Unable to load particle map", e); + } + + Iterator> particlesIterator = particleEntries.fields(); + while (particlesIterator.hasNext()) { + Map.Entry entry = particlesIterator.next(); + try { + particleTypeMap.put(ParticleType.valueOf(entry.getKey().toUpperCase()), LevelEventType.valueOf(entry.getValue().asText().toUpperCase())); + } catch (IllegalArgumentException e1) { + try { + particleStringMap.put(ParticleType.valueOf(entry.getKey().toUpperCase()), entry.getValue().asText()); + GeyserConnector.getInstance().getLogger().debug("Force to map particle " + + entry.getKey() + + "=>" + + entry.getValue().asText() + + ", it will take effect."); + } catch (IllegalArgumentException e2){ + GeyserConnector.getInstance().getLogger().warning("Fail to map particle " + entry.getKey() + "=>" + entry.getValue().asText()); + } + } + } + + /* Load effects */ + InputStream effectsStream = FileUtils.getResource("mappings/effects.json"); + JsonNode effects; + try { + effects = GeyserConnector.JSON_MAPPER.readTree(effectsStream); + } catch (Exception e) { + throw new AssertionError("Unable to load effects mappings", e); + } + + Iterator> effectsIterator = effects.fields(); + while (effectsIterator.hasNext()) { + Map.Entry entry = effectsIterator.next(); + // Separate records database since they're handled differently between the two versions + if (entry.getValue().has("records")) { + JsonNode records = entry.getValue().get("records"); + Iterator> recordsIterator = records.fields(); + while (recordsIterator.hasNext()) { + Map.Entry recordEntry = recordsIterator.next(); + RECORDS.put(Integer.parseInt(recordEntry.getKey()), SoundEvent.valueOf(recordEntry.getValue().asText())); + } + } + String identifier = (entry.getValue().has("identifier")) ? entry.getValue().get("identifier").asText() : ""; + int data = (entry.getValue().has("data")) ? entry.getValue().get("data").asInt() : -1; + Effect effect = new Effect(entry.getKey(), entry.getValue().get("name").asText(), entry.getValue().get("type").asText(), data, identifier); + EFFECTS.put(entry.getKey(), effect); + } + } + + public static LevelEventType getParticleLevelEventType(@NonNull ParticleType type) { + return particleTypeMap.getOrDefault(type, null); + } + + public static String getParticleString(@NonNull ParticleType type){ + return particleStringMap.getOrDefault(type, null); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java index 8df72d36..f301d2b5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java @@ -108,7 +108,7 @@ public class AnvilInventoryTranslator extends BlockInventoryTranslator { rename = ""; } ClientRenameItemPacket renameItemPacket = new ClientRenameItemPacket(rename); - session.getDownstream().getSession().send(renameItemPacket); + session.sendDownstreamPacket(renameItemPacket); } if (anvilResult != null) { //client will send another packet to grab anvil output @@ -138,7 +138,7 @@ public class AnvilInventoryTranslator extends BlockInventoryTranslator { rename = ""; } ClientRenameItemPacket renameItemPacket = new ClientRenameItemPacket(rename); - session.getDownstream().getSession().send(renameItemPacket); + session.sendDownstreamPacket(renameItemPacket); } } super.updateSlot(session, inventory, slot); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BlockInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BlockInventoryTranslator.java index 5f6274f0..ab410ea8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BlockInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BlockInventoryTranslator.java @@ -29,7 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; import com.nukkitx.protocol.bedrock.data.ContainerType; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.block.BlockTranslator; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.inventory.holder.BlockInventoryHolder; import org.geysermc.connector.network.translators.inventory.holder.InventoryHolder; import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BrewingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BrewingInventoryTranslator.java index bd143698..acda7c91 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BrewingInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BrewingInventoryTranslator.java @@ -44,7 +44,7 @@ public class BrewingInventoryTranslator extends BlockInventoryTranslator { dataPacket.setWindowId((byte) inventory.getId()); dataPacket.setProperty(ContainerSetDataPacket.BREWING_STAND_FUEL_TOTAL); dataPacket.setValue(20); - session.getUpstream().sendPacket(dataPacket); + session.sendUpstreamPacket(dataPacket); } @Override @@ -62,7 +62,7 @@ public class BrewingInventoryTranslator extends BlockInventoryTranslator { return; } dataPacket.setValue(value); - session.getUpstream().sendPacket(dataPacket); + session.sendUpstreamPacket(dataPacket); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/ChestInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/ChestInventoryTranslator.java new file mode 100644 index 00000000..13684a60 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/ChestInventoryTranslator.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory; + +import com.nukkitx.protocol.bedrock.data.InventoryActionData; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.updater.ChestInventoryUpdater; +import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; +import org.geysermc.connector.utils.InventoryUtils; + +import java.util.List; + +public abstract class ChestInventoryTranslator extends BaseInventoryTranslator { + private final InventoryUpdater updater; + + public ChestInventoryTranslator(int size, int paddedSize) { + super(size); + this.updater = new ChestInventoryUpdater(paddedSize); + } + + @Override + public void updateInventory(GeyserSession session, Inventory inventory) { + updater.updateInventory(this, session, inventory); + } + + @Override + public void updateSlot(GeyserSession session, Inventory inventory, int slot) { + updater.updateSlot(this, session, inventory, slot); + } + + @Override + public void translateActions(GeyserSession session, Inventory inventory, List actions) { + for (InventoryActionData action : actions) { + if (action.getSource().getContainerId() == inventory.getId()) { + if (action.getSlot() >= size) { + updateInventory(session, inventory); + InventoryUtils.updateCursor(session); + return; + } + } + } + + super.translateActions(session, inventory, actions); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java index 92a1d90e..e31eb1e3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java @@ -59,7 +59,7 @@ public class CraftingInventoryTranslator extends BaseInventoryTranslator { containerOpenPacket.setType((byte) ContainerType.WORKBENCH.id()); containerOpenPacket.setBlockPosition(inventory.getHolderPosition()); containerOpenPacket.setUniqueEntityId(inventory.getHolderId()); - session.getUpstream().sendPacket(containerOpenPacket); + session.sendUpstreamPacket(containerOpenPacket); } @Override @@ -92,7 +92,10 @@ public class CraftingInventoryTranslator extends BaseInventoryTranslator { @Override public int javaSlotToBedrock(int slot) { - return slot == 0 ? 50 : slot + 31; + if (slot < size) { + return slot == 0 ? 50 : slot + 31; + } + return super.javaSlotToBedrock(slot); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/DoubleChestInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/DoubleChestInventoryTranslator.java index c70a8995..6d6cadd7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/DoubleChestInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/DoubleChestInventoryTranslator.java @@ -35,19 +35,17 @@ import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.block.BlockTranslator; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.inventory.updater.ChestInventoryUpdater; import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; -public class DoubleChestInventoryTranslator extends BaseInventoryTranslator { +public class DoubleChestInventoryTranslator extends ChestInventoryTranslator { private final int blockId; - private final InventoryUpdater updater; public DoubleChestInventoryTranslator(int size) { - super(size); + super(size, 54); BlockState javaBlockState = BlockTranslator.getJavaBlockState("minecraft:chest[facing=north,type=single,waterlogged=false]"); this.blockId = BlockTranslator.getBedrockBlockId(javaBlockState); - this.updater = new ChestInventoryUpdater(54); } @Override @@ -60,7 +58,7 @@ public class DoubleChestInventoryTranslator extends BaseInventoryTranslator { blockPacket.setBlockPosition(position); blockPacket.setRuntimeId(blockId); blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); - session.getUpstream().sendPacket(blockPacket); + session.sendUpstreamPacket(blockPacket); CompoundTag tag = CompoundTag.builder() .stringTag("id", "Chest") @@ -73,14 +71,14 @@ public class DoubleChestInventoryTranslator extends BaseInventoryTranslator { BlockEntityDataPacket dataPacket = new BlockEntityDataPacket(); dataPacket.setData(tag); dataPacket.setBlockPosition(position); - session.getUpstream().sendPacket(dataPacket); + session.sendUpstreamPacket(dataPacket); blockPacket = new UpdateBlockPacket(); blockPacket.setDataLayer(0); blockPacket.setBlockPosition(pairPosition); blockPacket.setRuntimeId(blockId); blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); - session.getUpstream().sendPacket(blockPacket); + session.sendUpstreamPacket(blockPacket); tag = CompoundTag.builder() .stringTag("id", "Chest") @@ -93,7 +91,7 @@ public class DoubleChestInventoryTranslator extends BaseInventoryTranslator { dataPacket = new BlockEntityDataPacket(); dataPacket.setData(tag); dataPacket.setBlockPosition(pairPosition); - session.getUpstream().sendPacket(dataPacket); + session.sendUpstreamPacket(dataPacket); inventory.setHolderPosition(position); } @@ -105,37 +103,27 @@ public class DoubleChestInventoryTranslator extends BaseInventoryTranslator { containerOpenPacket.setType((byte) ContainerType.CONTAINER.id()); containerOpenPacket.setBlockPosition(inventory.getHolderPosition()); containerOpenPacket.setUniqueEntityId(inventory.getHolderId()); - session.getUpstream().sendPacket(containerOpenPacket); + session.sendUpstreamPacket(containerOpenPacket); } @Override public void closeInventory(GeyserSession session, Inventory inventory) { Vector3i holderPos = inventory.getHolderPosition(); Position pos = new Position(holderPos.getX(), holderPos.getY(), holderPos.getZ()); - BlockState realBlock = session.getChunkCache().getBlockAt(pos); + BlockState realBlock = session.getConnector().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ()); UpdateBlockPacket blockPacket = new UpdateBlockPacket(); blockPacket.setDataLayer(0); blockPacket.setBlockPosition(holderPos); blockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(realBlock)); - session.getUpstream().sendPacket(blockPacket); + session.sendUpstreamPacket(blockPacket); holderPos = holderPos.add(Vector3i.UNIT_X); pos = new Position(holderPos.getX(), holderPos.getY(), holderPos.getZ()); - realBlock = session.getChunkCache().getBlockAt(pos); + realBlock = session.getConnector().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ()); blockPacket = new UpdateBlockPacket(); blockPacket.setDataLayer(0); blockPacket.setBlockPosition(holderPos); blockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(realBlock)); - session.getUpstream().sendPacket(blockPacket); - } - - @Override - public void updateInventory(GeyserSession session, Inventory inventory) { - updater.updateInventory(this, session, inventory); - } - - @Override - public void updateSlot(GeyserSession session, Inventory inventory, int slot) { - updater.updateSlot(this, session, inventory, slot); + session.sendUpstreamPacket(blockPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/FurnaceInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/FurnaceInventoryTranslator.java index 9b45201e..5c6de0e8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/FurnaceInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/FurnaceInventoryTranslator.java @@ -58,7 +58,7 @@ public class FurnaceInventoryTranslator extends BlockInventoryTranslator { return; } dataPacket.setValue(value); - session.getUpstream().sendPacket(dataPacket); + session.sendUpstreamPacket(dataPacket); } @Override 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 2a5afb8c..7b4b26e6 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 @@ -25,15 +25,50 @@ package org.geysermc.connector.network.translators.inventory; +import com.github.steveice10.mc.protocol.data.game.window.WindowType; +import com.nukkitx.protocol.bedrock.data.ContainerType; import com.nukkitx.protocol.bedrock.data.InventoryActionData; import lombok.AllArgsConstructor; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater; +import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; +import java.util.HashMap; import java.util.List; +import java.util.Map; @AllArgsConstructor public abstract class InventoryTranslator { + + public static final Map INVENTORY_TRANSLATORS = new HashMap() { + { + put(null, new PlayerInventoryTranslator()); //player inventory + put(WindowType.GENERIC_9X1, new SingleChestInventoryTranslator(9)); + put(WindowType.GENERIC_9X2, new SingleChestInventoryTranslator(18)); + put(WindowType.GENERIC_9X3, new SingleChestInventoryTranslator(27)); + put(WindowType.GENERIC_9X4, new DoubleChestInventoryTranslator(36)); + put(WindowType.GENERIC_9X5, new DoubleChestInventoryTranslator(45)); + put(WindowType.GENERIC_9X6, new DoubleChestInventoryTranslator(54)); + put(WindowType.BREWING_STAND, new BrewingInventoryTranslator()); + put(WindowType.ANVIL, new AnvilInventoryTranslator()); + put(WindowType.CRAFTING, new CraftingInventoryTranslator()); + put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator()); + //put(WindowType.ENCHANTMENT, new EnchantmentInventoryTranslator()); //TODO + + InventoryTranslator furnace = new FurnaceInventoryTranslator(); + put(WindowType.FURNACE, furnace); + put(WindowType.BLAST_FURNACE, furnace); + put(WindowType.SMOKER, furnace); + + InventoryUpdater containerUpdater = new ContainerInventoryUpdater(); + put(WindowType.GENERIC_3X3, new BlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER, containerUpdater)); + put(WindowType.HOPPER, new BlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER, containerUpdater)); + put(WindowType.SHULKER_BOX, new BlockInventoryTranslator(27, "minecraft:shulker_box[facing=north]", ContainerType.CONTAINER, containerUpdater)); + //put(WindowType.BEACON, new BlockInventoryTranslator(1, "minecraft:beacon", ContainerType.BEACON)); //TODO + } + }; + public final int size; public abstract void prepareInventory(GeyserSession session, Inventory inventory); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/PlayerInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/PlayerInventoryTranslator.java index 668612c5..28986e58 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/PlayerInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/PlayerInventoryTranslator.java @@ -34,17 +34,17 @@ import com.nukkitx.protocol.bedrock.data.InventorySource; import com.nukkitx.protocol.bedrock.data.ItemData; import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; -import it.unimi.dsi.fastutil.longs.LongArraySet; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.Translators; import org.geysermc.connector.network.translators.inventory.action.InventoryActionDataTranslator; +import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.utils.InventoryUtils; -import org.geysermc.connector.utils.Toolbox; import java.util.List; public class PlayerInventoryTranslator extends InventoryTranslator { + private static final ItemData UNUSUABLE_CRAFTING_SPACE_BLOCK = InventoryUtils.createUnusableSpaceBlock( + "The creative crafting grid is\nunavailable in Java Edition"); public PlayerInventoryTranslator() { super(46); @@ -59,30 +59,30 @@ public class PlayerInventoryTranslator extends InventoryTranslator { ItemData[] contents = new ItemData[36]; // Inventory for (int i = 9; i < 36; i++) { - contents[i] = Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(i)); + contents[i] = ItemTranslator.translateToBedrock(session, inventory.getItem(i)); } // Hotbar for (int i = 36; i < 45; i++) { - contents[i - 36] = Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(i)); + contents[i - 36] = ItemTranslator.translateToBedrock(session, inventory.getItem(i)); } inventoryContentPacket.setContents(contents); - session.getUpstream().sendPacket(inventoryContentPacket); + session.sendUpstreamPacket(inventoryContentPacket); // Armor InventoryContentPacket armorContentPacket = new InventoryContentPacket(); armorContentPacket.setContainerId(ContainerId.ARMOR); contents = new ItemData[4]; for (int i = 5; i < 9; i++) { - contents[i - 5] = Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(i)); + contents[i - 5] = ItemTranslator.translateToBedrock(session, inventory.getItem(i)); } armorContentPacket.setContents(contents); - session.getUpstream().sendPacket(armorContentPacket); + session.sendUpstreamPacket(armorContentPacket); // Offhand InventoryContentPacket offhandPacket = new InventoryContentPacket(); offhandPacket.setContainerId(ContainerId.OFFHAND); - offhandPacket.setContents(new ItemData[]{Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(45))}); - session.getUpstream().sendPacket(offhandPacket); + offhandPacket.setContents(new ItemData[]{ItemTranslator.translateToBedrock(session, inventory.getItem(45))}); + session.sendUpstreamPacket(offhandPacket); } /** @@ -98,12 +98,12 @@ public class PlayerInventoryTranslator extends InventoryTranslator { slotPacket.setSlot(i + 27); if (session.getGameMode() == GameMode.CREATIVE) { - slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(session, new ItemStack(Toolbox.BARRIER_INDEX))); + slotPacket.setItem(UNUSUABLE_CRAFTING_SPACE_BLOCK); }else{ - slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(i))); + slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(i))); } - session.getUpstream().sendPacket(slotPacket); + session.sendUpstreamPacket(slotPacket); } } @@ -125,13 +125,13 @@ public class PlayerInventoryTranslator extends InventoryTranslator { slotPacket.setContainerId(ContainerId.CURSOR); slotPacket.setSlot(slot + 27); } - slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(slot))); - session.getUpstream().sendPacket(slotPacket); + slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(slot))); + session.sendUpstreamPacket(slotPacket); } else if (slot == 45) { InventoryContentPacket offhandPacket = new InventoryContentPacket(); offhandPacket.setContainerId(ContainerId.OFFHAND); - offhandPacket.setContents(new ItemData[]{Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(slot))}); - session.getUpstream().sendPacket(offhandPacket); + offhandPacket.setContents(new ItemData[]{ItemTranslator.translateToBedrock(session, inventory.getItem(slot))}); + session.sendUpstreamPacket(offhandPacket); } } @@ -201,23 +201,23 @@ public class PlayerInventoryTranslator extends InventoryTranslator { if (action.getToItem().getId() == 0) { javaItem = new ItemStack(-1, 0, null); } else { - javaItem = Translators.getItemTranslator().translateToJava(session, action.getToItem()); + javaItem = ItemTranslator.translateToJava(action.getToItem()); } ClientCreativeInventoryActionPacket creativePacket = new ClientCreativeInventoryActionPacket(javaSlot, javaItem); - session.getDownstream().getSession().send(creativePacket); + session.sendDownstreamPacket(creativePacket); inventory.setItem(javaSlot, javaItem); break; case ContainerId.CURSOR: if (action.getSlot() == 0) { - session.getInventory().setCursor(Translators.getItemTranslator().translateToJava(session, action.getToItem())); + session.getInventory().setCursor(ItemTranslator.translateToJava(action.getToItem())); } break; case ContainerId.NONE: if (action.getSource().getType() == InventorySource.Type.WORLD_INTERACTION && action.getSource().getFlag() == InventorySource.Flag.DROP_ITEM) { - javaItem = Translators.getItemTranslator().translateToJava(session, action.getToItem()); + javaItem = ItemTranslator.translateToJava(action.getToItem()); ClientCreativeInventoryActionPacket creativeDropPacket = new ClientCreativeInventoryActionPacket(-1, javaItem); - session.getDownstream().getSession().send(creativeDropPacket); + session.sendDownstreamPacket(creativeDropPacket); } break; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SingleChestInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SingleChestInventoryTranslator.java index 5c99b012..3f1a58f4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SingleChestInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SingleChestInventoryTranslator.java @@ -25,11 +25,35 @@ package org.geysermc.connector.network.translators.inventory; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; import com.nukkitx.protocol.bedrock.data.ContainerType; -import org.geysermc.connector.network.translators.inventory.updater.ChestInventoryUpdater; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.holder.BlockInventoryHolder; +import org.geysermc.connector.network.translators.inventory.holder.InventoryHolder; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; + +public class SingleChestInventoryTranslator extends ChestInventoryTranslator { + private final InventoryHolder holder; -public class SingleChestInventoryTranslator extends BlockInventoryTranslator { public SingleChestInventoryTranslator(int size) { - super(size, "minecraft:chest[facing=north,type=single,waterlogged=false]", ContainerType.CONTAINER, new ChestInventoryUpdater(27)); + super(size, 27); + BlockState javaBlockState = BlockTranslator.getJavaBlockState("minecraft:chest[facing=north,type=single,waterlogged=false]"); + this.holder = new BlockInventoryHolder(BlockTranslator.getBedrockBlockId(javaBlockState), ContainerType.CONTAINER); + } + + @Override + public void prepareInventory(GeyserSession session, Inventory inventory) { + holder.prepareInventory(this, session, inventory); + } + + @Override + public void openInventory(GeyserSession session, Inventory inventory) { + holder.openInventory(this, session, inventory); + } + + @Override + public void closeInventory(GeyserSession session, Inventory inventory) { + holder.closeInventory(this, session, inventory); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/ClickPlan.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/ClickPlan.java index cdc42f96..a9c1eddc 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/ClickPlan.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/ClickPlan.java @@ -104,8 +104,8 @@ class ClickPlan { break; } } - session.getDownstream().getSession().send(clickPacket); - session.getDownstream().getSession().send(new ClientConfirmTransactionPacket(inventory.getId(), actionId, true)); + session.sendDownstreamPacket(clickPacket); + session.sendDownstreamPacket(new ClientConfirmTransactionPacket(inventory.getId(), actionId, true)); } /*if (refresh) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/InventoryActionDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/InventoryActionDataTranslator.java index 370d4177..209df074 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/InventoryActionDataTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/InventoryActionDataTranslator.java @@ -38,9 +38,9 @@ import com.nukkitx.protocol.bedrock.data.InventorySource; import com.nukkitx.protocol.bedrock.data.ItemData; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.Translators; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.network.translators.inventory.SlotType; +import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.utils.InventoryUtils; import java.util.*; @@ -61,13 +61,13 @@ public class InventoryActionDataTranslator { worldAction = action; } else if (action.getSource().getContainerId() == ContainerId.CURSOR && action.getSlot() == 0) { cursorAction = action; - ItemData translatedCursor = Translators.getItemTranslator().translateToBedrock(session, session.getInventory().getCursor()); + ItemData translatedCursor = ItemTranslator.translateToBedrock(session, session.getInventory().getCursor()); if (!translatedCursor.equals(action.getFromItem())) { refresh = true; } } else { containerAction = action; - ItemData translatedItem = Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(translator.bedrockSlotToJava(action))); + ItemData translatedItem = ItemTranslator.translateToBedrock(session, inventory.getItem(translator.bedrockSlotToJava(action))); if (!translatedItem.equals(action.getFromItem())) { refresh = true; } @@ -94,7 +94,7 @@ public class InventoryActionDataTranslator { ClientPlayerActionPacket actionPacket = new ClientPlayerActionPacket( sourceAction.getToItem().getCount() == 0 ? PlayerAction.DROP_ITEM_STACK : PlayerAction.DROP_ITEM, new Position(0, 0, 0), BlockFace.DOWN); - session.getDownstream().getSession().send(actionPacket); + session.sendDownstreamPacket(actionPacket); ItemStack item = session.getInventory().getItem(heldSlot); if (item != null) { session.getInventory().setItem(heldSlot, new ItemStack(item.getId(), item.getAmount() - 1, item.getNbt())); @@ -110,14 +110,14 @@ public class InventoryActionDataTranslator { inventory.getTransactionId().getAndIncrement(), javaSlot, null, WindowAction.DROP_ITEM, DropItemParam.DROP_SELECTED_STACK); - session.getDownstream().getSession().send(dropPacket); + session.sendDownstreamPacket(dropPacket); } else { for (int i = 0; i < dropAmount; i++) { ClientWindowActionPacket dropPacket = new ClientWindowActionPacket(inventory.getId(), inventory.getTransactionId().getAndIncrement(), javaSlot, null, WindowAction.DROP_ITEM, DropItemParam.DROP_FROM_SELECTED); - session.getDownstream().getSession().send(dropPacket); + session.sendDownstreamPacket(dropPacket); } } ItemStack item = session.getInventory().getItem(javaSlot); @@ -129,7 +129,7 @@ public class InventoryActionDataTranslator { ClientWindowActionPacket dropPacket = new ClientWindowActionPacket(inventory.getId(), inventory.getTransactionId().getAndIncrement(), -999, null, WindowAction.CLICK_ITEM, dropAmount > 1 ? ClickItemParam.LEFT_CLICK : ClickItemParam.RIGHT_CLICK); - session.getDownstream().getSession().send(dropPacket); + session.sendDownstreamPacket(dropPacket); ItemStack cursor = session.getInventory().getCursor(); if (cursor != null) { session.getInventory().setCursor(new ItemStack(cursor.getId(), dropAmount > 1 ? 0 : cursor.getAmount() - 1, cursor.getNbt())); @@ -180,18 +180,19 @@ public class InventoryActionDataTranslator { inventory.getTransactionId().getAndIncrement(), javaSlot, InventoryUtils.REFRESH_ITEM, WindowAction.SHIFT_CLICK_ITEM, ShiftClickItemParam.LEFT_CLICK); - session.getDownstream().getSession().send(shiftClickPacket); + session.sendDownstreamPacket(shiftClickPacket); translator.updateInventory(session, inventory); return; } } else if (translator.getSlotType(javaSlot) == SlotType.OUTPUT) { plan.add(Click.LEFT, javaSlot); } else { - int cursorSlot = findTempSlot(inventory, session.getInventory().getCursor(), Collections.singletonList(javaSlot)); + int cursorSlot = findTempSlot(inventory, session.getInventory().getCursor(), Collections.singletonList(javaSlot), false); if (cursorSlot != -1) { plan.add(Click.LEFT, cursorSlot); } else { translator.updateInventory(session, inventory); + InventoryUtils.updateCursor(session); return; } plan.add(Click.LEFT, javaSlot); @@ -245,11 +246,15 @@ public class InventoryActionDataTranslator { int cursorSlot = -1; if (session.getInventory().getCursor() != null) { //move cursor contents to a temporary slot - cursorSlot = findTempSlot(inventory, session.getInventory().getCursor(), Arrays.asList(fromSlot, toSlot)); + cursorSlot = findTempSlot(inventory, + session.getInventory().getCursor(), + Arrays.asList(fromSlot, toSlot), + translator.getSlotType(fromSlot) == SlotType.OUTPUT); if (cursorSlot != -1) { plan.add(Click.LEFT, cursorSlot); } else { translator.updateInventory(session, inventory); + InventoryUtils.updateCursor(session); return; } } @@ -266,7 +271,7 @@ public class InventoryActionDataTranslator { inventory.getTransactionId().getAndIncrement(), fromSlot, InventoryUtils.REFRESH_ITEM, WindowAction.SHIFT_CLICK_ITEM, ShiftClickItemParam.LEFT_CLICK); - session.getDownstream().getSession().send(shiftClickPacket); + session.sendDownstreamPacket(shiftClickPacket); translator.updateInventory(session, inventory); return; } else if (translator.getSlotType(fromSlot) == SlotType.OUTPUT) { @@ -298,7 +303,7 @@ public class InventoryActionDataTranslator { InventoryUtils.updateCursor(session); } - private static int findTempSlot(Inventory inventory, ItemStack item, List slotBlacklist) { + private static int findTempSlot(Inventory inventory, ItemStack item, List slotBlacklist, boolean emptyOnly) { /*try and find a slot that can temporarily store the given item only look in the main inventory and hotbar only slots that are empty or contain a different type of item are valid*/ @@ -314,6 +319,9 @@ public class InventoryActionDataTranslator { ItemStack testItem = inventory.getItem(i); boolean acceptable = true; if (testItem != null) { + if (emptyOnly) { + continue; + } for (ItemStack blacklistItem : itemBlacklist) { if (InventoryUtils.canStack(testItem, blacklistItem)) { acceptable = false; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java index a3235445..67ce2ce1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java @@ -36,7 +36,7 @@ import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import lombok.AllArgsConstructor; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.block.BlockTranslator; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.utils.LocaleUtils; @@ -54,7 +54,7 @@ public class BlockInventoryHolder extends InventoryHolder { blockPacket.setBlockPosition(position); blockPacket.setRuntimeId(blockId); blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); - session.getUpstream().sendPacket(blockPacket); + session.sendUpstreamPacket(blockPacket); inventory.setHolderPosition(position); CompoundTag tag = CompoundTag.builder() @@ -65,7 +65,7 @@ public class BlockInventoryHolder extends InventoryHolder { BlockEntityDataPacket dataPacket = new BlockEntityDataPacket(); dataPacket.setData(tag); dataPacket.setBlockPosition(position); - session.getUpstream().sendPacket(dataPacket); + session.sendUpstreamPacket(dataPacket); } @Override @@ -75,18 +75,18 @@ public class BlockInventoryHolder extends InventoryHolder { containerOpenPacket.setType((byte) containerType.id()); containerOpenPacket.setBlockPosition(inventory.getHolderPosition()); containerOpenPacket.setUniqueEntityId(inventory.getHolderId()); - session.getUpstream().sendPacket(containerOpenPacket); + session.sendUpstreamPacket(containerOpenPacket); } @Override public void closeInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { Vector3i holderPos = inventory.getHolderPosition(); Position pos = new Position(holderPos.getX(), holderPos.getY(), holderPos.getZ()); - BlockState realBlock = session.getChunkCache().getBlockAt(pos); + BlockState realBlock = session.getConnector().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ()); UpdateBlockPacket blockPacket = new UpdateBlockPacket(); blockPacket.setDataLayer(0); blockPacket.setBlockPosition(holderPos); blockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(realBlock)); - session.getUpstream().sendPacket(blockPacket); + session.sendUpstreamPacket(blockPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java index e8e0fc45..6ec8d481 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java @@ -31,11 +31,15 @@ import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; import lombok.AllArgsConstructor; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.Translators; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +import org.geysermc.connector.network.translators.item.ItemTranslator; +import org.geysermc.connector.utils.InventoryUtils; @AllArgsConstructor public class ChestInventoryUpdater extends InventoryUpdater { + private static final ItemData UNUSUABLE_SPACE_BLOCK = InventoryUtils.createUnusableSpaceBlock( + "This slot does not exist in the inventory\non Java Edition, as there is less\nrows than possible in Bedrock"); + private final int paddedSize; @Override @@ -44,17 +48,17 @@ public class ChestInventoryUpdater extends InventoryUpdater { ItemData[] bedrockItems = new ItemData[paddedSize]; for (int i = 0; i < bedrockItems.length; i++) { - if (i <= translator.size) { - bedrockItems[i] = Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(i)); + if (i < translator.size) { + bedrockItems[i] = ItemTranslator.translateToBedrock(session, inventory.getItem(i)); } else { - bedrockItems[i] = ItemData.AIR; + bedrockItems[i] = UNUSUABLE_SPACE_BLOCK; } } InventoryContentPacket contentPacket = new InventoryContentPacket(); contentPacket.setContainerId(inventory.getId()); contentPacket.setContents(bedrockItems); - session.getUpstream().sendPacket(contentPacket); + session.sendUpstreamPacket(contentPacket); } @Override @@ -65,8 +69,8 @@ public class ChestInventoryUpdater extends InventoryUpdater { InventorySlotPacket slotPacket = new InventorySlotPacket(); slotPacket.setContainerId(inventory.getId()); slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); - slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(javaSlot))); - session.getUpstream().sendPacket(slotPacket); + slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(javaSlot))); + session.sendUpstreamPacket(slotPacket); return true; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ContainerInventoryUpdater.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ContainerInventoryUpdater.java index d187ffd9..ec6175c3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ContainerInventoryUpdater.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ContainerInventoryUpdater.java @@ -30,8 +30,8 @@ import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.Translators; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +import org.geysermc.connector.network.translators.item.ItemTranslator; public class ContainerInventoryUpdater extends InventoryUpdater { @Override @@ -40,13 +40,13 @@ public class ContainerInventoryUpdater extends InventoryUpdater { ItemData[] bedrockItems = new ItemData[translator.size]; for (int i = 0; i < bedrockItems.length; i++) { - bedrockItems[translator.javaSlotToBedrock(i)] = Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(i)); + bedrockItems[translator.javaSlotToBedrock(i)] = ItemTranslator.translateToBedrock(session, inventory.getItem(i)); } InventoryContentPacket contentPacket = new InventoryContentPacket(); contentPacket.setContainerId(inventory.getId()); contentPacket.setContents(bedrockItems); - session.getUpstream().sendPacket(contentPacket); + session.sendUpstreamPacket(contentPacket); } @Override @@ -57,8 +57,8 @@ public class ContainerInventoryUpdater extends InventoryUpdater { InventorySlotPacket slotPacket = new InventorySlotPacket(); slotPacket.setContainerId(inventory.getId()); slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); - slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(javaSlot))); - session.getUpstream().sendPacket(slotPacket); + slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(javaSlot))); + session.sendUpstreamPacket(slotPacket); return true; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/CursorInventoryUpdater.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/CursorInventoryUpdater.java index e3dc1864..adbbdbac 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/CursorInventoryUpdater.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/CursorInventoryUpdater.java @@ -29,8 +29,8 @@ import com.nukkitx.protocol.bedrock.data.ContainerId; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.Translators; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +import org.geysermc.connector.network.translators.item.ItemTranslator; public class CursorInventoryUpdater extends InventoryUpdater { @Override @@ -44,8 +44,8 @@ public class CursorInventoryUpdater extends InventoryUpdater { InventorySlotPacket slotPacket = new InventorySlotPacket(); slotPacket.setContainerId(ContainerId.CURSOR); slotPacket.setSlot(bedrockSlot); - slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(i))); - session.getUpstream().sendPacket(slotPacket); + slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(i))); + session.sendUpstreamPacket(slotPacket); } } @@ -57,8 +57,8 @@ public class CursorInventoryUpdater extends InventoryUpdater { InventorySlotPacket slotPacket = new InventorySlotPacket(); slotPacket.setContainerId(ContainerId.CURSOR); slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); - slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(javaSlot))); - session.getUpstream().sendPacket(slotPacket); + slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(javaSlot))); + session.sendUpstreamPacket(slotPacket); return true; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java index 2f139e27..88157df0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java @@ -31,20 +31,20 @@ import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.Translators; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +import org.geysermc.connector.network.translators.item.ItemTranslator; public abstract class InventoryUpdater { public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { ItemData[] bedrockItems = new ItemData[36]; for (int i = 0; i < 36; i++) { final int offset = i < 9 ? 27 : -9; - bedrockItems[i] = Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(translator.size + i + offset)); + bedrockItems[i] = ItemTranslator.translateToBedrock(session, inventory.getItem(translator.size + i + offset)); } InventoryContentPacket contentPacket = new InventoryContentPacket(); contentPacket.setContainerId(ContainerId.INVENTORY); contentPacket.setContents(bedrockItems); - session.getUpstream().sendPacket(contentPacket); + session.sendUpstreamPacket(contentPacket); } public boolean updateSlot(InventoryTranslator translator, GeyserSession session, Inventory inventory, int javaSlot) { @@ -52,8 +52,8 @@ public abstract class InventoryUpdater { InventorySlotPacket slotPacket = new InventorySlotPacket(); slotPacket.setContainerId(ContainerId.INVENTORY); slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); - slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(javaSlot))); - session.getUpstream().sendPacket(slotPacket); + slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(javaSlot))); + session.sendUpstreamPacket(slotPacket); return true; } return false; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java index e579c20e..9c072ad1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java @@ -32,14 +32,15 @@ import lombok.Getter; @AllArgsConstructor public class ItemEntry { - public static ItemEntry AIR = new ItemEntry("minecraft:air", 0, 0, 0); + public static ItemEntry AIR = new ItemEntry("minecraft:air", 0, 0, 0, false); private final String javaIdentifier; private final int javaId; - private final int bedrockId; private final int bedrockData; + private final boolean block; + @Override public boolean equals(Object obj) { return obj == this || (obj instanceof ItemEntry && ((ItemEntry) obj).getBedrockId() == this.getBedrockId() && ((ItemEntry) obj).getJavaIdentifier().equals(this.getJavaIdentifier())); 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 new file mode 100644 index 00000000..ed99ece3 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.network.translators.item; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.nukkitx.nbt.NbtUtils; +import com.nukkitx.protocol.bedrock.data.ItemData; +import com.nukkitx.protocol.bedrock.packet.StartGamePacket; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.utils.FileUtils; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Base64; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Registry for anything item related. + */ +public class ItemRegistry { + + private static final Map JAVA_IDENTIFIER_MAP = new HashMap<>(); + + public static final ItemData[] CREATIVE_ITEMS; + + public static final List ITEMS = new ArrayList<>(); + public static final Int2ObjectMap ITEM_ENTRIES = new Int2ObjectOpenHashMap<>(); + + // Shield ID, used in Entity.java + public static final int SHIELD = 829; + // Boat ID, used in BedrockInventoryTransactionTranslator.java + public static final int BOAT = 333; + + public static int BARRIER_INDEX = 0; + + public static void init() { + // no-op + } + + static { + /* Load item palette */ + InputStream stream = FileUtils.getResource("bedrock/items.json"); + + TypeReference> itemEntriesType = new TypeReference>() { + }; + + List itemEntries; + try { + itemEntries = GeyserConnector.JSON_MAPPER.readValue(stream, itemEntriesType); + } catch (Exception e) { + throw new AssertionError("Unable to load Bedrock runtime item IDs", e); + } + + for (JsonNode entry : itemEntries) { + ITEMS.add(new StartGamePacket.ItemEntry(entry.get("name").textValue(), (short) entry.get("id").intValue())); + } + + stream = FileUtils.getResource("mappings/items.json"); + + JsonNode items; + try { + items = GeyserConnector.JSON_MAPPER.readTree(stream); + } catch (Exception e) { + throw new AssertionError("Unable to load Java runtime item IDs", e); + } + + int itemIndex = 0; + Iterator> iterator = items.fields(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + if (entry.getValue().has("tool_type")) { + if (entry.getValue().has("tool_tier")) { + ITEM_ENTRIES.put(itemIndex, new ToolItemEntry( + entry.getKey(), itemIndex, + entry.getValue().get("bedrock_id").intValue(), + entry.getValue().get("bedrock_data").intValue(), + entry.getValue().get("tool_type").textValue(), + entry.getValue().get("tool_tier").textValue(), + entry.getValue().get("is_block").booleanValue())); + } else { + ITEM_ENTRIES.put(itemIndex, new ToolItemEntry( + entry.getKey(), itemIndex, + entry.getValue().get("bedrock_id").intValue(), + entry.getValue().get("bedrock_data").intValue(), + entry.getValue().get("tool_type").textValue(), + "", + entry.getValue().get("is_block").booleanValue())); + } + } else { + ITEM_ENTRIES.put(itemIndex, new ItemEntry( + entry.getKey(), itemIndex, + entry.getValue().get("bedrock_id").intValue(), + entry.getValue().get("bedrock_data").intValue(), + entry.getValue().get("is_block").booleanValue())); + } + if (entry.getKey().equals("minecraft:barrier")) { + BARRIER_INDEX = itemIndex; + } + + itemIndex++; + } + + /* Load creative items */ + stream = FileUtils.getResource("bedrock/creative_items.json"); + + JsonNode creativeItemEntries; + try { + creativeItemEntries = GeyserConnector.JSON_MAPPER.readTree(stream).get("items"); + } catch (Exception e) { + throw new AssertionError("Unable to load creative items", e); + } + + List creativeItems = new ArrayList<>(); + for (JsonNode itemNode : creativeItemEntries) { + short damage = 0; + 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); + try { + com.nukkitx.nbt.tag.CompoundTag tag = (com.nukkitx.nbt.tag.CompoundTag) NbtUtils.createReaderLE(bais).readTag(); + creativeItems.add(ItemData.of(itemNode.get("id").asInt(), damage, 1, tag)); + } catch (IOException e) { + e.printStackTrace(); + } + } else { + creativeItems.add(ItemData.of(itemNode.get("id").asInt(), damage, 1)); + } + } + CREATIVE_ITEMS = creativeItems.toArray(new ItemData[0]); + } + + /** + * Gets an {@link ItemEntry} from the given {@link ItemStack}. + * + * @param stack the item stack + * @return an item entry from the given item stack + */ + public static ItemEntry getItem(ItemStack stack) { + return ITEM_ENTRIES.get(stack.getId()); + } + + /** + * Gets an {@link ItemEntry} from the given {@link ItemData}. + * + * @param data the item data + * @return an item entry from the given item data + */ + public static ItemEntry getItem(ItemData data) { + for (ItemEntry itemEntry : ITEM_ENTRIES.values()) { + if (itemEntry.getBedrockId() == data.getId() && (itemEntry.getBedrockData() == data.getDamage() || itemEntry.getJavaIdentifier().endsWith("potion"))) { + return itemEntry; + } + } + // If item find was unsuccessful first time, we try again while ignoring damage + // Fixes piston, sticky pistons, dispensers and droppers turning into air from creative inventory + for (ItemEntry itemEntry : ITEM_ENTRIES.values()) { + if (itemEntry.getBedrockId() == data.getId()) { + return itemEntry; + } + } + + GeyserConnector.getInstance().getLogger().debug("Missing mapping for bedrock item " + data.getId() + ":" + data.getDamage()); + return ItemEntry.AIR; + } + + /** + * Gets an {@link ItemEntry} from the given Minecraft: Java Edition + * block state identifier. + * + * @param javaIdentifier the block state identifier + * @return an item entry from the given java edition identifier + */ + public static ItemEntry getItemEntry(String javaIdentifier) { + return JAVA_IDENTIFIER_MAP.computeIfAbsent(javaIdentifier, key -> ITEM_ENTRIES.values() + .stream().filter(itemEntry -> itemEntry.getJavaIdentifier().equals(key)).findFirst().orElse(null)); + } +} 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 5e0361c0..273226fe 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 @@ -1,55 +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: + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser */ package org.geysermc.connector.network.translators.item; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.message.Message; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.github.steveice10.opennbt.tag.builtin.*; +import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.nbt.tag.Tag; import com.nukkitx.protocol.bedrock.data.ItemData; - import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.geysermc.connector.GeyserConnector; - import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.*; -import org.geysermc.connector.utils.Toolbox; +import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.utils.MessageUtils; import org.reflections.Reflections; -import java.util.*; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.stream.Collectors; -public class ItemTranslator { +public abstract class ItemTranslator { - private Int2ObjectMap itemTranslators = new Int2ObjectOpenHashMap(); - private List nbtItemTranslators; - private Map javaIdentifierMap = new HashMap<>(); - - // Shield ID, used in Entity.java - public static final int SHIELD = 829; + private static final Int2ObjectMap ITEM_STACK_TRANSLATORS = new Int2ObjectOpenHashMap<>(); + private static final List NBT_TRANSLATORS; - public void init() { + protected ItemTranslator() { + } + + public static void init() { + // no-op + } + + static { + /* Load item translators */ Reflections ref = new Reflections("org.geysermc.connector.network.translators.item"); Map loadedNbtItemTranslators = new HashMap<>(); @@ -64,35 +76,33 @@ public class ItemTranslator { loadedNbtItemTranslators.put(nbtItemTranslator, priority); continue; } - ItemStackTranslator itemStackTranslator = (ItemStackTranslator) clazz.newInstance(); + ItemTranslator itemStackTranslator = (ItemTranslator) clazz.newInstance(); List appliedItems = itemStackTranslator.getAppliedItems(); for (ItemEntry item : appliedItems) { - ItemStackTranslator registered = itemTranslators.get(item.getJavaId()); + ItemTranslator registered = ITEM_STACK_TRANSLATORS.get(item.getJavaId()); if (registered != null) { GeyserConnector.getInstance().getLogger().error("Could not instantiate annotated item translator " + clazz.getCanonicalName() + "." + " Item translator " + registered.getClass().getCanonicalName() + " is already registered for the item " + item.getJavaIdentifier()); continue; } - itemTranslators.put(item.getJavaId(), itemStackTranslator); + ITEM_STACK_TRANSLATORS.put(item.getJavaId(), itemStackTranslator); } } catch (InstantiationException | IllegalAccessException e) { GeyserConnector.getInstance().getLogger().error("Could not instantiate annotated item translator " + clazz.getCanonicalName() + "."); } } - nbtItemTranslators = loadedNbtItemTranslators.keySet().stream() - .sorted(Comparator.comparingInt(value -> loadedNbtItemTranslators.get(value))).collect(Collectors.toList()); + NBT_TRANSLATORS = loadedNbtItemTranslators.keySet().stream().sorted(Comparator.comparingInt(loadedNbtItemTranslators::get)).collect(Collectors.toList()); } - public ItemStack translateToJava(GeyserSession session, ItemData data) { + public static ItemStack translateToJava(ItemData data) { if (data == null) { return new ItemStack(0); } - ItemEntry javaItem = getItem(data); + ItemEntry javaItem = ItemRegistry.getItem(data); ItemStack itemStack; - - ItemStackTranslator itemStackTranslator = itemTranslators.get(javaItem.getJavaId()); + ItemTranslator itemStackTranslator = ITEM_STACK_TRANSLATORS.get(javaItem.getJavaId()); if (itemStackTranslator != null) { itemStack = itemStackTranslator.translateToJava(data, javaItem); } else { @@ -100,7 +110,7 @@ public class ItemTranslator { } if (itemStack != null && itemStack.getNbt() != null) { - for (NbtItemStackTranslator translator : nbtItemTranslators) { + for (NbtItemStackTranslator translator : NBT_TRANSLATORS) { if (translator.acceptItem(javaItem)) { translator.translateToJava(itemStack.getNbt(), javaItem); } @@ -109,60 +119,271 @@ public class ItemTranslator { return itemStack; } - public ItemData translateToBedrock(GeyserSession session, ItemStack stack) { + public static ItemData translateToBedrock(GeyserSession session, ItemStack stack) { if (stack == null) { return ItemData.AIR; } - ItemEntry bedrockItem = getItem(stack); + ItemEntry bedrockItem = ItemRegistry.getItem(stack); - if (stack != null && stack.getNbt() != null) { - for (NbtItemStackTranslator translator : nbtItemTranslators) { + ItemStack itemStack = new ItemStack(stack.getId(), stack.getAmount(), stack.getNbt() != null ? stack.getNbt().clone() : null); + + if (itemStack.getNbt() != null) { + for (NbtItemStackTranslator translator : NBT_TRANSLATORS) { if (translator.acceptItem(bedrockItem)) { - translator.translateToBedrock(stack.getNbt(), bedrockItem); + translator.translateToBedrock(itemStack.getNbt(), bedrockItem); } } } - ItemStackTranslator itemStackTranslator = itemTranslators.get(bedrockItem.getJavaId()); + ItemData itemData; + ItemTranslator itemStackTranslator = ITEM_STACK_TRANSLATORS.get(bedrockItem.getJavaId()); if (itemStackTranslator != null) { - return itemStackTranslator.translateToBedrock(stack, bedrockItem); + itemData = itemStackTranslator.translateToBedrock(itemStack, bedrockItem); } else { - return DEFAULT_TRANSLATOR.translateToBedrock(stack, bedrockItem); + itemData = DEFAULT_TRANSLATOR.translateToBedrock(itemStack, bedrockItem); } - } - public ItemEntry getItem(ItemStack stack) { - return Toolbox.ITEM_ENTRIES.get(stack.getId()); - } - public ItemEntry getItem(ItemData data) { - for (ItemEntry itemEntry : Toolbox.ITEM_ENTRIES.values()) { - if (itemEntry.getBedrockId() == data.getId() && (itemEntry.getBedrockData() == data.getDamage() || itemEntry.getJavaIdentifier().endsWith("potion"))) { - return itemEntry; - } - } - // If item find was unsuccessful first time, we try again while ignoring damage - // Fixes piston, sticky pistons, dispensers and droppers turning into air from creative inventory - for (ItemEntry itemEntry : Toolbox.ITEM_ENTRIES.values()) { - if (itemEntry.getBedrockId() == data.getId()) { - return itemEntry; + // Get the display name of the item + CompoundTag tag = itemData.getTag(); + if (tag != null) { + CompoundTag display = tag.getCompound("display"); + if (display != null) { + String name = display.getString("Name"); + + // Check if its a message to translate + if (MessageUtils.isMessage(name)) { + // Get the translated name + name = MessageUtils.getTranslatedBedrockMessage(Message.fromString(name), session.getClientData().getLanguageCode()); + + // Build the new display tag + CompoundTagBuilder displayBuilder = display.toBuilder(); + displayBuilder.stringTag("Name", name); + + // Build the new root tag + CompoundTagBuilder builder = tag.toBuilder(); + builder.tag(displayBuilder.build("display")); + + // Create a new item with the original data + updated name + itemData = ItemData.of(itemData.getId(), itemData.getDamage(), itemData.getCount(), builder.buildRootTag()); + } } } - GeyserConnector.getInstance().getLogger().debug("Missing mapping for bedrock item " + data.getId() + ":" + data.getDamage()); - return ItemEntry.AIR; + return itemData; } - public ItemEntry getItemEntry(String javaIdentifier) { - return javaIdentifierMap.computeIfAbsent(javaIdentifier, key -> Toolbox.ITEM_ENTRIES.values() - .stream().filter(itemEntry -> itemEntry.getJavaIdentifier().equals(key)).findFirst().orElse(null)); - } - - private static final ItemStackTranslator DEFAULT_TRANSLATOR = new ItemStackTranslator() { + private static final ItemTranslator DEFAULT_TRANSLATOR = new ItemTranslator() { @Override public List getAppliedItems() { return null; } }; + + public ItemData translateToBedrock(ItemStack itemStack, ItemEntry itemEntry) { + if (itemStack == null) { + return ItemData.AIR; + } + if (itemStack.getNbt() == null) { + return ItemData.of(itemEntry.getBedrockId(), (short) itemEntry.getBedrockData(), itemStack.getAmount()); + } + return ItemData.of(itemEntry.getBedrockId(), (short) itemEntry.getBedrockData(), itemStack.getAmount(), this.translateNbtToBedrock(itemStack.getNbt())); + } + + public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { + if (itemData == null) return null; + if (itemData.getTag() == null) { + return new ItemStack(itemEntry.getJavaId(), itemData.getCount(), new com.github.steveice10.opennbt.tag.builtin.CompoundTag("")); + } + return new ItemStack(itemEntry.getJavaId(), itemData.getCount(), this.translateToJavaNBT(itemData.getTag())); + } + + public abstract List getAppliedItems(); + + public CompoundTag translateNbtToBedrock(com.github.steveice10.opennbt.tag.builtin.CompoundTag tag) { + Map> javaValue = new HashMap<>(); + if (tag.getValue() != null && !tag.getValue().isEmpty()) { + for (String str : tag.getValue().keySet()) { + com.github.steveice10.opennbt.tag.builtin.Tag javaTag = tag.get(str); + com.nukkitx.nbt.tag.Tag translatedTag = translateToBedrockNBT(javaTag); + if (translatedTag == null) + continue; + + javaValue.put(translatedTag.getName(), translatedTag); + } + } + + return new CompoundTag(tag.getName(), javaValue); + } + + private Tag translateToBedrockNBT(com.github.steveice10.opennbt.tag.builtin.Tag tag) { + if (tag instanceof ByteArrayTag) { + ByteArrayTag byteArrayTag = (ByteArrayTag) tag; + return new com.nukkitx.nbt.tag.ByteArrayTag(byteArrayTag.getName(), byteArrayTag.getValue()); + } + + if (tag instanceof ByteTag) { + ByteTag byteTag = (ByteTag) tag; + return new com.nukkitx.nbt.tag.ByteTag(byteTag.getName(), byteTag.getValue()); + } + + if (tag instanceof DoubleTag) { + DoubleTag doubleTag = (DoubleTag) tag; + return new com.nukkitx.nbt.tag.DoubleTag(doubleTag.getName(), doubleTag.getValue()); + } + + if (tag instanceof FloatTag) { + FloatTag floatTag = (FloatTag) tag; + return new com.nukkitx.nbt.tag.FloatTag(floatTag.getName(), floatTag.getValue()); + } + + if (tag instanceof IntArrayTag) { + IntArrayTag intArrayTag = (IntArrayTag) tag; + return new com.nukkitx.nbt.tag.IntArrayTag(intArrayTag.getName(), intArrayTag.getValue()); + } + + if (tag instanceof IntTag) { + IntTag intTag = (IntTag) tag; + return new com.nukkitx.nbt.tag.IntTag(intTag.getName(), intTag.getValue()); + } + + if (tag instanceof LongArrayTag) { + LongArrayTag longArrayTag = (LongArrayTag) tag; + return new com.nukkitx.nbt.tag.LongArrayTag(longArrayTag.getName(), longArrayTag.getValue()); + } + + if (tag instanceof LongTag) { + LongTag longTag = (LongTag) tag; + return new com.nukkitx.nbt.tag.LongTag(longTag.getName(), longTag.getValue()); + } + + if (tag instanceof ShortTag) { + ShortTag shortTag = (ShortTag) tag; + return new com.nukkitx.nbt.tag.ShortTag(shortTag.getName(), shortTag.getValue()); + } + + if (tag instanceof StringTag) { + StringTag stringTag = (StringTag) tag; + return new com.nukkitx.nbt.tag.StringTag(stringTag.getName(), stringTag.getValue()); + } + + if (tag instanceof ListTag) { + ListTag listTag = (ListTag) tag; + + List> tagList = new ArrayList<>(); + for (com.github.steveice10.opennbt.tag.builtin.Tag value : listTag) { + tagList.add(translateToBedrockNBT(value)); + } + Class clazz = CompoundTag.class; + if (!tagList.isEmpty()) { + clazz = tagList.get(0).getClass(); + } + return new com.nukkitx.nbt.tag.ListTag(listTag.getName(), clazz, tagList); + } + + if (tag instanceof com.github.steveice10.opennbt.tag.builtin.CompoundTag) { + com.github.steveice10.opennbt.tag.builtin.CompoundTag compoundTag = (com.github.steveice10.opennbt.tag.builtin.CompoundTag) tag; + + return translateNbtToBedrock(compoundTag); + } + + return null; + } + + public com.github.steveice10.opennbt.tag.builtin.CompoundTag translateToJavaNBT(com.nukkitx.nbt.tag.CompoundTag tag) { + com.github.steveice10.opennbt.tag.builtin.CompoundTag javaTag = new com.github.steveice10.opennbt.tag.builtin.CompoundTag(tag.getName()); + Map javaValue = javaTag.getValue(); + if (tag.getValue() != null && !tag.getValue().isEmpty()) { + for (String str : tag.getValue().keySet()) { + Tag bedrockTag = tag.get(str); + com.github.steveice10.opennbt.tag.builtin.Tag translatedTag = translateToJavaNBT(bedrockTag); + if (translatedTag == null) + continue; + + javaValue.put(translatedTag.getName(), translatedTag); + } + } + + javaTag.setValue(javaValue); + return javaTag; + } + + private com.github.steveice10.opennbt.tag.builtin.Tag translateToJavaNBT(com.nukkitx.nbt.tag.Tag tag) { + if (tag instanceof com.nukkitx.nbt.tag.ByteArrayTag) { + com.nukkitx.nbt.tag.ByteArrayTag byteArrayTag = (com.nukkitx.nbt.tag.ByteArrayTag) tag; + return new ByteArrayTag(byteArrayTag.getName(), byteArrayTag.getValue()); + } + + if (tag instanceof com.nukkitx.nbt.tag.ByteTag) { + com.nukkitx.nbt.tag.ByteTag byteTag = (com.nukkitx.nbt.tag.ByteTag) tag; + return new ByteTag(byteTag.getName(), byteTag.getValue()); + } + + if (tag instanceof com.nukkitx.nbt.tag.DoubleTag) { + com.nukkitx.nbt.tag.DoubleTag doubleTag = (com.nukkitx.nbt.tag.DoubleTag) tag; + return new DoubleTag(doubleTag.getName(), doubleTag.getValue()); + } + + if (tag instanceof com.nukkitx.nbt.tag.FloatTag) { + com.nukkitx.nbt.tag.FloatTag floatTag = (com.nukkitx.nbt.tag.FloatTag) tag; + return new FloatTag(floatTag.getName(), floatTag.getValue()); + } + + if (tag instanceof com.nukkitx.nbt.tag.IntArrayTag) { + com.nukkitx.nbt.tag.IntArrayTag intArrayTag = (com.nukkitx.nbt.tag.IntArrayTag) tag; + return new IntArrayTag(intArrayTag.getName(), intArrayTag.getValue()); + } + + if (tag instanceof com.nukkitx.nbt.tag.IntTag) { + com.nukkitx.nbt.tag.IntTag intTag = (com.nukkitx.nbt.tag.IntTag) tag; + return new IntTag(intTag.getName(), intTag.getValue()); + } + + if (tag instanceof com.nukkitx.nbt.tag.LongArrayTag) { + com.nukkitx.nbt.tag.LongArrayTag longArrayTag = (com.nukkitx.nbt.tag.LongArrayTag) tag; + return new LongArrayTag(longArrayTag.getName(), longArrayTag.getValue()); + } + + if (tag instanceof com.nukkitx.nbt.tag.LongTag) { + com.nukkitx.nbt.tag.LongTag longTag = (com.nukkitx.nbt.tag.LongTag) tag; + return new LongTag(longTag.getName(), longTag.getValue()); + } + + if (tag instanceof com.nukkitx.nbt.tag.ShortTag) { + com.nukkitx.nbt.tag.ShortTag shortTag = (com.nukkitx.nbt.tag.ShortTag) tag; + return new ShortTag(shortTag.getName(), shortTag.getValue()); + } + + if (tag instanceof com.nukkitx.nbt.tag.StringTag) { + com.nukkitx.nbt.tag.StringTag stringTag = (com.nukkitx.nbt.tag.StringTag) tag; + return new StringTag(stringTag.getName(), stringTag.getValue()); + } + + if (tag instanceof com.nukkitx.nbt.tag.ListTag) { + com.nukkitx.nbt.tag.ListTag listTag = (com.nukkitx.nbt.tag.ListTag) tag; + + List tags = new ArrayList<>(); + + for (Object value : listTag.getValue()) { + if (!(value instanceof com.nukkitx.nbt.tag.Tag)) + continue; + + com.nukkitx.nbt.tag.Tag tagValue = (com.nukkitx.nbt.tag.Tag) value; + com.github.steveice10.opennbt.tag.builtin.Tag javaTag = translateToJavaNBT(tagValue); + if (javaTag != null) + tags.add(javaTag); + } + return new ListTag(listTag.getName(), tags); + } + + if (tag instanceof com.nukkitx.nbt.tag.CompoundTag) { + com.nukkitx.nbt.tag.CompoundTag compoundTag = (com.nukkitx.nbt.tag.CompoundTag) tag; + return translateToJavaNBT(compoundTag); + } + + return null; + } + + } 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 new file mode 100644 index 00000000..d8fcbc4a --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/NbtItemStackTranslator.java @@ -0,0 +1,45 @@ +/* + * 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.github.steveice10.opennbt.tag.builtin.CompoundTag; + +public class NbtItemStackTranslator { + + public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) { + + } + + public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { + + } + + public boolean acceptItem(ItemEntry itemEntry) { + return true; + } + +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java index 5d1ddd26..cfc05a4a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java @@ -7,8 +7,8 @@ public class ToolItemEntry extends ItemEntry { private final String toolType; private final String toolTier; - public ToolItemEntry(String javaIdentifier, int javaId, int bedrockId, int bedrockData, String toolType, String toolTier) { - super(javaIdentifier, javaId, bedrockId, bedrockData); + public ToolItemEntry(String javaIdentifier, int javaId, int bedrockId, int bedrockData, String toolType, String toolTier, boolean isBlock) { + super(javaIdentifier, javaId, bedrockId, bedrockData, isBlock); this.toolType = toolType; this.toolTier = toolTier; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java new file mode 100644 index 00000000..f4f545ff --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.item.translators; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.IntTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.protocol.bedrock.data.ItemData; +import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.item.ItemTranslator; +import org.geysermc.connector.network.translators.item.ItemEntry; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@ItemRemapper +public class BannerTranslator extends ItemTranslator { + + private List appliedItems; + + public BannerTranslator() { + appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry -> entry.getJavaIdentifier().endsWith("banner")).collect(Collectors.toList()); + } + + @Override + public ItemData translateToBedrock(ItemStack itemStack, ItemEntry itemEntry) { + if (itemStack.getNbt() == null) return super.translateToBedrock(itemStack, itemEntry); + + ItemData itemData = super.translateToBedrock(itemStack, itemEntry); + + CompoundTag blockEntityTag = itemStack.getNbt().get("BlockEntityTag"); + if (blockEntityTag.contains("Patterns")) { + ListTag patterns = blockEntityTag.get("Patterns"); + + CompoundTagBuilder builder = itemData.getTag().toBuilder(); + builder.tag(convertBannerPattern(patterns)); + + itemData = ItemData.of(itemData.getId(), itemData.getDamage(), itemData.getCount(), builder.buildRootTag()); + } + + return itemData; + } + + @Override + public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { + if (itemData.getTag() == null) return super.translateToJava(itemData, itemEntry); + + ItemStack itemStack = super.translateToJava(itemData, itemEntry); + + com.nukkitx.nbt.tag.CompoundTag nbtTag = itemData.getTag(); + if (nbtTag.contains("Patterns")) { + com.nukkitx.nbt.tag.ListTag patterns = nbtTag.get("Patterns"); + + CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag"); + blockEntityTag.put(convertBannerPattern(patterns)); + + itemStack.getNbt().put(blockEntityTag); + } + + return itemStack; + } + + @Override + public List getAppliedItems() { + return appliedItems; + } + + /** + * Convert a list of patterns from Java nbt to Bedrock nbt + * + * @param patterns The patterns to convert + * @return The new converted patterns + */ + public static com.nukkitx.nbt.tag.ListTag convertBannerPattern(ListTag patterns) { + List tagsList = new ArrayList<>(); + for (com.github.steveice10.opennbt.tag.builtin.Tag patternTag : patterns.getValue()) { + com.nukkitx.nbt.tag.CompoundTag newPatternTag = getBedrockBannerPattern((CompoundTag) patternTag); + if (newPatternTag != null) { + tagsList.add(newPatternTag); + } + } + + return new com.nukkitx.nbt.tag.ListTag<>("Patterns", com.nukkitx.nbt.tag.CompoundTag.class, tagsList); + } + + /** + * Convert the Java edition banner pattern nbt to Bedrock edition, null if the pattern doesn't exist + * + * @param pattern Java edition pattern nbt + * @return The Bedrock edition format pattern nbt + */ + public static com.nukkitx.nbt.tag.CompoundTag getBedrockBannerPattern(CompoundTag pattern) { + String patternName = (String) pattern.get("Pattern").getValue(); + + // Return null if its the globe pattern as it doesn't exist on bedrock + if (patternName.equals("glb")) { + return null; + } + + return CompoundTagBuilder.builder() + .intTag("Color", 15 - (int) pattern.get("Color").getValue()) + .stringTag("Pattern", (String) pattern.get("Pattern").getValue()) + .stringTag("Pattern", patternName) + .buildRootTag(); + } + + /** + * Convert a list of patterns from Bedrock nbt to Java nbt + * + * @param patterns The patterns to convert + * @return The new converted patterns + */ + public static ListTag convertBannerPattern(com.nukkitx.nbt.tag.ListTag patterns) { + List tagsList = new ArrayList<>(); + for (Object patternTag : patterns.getValue()) { + CompoundTag newPatternTag = getJavaBannerPattern((com.nukkitx.nbt.tag.CompoundTag) patternTag); + tagsList.add(newPatternTag); + } + + return new ListTag("Patterns", tagsList); + } + + /** + * Convert the Bedrock edition banner pattern nbt to Java edition + * + * @param pattern Bedorck edition pattern nbt + * @return The Java edition format pattern nbt + */ + public static CompoundTag getJavaBannerPattern(com.nukkitx.nbt.tag.CompoundTag pattern) { + Map tags = new HashMap<>(); + tags.put("Color", new IntTag("Color", 15 - pattern.getInt("Color"))); + tags.put("Pattern", new StringTag("Pattern", pattern.getString("Pattern"))); + + return new CompoundTag("", tags); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/PotionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/PotionTranslator.java index e528b448..2fdde31d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/PotionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/PotionTranslator.java @@ -30,22 +30,22 @@ import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.protocol.bedrock.data.ItemData; import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.network.translators.ItemStackTranslator; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.Potion; -import org.geysermc.connector.utils.Toolbox; import java.util.List; import java.util.stream.Collectors; @ItemRemapper -public class PotionTranslator extends ItemStackTranslator { +public class PotionTranslator extends ItemTranslator { private List appliedItems; public PotionTranslator() { - appliedItems = Toolbox.ITEM_ENTRIES.values().stream().filter(entry -> entry.getJavaIdentifier().endsWith("potion")).collect(Collectors.toList()); + appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry -> entry.getJavaIdentifier().endsWith("potion")).collect(Collectors.toList()); } @Override 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 new file mode 100644 index 00000000..776cec72 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.item.translators.nbt; + +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.serializer.gson.GsonComponentSerializer; +import net.kyori.text.serializer.legacy.LegacyComponentSerializer; +import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; +import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.utils.MessageUtils; + +import java.util.ArrayList; +import java.util.List; + +@ItemRemapper(priority = -1) +public class BasicItemTranslator extends NbtItemStackTranslator { + + @Override + public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) { + if (!itemTag.contains("display")) { + return; + } + CompoundTag displayTag = itemTag.get("display"); + if (displayTag.contains("Name")) { + StringTag nameTag = displayTag.get("Name"); + try { + displayTag.put(new StringTag("Name", toBedrockMessage(nameTag))); + } catch (Exception ex) { + } + } + + if (displayTag.contains("Lore")) { + ListTag loreTag = displayTag.get("Lore"); + List lore = new ArrayList<>(); + for (Tag tag : loreTag.getValue()) { + if (!(tag instanceof StringTag)) return; + try { + lore.add(new StringTag("", toBedrockMessage((StringTag) tag))); + } catch (Exception ex) { + } + } + displayTag.put(new ListTag("Lore", lore)); + } + } + + @Override + public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { + if (!itemTag.contains("display")) { + return; + } + CompoundTag displayTag = itemTag.get("display"); + if (displayTag.contains("Name")) { + StringTag nameTag = displayTag.get("Name"); + displayTag.put(new StringTag("Name", toJavaMessage(nameTag))); + } + + if (displayTag.contains("Lore")) { + ListTag loreTag = displayTag.get("Lore"); + List lore = new ArrayList<>(); + for (Tag tag : loreTag.getValue()) { + if (!(tag instanceof StringTag)) return; + lore.add(new StringTag("", "§r" + toJavaMessage((StringTag) tag))); + } + displayTag.put(new ListTag("Lore", lore)); + } + } + + private String toJavaMessage(StringTag tag) { + String message = tag.getValue(); + if (message == null) return null; + if (message.startsWith("§r")) { + message = message.replaceFirst("§r", ""); + } + Component component = TextComponent.of(message); + return GsonComponentSerializer.INSTANCE.serialize(component); + } + + private String toBedrockMessage(StringTag tag) { + String message = tag.getValue(); + if (message == null) return null; + TextComponent component = (TextComponent) MessageUtils.phraseJavaMessage(message); + String legacy = LegacyComponentSerializer.legacy().serialize(component); + if (hasFormatting(LegacyComponentSerializer.legacy().deserialize(legacy))) { + return "§r" + legacy; + } + return legacy; + } + + private boolean hasFormatting(Component component) { + if (component.hasStyling()) return true; + for (Component child : component.children()) { + if (hasFormatting(child)) { + return true; + } + } + return false; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java index f29f16fe..e802f017 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 @@ -30,7 +30,7 @@ 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.translators.ItemRemapper; -import org.geysermc.connector.network.translators.NbtItemStackTranslator; +import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.utils.MessageUtils; @@ -42,43 +42,45 @@ public class BookPagesTranslator extends NbtItemStackTranslator { @Override public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) { - if (itemTag.contains("pages")) { - List pages = new ArrayList<>(); - ListTag pagesTag = itemTag.get("pages"); - for (Tag tag : pagesTag.getValue()) { - if (!(tag instanceof StringTag)) - continue; - - StringTag textTag = (StringTag) tag; - - CompoundTag pageTag = new CompoundTag(""); - pageTag.put(new StringTag("photoname", "")); - pageTag.put(new StringTag("text", MessageUtils.getBedrockMessage(textTag.getValue()))); - pages.add(pageTag); - } - - itemTag.remove("pages"); - itemTag.put(new ListTag("pages", pages)); + if (!itemTag.contains("pages")) { + return; } + List pages = new ArrayList<>(); + ListTag pagesTag = itemTag.get("pages"); + for (Tag tag : pagesTag.getValue()) { + if (!(tag instanceof StringTag)) + continue; + + StringTag textTag = (StringTag) tag; + + CompoundTag pageTag = new CompoundTag(""); + pageTag.put(new StringTag("photoname", "")); + pageTag.put(new StringTag("text", MessageUtils.getBedrockMessageLenient(textTag.getValue()))); + pages.add(pageTag); + } + + itemTag.remove("pages"); + itemTag.put(new ListTag("pages", pages)); } @Override public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { - if (itemTag.contains("pages")) { - List pages = new ArrayList<>(); - ListTag pagesTag = itemTag.get("pages"); - for (Tag tag : pagesTag.getValue()) { - if (!(tag instanceof CompoundTag)) - continue; - - CompoundTag pageTag = (CompoundTag) tag; - - StringTag textTag = pageTag.get("text"); - pages.add(new StringTag(MessageUtils.getJavaMessage(textTag.getValue()))); - } - - itemTag.remove("pages"); - itemTag.put(new ListTag("pages", pages)); + if (!itemTag.contains("pages")) { + return; } + List pages = new ArrayList<>(); + ListTag pagesTag = itemTag.get("pages"); + for (Tag tag : pagesTag.getValue()) { + if (!(tag instanceof CompoundTag)) + continue; + + CompoundTag pageTag = (CompoundTag) tag; + + StringTag textTag = pageTag.get("text"); + pages.add(new StringTag(MessageUtils.getJavaMessage(textTag.getValue()))); + } + + itemTag.remove("pages"); + itemTag.put(new ListTag("pages", pages)); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java new file mode 100644 index 00000000..3c5271eb --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java @@ -0,0 +1,78 @@ +/* + * 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.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.translators.ItemRemapper; +import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; +import org.geysermc.connector.network.translators.item.ItemEntry; + +@ItemRemapper +public class CrossbowTranslator extends NbtItemStackTranslator { + + @Override + public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) { + if (itemTag.get("ChargedProjectiles") != null) { + ListTag chargedProjectiles = itemTag.get("ChargedProjectiles"); + if (!chargedProjectiles.getValue().isEmpty()) { + CompoundTag projectile = (CompoundTag) chargedProjectiles.getValue().get(0); + + CompoundTag newProjectile = new CompoundTag("chargedItem"); + newProjectile.put(new ByteTag("Count", (byte) projectile.get("Count").getValue())); + newProjectile.put(new StringTag("Name", (String) projectile.get("id").getValue())); + + // Not sure what this is for + newProjectile.put(new ByteTag("Damage", (byte) 0)); + + itemTag.put(newProjectile); + } + } + } + + @Override + public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { + if (itemTag.get("chargedItem") != null) { + CompoundTag chargedItem = itemTag.get("chargedItem"); + + CompoundTag newProjectile = new CompoundTag(""); + newProjectile.put(new ByteTag("Count", (byte) chargedItem.get("Count").getValue())); + newProjectile.put(new StringTag("id", (String) chargedItem.get("Name").getValue())); + + ListTag chargedProjectiles = new ListTag("ChargedProjectiles"); + chargedProjectiles.add(newProjectile); + + itemTag.put(chargedProjectiles); + } + } + + @Override + public boolean acceptItem(ItemEntry itemEntry) { + return "minecraft:crossbow".equals(itemEntry.getJavaIdentifier()); + } +} 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 2ca6e35c..63ac6c51 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 @@ -29,7 +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.Tag; import org.geysermc.connector.network.translators.ItemRemapper; -import org.geysermc.connector.network.translators.NbtItemStackTranslator; +import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; import org.geysermc.connector.network.translators.item.ItemEntry; @ItemRemapper(priority = 1) @@ -37,25 +37,27 @@ public class EnchantedBookTranslator extends NbtItemStackTranslator { @Override public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) { - if (itemTag.contains("StoredEnchantments")) { - Tag enchTag = itemTag.get("StoredEnchantments"); - if (enchTag instanceof ListTag) { - enchTag = new ListTag("Enchantments", ((ListTag) enchTag).getValue()); - itemTag.remove("StoredEnchantments"); - itemTag.put(enchTag); - } + if (!itemTag.contains("StoredEnchantments")) { + return; + } + Tag enchTag = itemTag.get("StoredEnchantments"); + if (enchTag instanceof ListTag) { + enchTag = new ListTag("Enchantments", ((ListTag) enchTag).getValue()); + itemTag.remove("StoredEnchantments"); + itemTag.put(enchTag); } } @Override public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { - if (itemTag.contains("Enchantments")) { - Tag enchTag = itemTag.get("Enchantments"); - if (enchTag instanceof ListTag) { - enchTag = new ListTag("StoredEnchantments", ((ListTag) enchTag).getValue()); - itemTag.remove("Enchantments"); - itemTag.put(enchTag); - } + if (!itemTag.contains("Enchantments")) { + return; + } + Tag enchTag = itemTag.get("Enchantments"); + if (enchTag instanceof ListTag) { + enchTag = new ListTag("StoredEnchantments", ((ListTag) enchTag).getValue()); + itemTag.remove("Enchantments"); + itemTag.put(enchTag); } } 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 41e1ae36..404d7824 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 @@ -28,7 +28,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.translators.ItemRemapper; -import org.geysermc.connector.network.translators.NbtItemStackTranslator; +import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; import org.geysermc.connector.network.translators.item.Enchantment; import org.geysermc.connector.network.translators.item.ItemEntry; @@ -73,48 +73,50 @@ public class EnchantmentTranslator extends NbtItemStackTranslator { @Override public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { - if (itemTag.contains("ench")) { - ListTag enchantmentTag = itemTag.get("ench"); - List enchantments = new ArrayList<>(); - List storedEnchantments = new ArrayList<>(); - for (Tag value : enchantmentTag.getValue()) { - if (!(value instanceof CompoundTag)) - continue; - - CompoundTag tagValue = (CompoundTag) value; - ShortTag bedrockId = tagValue.get("id"); - if (bedrockId == null) continue; - - ShortTag geyserStoredEnchantmentTag = tagValue.get("GeyserStoredEnchantment"); - - Enchantment enchantment = Enchantment.getByBedrockId(bedrockId.getValue()); - if (enchantment != null) { - CompoundTag javaTag = new CompoundTag(""); - Map javaValue = javaTag.getValue(); - javaValue.put("id", new StringTag("id", enchantment.getJavaIdentifier())); - ShortTag levelTag = tagValue.get("lvl"); - javaValue.put("lvl", new IntTag("lvl", levelTag != null ? levelTag.getValue() : 1)); - javaTag.setValue(javaValue); - - - if (geyserStoredEnchantmentTag != null) { - tagValue.remove("GeyserStoredEnchantment"); - storedEnchantments.add(javaTag); - } else { - enchantments.add(javaTag); - } - } else { - GeyserConnector.getInstance().getLogger().debug("Unknown bedrock enchantment: " + bedrockId); - } - } - if (!enchantments.isEmpty()) { - itemTag.put(new ListTag("Enchantments", enchantments)); - } - if (!storedEnchantments.isEmpty()) { - itemTag.put(new ListTag("StoredEnchantments", enchantments)); - } - itemTag.remove("ench"); + if (!itemTag.contains("ench")) { + return; } + + ListTag enchantmentTag = itemTag.get("ench"); + List enchantments = new ArrayList<>(); + List storedEnchantments = new ArrayList<>(); + for (Tag value : enchantmentTag.getValue()) { + if (!(value instanceof CompoundTag)) + continue; + + CompoundTag tagValue = (CompoundTag) value; + ShortTag bedrockId = tagValue.get("id"); + if (bedrockId == null) continue; + + ShortTag geyserStoredEnchantmentTag = tagValue.get("GeyserStoredEnchantment"); + + Enchantment enchantment = Enchantment.getByBedrockId(bedrockId.getValue()); + if (enchantment != null) { + CompoundTag javaTag = new CompoundTag(""); + Map javaValue = javaTag.getValue(); + javaValue.put("id", new StringTag("id", enchantment.getJavaIdentifier())); + ShortTag levelTag = tagValue.get("lvl"); + javaValue.put("lvl", new IntTag("lvl", levelTag != null ? levelTag.getValue() : 1)); + javaTag.setValue(javaValue); + + + if (geyserStoredEnchantmentTag != null) { + tagValue.remove("GeyserStoredEnchantment"); + storedEnchantments.add(javaTag); + } else { + enchantments.add(javaTag); + } + } else { + GeyserConnector.getInstance().getLogger().debug("Unknown bedrock enchantment: " + bedrockId); + } + } + if (!enchantments.isEmpty()) { + itemTag.put(new ListTag("Enchantments", enchantments)); + } + if (!storedEnchantments.isEmpty()) { + itemTag.put(new ListTag("StoredEnchantments", enchantments)); + } + itemTag.remove("ench"); } 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 new file mode 100644 index 00000000..5b5182a5 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java @@ -0,0 +1,157 @@ +/* + * 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.translators.ItemRemapper; +import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; +import org.geysermc.connector.utils.FireworkColor; +import org.geysermc.connector.utils.MathUtils; + +@ItemRemapper +public class FireworkTranslator extends NbtItemStackTranslator { + + @Override + public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) { + if (!itemTag.contains("Fireworks")) { + return; + } + + CompoundTag fireworks = itemTag.get("Fireworks"); + if (fireworks.get("Flight") != null) { + fireworks.put(new ByteTag("Flight", MathUtils.convertByte(fireworks.get("Flight").getValue()))); + } + + ListTag explosions = fireworks.get("Explosions"); + if (explosions == null) { + return; + } + for (Tag effect : explosions.getValue()) { + CompoundTag effectData = (CompoundTag) effect; + + CompoundTag newEffectData = new CompoundTag(""); + + if (effectData.get("Type") != null) { + newEffectData.put(new ByteTag("FireworkType", MathUtils.convertByte(effectData.get("Type").getValue()))); + } + + if (effectData.get("Colors") != null) { + int[] oldColors = (int[]) effectData.get("Colors").getValue(); + byte[] colors = new byte[oldColors.length]; + + int i = 0; + for (int color : oldColors) { + colors[i++] = FireworkColor.fromJavaID(color).getBedrockID(); + } + + newEffectData.put(new ByteArrayTag("FireworkColor", colors)); + } + + if (effectData.get("FadeColors") != null) { + int[] oldColors = (int[]) effectData.get("FadeColors").getValue(); + byte[] colors = new byte[oldColors.length]; + + int i = 0; + for (int color : oldColors) { + colors[i++] = FireworkColor.fromJavaID(color).getBedrockID(); + } + + newEffectData.put(new ByteArrayTag("FireworkFade", colors)); + } + + if (effectData.get("Trail") != null) { + newEffectData.put(new ByteTag("FireworkTrail", MathUtils.convertByte(effectData.get("Trail").getValue()))); + } + + if (effectData.get("Flicker") != null) { + newEffectData.put(new ByteTag("FireworkFlicker", MathUtils.convertByte(effectData.get("Flicker").getValue()))); + } + + explosions.remove(effect); + explosions.add(newEffectData); + } + } + + @Override + public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { + CompoundTag fireworks = itemTag.get("Fireworks"); + if (fireworks.get("Flight") != null) { + fireworks.put(new ByteTag("Flight", MathUtils.convertByte(fireworks.get("Flight").getValue()))); + } + + ListTag explosions = fireworks.get("Explosions"); + for (Tag effect : explosions.getValue()) { + CompoundTag effectData = (CompoundTag) effect; + + CompoundTag newEffectData = new CompoundTag(""); + + if (effectData.get("FireworkType") != null) { + newEffectData.put(new ByteTag("Type", MathUtils.convertByte(effectData.get("FireworkType").getValue()))); + } + + if (effectData.get("FireworkColor") != null) { + byte[] oldColors = (byte[]) effectData.get("FireworkColor").getValue(); + int[] colors = new int[oldColors.length]; + + int i = 0; + for (byte color : oldColors) { + colors[i++] = FireworkColor.fromBedrockID(color).getJavaID(); + } + + newEffectData.put(new IntArrayTag("Colors", colors)); + } + + if (effectData.get("FireworkFade") != null) { + byte[] oldColors = (byte[]) effectData.get("FireworkFade").getValue(); + int[] colors = new int[oldColors.length]; + + int i = 0; + for (byte color : oldColors) { + colors[i++] = FireworkColor.fromBedrockID(color).getJavaID(); + } + + newEffectData.put(new IntArrayTag("FadeColors", colors)); + } + + if (effectData.get("FireworkTrail") != null) { + newEffectData.put(new ByteTag("Trail", MathUtils.convertByte(effectData.get("FireworkTrail").getValue()))); + } + + if (effectData.get("FireworkFlicker") != null) { + newEffectData.put(new ByteTag("Flicker", MathUtils.convertByte(effectData.get("FireworkFlicker").getValue()))); + } + + explosions.remove(effect); + explosions.add(newEffectData); + } + } + + @Override + public boolean acceptItem(ItemEntry itemEntry) { + return "minecraft:firework_rocket".equals(itemEntry.getJavaIdentifier()); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/LeatherArmorTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/LeatherArmorTranslator.java index 4b01d912..9f864ccf 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 @@ -28,7 +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.IntTag; import org.geysermc.connector.network.translators.ItemRemapper; -import org.geysermc.connector.network.translators.NbtItemStackTranslator; +import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; import org.geysermc.connector.network.translators.item.ItemEntry; @ItemRemapper @@ -38,29 +38,31 @@ public class LeatherArmorTranslator extends NbtItemStackTranslator { @Override public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) { - if (itemTag.contains("display")) { - CompoundTag displayTag = itemTag.get("display"); - if (displayTag.contains("color")) { - IntTag color = displayTag.get("color"); - if (color != null) { - itemTag.put(new IntTag("customColor", color.getValue())); - displayTag.remove("color"); - } + if (!itemTag.contains("display")) { + return; + } + CompoundTag displayTag = itemTag.get("display"); + if (displayTag.contains("color")) { + IntTag color = displayTag.get("color"); + if (color != null) { + itemTag.put(new IntTag("customColor", color.getValue())); + displayTag.remove("color"); } } } @Override public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { - if (itemTag.contains("customColor")) { - IntTag color = itemTag.get("customColor"); - CompoundTag displayTag = itemTag.get("display"); - if (displayTag == null) { - displayTag = new CompoundTag("display"); - } - displayTag.put(color); - itemTag.remove("customColor"); + if (!itemTag.contains("customColor")) { + return; } + IntTag color = itemTag.get("customColor"); + CompoundTag displayTag = itemTag.get("display"); + if (displayTag == null) { + displayTag = new CompoundTag("display"); + } + displayTag.put(color); + itemTag.remove("customColor"); } @Override 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 cdf272ec..8c418c0f 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 @@ -25,11 +25,12 @@ package org.geysermc.connector.network.translators.item.translators.nbt; +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.StringTag; import org.geysermc.connector.network.translators.ItemRemapper; -import org.geysermc.connector.network.translators.NbtItemStackTranslator; +import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; import org.geysermc.connector.network.translators.item.ItemEntry; @ItemRemapper @@ -42,6 +43,7 @@ public class MapItemTranslator extends NbtItemStackTranslator { if (mapId != null) { itemTag.put(new StringTag("map_uuid", mapId.getValue().toString())); itemTag.put(new IntTag("map_name_index", mapId.getValue())); + itemTag.put(new ByteTag("map_display_players", (byte) 1)); itemTag.remove("map"); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaBossBarTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaBossBarTranslator.java index 2c32ef6f..3da76a22 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaBossBarTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaBossBarTranslator.java @@ -32,8 +32,6 @@ import org.geysermc.connector.network.translators.Translator; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerBossBarPacket; -import java.awt.*; - @Translator(packet = ServerBossBarPacket.class) public class JavaBossBarTranslator extends PacketTranslator { @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java index a527866c..53ce6811 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java @@ -76,6 +76,6 @@ public class JavaChatTranslator extends PacketTranslator { textPacket.setMessage(MessageUtils.getTranslatedBedrockMessage(packet.getMessage(), locale, false)); } - session.getUpstream().sendPacket(textPacket); + session.sendUpstreamPacket(textPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaServerDeclareCommandsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareCommandsTranslator.java similarity index 96% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/JavaServerDeclareCommandsTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareCommandsTranslator.java index d57b8948..053630d5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaServerDeclareCommandsTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareCommandsTranslator.java @@ -43,9 +43,14 @@ import org.geysermc.connector.network.translators.Translator; import java.util.*; @Translator(packet = ServerDeclareCommandsPacket.class) -public class JavaServerDeclareCommandsTranslator extends PacketTranslator { +public class JavaDeclareCommandsTranslator extends PacketTranslator { @Override public void translate(ServerDeclareCommandsPacket packet, GeyserSession session) { + // Don't send command suggestions if they are disabled + if (!session.getConnector().getConfig().isCommandSuggestions()) { + session.getConnector().getLogger().debug("Not sending command suggestions as they are disabled."); + return; + } List commandData = new ArrayList<>(); Int2ObjectMap commands = new Int2ObjectOpenHashMap<>(); Int2ObjectMap> commandArgs = new Int2ObjectOpenHashMap<>(); @@ -59,6 +64,7 @@ public class JavaServerDeclareCommandsTranslator extends PacketTranslator= 1) { @@ -99,7 +105,7 @@ public class JavaServerDeclareCommandsTranslator extends PacketTranslator> groupedByIds = Arrays.stream(ingredient.getOptions()) - .map(item -> Translators.getItemTranslator().translateToBedrock(session, item)) + .map(item -> ItemTranslator.translateToBedrock(session, item)) .collect(Collectors.groupingBy(item -> new GroupedItem(item.getId(), item.getCount(), item.getTag()))); Set optionSet = new HashSet<>(groupedByIds.size()); for (Map.Entry> entry : groupedByIds.entrySet()) { @@ -111,7 +111,7 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator 0) { - translatedItems[i] = Translators.getItemTranslator().translateToBedrock(session, ingredients[i].getOptions()[0]); + translatedItems[i] = ItemTranslator.translateToBedrock(session, ingredients[i].getOptions()[0]); } else { translatedItems[i] = ItemData.AIR; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDifficultyTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDifficultyTranslator.java index 1abf8e46..7e4d9ca8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDifficultyTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDifficultyTranslator.java @@ -39,6 +39,6 @@ public class JavaDifficultyTranslator extends PacketTranslator { @@ -49,24 +56,30 @@ public class JavaJoinGameTranslator extends PacketTranslator skinParts = Arrays.asList(SkinPart.values()); + ClientSettingsPacket clientSettingsPacket = new ClientSettingsPacket(locale, (byte) session.getRenderDistance(), ChatVisibility.FULL, true, skinParts, Hand.MAIN_HAND); + session.sendDownstreamPacket(clientSettingsPacket); + if (DimensionUtils.javaToBedrock(packet.getDimension()) != entity.getDimension()) { DimensionUtils.switchDimension(session, packet.getDimension()); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginPluginMessageTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginPluginMessageTranslator.java index 1ce17fe9..f2a2bf02 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginPluginMessageTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginPluginMessageTranslator.java @@ -12,7 +12,7 @@ public class JavaLoginPluginMessageTranslator extends PacketTranslator SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket(); playerGameTypePacket.setGamemode(packet.getGamemode().ordinal()); - session.getUpstream().sendPacket(playerGameTypePacket); + session.sendUpstreamPacket(playerGameTypePacket); session.setGameMode(packet.getGamemode()); LevelEventPacket stopRainPacket = new LevelEventPacket(); stopRainPacket.setType(LevelEventType.STOP_RAIN); stopRainPacket.setData(ThreadLocalRandom.current().nextInt(50000) + 10000); stopRainPacket.setPosition(Vector3f.ZERO); - session.getUpstream().sendPacket(stopRainPacket); + session.sendUpstreamPacket(stopRainPacket); if (entity.getDimension() != DimensionUtils.javaToBedrock(packet.getDimension())) { DimensionUtils.switchDimension(session, packet.getDimension()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaTitleTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaTitleTranslator.java index 7334f8f8..8ecf4e30 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaTitleTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaTitleTranslator.java @@ -65,6 +65,6 @@ public class JavaTitleTranslator extends PacketTranslator { break; } - session.getUpstream().sendPacket(titlePacket); + session.sendUpstreamPacket(titlePacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAnimationTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAnimationTranslator.java index 9d8853e7..4f2fe022 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAnimationTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAnimationTranslator.java @@ -62,6 +62,6 @@ public class JavaEntityAnimationTranslator extends PacketTranslator { + + @Override + public void translate(ServerEntityAttachPacket packet, GeyserSession session) { + + Entity holderId; + if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { + holderId = session.getPlayerEntity(); + } else { + holderId = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); + if (holderId == null) { + return; + } + } + + Entity attachedToId; + if (packet.getAttachedToId() == session.getPlayerEntity().getEntityId()) { + attachedToId = session.getPlayerEntity(); + } else { + attachedToId = session.getEntityCache().getEntityByJavaId(packet.getAttachedToId()); + if ((attachedToId == null || packet.getAttachedToId() == 0)) { + // Is not being leashed + holderId.getMetadata().getFlags().setFlag(EntityFlag.LEASHED, false); + holderId.getMetadata().put(EntityData.LEAD_HOLDER_EID, 0); + holderId.updateBedrockMetadata(session); + EntityEventPacket eventPacket = new EntityEventPacket(); + eventPacket.setRuntimeEntityId(holderId.getGeyserId()); + eventPacket.setType(EntityEventType.REMOVE_LEASH); + eventPacket.setData(0); + session.sendUpstreamPacket(eventPacket); + return; + } + } + + holderId.getMetadata().getFlags().setFlag(EntityFlag.LEASHED, true); + holderId.getMetadata().put(EntityData.LEAD_HOLDER_EID, attachedToId.getGeyserId()); + holderId.updateBedrockMetadata(session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEffectTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEffectTranslator.java index 88e0969e..5905b1ec 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEffectTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEffectTranslator.java @@ -55,6 +55,6 @@ public class JavaEntityEffectTranslator extends PacketTranslator { @@ -55,7 +55,7 @@ public class JavaEntityEquipmentTranslator extends PacketTranslator { + + @Override + public void translate(ServerEntitySetPassengersPacket packet, GeyserSession session) { + Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); + if (entity == null) return; + + LongOpenHashSet passengers = entity.getPassengers().clone(); + boolean rider = true; + for (long passengerId : packet.getPassengerIds()) { + Entity passenger = session.getEntityCache().getEntityByJavaId(passengerId); + if (passengerId == session.getPlayerEntity().getEntityId()) { + passenger = session.getPlayerEntity(); + session.setRidingVehicleEntity(entity); + } + // Passenger hasn't loaded in and entity link needs to be set later + if (passenger == null && passengerId != 0) { + session.getEntityCache().addCachedPlayerEntityLink(passengerId, packet.getEntityId()); + } + if (passenger == null) { + continue; + } + + EntityLink.Type type = rider ? EntityLink.Type.RIDER : EntityLink.Type.PASSENGER; + SetEntityLinkPacket linkPacket = new SetEntityLinkPacket(); + linkPacket.setEntityLink(new EntityLink(entity.getGeyserId(), passenger.getGeyserId(), type, false)); + session.sendUpstreamPacket(linkPacket); + passengers.add(passengerId); + + // Head rotation on boats + if (entity.getEntityType() == EntityType.BOAT) { + passenger.getMetadata().put(EntityData.RIDER_ROTATION_LOCKED, (byte) 1); + passenger.getMetadata().put(EntityData.RIDER_MAX_ROTATION, 90f); + passenger.getMetadata().put(EntityData.RIDER_MIN_ROTATION, !passengers.isEmpty() ? -90f : 0f); + } else { + passenger.getMetadata().remove(EntityData.RIDER_ROTATION_LOCKED); + passenger.getMetadata().remove(EntityData.RIDER_MAX_ROTATION); + passenger.getMetadata().remove(EntityData.RIDER_MIN_ROTATION); + } + + passenger.updateBedrockMetadata(session); + this.updateOffset(passenger, entity.getEntityType(), session, rider, true, (passengers.size() > 1)); + rider = false; + } + + entity.setPassengers(passengers); + + for (long passengerId : entity.getPassengers()) { + Entity passenger = session.getEntityCache().getEntityByJavaId(passengerId); + if (passenger == null) { + continue; + } + if (Arrays.stream(packet.getPassengerIds()).noneMatch(id -> id == passengerId)) { + SetEntityLinkPacket linkPacket = new SetEntityLinkPacket(); + linkPacket.setEntityLink(new EntityLink(entity.getGeyserId(), passenger.getGeyserId(), EntityLink.Type.REMOVE, false)); + session.sendUpstreamPacket(linkPacket); + passengers.remove(passenger.getEntityId()); + + this.updateOffset(passenger, entity.getEntityType(), session, false, false, (passengers.size() > 1)); + } + } + + if (entity.getEntityType() == EntityType.HORSE) { + entity.getMetadata().put(EntityData.RIDER_SEAT_POSITION, Vector3f.from(0.0f, 2.3200102f, -0.2f)); + entity.getMetadata().put(EntityData.RIDER_MAX_ROTATION, 181.0f); + + entity.updateBedrockMetadata(session); + } + } + + private void updateOffset(Entity passenger, EntityType mountType, GeyserSession session, boolean rider, boolean riding, boolean moreThanOneEntity) { + // Without the Y offset, Bedrock players will find themselves in the floor when mounting + float yOffset = 0; + switch (mountType) { + case BOAT: + yOffset = passenger.getEntityType() == EntityType.PLAYER ? 1.02001f : -0.2f; + break; + case MINECART: + yOffset = passenger.getEntityType() == EntityType.PLAYER ? 1.02001f : 0f; + break; + case DONKEY: + yOffset = 2.1f; + break; + case HORSE: + case SKELETON_HORSE: + case ZOMBIE_HORSE: + case MULE: + yOffset = 2.3f; + break; + case LLAMA: + case TRADER_LLAMA: + yOffset = 2.5f; + break; + case PIG: + yOffset = 1.85001f; + break; + case ARMOR_STAND: + yOffset = 1.3f; + break; + } + Vector3f offset = Vector3f.from(0f, yOffset, 0f); + // Without the X offset, more than one entity on a boat is stacked on top of each other + if (rider && moreThanOneEntity) { + offset = offset.add(Vector3f.from(0.2, 0, 0)); + } else if (moreThanOneEntity) { + offset = offset.add(Vector3f.from(-0.6, 0, 0)); + } + passenger.getMetadata().getFlags().setFlag(EntityFlag.RIDING, riding); + if (riding) { + passenger.getMetadata().put(EntityData.RIDER_SEAT_POSITION, offset); + } + passenger.updateBedrockMetadata(session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java index d3e3973c..b8675dbf 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java @@ -49,6 +49,8 @@ public class JavaEntityStatusTranslator extends PacketTranslator playerFlags = new ObjectOpenHashSet<>(); playerFlags.add(AdventureSettingsPacket.Flag.AUTO_JUMP); @@ -73,6 +73,6 @@ public class JavaPlayerAbilitiesTranslator extends PacketTranslator { @Override public void translate(ServerPlayerListEntryPacket packet, GeyserSession session) { - if (packet.getAction() != PlayerListEntryAction.ADD_PLAYER && packet.getAction() != PlayerListEntryAction.REMOVE_PLAYER) return; + if (packet.getAction() != PlayerListEntryAction.ADD_PLAYER && packet.getAction() != PlayerListEntryAction.REMOVE_PLAYER) + return; PlayerListPacket translate = new PlayerListPacket(); translate.setAction(packet.getAction() == PlayerListEntryAction.ADD_PLAYER ? PlayerListPacket.Action.ADD : PlayerListPacket.Action.REMOVE); for (PlayerListEntry entry : packet.getEntries()) { - if (packet.getAction() == PlayerListEntryAction.ADD_PLAYER) { - boolean self = entry.getProfile().getId().equals(session.getPlayerEntity().getUuid()); + switch (packet.getAction()) { + case ADD_PLAYER: + PlayerEntity playerEntity; + boolean self = entry.getProfile().getId().equals(session.getPlayerEntity().getUuid()); - PlayerEntity playerEntity = session.getPlayerEntity(); - if (self) playerEntity.setProfile(entry.getProfile()); - else { - playerEntity = new PlayerEntity( - entry.getProfile(), - -1, - session.getEntityCache().getNextEntityId().incrementAndGet(), - Vector3f.ZERO, - Vector3f.ZERO, - Vector3f.ZERO - ); - } + if (self) { + // Entity is ourself + playerEntity = session.getPlayerEntity(); + SkinUtils.requestAndHandleSkinAndCape(playerEntity, session, skinAndCape -> { + GeyserConnector.getInstance().getLogger().debug("Loading Local Bedrock Java Skin Data"); + }); + } else { + playerEntity = session.getEntityCache().getPlayerEntity(entry.getProfile().getId()); + } - session.getEntityCache().addPlayerEntity(playerEntity); - playerEntity.setPlayerList(true); + if (playerEntity == null) { + // It's a new player + playerEntity = new PlayerEntity( + entry.getProfile(), + -1, + session.getEntityCache().getNextEntityId().incrementAndGet(), + Vector3f.ZERO, + Vector3f.ZERO, + Vector3f.ZERO + ); + } - translate.getEntries().add(SkinUtils.buildCachedEntry(entry.getProfile(), playerEntity.getGeyserId())); - } else { - PlayerEntity entity = session.getEntityCache().getPlayerEntity(entry.getProfile().getId()); - if (entity != null && entity.isValid()) { - // remove from tablist but player entity is still there - entity.setPlayerList(false); - } else { - // just remove it from caching - session.getEntityCache().removePlayerEntity(entry.getProfile().getId()); - } - translate.getEntries().add(new PlayerListPacket.Entry(entry.getProfile().getId())); + session.getEntityCache().addPlayerEntity(playerEntity); + + playerEntity.setProfile(entry.getProfile()); + playerEntity.setPlayerList(true); + playerEntity.setValid(true); + + PlayerListPacket.Entry playerListEntry = SkinUtils.buildCachedEntry(entry.getProfile(), playerEntity.getGeyserId()); + if (self) { + // Copy the entry with our identity instead. + PlayerListPacket.Entry copy = new PlayerListPacket.Entry(session.getAuthData().getUUID()); + copy.setName(playerListEntry.getName()); + copy.setEntityId(playerListEntry.getEntityId()); + copy.setSkin(playerListEntry.getSkin()); + copy.setXuid(playerListEntry.getXuid()); + copy.setPlatformChatId(playerListEntry.getPlatformChatId()); + copy.setTeacher(playerListEntry.isTeacher()); + playerListEntry = copy; + } + + translate.getEntries().add(playerListEntry); + break; + case REMOVE_PLAYER: + PlayerEntity entity = session.getEntityCache().getPlayerEntity(entry.getProfile().getId()); + if (entity != null && entity.isValid()) { + // remove from tablist but player entity is still there + entity.setPlayerList(false); + } else { + // just remove it from caching + if (entity == null) { + session.getEntityCache().removePlayerEntity(entry.getProfile().getId()); + } else { + entity.setPlayerList(false); + session.getEntityCache().removeEntity(entity, false); + } + } + translate.getEntries().add(new PlayerListPacket.Entry(entry.getProfile().getId())); + break; } } if (packet.getAction() == PlayerListEntryAction.REMOVE_PLAYER || session.getUpstream().isInitialized()) { - session.getUpstream().sendPacket(translate); + session.sendUpstreamPacket(translate); } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java index cfeb2371..a44b200e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java @@ -25,13 +25,7 @@ package org.geysermc.connector.network.translators.java.entity.player; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.ChunkUtils; - +import com.github.steveice10.mc.protocol.data.game.entity.player.PositionElement; import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerPositionRotationPacket; import com.nukkitx.math.vector.Vector3f; @@ -40,13 +34,20 @@ import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; import com.nukkitx.protocol.bedrock.packet.RespawnPacket; import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; +import org.geysermc.connector.entity.PlayerEntity; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.session.cache.TeleportCache; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.utils.ChunkUtils; @Translator(packet = ServerPlayerPositionRotationPacket.class) public class JavaPlayerPositionRotationTranslator extends PacketTranslator { @Override public void translate(ServerPlayerPositionRotationPacket packet, GeyserSession session) { - Entity entity = session.getPlayerEntity(); + PlayerEntity entity = session.getPlayerEntity(); if (entity == null) return; @@ -54,7 +55,7 @@ public class JavaPlayerPositionRotationTranslator extends PacketTranslator 1.5 || (yDis < 1.45 || yDis > (session.isJumping() ? 4.3 : (session.isSprinting() ? 2.5 : 1.9))) || zDis > 1.5) { - entity.moveAbsolute(session, Vector3f.from(packet.getX(), packet.getY(), packet.getZ()), packet.getYaw(), packet.getPitch(), true); + if (!(xDis > 1.5 || (yDis < 1.45 || yDis > (session.isJumping() ? 4.3 : (session.isSprinting() ? 2.5 : 1.9))) || zDis > 1.5)) { + // Fake confirm the teleport but don't send it to the client + ClientTeleportConfirmPacket teleportConfirmPacket = new ClientTeleportConfirmPacket(packet.getTeleportId()); + session.sendDownstreamPacket(teleportConfirmPacket); + return; } } - ClientTeleportConfirmPacket teleportConfirmPacket = new ClientTeleportConfirmPacket(packet.getTeleportId()); - session.getDownstream().getSession().send(teleportConfirmPacket); + // If coordinates are relative, then add to the existing coordinate + double newX = packet.getX() + + (packet.getRelative().contains(PositionElement.X) ? entity.getPosition().getX() : 0); + double newY = packet.getY() + + (packet.getRelative().contains(PositionElement.Y) ? entity.getPosition().getY() - EntityType.PLAYER.getOffset() : 0); + double newZ = packet.getZ() + + (packet.getRelative().contains(PositionElement.Z) ? entity.getPosition().getZ() : 0); + + double newPitch = packet.getPitch() + + (packet.getRelative().contains(PositionElement.PITCH) ? entity.getBedrockRotation().getX() : 0); + double newYaw = packet.getYaw() + + (packet.getRelative().contains(PositionElement.YAW) ? entity.getBedrockRotation().getY() : 0); + + + session.setTeleportCache(new TeleportCache(newX, newY, newZ, packet.getTeleportId())); + entity.moveAbsolute(session, Vector3f.from(newX, newY, newZ), (float) newYaw, (float) newPitch, true, true); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerStopSoundTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerStopSoundTranslator.java new file mode 100644 index 00000000..906c68db --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerStopSoundTranslator.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.java.entity.player; + +import com.github.steveice10.mc.protocol.data.game.world.sound.BuiltinSound; +import com.github.steveice10.mc.protocol.data.game.world.sound.CustomSound; +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerStopSoundPacket; +import com.nukkitx.protocol.bedrock.packet.StopSoundPacket; +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.sound.SoundRegistry; + +@Translator(packet = ServerStopSoundPacket.class) +public class JavaPlayerStopSoundTranslator extends PacketTranslator { + + @Override + public void translate(ServerStopSoundPacket packet, GeyserSession session) { + String packetSound; + if(packet.getSound() instanceof BuiltinSound) { + packetSound = ((BuiltinSound) packet.getSound()).getName(); + } else if(packet.getSound() instanceof CustomSound) { + packetSound = ((CustomSound) packet.getSound()).getName(); + } else { + session.getConnector().getLogger().debug("Unknown sound packet, we were unable to map this. " + packet.toString()); + return; + } + SoundRegistry.SoundMapping soundMapping = SoundRegistry.fromJava(packetSound); + session.getConnector().getLogger() + .debug("[StopSound] Sound mapping " + packetSound + " -> " + + soundMapping + (soundMapping == null ? "[not found]" : "") + + " - " + packet.toString()); + String playsound; + if(soundMapping == null || soundMapping.getPlaysound() == null) { + // no mapping + session.getConnector().getLogger() + .debug("[StopSound] Defaulting to sound server gave us."); + playsound = packetSound; + } else { + playsound = soundMapping.getPlaysound(); + } + + StopSoundPacket stopSoundPacket = new StopSoundPacket(); + stopSoundPacket.setSoundName(playsound); + // packet not mapped in the library + stopSoundPacket.setStoppingAllSound(false); + + session.sendUpstreamPacket(stopSoundPacket); + session.getConnector().getLogger().debug("[StopSound] Packet sent - " + packet.toString() + " --> " + stopSoundPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnObjectTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java similarity index 66% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnObjectTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java index c3998f87..920969a7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnObjectTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java @@ -25,35 +25,35 @@ package org.geysermc.connector.network.translators.java.entity.spawn; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; - -import com.github.steveice10.mc.protocol.data.game.entity.type.object.FallingBlockData; +import com.github.steveice10.mc.protocol.data.game.entity.object.FallingBlockData; +import com.github.steveice10.mc.protocol.data.game.entity.object.HangingDirection; +import com.github.steveice10.mc.protocol.data.game.entity.object.ProjectileData; +import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnEntityPacket; +import com.nukkitx.math.vector.Vector3f; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.FallingBlockEntity; -import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.entity.FishingHookEntity; +import org.geysermc.connector.entity.ItemFrameEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.utils.EntityUtils; -import com.github.steveice10.mc.protocol.data.game.entity.type.object.ObjectType; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnObjectPacket; -import com.nukkitx.math.vector.Vector3f; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; -@Translator(packet = ServerSpawnObjectPacket.class) -public class JavaSpawnObjectTranslator extends PacketTranslator { +@Translator(packet = ServerSpawnEntityPacket.class) +public class JavaSpawnEntityTranslator extends PacketTranslator { @Override - public void translate(ServerSpawnObjectPacket packet, GeyserSession session) { - if (packet.getType() == ObjectType.ITEM_FRAME) - return; + public void translate(ServerSpawnEntityPacket packet, GeyserSession session) { Vector3f position = Vector3f.from(packet.getX(), packet.getY(), packet.getZ()); Vector3f motion = Vector3f.from(packet.getMotionX(), packet.getMotionY(), packet.getMotionZ()); Vector3f rotation = Vector3f.from(packet.getYaw(), packet.getPitch(), 0); - EntityType type = EntityUtils.toBedrockEntity(packet.getType()); + org.geysermc.connector.entity.type.EntityType type = EntityUtils.toBedrockEntity(packet.getType()); if (type == null) { session.getConnector().getLogger().warning("Entity type " + packet.getType() + " was null."); return; @@ -62,11 +62,19 @@ public class JavaSpawnObjectTranslator extends PacketTranslator entityClass = type.getEntityClass(); try { Entity entity; - if (packet.getType() == ObjectType.FALLING_BLOCK) { + if (packet.getType() == EntityType.FALLING_BLOCK) { entity = new FallingBlockEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), type, position, motion, rotation, ((FallingBlockData) packet.getData()).getId()); + } else if (packet.getType() == EntityType.ITEM_FRAME) { + // Item frames need the hanging direction + entity = new ItemFrameEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), + type, position, motion, rotation, (HangingDirection) packet.getData()); + } else if (packet.getType() == EntityType.FISHING_BOBBER) { + // Fishing bobbers need the owner for the line + entity = new FishingHookEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), + type, position, motion, rotation, (ProjectileData) packet.getData()); } else { - Constructor entityConstructor = entityClass.getConstructor(long.class, long.class, EntityType.class, + Constructor entityConstructor = entityClass.getConstructor(long.class, long.class, org.geysermc.connector.entity.type.EntityType.class, Vector3f.class, Vector3f.class, Vector3f.class); entity = entityConstructor.newInstance(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnMobTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnLivingEntityTranslator.java similarity index 91% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnMobTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnLivingEntityTranslator.java index d955ecfd..8c246e51 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnMobTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnLivingEntityTranslator.java @@ -25,9 +25,8 @@ package org.geysermc.connector.network.translators.java.entity.spawn; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; - +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnLivingEntityPacket; +import com.nukkitx.math.vector.Vector3f; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -35,14 +34,14 @@ import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.utils.EntityUtils; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnMobPacket; -import com.nukkitx.math.vector.Vector3f; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; -@Translator(packet = ServerSpawnMobPacket.class) -public class JavaSpawnMobTranslator extends PacketTranslator { +@Translator(packet = ServerSpawnLivingEntityPacket.class) +public class JavaSpawnLivingEntityTranslator extends PacketTranslator { @Override - public void translate(ServerSpawnMobPacket packet, GeyserSession session) { + public void translate(ServerSpawnLivingEntityPacket packet, GeyserSession session) { Vector3f position = Vector3f.from(packet.getX(), packet.getY(), packet.getZ()); Vector3f motion = Vector3f.from(packet.getMotionX(), packet.getMotionY(), packet.getMotionZ()); Vector3f rotation = Vector3f.from(packet.getYaw(), packet.getPitch(), packet.getHeadYaw()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java index 331eb094..e01b95e9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java @@ -25,23 +25,21 @@ package org.geysermc.connector.network.translators.java.entity.spawn; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnPlayerPacket; +import com.nukkitx.math.vector.Vector3f; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.entity.PlayerEntity; -import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.utils.SkinUtils; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnPlayerPacket; -import com.nukkitx.math.vector.Vector3f; - @Translator(packet = ServerSpawnPlayerPacket.class) public class JavaSpawnPlayerTranslator extends PacketTranslator { @Override public void translate(ServerSpawnPlayerPacket packet, GeyserSession session) { - Vector3f position = Vector3f.from(packet.getX(), packet.getY() - EntityType.PLAYER.getOffset(), packet.getZ()); + Vector3f position = Vector3f.from(packet.getX(), packet.getY(), packet.getZ()); Vector3f rotation = Vector3f.from(packet.getYaw(), packet.getPitch(), packet.getYaw()); PlayerEntity entity = session.getEntityCache().getPlayerEntity(packet.getUuid()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnGlobalEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnWeatherEntityTranslator.java similarity index 85% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnGlobalEntityTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnWeatherEntityTranslator.java index da2e59af..c50686a0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnGlobalEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnWeatherEntityTranslator.java @@ -31,17 +31,17 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnGlobalEntityPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnWeatherEntityPacket; import com.nukkitx.math.vector.Vector3f; -@Translator(packet = ServerSpawnGlobalEntityPacket.class) -public class JavaSpawnGlobalEntityTranslator extends PacketTranslator { +@Translator(packet = ServerSpawnWeatherEntityPacket.class) +public class JavaSpawnWeatherEntityTranslator extends PacketTranslator { @Override - public void translate(ServerSpawnGlobalEntityPacket packet, GeyserSession session) { + public void translate(ServerSpawnWeatherEntityPacket packet, GeyserSession session) { Vector3f position = Vector3f.from(packet.getX(), packet.getY(), packet.getZ()); - // Currently GlobalEntityType only has a lightning bolt + // Currently WeatherEntityType only has a lightning bolt Entity entity = new Entity( packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.LIGHTNING_BOLT, position, Vector3f.ZERO, Vector3f.ZERO diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java index 8162b82a..93cfa08e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java @@ -26,7 +26,6 @@ package org.geysermc.connector.network.translators.java.window; import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerCloseWindowPacket; -import com.nukkitx.protocol.bedrock.packet.ContainerClosePacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -37,9 +36,7 @@ public class JavaCloseWindowTranslator extends PacketTranslator { @@ -41,7 +38,7 @@ public class JavaConfirmTransactionTranslator extends PacketTranslator { @@ -49,17 +46,17 @@ public class JavaOpenWindowTranslator extends PacketTranslator InventoryUtils.openInventory(session, newInventory), 500, TimeUnit.MILLISECONDS); + session.getInventoryCache().setOpenInventory(newInventory); + //The new window will be opened when the bedrock client sends the + //window close confirmation in BedrockContainerCloseTranslator return; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java index 6fafa2a4..19d7db21 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java @@ -29,7 +29,6 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerSetSl 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.Translators; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.utils.InventoryUtils; @@ -42,8 +41,6 @@ public class JavaSetSlotTranslator extends PacketTranslator @Override public void translate(ServerSetSlotPacket packet, GeyserSession session) { if (packet.getWindowId() == 255 && packet.getSlot() == -1) { //cursor - if (Objects.equals(session.getInventory().getCursor(), packet.getItem())) - return; if (session.getCraftSlot() != 0) return; @@ -56,7 +53,7 @@ public class JavaSetSlotTranslator extends PacketTranslator if (inventory == null || (packet.getWindowId() != 0 && inventory.getWindowType() == null)) return; - InventoryTranslator translator = Translators.getInventoryTranslators().get(inventory.getWindowType()); + InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()); if (translator != null) { inventory.setItem(packet.getSlot(), packet.getItem()); translator.updateSlot(session, inventory, packet.getSlot()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java index eab57a64..2cc392f5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java @@ -30,7 +30,6 @@ 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.Translators; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import java.util.Arrays; @@ -50,7 +49,7 @@ public class JavaWindowItemsTranslator extends PacketTranslator { + + @Override + public void translate(ServerBlockBreakAnimPacket packet, GeyserSession session) { + BlockState state = session.getConnector().getWorldManager().getBlockAt(session, packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ()); + int breakTime = (int) (65535 / Math.ceil(BlockUtils.getBreakTime(BlockTranslator.JAVA_RUNTIME_ID_TO_HARDNESS.get(state.getId()), state.getId(), ItemEntry.AIR, new CompoundTag(""), null) * 20)); + LevelEventPacket levelEventPacket = new LevelEventPacket(); + levelEventPacket.setPosition(Vector3f.from( + packet.getPosition().getX(), + packet.getPosition().getY(), + packet.getPosition().getZ() + )); + levelEventPacket.setType(LevelEventType.BLOCK_START_BREAK); + + switch (packet.getStage()) { + case STAGE_1: + levelEventPacket.setData(breakTime); + break; + case STAGE_2: + levelEventPacket.setData(breakTime * 2); + break; + case STAGE_3: + levelEventPacket.setData(breakTime * 3); + break; + case STAGE_4: + levelEventPacket.setData(breakTime * 4); + break; + case STAGE_5: + levelEventPacket.setData(breakTime * 5); + break; + case STAGE_6: + levelEventPacket.setData(breakTime * 6); + break; + case STAGE_7: + levelEventPacket.setData(breakTime * 7); + break; + case STAGE_8: + levelEventPacket.setData(breakTime * 8); + break; + case STAGE_9: + levelEventPacket.setData(breakTime * 9); + break; + case STAGE_10: + levelEventPacket.setData(breakTime * 10); + break; + case RESET: + levelEventPacket.setType(LevelEventType.BLOCK_STOP_BREAK); + levelEventPacket.setData(0); + break; + } + session.sendUpstreamPacket(levelEventPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java index b79525a9..e09b9248 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java @@ -25,9 +25,16 @@ package org.geysermc.connector.network.translators.java.world; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.data.SoundEvent; +import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; +import org.geysermc.common.PlatformType; 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.sound.BlockSoundInteractionHandler; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.ChunkUtils; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerBlockChangePacket; @@ -37,6 +44,65 @@ public class JavaBlockChangeTranslator extends PacketTranslator { @@ -47,11 +53,100 @@ public class JavaBlockValueTranslator extends PacketTranslator 0 ? 1 : 0); - session.getUpstream().sendPacket(blockEventPacket); + session.sendUpstreamPacket(blockEventPacket); } if (packet.getValue() instanceof EndGatewayValue) { blockEventPacket.setEventType(1); - session.getUpstream().sendPacket(blockEventPacket); + session.sendUpstreamPacket(blockEventPacket); + } + if (packet.getValue() instanceof NoteBlockValue) { + NoteblockBlockEntityTranslator.translate(session, packet.getPosition()); + return; + } + if (packet.getValue() instanceof PistonValue) { + PistonValueType type = (PistonValueType) packet.getType(); + + // Unlike everything else, pistons need a block entity packet to convey motion + // TODO: Doesn't register on chunk load; needs to be interacted with first + Vector3i position = Vector3i.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ()); + if (type == PistonValueType.PUSHING) { + extendPiston(session, position, 0.0f, 0.0f); + } else { + retractPiston(session, position, 1.0f, 1.0f); + } + } + if (packet.getValue() instanceof BeaconValue) { + blockEventPacket.setEventType(1); + session.sendUpstreamPacket(blockEventPacket); + } + if (packet.getValue() instanceof MobSpawnerValue) { + blockEventPacket.setEventType(1); + session.sendUpstreamPacket(blockEventPacket); + } + if (packet.getValue() instanceof EndGatewayValue) { + blockEventPacket.setEventType(1); + session.sendUpstreamPacket(blockEventPacket); } } + + /** + * Emulating a piston extending + * @param session GeyserSession + * @param position Block position + * @param progress How far the piston is + * @param lastProgress How far the piston last was + */ + private void extendPiston(GeyserSession session, Vector3i position, float progress, float lastProgress) { + BlockEntityDataPacket blockEntityDataPacket = new BlockEntityDataPacket(); + blockEntityDataPacket.setBlockPosition(position); + byte state = (byte) ((progress == 1.0f && lastProgress == 1.0f) ? 2 : 1); + blockEntityDataPacket.setData(buildPistonTag(position, progress, lastProgress, state)); + session.sendUpstreamPacket(blockEntityDataPacket); + if (lastProgress != 1.0f) { + session.getConnector().getGeneralThreadPool().schedule(() -> + extendPiston(session, position, (progress >= 1.0f) ? 1.0f : progress + 0.5f, progress), + 20, TimeUnit.MILLISECONDS); + } + } + + /** + * Emulate a piston retracting. + * @param session GeyserSession + * @param position Block position + * @param progress Current progress of piston + * @param lastProgress Last progress of piston + */ + private void retractPiston(GeyserSession session, Vector3i position, float progress, float lastProgress) { + BlockEntityDataPacket blockEntityDataPacket = new BlockEntityDataPacket(); + blockEntityDataPacket.setBlockPosition(position); + byte state = (byte) ((progress == 0.0f && lastProgress == 0.0f) ? 0 : 3); + blockEntityDataPacket.setData(buildPistonTag(position, progress, lastProgress, state)); + session.sendUpstreamPacket(blockEntityDataPacket); + if (lastProgress != 0.0f) { + session.getConnector().getGeneralThreadPool().schedule(() -> + retractPiston(session, position, (progress <= 0.0f) ? 0.0f : progress - 0.5f, progress), + 20, TimeUnit.MILLISECONDS); + } + } + + /** + * Build a piston tag + * @param position Piston position + * @param progress Current progress of piston + * @param lastProgress Last progress of piston + * @param state + * @return Bedrock CompoundTag of piston + */ + private CompoundTag buildPistonTag(Vector3i position, float progress, float lastProgress, byte state) { + CompoundTagBuilder builder = CompoundTag.EMPTY.toBuilder(); + builder.intTag("x", position.getX()) + .intTag("y", position.getY()) + .intTag("z", position.getZ()) + .floatTag("Progress", progress) + .floatTag("LastProgress", lastProgress) + .stringTag("id", "PistonArm") + .byteTag("NewState", state) + .byteTag("State", state); + return builder.buildRootTag(); + } } 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 e72038c5..2af7bb5b 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 @@ -44,9 +44,7 @@ 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.world.chunk.ChunkSection; - -import java.util.Map; +import org.geysermc.connector.network.translators.world.chunk.ChunkSection; @Translator(packet = ServerChunkDataPacket.class) public class JavaChunkDataTranslator extends PacketTranslator { @@ -100,7 +98,7 @@ public class JavaChunkDataTranslator extends PacketTranslator blockEntityEntry : chunkData.getLoadBlockEntitiesLater().object2IntEntrySet()) { @@ -110,7 +108,7 @@ public class JavaChunkDataTranslator extends PacketTranslator { + + @Override + public void translate(ServerExplosionPacket packet, GeyserSession session) { + for (ExplodedBlockRecord record : packet.getExploded()) { + Vector3f pos = Vector3f.from(packet.getX() + record.getX(), packet.getY() + record.getY(), packet.getZ() + record.getZ()); + ChunkUtils.updateBlock(session, BlockTranslator.AIR, pos.toInt()); + } + + Vector3f pos = Vector3f.from(packet.getX(), packet.getY(), packet.getZ()); + // Since bedrock does not play an explosion sound and particles sound, we have to manually do so + LevelEventPacket levelEventPacket = new LevelEventPacket(); + levelEventPacket.setType(packet.getRadius() >= 2.0f ? LevelEventType.PARTICLE_HUGE_EXPLODE : LevelEventType.PARTICLE_LARGE_EXPLOSION); + levelEventPacket.setData(0); + levelEventPacket.setPosition(pos.toFloat()); + session.sendUpstreamPacket(levelEventPacket); + + LevelSoundEventPacket levelSoundEventPacket = new LevelSoundEventPacket(); + levelSoundEventPacket.setRelativeVolumeDisabled(false); + levelSoundEventPacket.setBabySound(false); + levelSoundEventPacket.setExtraData(-1); + levelSoundEventPacket.setSound(SoundEvent.EXPLODE); + levelSoundEventPacket.setIdentifier(":"); + levelSoundEventPacket.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); + session.sendUpstreamPacket(levelSoundEventPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java index 78681f8f..c8be3a56 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java @@ -26,11 +26,15 @@ package org.geysermc.connector.network.translators.java.world; import com.github.steveice10.mc.protocol.data.game.world.map.MapData; +import com.github.steveice10.mc.protocol.data.game.world.map.MapIcon; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerMapDataPacket; +import com.nukkitx.protocol.bedrock.data.MapDecoration; +import com.nukkitx.protocol.bedrock.data.MapTrackedObject; import com.nukkitx.protocol.bedrock.packet.ClientboundMapItemDataPacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.utils.BedrockMapIcon; import org.geysermc.connector.utils.MapColor; @Translator(packet = ServerMapDataPacket.class) @@ -62,6 +66,16 @@ public class JavaMapDataTranslator extends PacketTranslator mapItemDataPacket.setColors(colors); } + // Bedrock needs an entity id to display an icon + int id = 0; + for (MapIcon icon : packet.getIcons()) { + BedrockMapIcon bedrockMapIcon = BedrockMapIcon.fromType(icon.getIconType()); + + mapItemDataPacket.getTrackedObjects().add(new MapTrackedObject(id)); + mapItemDataPacket.getDecorations().add(new MapDecoration(bedrockMapIcon.getIconID(), icon.getIconRotation(), icon.getCenterX(), icon.getCenterZ(), "", bedrockMapIcon.toARGB())); + id++; + } + session.getUpstream().getSession().sendPacket(mapItemDataPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java index 3c11d87b..781018b2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java @@ -31,10 +31,7 @@ import com.github.steveice10.mc.protocol.data.game.world.notify.EnterCreditsValu import com.github.steveice10.mc.protocol.packet.ingame.client.ClientRequestPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerNotifyClientPacket; import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.EntityDataMap; -import com.nukkitx.protocol.bedrock.data.EntityFlag; -import com.nukkitx.protocol.bedrock.data.LevelEventType; -import com.nukkitx.protocol.bedrock.data.PlayerPermission; +import com.nukkitx.protocol.bedrock.data.*; import com.nukkitx.protocol.bedrock.packet.*; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import org.geysermc.connector.entity.Entity; @@ -45,6 +42,7 @@ import org.geysermc.connector.network.translators.inventory.PlayerInventoryTrans import java.util.Set; import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; @Translator(packet = ServerNotifyClientPacket.class) public class JavaNotifyClientTranslator extends PacketTranslator { @@ -61,14 +59,14 @@ public class JavaNotifyClientTranslator extends PacketTranslator playerFlags = new ObjectOpenHashSet<>(); @@ -83,47 +81,57 @@ public class JavaNotifyClientTranslator extends PacketTranslator { + AdventureSettingsPacket adventureSettingsPacket = new AdventureSettingsPacket(); + adventureSettingsPacket.setPlayerPermission(PlayerPermission.MEMBER); + adventureSettingsPacket.setCommandPermission(CommandPermission.NORMAL); + adventureSettingsPacket.setUniqueEntityId(entity.getGeyserId()); + adventureSettingsPacket.getFlags().addAll(playerFlags); + session.sendUpstreamPacket(adventureSettingsPacket); + }, 50, TimeUnit.MILLISECONDS); EntityDataMap metadata = entity.getMetadata(); - metadata.getFlags().setFlag(EntityFlag.CAN_FLY, gameMode == GameMode.CREATIVE || gameMode == GameMode.SPECTATOR); + metadata.getFlags().setFlag(EntityFlag.CAN_FLY, gameMode == GameMode.CREATIVE); SetEntityDataPacket entityDataPacket = new SetEntityDataPacket(); entityDataPacket.setRuntimeEntityId(entity.getGeyserId()); entityDataPacket.getMetadata().putAll(metadata); - session.getUpstream().sendPacket(entityDataPacket); + session.sendUpstreamPacket(entityDataPacket); // Update the crafting grid to add/remove barriers for creative inventory PlayerInventoryTranslator.updateCraftingGrid(session, session.getInventory()); - break; case ENTER_CREDITS: switch ((EnterCreditsValue) packet.getValue()) { case SEEN_BEFORE: ClientRequestPacket javaRespawnPacket = new ClientRequestPacket(ClientRequest.RESPAWN); - session.getDownstream().getSession().send(javaRespawnPacket); + session.sendDownstreamPacket(javaRespawnPacket); break; case FIRST_TIME: ShowCreditsPacket showCreditsPacket = new ShowCreditsPacket(); showCreditsPacket.setStatus(ShowCreditsPacket.Status.START_CREDITS); showCreditsPacket.setRuntimeEntityId(entity.getGeyserId()); - session.getUpstream().sendPacket(showCreditsPacket); + session.sendUpstreamPacket(showCreditsPacket); break; } break; + case AFFECTED_BY_ELDER_GUARDIAN: + EntityEventPacket eventPacket = new EntityEventPacket(); + eventPacket.setType(EntityEventType.ELDER_GUARDIAN_CURSE); + eventPacket.setData(0); + eventPacket.setRuntimeEntityId(entity.getGeyserId()); + session.sendUpstreamPacket(eventPacket); default: break; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlayBuiltinSoundTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlayBuiltinSoundTranslator.java new file mode 100644 index 00000000..f51e35fe --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlayBuiltinSoundTranslator.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.java.world; + +import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerPlayBuiltinSoundPacket; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.data.SoundEvent; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; +import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.connector.network.translators.sound.SoundRegistry; + +@Translator(packet = ServerPlayBuiltinSoundPacket.class) +public class JavaPlayBuiltinSoundTranslator extends PacketTranslator { + + @Override + public void translate(ServerPlayBuiltinSoundPacket packet, GeyserSession session) { + String packetSound = packet.getSound().getName(); + + SoundRegistry.SoundMapping soundMapping = SoundRegistry.fromJava(packetSound); + if (soundMapping == null) { + session.getConnector().getLogger().debug("[Builtin] Sound mapping " + packetSound + " not found - " + packet.toString()); + return; + } + + if (soundMapping.isLevelEvent()) { + LevelEventPacket levelEventPacket = new LevelEventPacket(); + levelEventPacket.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); + levelEventPacket.setData(0); + levelEventPacket.setType(LevelEventType.valueOf(soundMapping.getBedrock())); + session.sendUpstreamPacket(levelEventPacket); + return; + } + LevelSoundEventPacket soundPacket = new LevelSoundEventPacket(); + SoundEvent sound = SoundRegistry.toSoundEvent(soundMapping.getBedrock()); + if (sound == null) { + sound = SoundRegistry.toSoundEvent(soundMapping.getBedrock()); + } + if (sound == null) { + sound = SoundRegistry.toSoundEvent(packetSound); + } + if (sound == null) { + session.getConnector().getLogger().debug("[Builtin] Sound for original " + packetSound + " to mappings " + soundPacket + + " was not a playable level sound, or has yet to be mapped to an enum in " + + "NukkitX SoundEvent "); + + } + soundPacket.setSound(sound); + soundPacket.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); + soundPacket.setIdentifier(soundMapping.getIdentifier()); + if (sound == SoundEvent.NOTE) { + // Minecraft Wiki: 2^(x/12) = Java pitch where x is -12 to 12 + // Java sends the note value as above starting with -12 and ending at 12 + // Bedrock has a number for each type of note, then proceeds up the scale by adding to that number + soundPacket.setExtraData(soundMapping.getExtraData() + (int)(Math.round((Math.log10(packet.getPitch()) / Math.log10(2)) * 12)) + 12); + } else if (sound == SoundEvent.PLACE && soundMapping.getExtraData() == -1) { + soundPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaBlockState(soundMapping.getIdentifier()))); + soundPacket.setIdentifier(":"); + } else { + soundPacket.setExtraData(soundMapping.getExtraData()); + } + + + soundPacket.setBabySound(false); // might need to adjust this in the future + soundPacket.setRelativeVolumeDisabled(false); + session.sendUpstreamPacket(soundPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlayEffectTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlayEffectTranslator.java new file mode 100644 index 00000000..da23adb5 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlayEffectTranslator.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.java.world; + +import com.github.steveice10.mc.protocol.data.game.world.effect.ParticleEffect; +import com.github.steveice10.mc.protocol.data.game.world.effect.*; +import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerPlayEffectPacket; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.data.SoundEvent; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; +import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; +import com.nukkitx.protocol.bedrock.packet.TextPacket; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.connector.network.translators.effect.Effect; +import org.geysermc.connector.network.translators.effect.EffectRegistry; +import org.geysermc.connector.utils.LocaleUtils; + +import java.util.ArrayList; +import java.util.List; + +@Translator(packet = ServerPlayEffectPacket.class) +public class JavaPlayEffectTranslator extends PacketTranslator { + + @Override + public void translate(ServerPlayEffectPacket packet, GeyserSession session) { + LevelEventPacket effect = new LevelEventPacket(); + // Some things here are particles, others are not + if (packet.getEffect() instanceof ParticleEffect) { + ParticleEffect particleEffect = (ParticleEffect) packet.getEffect(); + Effect geyserEffect = EffectRegistry.EFFECTS.get(particleEffect.name()); + if (geyserEffect != null) { + String name = geyserEffect.getBedrockName(); + effect.setType(LevelEventType.valueOf(name)); + } else { + switch (particleEffect) { + // TODO: BREAK_SPLASH_POTION has additional data + case BONEMEAL_GROW: + effect.setType(LevelEventType.BONEMEAL); + BonemealGrowEffectData growEffectData = (BonemealGrowEffectData) packet.getData(); + effect.setData(growEffectData.getParticleCount()); + break; + //TODO: Block break particles when under fire + case BREAK_BLOCK: + effect.setType(LevelEventType.DESTROY); + BreakBlockEffectData breakBlockEffectData = (BreakBlockEffectData) packet.getData(); + effect.setData(BlockTranslator.getBedrockBlockId(breakBlockEffectData.getBlockState())); + break; + case EXPLOSION: + effect.setType(LevelEventType.PARTICLE_LARGE_EXPLOSION); + break; + case MOB_SPAWN: + effect.setType(LevelEventType.ENTITY_SPAWN); + break; + // Done with a dispenser + case SMOKE: + // Might need to be SHOOT + effect.setType(LevelEventType.PARTICLE_SMOKE); + break; + case COMPOSTER: + effect.setType(LevelEventType.BONEMEAL); + + ComposterEffectData composterEffectData = (ComposterEffectData) packet.getData(); + LevelSoundEventPacket soundEvent = new LevelSoundEventPacket(); + soundEvent.setSound(SoundEvent.valueOf("COMPOSTER_" + composterEffectData.name())); + soundEvent.setPosition(Vector3f.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ())); + soundEvent.setIdentifier(":"); + soundEvent.setExtraData(-1); + soundEvent.setBabySound(false); + soundEvent.setRelativeVolumeDisabled(false); + session.sendUpstreamPacket(soundEvent); + break; + case BLOCK_LAVA_EXTINGUISH: + effect.setType(LevelEventType.SHOOT); + effect.setPosition(Vector3f.from(packet.getPosition().getX(), packet.getPosition().getY() + 1, packet.getPosition().getZ())); + session.sendUpstreamPacket(effect); + + LevelSoundEventPacket soundEventPacket = new LevelSoundEventPacket(); + soundEventPacket.setSound(SoundEvent.EXTINGUISH_FIRE); + soundEventPacket.setPosition(Vector3f.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ())); + soundEventPacket.setIdentifier(":"); + soundEventPacket.setExtraData(-1); + soundEventPacket.setBabySound(false); + soundEventPacket.setRelativeVolumeDisabled(false); + session.sendUpstreamPacket(soundEventPacket); + return; + default: + GeyserConnector.getInstance().getLogger().debug("No effect handling for particle effect: " + packet.getEffect()); + } + } + effect.setPosition(Vector3f.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ())); + session.sendUpstreamPacket(effect); + } else if (packet.getEffect() instanceof SoundEffect) { + SoundEffect soundEffect = (SoundEffect) packet.getEffect(); + Effect geyserEffect = EffectRegistry.EFFECTS.get(soundEffect.name()); + if (geyserEffect != null) { + // Some events are LevelEventTypes, some are SoundEvents. + if (geyserEffect.getType().equals("soundLevel")) { + effect.setType(LevelEventType.valueOf(geyserEffect.getBedrockName())); + } else if (geyserEffect.getType().equals("soundEvent")) { + LevelSoundEventPacket soundEvent = new LevelSoundEventPacket(); + // Separate case since each RecordEffectData in Java is an individual track in Bedrock + if (geyserEffect.getJavaName().equals("RECORD")) { + RecordEffectData recordEffectData = (RecordEffectData) packet.getData(); + soundEvent.setSound(EffectRegistry.RECORDS.get(recordEffectData.getRecordId())); + if (EffectRegistry.RECORDS.get(recordEffectData.getRecordId()) != SoundEvent.STOP_RECORD) { + // Send text packet as it seems to be handled in Java Edition client-side. + TextPacket textPacket = new TextPacket(); + textPacket.setType(TextPacket.Type.JUKEBOX_POPUP); + textPacket.setNeedsTranslation(true); + textPacket.setXuid(""); + textPacket.setPlatformChatId(""); + textPacket.setSourceName(null); + textPacket.setMessage("record.nowPlaying"); + List params = new ArrayList<>(); + String recordString = "%item." + EffectRegistry.RECORDS.get(recordEffectData.getRecordId()).name().toLowerCase() + ".desc"; + params.add(LocaleUtils.getLocaleString(recordString, session.getClientData().getLanguageCode())); + textPacket.setParameters(params); + session.sendUpstreamPacket(textPacket); + } + } else { + soundEvent.setSound(SoundEvent.valueOf(geyserEffect.getBedrockName())); + } + soundEvent.setExtraData(geyserEffect.getData()); + soundEvent.setIdentifier(geyserEffect.getIdentifier()); + soundEvent.setPosition(Vector3f.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ())); + session.sendUpstreamPacket(soundEvent); + } + } else { + GeyserConnector.getInstance().getLogger().debug("No effect handling for sound effect: " + packet.getEffect()); + } + } + if (effect.getType() != null) { + effect.setPosition(Vector3f.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ())); + session.sendUpstreamPacket(effect); + } + + } +} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlayerPlaySoundTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlayerPlaySoundTranslator.java new file mode 100644 index 00000000..c99de3e3 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlayerPlaySoundTranslator.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.network.translators.java.world; + +import com.github.steveice10.mc.protocol.data.game.world.sound.BuiltinSound; +import com.github.steveice10.mc.protocol.data.game.world.sound.CustomSound; +import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerPlaySoundPacket; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.packet.*; +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.sound.SoundRegistry; + +@Translator(packet = ServerPlaySoundPacket.class) +public class JavaPlayerPlaySoundTranslator extends PacketTranslator { + + @Override + public void translate(ServerPlaySoundPacket packet, GeyserSession session) { + String packetSound; + if(packet.getSound() instanceof BuiltinSound) { + packetSound = ((BuiltinSound) packet.getSound()).getName(); + } else if(packet.getSound() instanceof CustomSound) { + packetSound = ((CustomSound) packet.getSound()).getName(); + } else { + session.getConnector().getLogger().debug("Unknown sound packet, we were unable to map this. " + packet.toString()); + return; + } + + SoundRegistry.SoundMapping soundMapping = SoundRegistry.fromJava(packetSound.replace("minecraft:", "")); + String playsound; + if(soundMapping == null || soundMapping.getPlaysound() == null) { + // no mapping + session.getConnector().getLogger() + .debug("[PlaySound] Defaulting to sound server gave us for " + packet.toString()); + playsound = packetSound.replace("minecraft:", ""); + } else { + playsound = soundMapping.getPlaysound(); + } + + PlaySoundPacket playSoundPacket = new PlaySoundPacket(); + playSoundPacket.setSound(playsound); + playSoundPacket.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); + playSoundPacket.setVolume(packet.getVolume()); + playSoundPacket.setPitch(packet.getPitch()); + + session.sendUpstreamPacket(playSoundPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java new file mode 100644 index 00000000..63512047 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.java.world; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.world.particle.*; +import com.nukkitx.protocol.bedrock.data.ItemData; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; +import com.nukkitx.protocol.bedrock.packet.SpawnParticleEffectPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.item.ItemTranslator; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; + +import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerSpawnParticlePacket; +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.connector.network.translators.effect.EffectRegistry; + +@Translator(packet = ServerSpawnParticlePacket.class) +public class JavaSpawnParticleTranslator extends PacketTranslator { + + @Override + public void translate(ServerSpawnParticlePacket packet, GeyserSession session) { + LevelEventPacket particle = new LevelEventPacket(); + switch (packet.getParticle().getType()) { + case BLOCK: + particle.setType(LevelEventType.DESTROY); + particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); + particle.setData(BlockTranslator.getBedrockBlockId(((BlockParticleData) packet.getParticle().getData()).getBlockState())); + session.sendUpstreamPacket(particle); + break; + case FALLING_DUST: + //In fact, FallingDustParticle should have data like DustParticle, + //but in MCProtocol, its data is BlockState(1). + particle.setType(LevelEventType.PARTICLE_FALLING_DUST); + particle.setData(BlockTranslator.getBedrockBlockId(((FallingDustParticleData)packet.getParticle().getData()).getBlockState())); + particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); + session.sendUpstreamPacket(particle); + break; + case ITEM: + ItemStack javaItem = ((ItemParticleData)packet.getParticle().getData()).getItemStack(); + ItemData bedrockItem = ItemTranslator.translateToBedrock(session, javaItem); + int id = bedrockItem.getId(); + short damage = bedrockItem.getDamage(); + particle.setType(LevelEventType.PARTICLE_ITEM_BREAK); + particle.setData(id << 16 | damage); + particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); + session.sendUpstreamPacket(particle); + break; + case DUST: + DustParticleData data = (DustParticleData)packet.getParticle().getData(); + int r = (int) (data.getRed()*255); + int g = (int) (data.getGreen()*255); + int b = (int) (data.getBlue()*255); + particle.setType(LevelEventType.PARTICLE_FALLING_DUST); + particle.setData(((0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff)); + particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); + session.sendUpstreamPacket(particle); + break; + default: + LevelEventType typeParticle = EffectRegistry.getParticleLevelEventType(packet.getParticle().getType()); + if (typeParticle != null) { + particle.setType(typeParticle); + particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); + session.sendUpstreamPacket(particle); + } else { + String stringParticle = EffectRegistry.getParticleString(packet.getParticle().getType()); + if (stringParticle != null) { + SpawnParticleEffectPacket stringPacket = new SpawnParticleEffectPacket(); + stringPacket.setIdentifier(stringParticle); + stringPacket.setDimensionId(session.getPlayerEntity().getDimension()); + stringPacket.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); + session.sendUpstreamPacket(stringPacket); + } + } + break; + } + } + +} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnPositionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnPositionTranslator.java index d518a7ae..a59c71ea 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnPositionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnPositionTranslator.java @@ -42,6 +42,6 @@ public class JavaSpawnPositionTranslator extends PacketTranslator { - // This should be modified if sign text is not showing up - private static final int DELAY = 500; - @Override public void translate(ServerUpdateTileEntityPacket packet, GeyserSession session) { String id = BlockEntityUtils.getBedrockBlockEntityId(packet.getType().name()); @@ -52,11 +46,6 @@ public class JavaUpdateTileEntityTranslator extends PacketTranslator - BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(id, packet.getNbt(), null), packet.getPosition()), - DELAY, TimeUnit.MILLISECONDS); } else { BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(id, packet.getNbt(), null), packet.getPosition()); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java index 36533844..188e960d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java @@ -55,7 +55,7 @@ public class JavaUpdateTimeTranslator extends PacketTranslator("dodaylightcycle", doCycle)); - session.getUpstream().sendPacket(gameRulesChangedPacket); + session.sendUpstreamPacket(gameRulesChangedPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaVehicleMoveTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaVehicleMoveTranslator.java new file mode 100644 index 00000000..1bcd9919 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaVehicleMoveTranslator.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.java.world; + +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerVehicleMovePacket; +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +@Translator(packet = ServerVehicleMovePacket.class) +public class JavaVehicleMoveTranslator extends PacketTranslator { + + @Override + public void translate(ServerVehicleMovePacket packet, GeyserSession session) { + Entity entity = session.getRidingVehicleEntity(); + if (entity == null) return; + + entity.moveAbsolute(session, Vector3f.from(packet.getX(), packet.getY(), packet.getZ()), packet.getYaw(), packet.getPitch(), false, false); + + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/BlockSoundInteractionHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/BlockSoundInteractionHandler.java new file mode 100644 index 00000000..5a03b218 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/BlockSoundInteractionHandler.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.network.translators.sound; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemRegistry; + +import java.util.Map; + +/** + * Sound interaction handler for when a block is right-clicked. + */ +public interface BlockSoundInteractionHandler extends SoundInteractionHandler { + + /** + * Handles the block interaction when a player + * right-clicks a block. + * + * @param session the session interacting with the block + * @param position the position of the block + * @param identifier the identifier of the block + */ + static void handleBlockInteraction(GeyserSession session, Vector3f position, String identifier) { + for (Map.Entry> interactionEntry : SoundHandlerRegistry.INTERACTION_HANDLERS.entrySet()) { + if (!(interactionEntry.getValue() instanceof BlockSoundInteractionHandler)) { + continue; + } + if (interactionEntry.getKey().blocks().length != 0) { + boolean contains = false; + for (String blockIdentifier : interactionEntry.getKey().blocks()) { + if (identifier.contains(blockIdentifier)) { + contains = true; + break; + } + } + if (!contains) continue; + } + ItemStack itemInHand = session.getInventory().getItemInHand(); + if (interactionEntry.getKey().items().length != 0) { + if (itemInHand == null || itemInHand.getId() == 0) { + continue; + } + String handIdentifier = ItemRegistry.getItem(session.getInventory().getItemInHand()).getJavaIdentifier(); + boolean contains = false; + for (String itemIdentifier : interactionEntry.getKey().items()) { + if (handIdentifier.contains(itemIdentifier)) { + contains = true; + break; + } + } + if (!contains) continue; + } + if (session.isSneaking() && !interactionEntry.getKey().ignoreSneakingWhileHolding()) { + if (session.getInventory().getItemInHand() != null && session.getInventory().getItemInHand().getId() != 0) { + continue; + } + } + ((BlockSoundInteractionHandler) interactionEntry.getValue()).handleInteraction(session, position, identifier); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/EntitySoundInteractionHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/EntitySoundInteractionHandler.java new file mode 100644 index 00000000..138791b1 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/EntitySoundInteractionHandler.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.network.translators.sound; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemRegistry; + +import java.util.Map; + +/** + * Sound interaction handler for when an entity is right-clicked. + */ +public interface EntitySoundInteractionHandler extends SoundInteractionHandler { + + /** + * Handles the block interaction when a player + * right-clicks an entity. + * + * @param session the session interacting with the block + * @param position the position of the block + * @param entity the entity interacted with + */ + static void handleEntityInteraction(GeyserSession session, Vector3f position, Entity entity) { + for (Map.Entry> interactionEntry : SoundHandlerRegistry.INTERACTION_HANDLERS.entrySet()) { + if (!(interactionEntry.getValue() instanceof EntitySoundInteractionHandler)) { + continue; + } + if (interactionEntry.getKey().entities().length != 0) { + boolean contains = false; + for (String entityIdentifier : interactionEntry.getKey().entities()) { + if (entity.getEntityType().name().toLowerCase().contains(entityIdentifier)) { + contains = true; + break; + } + } + if (!contains) continue; + } + ItemStack itemInHand = session.getInventory().getItemInHand(); + if (interactionEntry.getKey().items().length != 0) { + if (itemInHand == null || itemInHand.getId() == 0) { + continue; + } + String handIdentifier = ItemRegistry.getItem(session.getInventory().getItemInHand()).getJavaIdentifier(); + boolean contains = false; + for (String itemIdentifier : interactionEntry.getKey().items()) { + if (handIdentifier.contains(itemIdentifier)) { + contains = true; + break; + } + } + if (!contains) continue; + } + if (session.isSneaking() && !interactionEntry.getKey().ignoreSneakingWhileHolding()) { + if (session.getInventory().getItemInHand() != null && session.getInventory().getItemInHand().getId() != 0) { + continue; + } + } + ((EntitySoundInteractionHandler) interactionEntry.getValue()).handleInteraction(session, position, entity); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandler.java new file mode 100644 index 00000000..52a76aa3 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandler.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.network.translators.sound; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Marks if a class should be handled as a + * {@link SoundInteractionHandler}. + */ +@Retention(value = RetentionPolicy.RUNTIME) +public @interface SoundHandler { + + /** + * The identifier(s) that the placed block must contain + * one of. Leave empty to ignore. + * + * Only applies to interaction handlers that are an + * instance of {@link BlockSoundInteractionHandler}. + * + * @return the value the interacted block must contain + */ + String[] blocks() default {}; + + /** + * The identifier(s) that the player's hand item + * must contain one of. Leave empty to ignore. + * + * @return the value the item in the player's hand must contain + */ + String[] items() default {}; + + /** + * The identifier(s) that the interacted entity must have. + * Leave empty to ignore. + * + * Only applies to interaction handlers that are an + * instance of {@link BlockSoundInteractionHandler}. + * + * @return the value the item in the player's hand must contain + */ + String[] entities() default {}; + + /** + * Controls if the interaction should still be + * called even if the player is sneaking while + * holding something in their hand. + * + * @return if the interaction should continue when player + * is holding something in their hand + */ + boolean ignoreSneakingWhileHolding() default false; +} 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 new file mode 100644 index 00000000..260efb41 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandlerRegistry.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.network.translators.sound; + +import org.reflections.Reflections; + +import java.util.HashMap; +import java.util.Map; + +/** + * Registry that holds {@link SoundInteractionHandler}s. + */ +public class SoundHandlerRegistry { + + static final Map> INTERACTION_HANDLERS = new HashMap<>(); + + static { + Reflections ref = new Reflections("org.geysermc.connector.network.translators.sound"); + for (Class clazz : ref.getTypesAnnotatedWith(SoundHandler.class)) { + try { + SoundInteractionHandler interactionHandler = (SoundInteractionHandler) clazz.newInstance(); + SoundHandler annotation = clazz.getAnnotation(SoundHandler.class); + INTERACTION_HANDLERS.put(annotation, interactionHandler); + } catch (InstantiationException | IllegalAccessException ex) { + ex.printStackTrace(); + } + } + } + + private SoundHandlerRegistry() { + } + + public static void init() { + // no-op + } + + /** + * Returns a map of the interaction handlers + * + * @return a map of the interaction handlers + */ + public static Map> getInteractionHandlers() { + return INTERACTION_HANDLERS; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundInteractionHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundInteractionHandler.java new file mode 100644 index 00000000..e68061ef --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundInteractionHandler.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.network.translators.sound; + +import com.nukkitx.math.vector.Vector3f; + +import org.geysermc.connector.network.session.GeyserSession; + +/** + * Handler for playing sounds when right-clicking + * various objects. Due to Minecraft: Bedrock Edition + * expecting interaction sounds to be played serverside + * and Minecraft: Java Edition handling them clientside, + * this had to be made to handle scenarios like that. + * + * @param the value + */ +public interface SoundInteractionHandler { + + /** + * Handles the interaction when a player + * right-clicks a block. + * + * @param session the session interacting with the block + * @param position the position of the block + * @param value the value + */ + void handleInteraction(GeyserSession session, Vector3f position, T value); +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundRegistry.java new file mode 100644 index 00000000..01b03476 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundRegistry.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.network.translators.sound; + +import com.fasterxml.jackson.databind.JsonNode; +import com.nukkitx.protocol.bedrock.data.SoundEvent; +import lombok.Data; +import lombok.ToString; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.utils.FileUtils; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +public class SoundRegistry { + + private static final Map SOUNDS; + + private SoundRegistry() { + } + + public static void init() { + // no-op + } + + static { + /* Load sound mappings */ + InputStream stream = FileUtils.getResource("mappings/sounds.json"); + JsonNode soundsTree; + try { + soundsTree = GeyserConnector.JSON_MAPPER.readTree(stream); + } catch (IOException e) { + throw new AssertionError("Unable to load sound mappings", e); + } + + Map soundMappings = new HashMap<>(); + Iterator> soundsIterator = soundsTree.fields(); + while(soundsIterator.hasNext()) { + Map.Entry next = soundsIterator.next(); + JsonNode brMap = next.getValue(); + + soundMappings.put(next.getKey(), new SoundMapping( + next.getKey(), + brMap.has("bedrock_mapping") && brMap.get("bedrock_mapping").isTextual() ? brMap.get("bedrock_mapping").asText() : null, + brMap.has("playsound_mapping") && brMap.get("playsound_mapping").isTextual() ? brMap.get("playsound_mapping").asText() : null, + brMap.has("extra_data") && brMap.get("extra_data").isInt() ? brMap.get("extra_data").asInt() : -1, + brMap.has("identifier") && brMap.get("identifier").isTextual() ? brMap.get("identifier").asText() : null, + brMap.has("level_event") && brMap.get("level_event").isBoolean() ? brMap.get("level_event").asBoolean() : false + ) + ); + } + SOUNDS = soundMappings; + } + + /** + * Get's the sound mapping for a Java edition sound identifier + * @param java Java edition sound identifier + * @return SoundMapping object with information for bedrock, nukkit, java, etc. null if not found + */ + public static SoundMapping fromJava(String java) { + return SOUNDS.get(java); + } + + /** + * Maps a sound name to a sound event, null if one + * does not exist. + * + * @param sound the sound name + * @return a sound event from the given sound + */ + public static SoundEvent toSoundEvent(String sound) { + try { + return SoundEvent.valueOf(sound.toUpperCase().replaceAll("\\.", "_")); + } catch (Exception ex) { + return null; + } + } + + @Data + @ToString + public static class SoundMapping { + private final String java; + private final String bedrock; + private final String playsound; + private final int extraData; + private String identifier; + private boolean levelEvent; + + public SoundMapping(String java, String bedrock, String playsound, int extraData, String identifier, boolean levelEvent) { + this.java = java; + this.bedrock = bedrock == null || bedrock.equalsIgnoreCase("") ? null : bedrock; + this.playsound = playsound == null || playsound.equalsIgnoreCase("") ? null : playsound; + this.extraData = extraData; + this.identifier = identifier == null || identifier.equalsIgnoreCase("") ? ":" : identifier; + this.levelEvent = levelEvent; + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/BucketSoundInteractionHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/BucketSoundInteractionHandler.java new file mode 100644 index 00000000..367f9beb --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/BucketSoundInteractionHandler.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.network.translators.sound.block; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.SoundEvent; +import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.sound.BlockSoundInteractionHandler; +import org.geysermc.connector.network.translators.sound.SoundHandler; + +@SoundHandler(items = "bucket") +public class BucketSoundInteractionHandler implements BlockSoundInteractionHandler { + + @Override + public void handleInteraction(GeyserSession session, Vector3f position, String identifier) { + String handItemIdentifier = ItemRegistry.getItem(session.getInventory().getItemInHand()).getJavaIdentifier(); + LevelSoundEventPacket soundEventPacket = new LevelSoundEventPacket(); + soundEventPacket.setPosition(position); + soundEventPacket.setIdentifier(":"); + soundEventPacket.setRelativeVolumeDisabled(false); + soundEventPacket.setBabySound(false); + soundEventPacket.setExtraData(-1); + SoundEvent soundEvent = null; + switch (handItemIdentifier) { + case "minecraft:bucket": + if (identifier.contains("water[")) { + soundEvent = SoundEvent.BUCKET_FILL_WATER; + } else if (identifier.contains("lava[")) { + soundEvent = SoundEvent.BUCKET_FILL_LAVA; + } + break; + case "minecraft:lava_bucket": + soundEvent = SoundEvent.BUCKET_EMPTY_LAVA; + break; + case "minecraft:fish_bucket": + soundEvent = SoundEvent.BUCKET_EMPTY_FISH; + break; + case "minecraft:water_bucket": + soundEvent = SoundEvent.BUCKET_EMPTY_WATER; + break; + } + if (soundEvent != null) { + soundEventPacket.setSound(soundEvent); + session.sendUpstreamPacket(soundEventPacket); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/ComparatorSoundInteractHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/ComparatorSoundInteractHandler.java new file mode 100644 index 00000000..4b74a678 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/ComparatorSoundInteractHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.network.translators.sound.block; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.sound.BlockSoundInteractionHandler; +import org.geysermc.connector.network.translators.sound.SoundHandler; + +@SoundHandler(blocks = "comparator") +public class ComparatorSoundInteractHandler implements BlockSoundInteractionHandler { + + @Override + public void handleInteraction(GeyserSession session, Vector3f position, String identifier) { + boolean powered = identifier.contains("mode=compare"); + LevelEventPacket levelEventPacket = new LevelEventPacket(); + levelEventPacket.setPosition(position); + levelEventPacket.setType(LevelEventType.REDSTONE_TRIGGER); + levelEventPacket.setData(powered ? 500 : 550); + session.sendUpstreamPacket(levelEventPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/DoorSoundInteractionHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/DoorSoundInteractionHandler.java new file mode 100644 index 00000000..39a07c3a --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/DoorSoundInteractionHandler.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.sound.block; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.sound.BlockSoundInteractionHandler; +import org.geysermc.connector.network.translators.sound.SoundHandler; + +@SoundHandler(blocks = {"door", "fence_gate"}) +public class DoorSoundInteractionHandler implements BlockSoundInteractionHandler { + + @Override + public void handleInteraction(GeyserSession session, Vector3f position, String identifier) { + LevelEventPacket levelEventPacket = new LevelEventPacket(); + levelEventPacket.setType(LevelEventType.SOUND_DOOR); + levelEventPacket.setPosition(position); + levelEventPacket.setData(0); + session.sendUpstreamPacket(levelEventPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/FlintAndSteelInteractionHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/FlintAndSteelInteractionHandler.java new file mode 100644 index 00000000..290aa7bd --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/FlintAndSteelInteractionHandler.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.network.translators.sound.block; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.SoundEvent; +import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.sound.BlockSoundInteractionHandler; +import org.geysermc.connector.network.translators.sound.SoundHandler; + +@SoundHandler(items = "flint_and_steel", ignoreSneakingWhileHolding = true) +public class FlintAndSteelInteractionHandler implements BlockSoundInteractionHandler { + + @Override + public void handleInteraction(GeyserSession session, Vector3f position, String identifier) { + LevelSoundEventPacket levelSoundEventPacket = new LevelSoundEventPacket(); + levelSoundEventPacket.setPosition(position); + levelSoundEventPacket.setBabySound(false); + levelSoundEventPacket.setRelativeVolumeDisabled(false); + levelSoundEventPacket.setIdentifier(":"); + levelSoundEventPacket.setSound(SoundEvent.IGNITE); + levelSoundEventPacket.setExtraData(-1); + session.sendUpstreamPacket(levelSoundEventPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/GrassPathInteractionHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/GrassPathInteractionHandler.java new file mode 100644 index 00000000..e5445e9d --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/GrassPathInteractionHandler.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.network.translators.sound.block; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.SoundEvent; +import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.sound.BlockSoundInteractionHandler; +import org.geysermc.connector.network.translators.sound.SoundHandler; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; + +@SoundHandler(blocks = "grass_path", items = "shovel", ignoreSneakingWhileHolding = true) +public class GrassPathInteractionHandler implements BlockSoundInteractionHandler { + + @Override + public void handleInteraction(GeyserSession session, Vector3f position, String identifier) { + LevelSoundEventPacket levelSoundEventPacket = new LevelSoundEventPacket(); + levelSoundEventPacket.setPosition(position); + levelSoundEventPacket.setBabySound(false); + levelSoundEventPacket.setRelativeVolumeDisabled(false); + levelSoundEventPacket.setIdentifier(":"); + levelSoundEventPacket.setSound(SoundEvent.ITEM_USE_ON); + levelSoundEventPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaBlockState(identifier))); + session.sendUpstreamPacket(levelSoundEventPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/HoeInteractionHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/HoeInteractionHandler.java new file mode 100644 index 00000000..17d346ae --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/HoeInteractionHandler.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.network.translators.sound.block; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.SoundEvent; +import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.sound.BlockSoundInteractionHandler; +import org.geysermc.connector.network.translators.sound.SoundHandler; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; + +@SoundHandler(blocks = "farmland", items = "hoe", ignoreSneakingWhileHolding = true) +public class HoeInteractionHandler implements BlockSoundInteractionHandler { + + @Override + public void handleInteraction(GeyserSession session, Vector3f position, String identifier) { + LevelSoundEventPacket levelSoundEventPacket = new LevelSoundEventPacket(); + levelSoundEventPacket.setPosition(position); + levelSoundEventPacket.setBabySound(false); + levelSoundEventPacket.setRelativeVolumeDisabled(false); + levelSoundEventPacket.setIdentifier(":"); + levelSoundEventPacket.setSound(SoundEvent.ITEM_USE_ON); + levelSoundEventPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaBlockState(identifier))); + session.sendUpstreamPacket(levelSoundEventPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/LeverSoundInteractionHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/LeverSoundInteractionHandler.java new file mode 100644 index 00000000..fb39d4ac --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/LeverSoundInteractionHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.network.translators.sound.block; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.sound.BlockSoundInteractionHandler; +import org.geysermc.connector.network.translators.sound.SoundHandler; + +@SoundHandler(blocks = "lever") +public class LeverSoundInteractionHandler implements BlockSoundInteractionHandler { + + @Override + public void handleInteraction(GeyserSession session, Vector3f position, String identifier) { + boolean powered = identifier.contains("powered=true"); + LevelEventPacket levelEventPacket = new LevelEventPacket(); + levelEventPacket.setPosition(position); + levelEventPacket.setType(LevelEventType.REDSTONE_TRIGGER); + levelEventPacket.setData(powered ? 600 : 500); + session.sendUpstreamPacket(levelEventPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/entity/MilkCowSoundInteractionHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/entity/MilkCowSoundInteractionHandler.java new file mode 100644 index 00000000..d33d68af --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/entity/MilkCowSoundInteractionHandler.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.network.translators.sound.entity; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.SoundEvent; +import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; +import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.sound.EntitySoundInteractionHandler; +import org.geysermc.connector.network.translators.sound.SoundHandler; + +@SoundHandler(entities = "cow", items = "bucket") +public class MilkCowSoundInteractionHandler implements EntitySoundInteractionHandler { + + @Override + public void handleInteraction(GeyserSession session, Vector3f position, Entity value) { + if (!ItemRegistry.getItem(session.getInventory().getItemInHand()).getJavaIdentifier().equals("minecraft:bucket")) { + return; + } + LevelSoundEventPacket levelSoundEventPacket = new LevelSoundEventPacket(); + levelSoundEventPacket.setPosition(position); + levelSoundEventPacket.setBabySound(false); + levelSoundEventPacket.setRelativeVolumeDisabled(false); + levelSoundEventPacket.setIdentifier(":"); + levelSoundEventPacket.setSound(SoundEvent.MILK); + levelSoundEventPacket.setExtraData(-1); + session.sendUpstreamPacket(levelSoundEventPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/CachedChunkManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/CachedChunkManager.java new file mode 100644 index 00000000..740e2df9 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/CachedChunkManager.java @@ -0,0 +1,39 @@ +/* + * 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; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import org.geysermc.connector.network.session.GeyserSession; + +public class CachedChunkManager extends WorldManager { + + @Override + public BlockState getBlockAt(GeyserSession session, int x, int y, int z) { + return session.getChunkCache().getBlockAt(new Position(x, y, z)); + } +} 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 new file mode 100644 index 00000000..d92d8454 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java @@ -0,0 +1,76 @@ +/* + * 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; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; + +import com.nukkitx.math.vector.Vector3i; +import org.geysermc.connector.network.session.GeyserSession; + +/** + * Class that manages or retrieves various information + * from the world. Everything in this class should be + * safe to return null or an empty value in the event + * that chunk caching or anything of the sort is disabled + * on the standalone version of Geyser. + */ +public abstract class WorldManager { + + /** + * Gets the {@link BlockState} at the specified location + * + * @param session the session + * @param position the position + * @return the block state at the specified location + */ + public BlockState getBlockAt(GeyserSession session, Position position) { + return this.getBlockAt(session, position.getX(), position.getY(), position.getZ()); + } + + /** + * Gets the {@link BlockState} at the specified location + * + * @param session the session + * @param vector the position + * @return the block state at the specified location + */ + public BlockState getBlockAt(GeyserSession session, Vector3i vector) { + return this.getBlockAt(session, vector.getX(), vector.getY(), vector.getZ()); + } + + /** + * Gets the {@link BlockState} at the specified location + * + * @param session the session + * @param x the x coordinate to get the block at + * @param y the y coordinate to get the block at + * @param z the z coordinate to get the block at + * @return the block state at the specified location + */ + public abstract BlockState getBlockAt(GeyserSession session, int x, int y, int z); +} 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 new file mode 100644 index 00000000..070a0592 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.world.block; + +import com.fasterxml.jackson.databind.JsonNode; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.nukkitx.nbt.tag.CompoundTag; +import it.unimi.dsi.fastutil.ints.Int2BooleanMap; +import it.unimi.dsi.fastutil.ints.Int2BooleanOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ByteMap; +import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; + +import java.util.HashMap; +import java.util.Map; + +/** + * Used for block entities if the Java block state contains Bedrock block information. + */ +public class BlockStateValues { + + private static final Object2IntMap BANNER_COLORS = new Object2IntOpenHashMap<>(); + private static final Object2ByteMap BED_COLORS = new Object2ByteOpenHashMap<>(); + private static final Int2ObjectMap DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>(); + private static final Map FLOWER_POT_BLOCKS = new HashMap<>(); + private static final Object2IntMap NOTEBLOCK_PITCHES = new Object2IntOpenHashMap<>(); + private static final Int2BooleanMap IS_STICKY_PISTON = new Int2BooleanOpenHashMap(); + private static final Int2BooleanMap PISTON_VALUES = new Int2BooleanOpenHashMap(); + private static final Object2ByteMap SKULL_VARIANTS = new Object2ByteOpenHashMap<>(); + private static final Object2ByteMap SKULL_ROTATIONS = new Object2ByteOpenHashMap<>(); + private static final Object2ByteMap SHULKERBOX_DIRECTIONS = new Object2ByteOpenHashMap<>(); + + /** + * Determines if the block state contains Bedrock block information + * @param entry The String to JsonNode map used in BlockTranslator + * @param javaBlockState the Java Block State of the block + */ + public static void storeBlockStateValues(Map.Entry entry, BlockState javaBlockState) { + JsonNode bannerColor = entry.getValue().get("banner_color"); + if (bannerColor != null) { + BANNER_COLORS.put(javaBlockState, (byte) bannerColor.intValue()); + return; // There will never be a banner color and a skull variant + } + + JsonNode bedColor = entry.getValue().get("bed_color"); + if (bedColor != null) { + BED_COLORS.put(javaBlockState, (byte) bedColor.intValue()); + return; + } + + if (entry.getValue().get("double_chest_position") != null) { + boolean isX = (entry.getValue().get("x") != null); + boolean isDirectionPositive = ((entry.getValue().get("x") != null && entry.getValue().get("x").asBoolean()) || + (entry.getValue().get("z") != null && entry.getValue().get("z").asBoolean())); + boolean isLeft = (entry.getValue().get("double_chest_position").asText().contains("left")); + DOUBLE_CHEST_VALUES.put(javaBlockState.getId(), new DoubleChestValue(isX, isDirectionPositive, isLeft)); + return; + } + + if (entry.getKey().contains("potted_")) { + FLOWER_POT_VALUES.put(javaBlockState.getId(), entry.getKey().replace("potted_", "")); + return; + } + + JsonNode notePitch = entry.getValue().get("note_pitch"); + if (notePitch != null) { + NOTEBLOCK_PITCHES.put(javaBlockState, entry.getValue().get("note_pitch").intValue()); + return; + } + + if (entry.getKey().contains("piston")) { + // True if extended, false if not + PISTON_VALUES.put(javaBlockState.getId(), entry.getKey().contains("extended=true")); + IS_STICKY_PISTON.put(javaBlockState.getId(), entry.getKey().contains("sticky")); + return; + } + + JsonNode skullVariation = entry.getValue().get("variation"); + if(skullVariation != null) { + SKULL_VARIANTS.put(javaBlockState, (byte) skullVariation.intValue()); + } + + JsonNode skullRotation = entry.getValue().get("skull_rotation"); + if (skullRotation != null) { + SKULL_ROTATIONS.put(javaBlockState, (byte) skullRotation.intValue()); + } + + JsonNode shulkerDirection = entry.getValue().get("shulker_direction"); + if (shulkerDirection != null) { + BlockStateValues.SHULKERBOX_DIRECTIONS.put(javaBlockState, (byte) shulkerDirection.intValue()); + } + } + + /** + * Banner colors are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. + * This gives an integer color that Bedrock can use. + * + * @param state BlockState of the block + * @return Banner color integer or -1 if no color + */ + public static int getBannerColor(BlockState state) { + if (BANNER_COLORS.containsKey(state)) { + return BANNER_COLORS.getInt(state); + } + return -1; + } + + /** + * Bed colors are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. + * This gives a byte color that Bedrock can use - Bedrock needs a byte in the final tag. + * + * @param state BlockState of the block + * @return Bed color byte or -1 if no color + */ + public static byte getBedColor(BlockState state) { + if (BED_COLORS.containsKey(state)) { + return BED_COLORS.getByte(state); + } + return -1; + } + + /** + * All double chest values are part of the block state in Java and part of the block entity tag in Bedrock. + * This gives the DoubleChestValue that can be calculated into the final tag. + * @return The map of all DoubleChestValues. + */ + public static Int2ObjectMap getDoubleChestValues() { + return DOUBLE_CHEST_VALUES; + } + + /** + * Get the Int2ObjectMap of flower pot block states to containing plant + * @return Int2ObjectMap of flower pot values + */ + public static Int2ObjectMap getFlowerPotValues() { + return FLOWER_POT_VALUES; + } + + /** + * Get the map of contained flower pot plants to Bedrock CompoundTag + * @return Map of flower pot blocks. + */ + public static Map getFlowerPotBlocks() { + return FLOWER_POT_BLOCKS; + } + + /** + * The note that noteblocks output when hit is part of the block state in Java but sent as a BlockEventPacket in Bedrock. + * This gives an integer pitch that Bedrock can use. + * @param state BlockState of the block + * @return note block note integer or -1 if not present + */ + public static int getNoteblockPitch(BlockState state) { + if (NOTEBLOCK_PITCHES.containsKey(state)) { + return NOTEBLOCK_PITCHES.getInt(state); + } + return -1; + } + + /** + * Get the Int2BooleanMap showing if a piston block state is extended or not. + * @return the Int2BooleanMap of piston extensions. + */ + public static Int2BooleanMap getPistonValues() { + return PISTON_VALUES; + } + + public static boolean isStickyPiston(BlockState blockState) { + return IS_STICKY_PISTON.get(blockState.getId()); + } + + /** + * Skull variations are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. + * This gives a byte variant ID that Bedrock can use. + * + * @param state BlockState of the block + * @return Skull variant byte or -1 if no variant + */ + public static byte getSkullVariant(BlockState state) { + if (SKULL_VARIANTS.containsKey(state)) { + return SKULL_VARIANTS.getByte(state); + } + return -1; + } + + /** + * Skull rotations are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. + * This gives a byte rotation that Bedrock can use. + * + * @param state BlockState of the block + * @return Skull rotation value or -1 if no value + */ + public static byte getSkullRotation(BlockState state) { + if (SKULL_ROTATIONS.containsKey(state)) { + return SKULL_ROTATIONS.getByte(state); + } + return -1; + } + + + /** + * Shulker box directions are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. + * This gives a byte direction that Bedrock can use. + * + * @param state BlockState of the block + * @return Shulker direction value or -1 if no value + */ + public static byte getShulkerBoxDirection(BlockState state) { + if (SHULKERBOX_DIRECTIONS.containsKey(state)) { + return SHULKERBOX_DIRECTIONS.getByte(state); + } + return -1; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java similarity index 80% rename from connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java index cf0da17b..0c2cab2c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java @@ -23,10 +23,12 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.block; +package org.geysermc.connector.network.translators.world.block; import com.fasterxml.jackson.databind.JsonNode; import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; import com.nukkitx.nbt.CompoundTagBuilder; import com.nukkitx.nbt.NbtUtils; import com.nukkitx.nbt.stream.NBTInputStream; @@ -36,8 +38,8 @@ import it.unimi.dsi.fastutil.ints.*; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.network.translators.block.entity.BlockEntity; -import org.geysermc.connector.utils.Toolbox; +import org.geysermc.connector.network.translators.world.block.entity.BlockEntity; +import org.geysermc.connector.utils.FileUtils; import org.reflections.Reflections; import java.io.InputStream; @@ -50,8 +52,9 @@ public class BlockTranslator { private static final Int2IntMap JAVA_TO_BEDROCK_BLOCK_MAP = new Int2IntOpenHashMap(); private static final Int2ObjectMap BEDROCK_TO_JAVA_BLOCK_MAP = new Int2ObjectOpenHashMap<>(); - private static final Map JAVA_ID_BLOCK_MAP = new HashMap<>(); + private static final BiMap JAVA_ID_BLOCK_MAP = HashBiMap.create(); private static final IntSet WATERLOGGED = new IntOpenHashSet(); + private static final Object2IntMap ITEM_FRAMES = new Object2IntOpenHashMap<>(); // Bedrock carpet ID, used in LlamaEntity.java for decoration public static final int CARPET = 171; @@ -66,11 +69,14 @@ public class BlockTranslator { public static final IntSet JAVA_RUNTIME_WOOL_IDS = new IntOpenHashSet(); public static final int JAVA_RUNTIME_COBWEB_ID; + public static final int JAVA_RUNTIME_FURNACE_ID; + public static final int JAVA_RUNTIME_FURNACE_LIT_ID; + private static final int BLOCK_STATE_VERSION = 17760256; static { /* Load block palette */ - InputStream stream = Toolbox.getResource("bedrock/runtime_block_states.dat"); + InputStream stream = FileUtils.getResource("bedrock/runtime_block_states.dat"); ListTag blocksTag; try (NBTInputStream nbtInputStream = NbtUtils.createNetworkReader(stream)) { @@ -88,10 +94,10 @@ public class BlockTranslator { } } - stream = Toolbox.getResource("mappings/blocks.json"); + stream = FileUtils.getResource("mappings/blocks.json"); JsonNode blocks; try { - blocks = Toolbox.JSON_MAPPER.readTree(stream); + blocks = GeyserConnector.JSON_MAPPER.readTree(stream); } catch (Exception e) { throw new AssertionError("Unable to load Java block mappings", e); } @@ -99,13 +105,15 @@ public class BlockTranslator { addedStatesMap.defaultReturnValue(-1); List paletteList = new ArrayList<>(); - Reflections ref = new Reflections("org.geysermc.connector.network.translators.block.entity"); + Reflections ref = 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 furnaceRuntimeId = -1; + int furnaceLitRuntimeId = -1; Iterator> blocksIterator = blocks.fields(); while (blocksIterator.hasNext()) { javaRuntimeId++; @@ -151,6 +159,11 @@ public class BlockTranslator { BlockStateValues.storeBlockStateValues(entry, javaBlockState); + // 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())); + } + if ("minecraft:water[level=0]".equals(javaId)) { waterRuntimeId = bedrockRuntimeId; } @@ -179,6 +192,14 @@ public class BlockTranslator { } JAVA_TO_BEDROCK_BLOCK_MAP.put(javaRuntimeId, bedrockRuntimeId); + if (javaId.startsWith("minecraft:furnace[facing=north")) { + if (javaId.contains("lit=true")) { + furnaceLitRuntimeId = javaRuntimeId; + } else { + furnaceRuntimeId = javaRuntimeId; + } + } + bedrockRuntimeId++; } @@ -187,6 +208,16 @@ public class BlockTranslator { } JAVA_RUNTIME_COBWEB_ID = cobwebRuntimeId; + if (furnaceRuntimeId == -1) { + throw new AssertionError("Unable to find furnace in palette"); + } + JAVA_RUNTIME_FURNACE_ID = furnaceRuntimeId; + + if (furnaceLitRuntimeId == -1) { + throw new AssertionError("Unable to find lit furnace in palette"); + } + JAVA_RUNTIME_FURNACE_LIT_ID = furnaceLitRuntimeId; + if (waterRuntimeId == -1) { throw new AssertionError("Unable to find water in palette"); } @@ -194,6 +225,16 @@ public class BlockTranslator { paletteList.addAll(blockStateMap.values()); // Add any missing mappings that could crash the client + // Loop around again to find all item frame runtime IDs + int frameRuntimeId = 0; + for (CompoundTag tag : paletteList) { + CompoundTag blockTag = tag.getCompound("block"); + if (blockTag.getString("name").equals("minecraft:frame")) { + ITEM_FRAMES.put(tag, frameRuntimeId); + } + frameRuntimeId++; + } + BLOCKS = new ListTag<>("", CompoundTag.class, paletteList); } @@ -245,6 +286,18 @@ public class BlockTranslator { return BEDROCK_TO_JAVA_BLOCK_MAP.get(bedrockId); } + public static int getItemFrame(CompoundTag tag) { + return ITEM_FRAMES.getOrDefault(tag, -1); + } + + public static boolean isItemFrame(int bedrockBlockRuntimeId) { + return ITEM_FRAMES.values().contains(bedrockBlockRuntimeId); + } + + public static int getBlockStateVersion() { + return BLOCK_STATE_VERSION; + } + public static BlockState getJavaBlockState(String javaId) { return JAVA_ID_BLOCK_MAP.get(javaId); } @@ -257,6 +310,10 @@ public class BlockTranslator { return WATERLOGGED.contains(state.getId()); } + public static BiMap getJavaIdBlockMap() { + return JAVA_ID_BLOCK_MAP; + } + public static BlockState getJavaWaterloggedState(int bedrockId) { return BEDROCK_TO_JAVA_BLOCK_MAP.get(1 << 31 | bedrockId); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/DoubleChestValue.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/DoubleChestValue.java new file mode 100644 index 00000000..5bd21724 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/DoubleChestValue.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.world.block; + +import lombok.AllArgsConstructor; + +/** + * This stores all values of double chests that are part of the Java block state. + */ +@AllArgsConstructor +public class DoubleChestValue { + + /** + * If true, then chest is facing east/west; if false, south/north + */ + public boolean isFacingEast; + + /** + * If true, direction is positive (east/south); if false, direction is negative (west/north) + */ + public boolean isDirectionPositive; + + /** + * If true, chest is the left of a pair; if false, chest is the right of a pair. + */ + public boolean isLeft; + +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BannerBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java similarity index 63% rename from connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BannerBlockEntityTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java index e81191a3..3e2c0a95 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BannerBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.block.entity; +package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; @@ -32,12 +32,13 @@ import com.nukkitx.nbt.CompoundTagBuilder; import com.nukkitx.nbt.tag.IntTag; import com.nukkitx.nbt.tag.StringTag; import com.nukkitx.nbt.tag.Tag; -import org.geysermc.connector.network.translators.block.BlockStateValues; +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.List; -@BlockEntity(name = "Banner", delay = false, regex = "banner") +@BlockEntity(name = "Banner", regex = "banner") public class BannerBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { @Override @@ -48,26 +49,21 @@ public class BannerBlockEntityTranslator extends BlockEntityTranslator implement @Override public List> translateTag(CompoundTag tag, BlockState blockState) { List> tags = new ArrayList<>(); + int bannerColor = BlockStateValues.getBannerColor(blockState); if (bannerColor != -1) { tags.add(new IntTag("Base", 15 - bannerColor)); } - ListTag patterns = tag.get("Patterns"); - List tagsList = new ArrayList<>(); + if (tag.contains("Patterns")) { - for (com.github.steveice10.opennbt.tag.builtin.Tag patternTag : patterns.getValue()) { - com.nukkitx.nbt.tag.CompoundTag newPatternTag = getPattern((CompoundTag) patternTag); - if (newPatternTag != null) { - tagsList.add(newPatternTag); - } - } - com.nukkitx.nbt.tag.ListTag bedrockPatterns = - new com.nukkitx.nbt.tag.ListTag<>("Patterns", com.nukkitx.nbt.tag.CompoundTag.class, tagsList); - tags.add(bedrockPatterns); + ListTag patterns = tag.get("Patterns"); + tags.add(BannerTranslator.convertBannerPattern(patterns)); } + if (tag.contains("CustomName")) { tags.add(new StringTag("CustomName", (String) tag.get("CustomName").getValue())); } + return tags; } @@ -84,25 +80,4 @@ public class BannerBlockEntityTranslator extends BlockEntityTranslator implement tagBuilder.listTag("Patterns", com.nukkitx.nbt.tag.CompoundTag.class, new ArrayList<>()); return tagBuilder.buildRootTag(); } - - /** - * Convert the Java edition pattern nbt to Bedrock edition, null if the pattern doesn't exist - * - * @param pattern Java edition pattern nbt - * @return The Bedrock edition format pattern nbt - */ - protected com.nukkitx.nbt.tag.CompoundTag getPattern(CompoundTag pattern) { - String patternName = (String) pattern.get("Pattern").getValue(); - - // Return null if its the globe pattern as it doesn't exist on bedrock - if (patternName.equals("glb")) { - return null; - } - - return CompoundTagBuilder.builder() - .intTag("Color", 15 - (int) pattern.get("Color").getValue()) - .stringTag("Pattern", (String) pattern.get("Pattern").getValue()) - .stringTag("Pattern", patternName) - .buildRootTag(); - } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BedBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java similarity index 92% rename from connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BedBlockEntityTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java index a8dae253..5f0b1cc0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BedBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java @@ -23,19 +23,19 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.block.entity; +package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.nbt.CompoundTagBuilder; import com.nukkitx.nbt.tag.ByteTag; import com.nukkitx.nbt.tag.Tag; -import org.geysermc.connector.network.translators.block.BlockStateValues; +import org.geysermc.connector.network.translators.world.block.BlockStateValues; import java.util.ArrayList; import java.util.List; -@BlockEntity(name = "Bed", delay = false, regex = "bed") +@BlockEntity(name = "Bed", regex = "bed") public class BedBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java new file mode 100644 index 00000000..5b325eba --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java @@ -0,0 +1,61 @@ +/* + * 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.mc.protocol.data.game.world.block.BlockState; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.tag.CompoundTag; +import org.geysermc.connector.network.session.GeyserSession; + +/** + * Implemented only if a block is a block entity in Bedrock and not Java Edition. + */ +public interface BedrockOnlyBlockEntity { + + /** + * Update the block on Bedrock Edition. + * @param session GeyserSession. + * @param blockState The Java block state. + * @param position The Bedrock block position. + */ + void updateBlock(GeyserSession session, BlockState blockState, Vector3i position); + + /** + * Get the tag of the Bedrock-only block entity + * @param position Bedrock position of block. + * @param blockState Java BlockState of block. + * @return Bedrock tag, or null if not a Bedrock-only Block Entity + */ + static CompoundTag getTag(Vector3i position, BlockState blockState) { + if (new FlowerPotBlockEntityTranslator().isBlock(blockState)) { + return FlowerPotBlockEntityTranslator.getTag(blockState, position); + } else if (PistonBlockEntityTranslator.isBlock(blockState)) { + return PistonBlockEntityTranslator.getTag(blockState, position); + } + return null; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BlockEntity.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntity.java similarity index 88% rename from connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BlockEntity.java rename to connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntity.java index 47cbbaf3..11bfe0ea 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BlockEntity.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntity.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.block.entity; +package org.geysermc.connector.network.translators.world.block.entity; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -31,12 +31,6 @@ import java.lang.annotation.RetentionPolicy; @Retention(value = RetentionPolicy.RUNTIME) public @interface BlockEntity { - /** - * Whether to delay the sending of the block entity - * @return the delay for when sending the block entity - */ - boolean delay(); - /** * The block entity name * @return the name of the block entity diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java similarity index 65% rename from connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BlockEntityTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java index f2825789..3d663926 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.block.entity; +package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; @@ -32,12 +32,49 @@ import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.nukkitx.nbt.CompoundTagBuilder; import com.nukkitx.nbt.tag.Tag; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.utils.BlockEntityUtils; +import org.reflections.Reflections; +import java.util.HashMap; import java.util.List; +import java.util.Map; public abstract class BlockEntityTranslator { + public static final Map BLOCK_ENTITY_TRANSLATORS = new HashMap<>(); + public static ObjectArrayList REQUIRES_BLOCK_STATE_LIST = new ObjectArrayList<>(); + + protected BlockEntityTranslator() { + } + + public static void init() { + // no-op + } + + static { + Reflections ref = new Reflections("org.geysermc.connector.network.translators.world.block.entity"); + for (Class clazz : ref.getTypesAnnotatedWith(BlockEntity.class)) { + GeyserConnector.getInstance().getLogger().debug("Found annotated block entity: " + clazz.getCanonicalName()); + + try { + BLOCK_ENTITY_TRANSLATORS.put(clazz.getAnnotation(BlockEntity.class).name(), (BlockEntityTranslator) clazz.newInstance()); + } catch (InstantiationException | IllegalAccessException e) { + GeyserConnector.getInstance().getLogger().error("Could not instantiate annotated block entity " + clazz.getCanonicalName() + "."); + } + } + for (Class clazz : ref.getSubTypesOf(RequiresBlockState.class)) { + GeyserConnector.getInstance().getLogger().debug("Found block entity that requires block state: " + clazz.getCanonicalName()); + + try { + REQUIRES_BLOCK_STATE_LIST.add((RequiresBlockState) clazz.newInstance()); + } catch (InstantiationException | IllegalAccessException e) { + GeyserConnector.getInstance().getLogger().error("Could not instantiate required block state " + clazz.getCanonicalName() + "."); + } + } + } + public abstract List> translateTag(CompoundTag tag, BlockState blockState); public abstract CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/CampfireBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java similarity index 91% rename from connector/src/main/java/org/geysermc/connector/network/translators/block/entity/CampfireBlockEntityTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java index 11b7e864..cd31636c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/CampfireBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java @@ -23,22 +23,21 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.block.entity; +package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.nukkitx.nbt.CompoundTagBuilder; import com.nukkitx.nbt.tag.Tag; - -import org.geysermc.connector.network.translators.Translators; import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.network.translators.item.ItemRegistry; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -@BlockEntity(name = "Campfire", delay = false, regex = "campfire") +@BlockEntity(name = "Campfire", regex = "campfire") public class CampfireBlockEntityTranslator extends BlockEntityTranslator { @Override @@ -71,7 +70,7 @@ public class CampfireBlockEntityTranslator extends BlockEntityTranslator { } protected com.nukkitx.nbt.tag.CompoundTag getItem(CompoundTag tag) { - ItemEntry entry = Translators.getItemTranslator().getItemEntry((String) tag.get("id").getValue()); + ItemEntry entry = ItemRegistry.getItemEntry((String) tag.get("id").getValue()); CompoundTagBuilder tagBuilder = CompoundTagBuilder.builder() .shortTag("id", (short) entry.getBedrockId()) .byteTag("Count", (byte) tag.get("Count").getValue()) 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 new file mode 100644 index 00000000..f5599832 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java @@ -0,0 +1,108 @@ +/* + * 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.mc.protocol.data.game.world.block.BlockState; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.ByteTag; +import com.nukkitx.nbt.tag.IntTag; +import com.nukkitx.nbt.tag.Tag; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockStateValues; +import org.geysermc.connector.network.translators.world.block.DoubleChestValue; +import org.geysermc.connector.utils.BlockEntityUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * Chests have more block entity properties in Bedrock, which is solved by implementing the BedrockOnlyBlockEntity + */ +@BlockEntity(name = "Chest", regex = "chest") +public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator implements BedrockOnlyBlockEntity, RequiresBlockState { + + @Override + public boolean isBlock(BlockState blockState) { + return BlockStateValues.getDoubleChestValues().containsKey(blockState.getId()); + } + + @Override + public void updateBlock(GeyserSession session, BlockState blockState, Vector3i position) { + CompoundTag javaTag = getConstantJavaTag("chest", position.getX(), position.getY(), position.getZ()); + CompoundTagBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId("chest"), position.getX(), position.getY(), position.getZ()).toBuilder(); + translateTag(javaTag, blockState).forEach(tagBuilder::tag); + BlockEntityUtils.updateBlockEntity(session, tagBuilder.buildRootTag(), position); + } + + @Override + public List> translateTag(CompoundTag tag, BlockState blockState) { + List> tags = new ArrayList<>(); + if (blockState != null && BlockStateValues.getDoubleChestValues().containsKey(blockState.getId())) { + DoubleChestValue chestValues = BlockStateValues.getDoubleChestValues().get(blockState.getId()); + if (chestValues != null) { + int x = (int) tag.getValue().get("x").getValue(); + int z = (int) tag.getValue().get("z").getValue(); + // Calculate the position of the other chest based on the Java block state + if (chestValues.isFacingEast) { + if (chestValues.isDirectionPositive) { + // East + z = z + (chestValues.isLeft ? 1 : -1); + } else { + // West + z = z + (chestValues.isLeft ? -1 : 1); + } + } else { + if (chestValues.isDirectionPositive) { + // South + x = x + (chestValues.isLeft ? -1 : 1); + } else { + // North + x = x + (chestValues.isLeft ? 1 : -1); + } + } + tags.add(new IntTag("pairx", x)); + tags.add(new IntTag("pairz", z)); + if (!chestValues.isLeft) { + tags.add(new ByteTag("pairlead", (byte) 1)); + } + } + } + return tags; + } + + @Override + public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { + return null; + } + + @Override + public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + return null; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/EmptyBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java similarity index 94% rename from connector/src/main/java/org/geysermc/connector/network/translators/block/entity/EmptyBlockEntityTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java index e4601400..d1068277 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/EmptyBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.block.entity; +package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; @@ -32,7 +32,7 @@ import com.nukkitx.nbt.tag.Tag; import java.util.ArrayList; import java.util.List; -@BlockEntity(name = "Empty", delay = false, regex = "") +@BlockEntity(name = "Empty", regex = "") public class EmptyBlockEntityTranslator extends BlockEntityTranslator { @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/EndGatewayBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java similarity index 96% rename from connector/src/main/java/org/geysermc/connector/network/translators/block/entity/EndGatewayBlockEntityTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java index de5868a4..4cd2eaa9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/EndGatewayBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.block.entity; +package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; @@ -36,7 +36,7 @@ import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; -@BlockEntity(name = "EndGateway", delay = true, regex = "end_gateway") +@BlockEntity(name = "EndGateway", regex = "end_gateway") public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator { @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java new file mode 100644 index 00000000..c4748c82 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.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.world.block.entity; + +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockStateValues; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.connector.utils.BlockEntityUtils; + +public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity, RequiresBlockState { + + @Override + public boolean isBlock(BlockState blockState) { + return (BlockStateValues.getFlowerPotValues().containsKey(blockState.getId())); + } + + @Override + public void updateBlock(GeyserSession session, BlockState blockState, Vector3i position) { + BlockEntityUtils.updateBlockEntity(session, getTag(blockState, position), position); + UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); + updateBlockPacket.setDataLayer(0); + updateBlockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(blockState)); + updateBlockPacket.setBlockPosition(position); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NONE); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); + session.sendUpstreamPacket(updateBlockPacket); + } + + /** + * Get the Nukkit CompoundTag of the flower pot. + * @param blockState Java BlockState of flower pot. + * @param position Bedrock position of flower pot. + * @return Bedrock tag of flower pot. + */ + public static CompoundTag getTag(BlockState blockState, Vector3i position) { + CompoundTagBuilder tagBuilder = CompoundTagBuilder.builder() + .intTag("x", position.getX()) + .intTag("y", position.getY()) + .intTag("z", position.getZ()) + .byteTag("isMovable", (byte) 1) + .stringTag("id", "FlowerPot"); + // Get the Java name of the plant inside. e.g. minecraft:oak_sapling + String name = BlockStateValues.getFlowerPotValues().get(blockState.getId()); + if (name != null) { + // Get the Bedrock CompoundTag of the block. + // This is where we need to store the *Java* name because Bedrock has six minecraft:sapling blocks with different block states. + CompoundTag plant = BlockStateValues.getFlowerPotBlocks().get(name); + if (plant != null) { + tagBuilder.tag(plant.toBuilder().build("PlantBlock")); + } + } + return tagBuilder.buildRootTag(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java new file mode 100644 index 00000000..168015f6 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java @@ -0,0 +1,58 @@ +/* + * 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.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.packet.BlockEventPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockStateValues; +import org.geysermc.connector.utils.ChunkUtils; + +/** + * Does not implement BlockEntityTranslator because it's only a block entity in Bedrock + */ +public class NoteblockBlockEntityTranslator implements RequiresBlockState { + + @Override + public boolean isBlock(BlockState blockState) { + return BlockStateValues.getNoteblockPitch(blockState) != -1; + } + + public static void translate(GeyserSession session, Position position) { + BlockState blockState = ChunkUtils.CACHED_BLOCK_ENTITIES.get(position); + BlockEventPacket blockEventPacket = new BlockEventPacket(); + blockEventPacket.setBlockPosition(Vector3i.from(position.getX(), position.getY(), position.getZ())); + blockEventPacket.setEventType(0); + blockEventPacket.setEventData(BlockStateValues.getNoteblockPitch(blockState)); + session.sendUpstreamPacket(blockEventPacket); + + ChunkUtils.CACHED_BLOCK_ENTITIES.remove(position); + } + +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/PistonBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/PistonBlockEntityTranslator.java new file mode 100644 index 00000000..2dffce24 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/PistonBlockEntityTranslator.java @@ -0,0 +1,72 @@ +/* + * 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.mc.protocol.data.game.world.block.BlockState; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.CompoundTag; +import org.geysermc.connector.network.translators.world.block.BlockStateValues; + +/** + * Pistons are a special case where they are only a block entity on Bedrock. + */ +public class PistonBlockEntityTranslator { + + /** + * Used in ChunkUtils to determine if the block is a piston. + * @param blockState Java BlockState of block. + * @return if block is a piston or not. + */ + public static boolean isBlock(BlockState blockState) { + return BlockStateValues.getPistonValues().containsKey(blockState.getId()); + } + + /** + * Calculates the Nukkit CompoundTag to send to the client on chunk + * @param blockState Java BlockState of block. + * @param position Bedrock position of piston. + * @return Bedrock tag of piston. + */ + public static CompoundTag getTag(BlockState blockState, Vector3i position) { + CompoundTagBuilder tagBuilder = CompoundTagBuilder.builder() + .intTag("x", position.getX()) + .intTag("y", position.getY()) + .intTag("z", position.getZ()) + .byteTag("isMovable", (byte) 1) + .stringTag("id", "PistonArm"); + if (BlockStateValues.getPistonValues().containsKey(blockState.getId())) { + boolean extended = BlockStateValues.getPistonValues().get(blockState.getId()); + // 1f if extended, otherwise 0f + tagBuilder.floatTag("Progress", (extended) ? 1.0f : 0.0f); + // 1 if sticky, 0 if not + tagBuilder.byteTag("Sticky", (byte)((BlockStateValues.isStickyPiston(blockState)) ? 1 : 0)); + } + return tagBuilder.buildRootTag(); + } + +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/RequiresBlockState.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/RequiresBlockState.java similarity index 95% rename from connector/src/main/java/org/geysermc/connector/network/translators/block/entity/RequiresBlockState.java rename to connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/RequiresBlockState.java index ed8e6ede..4df7292a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/RequiresBlockState.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/RequiresBlockState.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.block.entity; +package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; 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 new file mode 100644 index 00000000..373b963e --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java @@ -0,0 +1,65 @@ +/* + * 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.mc.protocol.data.game.world.block.BlockState; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.ByteTag; +import com.nukkitx.nbt.tag.Tag; +import org.geysermc.connector.network.translators.world.block.BlockStateValues; + +import java.util.ArrayList; +import java.util.List; + +@BlockEntity(name = "ShulkerBox", regex = "shulker_box") +public class ShulkerBoxBlockEntityTranslator extends BlockEntityTranslator { + + @Override + public List> translateTag(CompoundTag tag, BlockState blockState) { + List> tags = new ArrayList<>(); + + byte direction = BlockStateValues.getShulkerBoxDirection(blockState); + // Just in case... + if (direction == -1) direction = 1; + tags.add(new ByteTag("facing", direction)); + + return tags; + } + + @Override + public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { + return null; + } + + @Override + public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder(); + tagBuilder.byteTag("facing", (byte)1); + return tagBuilder.buildRootTag(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/SignBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java similarity index 77% rename from connector/src/main/java/org/geysermc/connector/network/translators/block/entity/SignBlockEntityTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java index 74dcdd13..6c170462 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/SignBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.block.entity; +package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; import com.github.steveice10.mc.protocol.data.message.Message; @@ -31,29 +31,35 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.nbt.CompoundTagBuilder; import com.nukkitx.nbt.tag.StringTag; import com.nukkitx.nbt.tag.Tag; +import io.netty.util.internal.StringUtil; import org.geysermc.connector.utils.MessageUtils; import java.util.ArrayList; import java.util.List; -@BlockEntity(name = "Sign", delay = true, regex = "sign") +@BlockEntity(name = "Sign", regex = "sign") public class SignBlockEntityTranslator extends BlockEntityTranslator { @Override public List> translateTag(CompoundTag tag, BlockState blockState) { List> tags = new ArrayList<>(); - String line1 = getOrDefault(tag.getValue().get("Text1"), ""); - String line2 = getOrDefault(tag.getValue().get("Text2"), ""); - String line3 = getOrDefault(tag.getValue().get("Text3"), ""); - String line4 = getOrDefault(tag.getValue().get("Text4"), ""); + StringBuilder signText = new StringBuilder(); + for(int i = 0; i < 4; i++) { + int currentLine = i+1; + String signLine = getOrDefault(tag.getValue().get("Text" + currentLine), ""); + signLine = MessageUtils.getBedrockMessage(Message.fromString(signLine)); - tags.add(new StringTag("Text", MessageUtils.getBedrockMessage(Message.fromString(line1)) - + "\n" + MessageUtils.getBedrockMessage(Message.fromString(line2)) - + "\n" + MessageUtils.getBedrockMessage(Message.fromString(line3)) - + "\n" + MessageUtils.getBedrockMessage(Message.fromString(line4)) - )); + //Java allows up to 16+ characters on certain symbols. + if(signLine.length() >= 15 && (signLine.contains("-") || signLine.contains("="))) { + signLine = signLine.substring(0, 14); + } + signText.append(signLine); + signText.append("\n"); + } + + tags.add(new StringTag("Text", MessageUtils.getBedrockMessage(Message.fromString(signText.toString())))); return tags; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/SkullBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java similarity index 91% rename from connector/src/main/java/org/geysermc/connector/network/translators/block/entity/SkullBlockEntityTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java index 2380663b..9393f7bb 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/SkullBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.block.entity; +package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; import com.nukkitx.nbt.CompoundTagBuilder; @@ -31,12 +31,12 @@ import com.nukkitx.nbt.tag.ByteTag; import com.nukkitx.nbt.tag.CompoundTag; import com.nukkitx.nbt.tag.FloatTag; import com.nukkitx.nbt.tag.Tag; -import org.geysermc.connector.network.translators.block.BlockStateValues; +import org.geysermc.connector.network.translators.world.block.BlockStateValues; import java.util.ArrayList; import java.util.List; -@BlockEntity(name = "Skull", delay = false, regex = "skull") +@BlockEntity(name = "Skull", regex = "skull") public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { @Override 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 new file mode 100644 index 00000000..100dbddd --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java @@ -0,0 +1,105 @@ +/* + * 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.mc.protocol.data.game.world.block.BlockState; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.*; +import org.geysermc.connector.entity.type.EntityType; + +import java.util.ArrayList; +import java.util.List; + +@BlockEntity(name = "MobSpawner", regex = "mob_spawner") +public class SpawnerBlockEntityTranslator extends BlockEntityTranslator { + + @Override + public List> translateTag(CompoundTag tag, BlockState blockState) { + List> tags = new ArrayList<>(); + + if (tag.get("MaxNearbyEntities") != null) { + tags.add(new ShortTag("MaxNearbyEntities", (short) tag.get("MaxNearbyEntities").getValue())); + } + + if (tag.get("RequiredPlayerRange") != null) { + tags.add(new ShortTag("RequiredPlayerRange", (short) tag.get("RequiredPlayerRange").getValue())); + } + + if (tag.get("SpawnCount") != null) { + tags.add(new ShortTag("SpawnCount", (short) tag.get("SpawnCount").getValue())); + } + + if (tag.get("MaxSpawnDelay") != null) { + tags.add(new ShortTag("MaxSpawnDelay", (short) tag.get("MaxSpawnDelay").getValue())); + } + + if (tag.get("Delay") != null) { + tags.add(new ShortTag("Delay", (short) tag.get("Delay").getValue())); + } + + if (tag.get("SpawnRange") != null) { + tags.add(new ShortTag("SpawnRange", (short) tag.get("SpawnRange").getValue())); + } + + if (tag.get("MinSpawnDelay") != null) { + tags.add(new ShortTag("MinSpawnDelay", (short) tag.get("MinSpawnDelay").getValue())); + } + + if (tag.get("SpawnData") != null) { + CompoundTag spawnData = tag.get("SpawnData"); + String entityID = (String) spawnData.get("id").getValue(); + tags.add(new StringTag("EntityIdentifier", entityID)); + + EntityType type = EntityType.getFromIdentifier(entityID); + if (type != null) { + tags.add(new FloatTag("DisplayEntityWidth", type.getWidth())); + tags.add(new FloatTag("DisplayEntityHeight", type.getHeight())); + tags.add(new FloatTag("DisplayEntityScale", 1.0f)); + } + } + + tags.add(new StringTag("id", "MobSpawner")); + tags.add(new ByteTag("isMovable", (byte) 1)); + + return tags; + } + + @Override + public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { + return null; + } + + @Override + public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder(); + tagBuilder.byteTag("isMovable", (byte) 1) + .stringTag("id", "MobSpawner"); + + return tagBuilder.buildRootTag(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/world/chunk/BlockStorage.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java similarity index 68% rename from connector/src/main/java/org/geysermc/connector/world/chunk/BlockStorage.java rename to connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java index 360fdea4..5995ecf9 100644 --- a/connector/src/main/java/org/geysermc/connector/world/chunk/BlockStorage.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java @@ -1,21 +1,37 @@ /* * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * - * This code in this file is derived from NukkitX and permission has - * been granted to us allowing the usage of it in Geyser. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser * - * Copyright (C) 2020 The NukkitX Project - * https://github.com/NukkitX/Nukkit */ -package org.geysermc.connector.world.chunk; +package org.geysermc.connector.network.translators.world.chunk; import com.nukkitx.network.VarInts; import io.netty.buffer.ByteBuf; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; -import org.geysermc.connector.world.chunk.bitarray.BitArray; -import org.geysermc.connector.world.chunk.bitarray.BitArrayVersion; +import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArray; +import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArrayVersion; import java.util.function.IntConsumer; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkPosition.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkPosition.java new file mode 100644 index 00000000..a51755d0 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkPosition.java @@ -0,0 +1,54 @@ +/* + * 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.chunk; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@EqualsAndHashCode +public class ChunkPosition { + + private int x; + private int z; + + public Position getBlock(int x, int y, int z) { + return new Position((this.x << 4) + x, y, (this.z << 4) + z); + } + + public Position getChunkBlock(int x, int y, int z) { + int chunkX = x & 15; + int chunkY = y & 15; + int chunkZ = z & 15; + return new Position(chunkX, chunkY, chunkZ); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/world/chunk/ChunkSection.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkSection.java similarity index 77% rename from connector/src/main/java/org/geysermc/connector/world/chunk/ChunkSection.java rename to connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkSection.java index c160d11b..4ede0c25 100644 --- a/connector/src/main/java/org/geysermc/connector/world/chunk/ChunkSection.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkSection.java @@ -1,14 +1,30 @@ /* * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * - * This code in this file is derived from NukkitX and permission has - * been granted to us allowing the usage of it in Geyser. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser * - * Copyright (C) 2020 The NukkitX Project - * https://github.com/NukkitX/Nukkit */ -package org.geysermc.connector.world.chunk; +package org.geysermc.connector.network.translators.world.chunk; import com.nukkitx.network.util.Preconditions; import io.netty.buffer.ByteBuf; diff --git a/connector/src/main/java/org/geysermc/connector/world/chunk/NibbleArray.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/NibbleArray.java similarity index 62% rename from connector/src/main/java/org/geysermc/connector/world/chunk/NibbleArray.java rename to connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/NibbleArray.java index 8da068f7..08303e18 100644 --- a/connector/src/main/java/org/geysermc/connector/world/chunk/NibbleArray.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/NibbleArray.java @@ -1,14 +1,30 @@ /* * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * - * This code in this file is derived from NukkitX and permission has - * been granted to us allowing the usage of it in Geyser. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser * - * Copyright (C) 2020 The NukkitX Project - * https://github.com/NukkitX/Nukkit */ -package org.geysermc.connector.world.chunk; +package org.geysermc.connector.network.translators.world.chunk; import com.nukkitx.network.util.Preconditions; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArray.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArray.java new file mode 100644 index 00000000..728fe237 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArray.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.network.translators.world.chunk.bitarray; + +public interface BitArray { + + void set(int index, int value); + + int get(int index); + + int size(); + + int[] getWords(); + + BitArrayVersion getVersion(); + + BitArray copy(); +} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/BitArrayVersion.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArrayVersion.java similarity index 60% rename from connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/BitArrayVersion.java rename to connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArrayVersion.java index f6e3ef5e..be91f13d 100644 --- a/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/BitArrayVersion.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArrayVersion.java @@ -1,14 +1,30 @@ /* * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * - * This code in this file is derived from NukkitX and permission has - * been granted to us allowing the usage of it in Geyser. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser * - * Copyright (C) 2020 The NukkitX Project - * https://github.com/NukkitX/Nukkit */ -package org.geysermc.connector.world.chunk.bitarray; +package org.geysermc.connector.network.translators.world.chunk.bitarray; import org.geysermc.connector.utils.MathUtils; diff --git a/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/PaddedBitArray.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/PaddedBitArray.java similarity index 65% rename from connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/PaddedBitArray.java rename to connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/PaddedBitArray.java index d2f9393a..36a97a30 100644 --- a/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/PaddedBitArray.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/PaddedBitArray.java @@ -1,14 +1,30 @@ /* * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * - * This code in this file is derived from NukkitX and permission has - * been granted to us allowing the usage of it in Geyser. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser * - * Copyright (C) 2020 The NukkitX Project - * https://github.com/NukkitX/Nukkit */ -package org.geysermc.connector.world.chunk.bitarray; +package org.geysermc.connector.network.translators.world.chunk.bitarray; import com.nukkitx.network.util.Preconditions; import org.geysermc.connector.utils.MathUtils; diff --git a/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/Pow2BitArray.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/Pow2BitArray.java similarity index 67% rename from connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/Pow2BitArray.java rename to connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/Pow2BitArray.java index bcb878a7..c9bd2764 100644 --- a/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/Pow2BitArray.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/Pow2BitArray.java @@ -1,14 +1,30 @@ /* * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * - * This code in this file is derived from NukkitX and permission has - * been granted to us allowing the usage of it in Geyser. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser * - * Copyright (C) 2020 The NukkitX Project - * https://github.com/NukkitX/Nukkit */ -package org.geysermc.connector.world.chunk.bitarray; +package org.geysermc.connector.network.translators.world.chunk.bitarray; import com.nukkitx.network.util.Preconditions; import org.geysermc.connector.utils.MathUtils; diff --git a/connector/src/main/java/org/geysermc/connector/ping/GeyserLegacyPingPassthrough.java b/connector/src/main/java/org/geysermc/connector/ping/GeyserLegacyPingPassthrough.java new file mode 100644 index 00000000..ea0d67c2 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/ping/GeyserLegacyPingPassthrough.java @@ -0,0 +1,91 @@ +/* + * 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.ping; + +import com.github.steveice10.mc.protocol.MinecraftConstants; +import com.github.steveice10.mc.protocol.MinecraftProtocol; +import com.github.steveice10.mc.protocol.data.SubProtocol; +import com.github.steveice10.mc.protocol.data.status.handler.ServerInfoHandler; +import com.github.steveice10.packetlib.Client; +import com.github.steveice10.packetlib.tcp.TcpSessionFactory; +import org.geysermc.common.ping.GeyserPingInfo; +import org.geysermc.connector.GeyserConfiguration; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.GeyserLogger; + +import java.util.concurrent.TimeUnit; + +public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runnable { + + private GeyserConnector connector; + + public GeyserLegacyPingPassthrough(GeyserConnector connector) { + this.connector = connector; + this.pingInfo = new GeyserPingInfo(null, 0, 0); + } + + private GeyserPingInfo pingInfo; + + private Client client; + + /** + * Start legacy ping passthrough thread + * @param connector GeyserConnector + * @return GeyserPingPassthrough, or null if not initialized + */ + public static IGeyserPingPassthrough init(GeyserConnector connector) { + if (connector.getConfig().isPassthroughMotd() || connector.getConfig().isPassthroughPlayerCounts()) { + GeyserLegacyPingPassthrough pingPassthrough = new GeyserLegacyPingPassthrough(connector); + // Ensure delay is not zero + int interval = (connector.getConfig().getPingPassthroughInterval() == 0) ? 1 : connector.getConfig().getPingPassthroughInterval(); + connector.getLogger().debug("Scheduling ping passthrough at an interval of " + interval + " second(s)."); + connector.getGeneralThreadPool().scheduleAtFixedRate(pingPassthrough, 1, interval, TimeUnit.SECONDS); + return pingPassthrough; + } + return null; + } + + @Override + public GeyserPingInfo getPingInformation() { + return pingInfo; + } + + @Override + public void run() { + try { + this.client = new Client(connector.getConfig().getRemote().getAddress(), connector.getConfig().getRemote().getPort(), new MinecraftProtocol(SubProtocol.STATUS), new TcpSessionFactory()); + this.client.getSession().setFlag(MinecraftConstants.SERVER_INFO_HANDLER_KEY, (ServerInfoHandler) (session, info) -> { + this.pingInfo = new GeyserPingInfo(info.getDescription().getFullText(), info.getPlayerInfo().getOnlinePlayers(), info.getPlayerInfo().getMaxPlayers()); + this.client.getSession().disconnect(null); + }); + + client.getSession().connect(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/ping/IGeyserPingPassthrough.java b/connector/src/main/java/org/geysermc/connector/ping/IGeyserPingPassthrough.java new file mode 100644 index 00000000..7bc842df --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/ping/IGeyserPingPassthrough.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.ping; + +import org.geysermc.common.ping.GeyserPingInfo; + +/** + * Interface that retrieves ping passthrough information from the Java server + */ +public interface IGeyserPingPassthrough { + + /** + * Get the MOTD of the server displayed on the multiplayer screen + * @return string of the MOTD + */ + GeyserPingInfo getPingInformation(); + +} 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 03ccb2fd..59d9b25f 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java @@ -187,7 +187,7 @@ public class Scoreboard { if (objective.getUpdateType() == REMOVE || update) { RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); - session.getUpstream().sendPacket(removeObjectivePacket); + session.sendUpstreamPacket(removeObjectivePacket); if (objective.getUpdateType() == REMOVE) { objectives.remove(objective.getObjectiveName()); // now we can deregister } @@ -199,7 +199,7 @@ public class Scoreboard { displayObjectivePacket.setCriteria("dummy"); displayObjectivePacket.setDisplaySlot(objective.getDisplaySlot()); displayObjectivePacket.setSortOrder(1); // ?? - session.getUpstream().sendPacket(displayObjectivePacket); + session.sendUpstreamPacket(displayObjectivePacket); } objective.setUpdateType(NOTHING); } @@ -208,21 +208,21 @@ public class Scoreboard { SetScorePacket setScorePacket = new SetScorePacket(); setScorePacket.setAction(SetScorePacket.Action.REMOVE); setScorePacket.setInfos(removeScores); - session.getUpstream().sendPacket(setScorePacket); + session.sendUpstreamPacket(setScorePacket); } if (!addScores.isEmpty()) { SetScorePacket setScorePacket = new SetScorePacket(); setScorePacket.setAction(SetScorePacket.Action.SET); setScorePacket.setInfos(addScores); - session.getUpstream().sendPacket(setScorePacket); + session.sendUpstreamPacket(setScorePacket); } } public void despawnObjective(Objective objective) { RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); - session.getUpstream().sendPacket(removeObjectivePacket); + session.sendUpstreamPacket(removeObjectivePacket); objectives.remove(objective.getDisplayName()); List toRemove = new ArrayList<>(); @@ -238,7 +238,7 @@ public class Scoreboard { SetScorePacket setScorePacket = new SetScorePacket(); setScorePacket.setAction(SetScorePacket.Action.REMOVE); setScorePacket.setInfos(toRemove); - session.getUpstream().sendPacket(setScorePacket); + session.sendUpstreamPacket(setScorePacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/thread/PingPassthroughThread.java b/connector/src/main/java/org/geysermc/connector/thread/PingPassthroughThread.java deleted file mode 100644 index fa87809b..00000000 --- a/connector/src/main/java/org/geysermc/connector/thread/PingPassthroughThread.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.thread; - -import com.github.steveice10.mc.protocol.MinecraftConstants; -import com.github.steveice10.mc.protocol.MinecraftProtocol; -import com.github.steveice10.mc.protocol.data.SubProtocol; -import com.github.steveice10.mc.protocol.data.status.ServerStatusInfo; -import com.github.steveice10.mc.protocol.data.status.handler.ServerInfoHandler; -import com.github.steveice10.packetlib.Client; -import com.github.steveice10.packetlib.tcp.TcpSessionFactory; -import lombok.Getter; -import org.geysermc.connector.GeyserConnector; - -public class PingPassthroughThread implements Runnable { - - private GeyserConnector connector; - - public PingPassthroughThread(GeyserConnector connector) { - this.connector = connector; - } - - @Getter - private ServerStatusInfo info; - - private Client client; - - @Override - public void run() { - try { - this.client = new Client(connector.getConfig().getRemote().getAddress(), connector.getConfig().getRemote().getPort(), new MinecraftProtocol(SubProtocol.STATUS), new TcpSessionFactory()); - this.client.getSession().setFlag(MinecraftConstants.SERVER_INFO_HANDLER_KEY, (ServerInfoHandler) (session, info) -> { - this.info = info; - this.client.getSession().disconnect(null); - }); - - client.getSession().connect(); - } catch (Exception ex) { - ex.printStackTrace(); - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/utils/BedrockMapIcon.java b/connector/src/main/java/org/geysermc/connector/utils/BedrockMapIcon.java new file mode 100644 index 00000000..f3ee956b --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/BedrockMapIcon.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.utils; + +import com.github.steveice10.mc.protocol.data.game.world.map.MapIconType; +import lombok.Getter; + +public enum BedrockMapIcon { + ICON_WHITE_ARROW(MapIconType.WHITE_ARROW, 0), + ICON_ITEM_FRAME(MapIconType.GREEN_ARROW, 7), + ICON_RED_ARROW(MapIconType.RED_ARROW, 2), + ICON_BLUE_ARROW(MapIconType.BLUE_ARROW, 3), + ICON_TREASURE_MARKER(MapIconType.TREASURE_MARKER, 4), + ICON_RED_POINTER(MapIconType.RED_POINTER, 5), + ICON_WHITE_CIRCLE(MapIconType.WHITE_CIRCLE, 6), + ICON_SMALL_WHITE_CIRCLE(MapIconType.SMALL_WHITE_CIRCLE, 13), + ICON_MANSION(MapIconType.MANSION, 14), + ICON_TEMPLE(MapIconType.TEMPLE, 15), + ICON_WHITE_BANNER(MapIconType.WHITE_BANNER, 13, 255, 255, 255), + ICON_ORANGE_BANNER(MapIconType.ORANGE_BANNER, 13, 249, 128, 29), + ICON_MAGENTA_BANNER(MapIconType.MAGENTA_BANNER, 13, 199, 78, 189), + ICON_LIGHT_BLUE_BANNER(MapIconType.LIGHT_BLUE_BANNER, 13, 58, 179, 218), + ICON_YELLOW_BANNER(MapIconType.YELLOW_BANNER, 13, 254, 216, 61), + ICON_LIME_BANNER(MapIconType.LIME_BANNER, 13, 128, 199, 31), + ICON_PINK_BANNER(MapIconType.PINK_BANNER, 13, 243, 139, 170), + ICON_GRAY_BANNER(MapIconType.GRAY_BANNER, 13, 71, 79, 82), + ICON_LIGHT_GRAY_BANNER(MapIconType.LIGHT_GRAY_BANNER, 13, 157, 157, 151), + ICON_CYAN_BANNER(MapIconType.CYAN_BANNER, 13, 22, 156, 156), + ICON_PURPLE_BANNER(MapIconType.PURPLE_BANNER, 13, 137, 50, 184), + ICON_BLUE_BANNER(MapIconType.BLUE_BANNER, 13, 60, 68, 170), + ICON_BROWN_BANNER(MapIconType.BROWN_BANNER, 13, 131, 84, 50), + ICON_GREEN_BANNER(MapIconType.GREEN_BANNER, 13, 94, 124, 22), + ICON_RED_BANNER(MapIconType.RED_BANNER, 13, 176, 46, 38), + ICON_BLACK_BANNER(MapIconType.BLACK_BANNER, 13, 29, 29, 33); + + private static final BedrockMapIcon[] VALUES = values(); + + private MapIconType iconType; + + @Getter + private int iconID; + + private int red; + private int green; + private int blue; + + BedrockMapIcon(MapIconType iconType, int iconID) { + this.iconType = iconType; + this.iconID = iconID; + + this.red = 255; + this.green = 255; + this.blue = 255; + } + + BedrockMapIcon(MapIconType iconType, int iconID, int red, int green, int blue) { + this.iconType = iconType; + this.iconID = iconID; + + this.red = red; + this.green = green; + this.blue = blue; + } + + /** + * Get the BedrockMapIcon for the Java MapIconType + * + * @param iconType A MapIconType + * @return The mapping for a BedrockMapIcon + */ + public static BedrockMapIcon fromType(MapIconType iconType) { + for (BedrockMapIcon icon : VALUES) { + if (icon.iconType.equals(iconType)) { + return icon; + } + } + + return null; + } + + /** + * Get the ARGB value of a BedrockMapIcon + * + * @return ARGB as an int + */ + public int toARGB() { + int alpha = 255; + + return ((alpha & 0xFF) << 24) | + ((red & 0xFF) << 16) | + ((green & 0xFF) << 8) | + ((blue & 0xFF) << 0); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java b/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java index 0dcd13ad..038084c3 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java @@ -3,20 +3,28 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; - import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.Translators; -import org.geysermc.connector.network.translators.block.entity.BlockEntityTranslator; +import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator; public class BlockEntityUtils { - private static final BlockEntityTranslator EMPTY_TRANSLATOR = Translators.getBlockEntityTranslators().get("Empty"); + private static final BlockEntityTranslator EMPTY_TRANSLATOR = BlockEntityTranslator.BLOCK_ENTITY_TRANSLATORS.get("Empty"); public static String getBedrockBlockEntityId(String id) { - // This is the only exception when it comes to block entity ids + // These are the only exceptions when it comes to block entity ids if (id.contains("piston_head")) return "PistonArm"; + if (id.contains("trapped_chest")) + return "Chest"; + + if (id.contains("EnderChest")) + return "EnderChest"; + + if (id.contains("enchanting_table")) { + return "EnchantTable"; + } + id = id.toLowerCase() .replace("minecraft:", "") .replace("_", " "); @@ -30,7 +38,7 @@ public class BlockEntityUtils { } public static BlockEntityTranslator getBlockEntityTranslator(String name) { - BlockEntityTranslator blockEntityTranslator = Translators.getBlockEntityTranslators().get(name); + BlockEntityTranslator blockEntityTranslator = BlockEntityTranslator.BLOCK_ENTITY_TRANSLATORS.get(name); if (blockEntityTranslator == null) { return EMPTY_TRANSLATOR; } @@ -39,9 +47,13 @@ public class BlockEntityUtils { } public static void updateBlockEntity(GeyserSession session, com.nukkitx.nbt.tag.CompoundTag blockEntity, Position position) { + updateBlockEntity(session, blockEntity, Vector3i.from(position.getX(), position.getY(), position.getZ())); + } + + public static void updateBlockEntity(GeyserSession session, com.nukkitx.nbt.tag.CompoundTag blockEntity, Vector3i position) { BlockEntityDataPacket blockEntityPacket = new BlockEntityDataPacket(); - blockEntityPacket.setBlockPosition(Vector3i.from(position.getX(), position.getY(), position.getZ())); + blockEntityPacket.setBlockPosition(position); blockEntityPacket.setData(blockEntity); - session.getUpstream().sendPacket(blockEntityPacket); + session.sendUpstreamPacket(blockEntityPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java index 34287073..6b9ab8ca 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java @@ -26,12 +26,15 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.game.entity.Effect; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import org.geysermc.connector.entity.PlayerEntity; -import org.geysermc.connector.network.translators.block.BlockTranslator; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ToolItemEntry; +import java.util.Optional; + public class BlockUtils { private static boolean correctTool(String blockToolType, String itemToolType) { @@ -63,8 +66,8 @@ public class BlockUtils { //http://minecraft.gamepedia.com/Breaking private static double calculateBreakTime(double blockHardness, String toolTier, boolean canHarvestWithHand, boolean correctTool, - String toolType, boolean isWoolBlock, boolean isCobweb, int toolEfficiencyLevel, int hasteLevel, int miningFatigueLevel - /*boolean insideOfWaterWithoutAquaAffinity, boolean outOfWaterButNotOnGround*/) { + String toolType, boolean isWoolBlock, boolean isCobweb, int toolEfficiencyLevel, int hasteLevel, int miningFatigueLevel, + boolean insideOfWaterWithoutAquaAffinity, boolean outOfWaterButNotOnGround, boolean insideWaterAndNotOnGround) { double baseTime = ((correctTool || canHarvestWithHand) ? 1.5 : 5.0) * blockHardness; double speed = 1.0 / baseTime; @@ -93,13 +96,13 @@ public class BlockUtils { break; } - //if (insideOfWaterWithoutAquaAffinity) speed *= 0.2; - //if (outOfWaterButNotOnGround) speed *= 0.2; - // else if insideWaterAndNotOnGround speed *= 0.2; + if (insideOfWaterWithoutAquaAffinity) speed *= 0.2; + if (outOfWaterButNotOnGround) speed *= 0.2; + if (insideWaterAndNotOnGround) speed *= 0.2; return 1.0 / speed; } - public static double getBreakTime(double blockHardness, int blockId, ItemEntry item, CompoundTag nbtData, PlayerEntity player) { + public static double getBreakTime(double blockHardness, int blockId, ItemEntry item, CompoundTag nbtData, GeyserSession session) { boolean isWoolBlock = BlockTranslator.JAVA_RUNTIME_WOOL_IDS.contains(blockId); boolean isCobweb = blockId == BlockTranslator.JAVA_RUNTIME_COBWEB_ID; String blockToolType = BlockTranslator.JAVA_RUNTIME_ID_TO_TOOL_TYPE.getOrDefault(blockId, ""); @@ -114,15 +117,25 @@ public class BlockUtils { correctTool = correctTool(blockToolType, toolType); } int toolEfficiencyLevel = ItemUtils.getEnchantmentLevel(nbtData, "minecraft:efficiency"); - int hasteLevel = player.getEffectCache().getEffectLevel(Effect.FASTER_DIG); - int miningFatigueLevel = player.getEffectCache().getEffectLevel(Effect.SLOWER_DIG); + int hasteLevel = 0; + int miningFatigueLevel = 0; - // TODO implement these checks and material check if possible - //boolean insideOfWaterWithoutAquaAffinity = player.isInsideOfWater() && - // Optional.ofNullable(player.getInventory().getHelmet().getEnchantment(Enchantment.ID_WATER_WORKER)) - // .map(Enchantment::getLevel).map(l -> l >= 1).orElse(false); - //boolean outOfWaterButNotOnGround = (!player.isInsideOfWater()) && (!player.isOnGround()); - return calculateBreakTime(blockHardness, toolTier, canHarvestWithHand, correctTool, toolType, isWoolBlock, isCobweb, toolEfficiencyLevel, hasteLevel, miningFatigueLevel); + if (session == null) { + return calculateBreakTime(blockHardness, toolTier, canHarvestWithHand, correctTool, toolType, isWoolBlock, isCobweb, toolEfficiencyLevel, hasteLevel, miningFatigueLevel, false, false, false); + } + + hasteLevel = session.getPlayerEntity().getEffectCache().getEffectLevel(Effect.FASTER_DIG); + miningFatigueLevel = session.getPlayerEntity().getEffectCache().getEffectLevel(Effect.SLOWER_DIG); + + boolean isInWater = session.getConnector().getConfig().isCacheChunks() + && BlockTranslator.getBedrockBlockId(session.getConnector().getWorldManager().getBlockAt(session, session.getPlayerEntity().getPosition().toInt())) == BlockTranslator.BEDROCK_WATER_ID; + + boolean insideOfWaterWithoutAquaAffinity = isInWater && + ItemUtils.getEnchantmentLevel(Optional.ofNullable(session.getInventory().getItem(5)).map(ItemStack::getNbt).orElse(null), "minecraft:aqua_affinity") < 1; + + boolean outOfWaterButNotOnGround = (!isInWater) && (!session.getPlayerEntity().isOnGround()); + boolean insideWaterNotOnGround = isInWater && !session.getPlayerEntity().isOnGround(); + return calculateBreakTime(blockHardness, toolTier, canHarvestWithHand, correctTool, toolType, isWoolBlock, isCobweb, toolEfficiencyLevel, hasteLevel, miningFatigueLevel, insideOfWaterWithoutAquaAffinity, outOfWaterButNotOnGround, insideWaterNotOnGround); } } 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 d496215a..010a87af 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -32,26 +32,31 @@ import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector2i; import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; -import com.nukkitx.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket; -import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; - +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.NbtUtils; +import com.nukkitx.nbt.stream.NBTOutputStream; +import com.nukkitx.protocol.bedrock.packet.*; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; - +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import lombok.Getter; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.ItemFrameEntity; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.block.entity.*; -import org.geysermc.connector.network.translators.Translators; -import org.geysermc.connector.network.translators.block.BlockTranslator; -import org.geysermc.connector.world.chunk.ChunkPosition; -import org.geysermc.connector.world.chunk.ChunkSection; +import org.geysermc.connector.network.translators.world.block.BlockStateValues; +import org.geysermc.connector.network.translators.world.block.entity.*; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.connector.network.translators.world.chunk.ChunkPosition; +import org.geysermc.connector.network.translators.world.chunk.ChunkSection; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.util.HashMap; import java.util.Map; -import static org.geysermc.connector.network.translators.block.BlockTranslator.BEDROCK_WATER_ID; +import static org.geysermc.connector.network.translators.world.block.BlockTranslator.AIR; +import static org.geysermc.connector.network.translators.world.block.BlockTranslator.BEDROCK_WATER_ID; public class ChunkUtils { @@ -60,6 +65,23 @@ public class ChunkUtils { */ public static final Map CACHED_BLOCK_ENTITIES = new HashMap<>(); + private static final com.nukkitx.nbt.tag.CompoundTag EMPTY_TAG = CompoundTagBuilder.builder().buildRootTag(); + public static final byte[] EMPTY_LEVEL_CHUNK_DATA; + + static { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + outputStream.write(new byte[258]); // Biomes + Border Size + Extra Data Size + + try (NBTOutputStream stream = NbtUtils.createNetworkWriter(outputStream)) { + stream.write(EMPTY_TAG); + } + + EMPTY_LEVEL_CHUNK_DATA = outputStream.toByteArray(); + }catch (IOException e) { + throw new AssertionError("Unable to generate empty level chunk data"); + } + } + public static ChunkData translateToBedrock(Column column) { ChunkData chunkData = new ChunkData(); Chunk[] chunks = column.getChunks(); @@ -69,6 +91,9 @@ public class ChunkUtils { // Temporarily stores positions of BlockState values per chunk load Map blockEntityPositions = new HashMap<>(); + // Temporarily stores compound tags of Bedrock-only block entities + ObjectArrayList bedrockOnlyBlockEntities = new ObjectArrayList<>(); + for (int chunkY = 0; chunkY < chunks.length; chunkY++) { chunkData.sections[chunkY] = new ChunkSection(); Chunk chunk = chunks[chunkY]; @@ -85,19 +110,17 @@ public class ChunkUtils { // Check to see if the name is in BlockTranslator.getBlockEntityString, and therefore must be handled differently if (BlockTranslator.getBlockEntityString(blockState) != null) { - // Get the block entity translator - BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(BlockTranslator.getBlockEntityString(blockState)); Position pos = new ChunkPosition(column.getX(), column.getZ()).getBlock(x, (chunkY << 4) + y, z); blockEntityPositions.put(pos, blockState); - // If there is a delay required for the block, allow it. - if (blockEntityTranslator.getClass().getAnnotation(BlockEntity.class).delay()) { - chunkData.loadBlockEntitiesLater.put(blockEntityTranslator.getDefaultBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(BlockTranslator.getBlockEntityString(blockState)), - pos.getX(), pos.getY(), pos.getZ()), blockState.getId()); - } else { - section.getBlockStorageArray()[0].setFullBlock(ChunkSection.blockPosition(x, y, z), id); - } - } else { - section.getBlockStorageArray()[0].setFullBlock(ChunkSection.blockPosition(x, y, z), id); + } + + section.getBlockStorageArray()[0].setFullBlock(ChunkSection.blockPosition(x, y, z), id); + + // Check if block is piston or flower - only block entities in Bedrock + if (BlockStateValues.getFlowerPotValues().containsKey(blockState.getId()) || + BlockStateValues.getPistonValues().containsKey(blockState.getId())) { + Position pos = new ChunkPosition(column.getX(), column.getZ()).getBlock(x, (chunkY << 4) + y, z); + bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag(Vector3i.from(pos.getX(), pos.getY(), pos.getZ()), blockState)); } if (BlockTranslator.isWaterlogged(blockState)) { @@ -109,8 +132,9 @@ public class ChunkUtils { } - com.nukkitx.nbt.tag.CompoundTag[] bedrockBlockEntities = new com.nukkitx.nbt.tag.CompoundTag[blockEntities.length]; - for (int i = 0; i < blockEntities.length; i++) { + com.nukkitx.nbt.tag.CompoundTag[] bedrockBlockEntities = new com.nukkitx.nbt.tag.CompoundTag[blockEntities.length + bedrockOnlyBlockEntities.size()]; + int i = 0; + while (i < blockEntities.length) { CompoundTag tag = blockEntities[i]; String tagName; if (!tag.contains("id")) { @@ -122,8 +146,14 @@ public class ChunkUtils { String id = BlockEntityUtils.getBedrockBlockEntityId(tagName); BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(id); - BlockState blockState = blockEntityPositions.get(new Position((int) tag.get("x").getValue(), (int) tag.get("y").getValue(), (int) tag.get("z").getValue())); + Position pos = new Position((int) tag.get("x").getValue(), (int) tag.get("y").getValue(), (int) tag.get("z").getValue()); + BlockState blockState = blockEntityPositions.get(pos); bedrockBlockEntities[i] = blockEntityTranslator.getBlockEntityTag(tagName, tag, blockState); + i++; + } + for (com.nukkitx.nbt.tag.CompoundTag tag : bedrockOnlyBlockEntities) { + bedrockBlockEntities[i] = tag; + i++; } chunkData.blockEntities = bedrockBlockEntities; @@ -138,7 +168,7 @@ public class ChunkUtils { NetworkChunkPublisherUpdatePacket chunkPublisherUpdatePacket = new NetworkChunkPublisherUpdatePacket(); chunkPublisherUpdatePacket.setPosition(position); chunkPublisherUpdatePacket.setRadius(session.getRenderDistance() << 4); - session.getUpstream().sendPacket(chunkPublisherUpdatePacket); + session.sendUpstreamPacket(chunkPublisherUpdatePacket); session.setLastChunkPosition(newChunkPos); } @@ -150,6 +180,19 @@ public class ChunkUtils { } public static void updateBlock(GeyserSession session, BlockState blockState, Vector3i position) { + // Checks for item frames so they aren't tripped up and removed + if (ItemFrameEntity.positionContainsItemFrame(session, position) && blockState.equals(AIR)) { + ((ItemFrameEntity) session.getEntityCache().getEntityByJavaId(ItemFrameEntity.getItemFrameEntityId(session, position))).updateBlock(session); + return; + } else if (ItemFrameEntity.positionContainsItemFrame(session, position)) { + Entity entity = session.getEntityCache().getEntityByJavaId(ItemFrameEntity.getItemFrameEntityId(session, position)); + if (entity != null) { + session.getEntityCache().removeEntity(entity, false); + } else { + ItemFrameEntity.removePosition(session, position); + } + } + int blockId = BlockTranslator.getBedrockBlockId(blockState); UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); @@ -157,7 +200,7 @@ public class ChunkUtils { updateBlockPacket.setBlockPosition(position); updateBlockPacket.setRuntimeId(blockId); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); - session.getUpstream().sendPacket(updateBlockPacket); + session.sendUpstreamPacket(updateBlockPacket); UpdateBlockPacket waterPacket = new UpdateBlockPacket(); waterPacket.setDataLayer(1); @@ -167,20 +210,23 @@ public class ChunkUtils { } else { waterPacket.setRuntimeId(0); } - session.getUpstream().sendPacket(waterPacket); + session.sendUpstreamPacket(waterPacket); // Since Java stores bed colors/skull information as part of the namespaced ID and Bedrock stores it as a tag // This is the only place I could find that interacts with the Java block state and block updates // Iterates through all block entity translators and determines if the block state needs to be saved - for (Map.Entry entry : Translators.getBlockEntityTranslators().entrySet()) { - if (entry.getValue() instanceof RequiresBlockState) { - RequiresBlockState requiresBlockState = (RequiresBlockState) entry.getValue(); - if (requiresBlockState.isBlock(blockState)) { - CACHED_BLOCK_ENTITIES.put(new Position(position.getX(), position.getY(), position.getZ()), blockState); - break; //No block will be a part of two classes + for (RequiresBlockState requiresBlockState : BlockEntityTranslator.REQUIRES_BLOCK_STATE_LIST) { + if (requiresBlockState.isBlock(blockState)) { + // Flower pots are block entities only in Bedrock and are not updated anywhere else like note blocks + if (requiresBlockState instanceof BedrockOnlyBlockEntity) { + ((BedrockOnlyBlockEntity) requiresBlockState).updateBlock(session, blockState, position); + break; } + CACHED_BLOCK_ENTITIES.put(new Position(position.getX(), position.getY(), position.getZ()), blockState); + break; //No block will be a part of two classes } } + session.getChunkCache().updateBlock(new Position(position.getX(), position.getY(), position.getZ()), blockState); } public static void sendEmptyChunks(GeyserSession session, Vector3i position, int radius, boolean forceUpdate) { @@ -192,9 +238,9 @@ public class ChunkUtils { data.setChunkX(chunkX + x); data.setChunkZ(chunkZ + z); data.setSubChunksLength(0); - data.setData(Translators.EMPTY_LEVEL_CHUNK_DATA); + data.setData(EMPTY_LEVEL_CHUNK_DATA); data.setCachingEnabled(false); - session.getUpstream().sendPacket(data); + session.sendUpstreamPacket(data); if (forceUpdate) { Vector3i pos = Vector3i.from(chunkX + x << 4, 80, chunkZ + z << 4); @@ -202,7 +248,7 @@ public class ChunkUtils { blockPacket.setBlockPosition(pos); blockPacket.setDataLayer(0); blockPacket.setRuntimeId(1); - session.getUpstream().sendPacket(blockPacket); + session.sendUpstreamPacket(blockPacket); } } } 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 9d874f13..6c8d9f94 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java @@ -25,12 +25,17 @@ package org.geysermc.connector.utils; +import com.github.steveice10.mc.protocol.data.game.entity.Effect; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.packet.*; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.network.session.GeyserSession; public class DimensionUtils { + + // Changes if the above-bedrock Nether building workaround is applied + private static int BEDROCK_NETHER_ID = 1; + public static void switchDimension(GeyserSession session, int javaDimension) { int bedrockDimension = javaToBedrock(javaDimension); Entity player = session.getPlayerEntity(); @@ -38,6 +43,7 @@ public class DimensionUtils { return; session.getEntityCache().removeAllEntities(); + session.getItemFrameCache().clear(); if (session.getPendingDimSwitches().getAndIncrement() > 0) { ChunkUtils.sendEmptyChunks(session, player.getPosition().toInt(), 3, true); } @@ -48,17 +54,27 @@ public class DimensionUtils { changeDimensionPacket.setDimension(bedrockDimension); changeDimensionPacket.setRespawn(true); changeDimensionPacket.setPosition(pos.toFloat()); - session.getUpstream().sendPacket(changeDimensionPacket); + session.sendUpstreamPacket(changeDimensionPacket); player.setDimension(bedrockDimension); player.setPosition(pos.toFloat()); session.setSpawned(false); session.setLastChunkPosition(null); + for (Effect effect : session.getPlayerEntity().getEffectCache().getEntityEffects().keySet()) { + MobEffectPacket mobEffectPacket = new MobEffectPacket(); + mobEffectPacket.setEvent(MobEffectPacket.Event.REMOVE); + mobEffectPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId()); + mobEffectPacket.setEffectId(EntityUtils.toBedrockEffectId(effect)); + session.sendUpstreamPacket(mobEffectPacket); + } + // Effects are re-sent from server + session.getPlayerEntity().getEffectCache().getEntityEffects().clear(); + //let java server handle portal travel sound StopSoundPacket stopSoundPacket = new StopSoundPacket(); stopSoundPacket.setStoppingAllSound(true); stopSoundPacket.setSoundName(""); - session.getUpstream().sendPacket(stopSoundPacket); + session.sendUpstreamPacket(stopSoundPacket); } /** @@ -70,11 +86,16 @@ public class DimensionUtils { public static int javaToBedrock(int javaDimension) { switch (javaDimension) { case -1: - return 1; + return BEDROCK_NETHER_ID; case 1: return 2; default: return javaDimension; } } + + public static void changeBedrockNetherId() { + // Change dimension ID to the End to allow for building above Bedrock + BEDROCK_NETHER_ID = 2; + } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/DockerCheck.java b/connector/src/main/java/org/geysermc/connector/utils/DockerCheck.java new file mode 100644 index 00000000..0a5a2278 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/DockerCheck.java @@ -0,0 +1,58 @@ +/* + * 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.bootstrap.GeyserBootstrap; + +import java.net.InetAddress; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class DockerCheck { + public static void check(GeyserBootstrap bootstrap) { + try { + String OS = System.getProperty("os.name").toLowerCase(); + String ipAddress = InetAddress.getLocalHost().getHostAddress(); + + // Check if the user is already using the recommended IP + if (ipAddress.equals(bootstrap.getGeyserConfig().getRemote().getAddress())) { + return; + } + + if (OS.indexOf("nix") >= 0 || OS.indexOf("nux") >= 0 || OS.indexOf("aix") > 0) { + bootstrap.getGeyserLogger().debug("We are on a Unix system, checking for Docker..."); + + String output = new String(Files.readAllBytes(Paths.get("/proc/1/cgroup"))); + + if (output.contains("docker")) { + bootstrap.getGeyserLogger().warning("You are most likely in a Docker container, this may cause connection issues from Geyser to the Java server"); + bootstrap.getGeyserLogger().warning("We recommended using the following IP as the remote address: " + ipAddress); + } + } + } catch (Exception e) { } // Ignore any errors, inc ip failed to fetch, process could not run or access denied + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/EntityUtils.java b/connector/src/main/java/org/geysermc/connector/utils/EntityUtils.java index 79ce9e8b..b5033c94 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/EntityUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/EntityUtils.java @@ -26,8 +26,6 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.game.entity.Effect; -import com.github.steveice10.mc.protocol.data.game.entity.type.MobType; -import com.github.steveice10.mc.protocol.data.game.entity.type.object.ObjectType; import org.geysermc.connector.entity.type.EntityType; public class EntityUtils { @@ -64,21 +62,7 @@ public class EntityUtils { * @param type The MobType to convert * @return Converted EntityType */ - public static EntityType toBedrockEntity(MobType type) { - try { - return EntityType.valueOf(type.name()); - } catch (IllegalArgumentException ex) { - return null; - } - } - - /** - * Converts a ObjectType to a Bedrock edition EntityType, returns null if the EntityType is not found - * - * @param type The ObjectType to convert - * @return Converted EntityType - */ - public static EntityType toBedrockEntity(ObjectType type) { + public static EntityType toBedrockEntity(com.github.steveice10.mc.protocol.data.game.entity.type.EntityType type) { try { return EntityType.valueOf(type.name()); } catch (IllegalArgumentException ex) { 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 5144628d..aedfb840 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java @@ -138,4 +138,18 @@ public class FileUtils { public static void writeFile(String name, char[] data) throws IOException { writeFile(new File(name), data); } + + /** + * Get an InputStream for the given resource path, throws AssertionError if resource is not found + * + * @param resource Resource to get + * @return InputStream of the given resource + */ + public static InputStream getResource(String resource) { + InputStream stream = FileUtils.class.getClassLoader().getResourceAsStream(resource); + if (stream == null) { + throw new AssertionError("Unable to find resource: " + resource); + } + return stream; + } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/FireworkColor.java b/connector/src/main/java/org/geysermc/connector/utils/FireworkColor.java new file mode 100644 index 00000000..d729e3a1 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/FireworkColor.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.utils; + +import lombok.Getter; + +public enum FireworkColor { + // Vanilla colors + BLACK((byte) 0, 1973019), + RED((byte) 1, 11743532), + GREEN((byte) 2, 3887386), + BROWN((byte) 3, 5320730), + BLUE((byte) 4, 2437522), + PURPLE((byte) 5, 8073150), + CYAN((byte) 6, 2651799), + LIGHT_GRAY((byte) 7, 11250603), + GRAY((byte) 8, 4408131), + PINK((byte) 9, 14188952), + LIME((byte) 10, 4312372), + YELLOW((byte) 11, 14602026), + LIGHT_BLUE((byte) 12, 6719955), + MAGENTA((byte) 13, 12801229), + ORANGE((byte) 14, 15435844), + WHITE((byte) 15, 15790320), + + // Bukkit colors + // https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Color.html + BUKKIT_WHITE((byte) 15, 0xFFFFFF), + BUKKIT_SILVER((byte) 7, 0xC0C0C0), + BUKKIT_GRAY((byte) 8, 0x808080), + BUKKIT_BLACK((byte) 0, 0x000000), + BUKKIT_RED((byte) 1, 0xFF0000), + BUKKIT_MAROON((byte) 1, 0x800000), // No perfect map but this is as close as it can be + BUKKIT_YELLOW((byte) 11, 0xFFFF00), + BUKKIT_OLIVE((byte) 2, 0x808000), // No perfect map but this is as close as it can be + BUKKIT_LIME((byte) 10, 0x00FF00), + BUKKIT_GREEN((byte) 2, 0x008000), + BUKKIT_AQUA((byte) 12, 0x00FFFF), + BUKKIT_TEAL((byte) 6, 0x008080), + BUKKIT_BLUE((byte) 4, 0x0000FF), + BUKKIT_NAVY((byte) 4, 0x000080), // No perfect map but this is as close as it can be + BUKKIT_FUCHSIA((byte) 9, 0xFF00FF), // No perfect map but this is as close as it can be + BUKKIT_PURPLE((byte) 5, 0x800080), + BUKKIT_ORANGE((byte) 14, 0xFFA500); + + private static final FireworkColor[] VALUES = values(); + + @Getter + private byte bedrockID; + @Getter + private int javaID; + + FireworkColor(byte bedrockID, int javaID) { + this.bedrockID = bedrockID; + this.javaID = javaID; + } + + public static FireworkColor fromJavaID(int id) { + for (FireworkColor color : VALUES) { + if (color.javaID == id) { + return color; + } + } + + return WHITE; + } + + public static FireworkColor fromBedrockID(int id) { + for (FireworkColor color : VALUES) { + if (color.bedrockID == id) { + return color; + } + } + + return WHITE; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java index 3717b432..5574a3a9 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java @@ -27,16 +27,22 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.StringTag; import com.nukkitx.protocol.bedrock.data.ContainerId; import com.nukkitx.protocol.bedrock.data.ItemData; +import com.nukkitx.protocol.bedrock.packet.ContainerClosePacket; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; +import org.geysermc.common.ChatColor; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.Translators; import org.geysermc.connector.network.translators.inventory.DoubleChestInventoryTranslator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.item.ItemTranslator; +import java.util.Collections; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -44,7 +50,7 @@ public class InventoryUtils { public static final ItemStack REFRESH_ITEM = new ItemStack(1, 127, new CompoundTag("")); //TODO: stop using this public static void openInventory(GeyserSession session, Inventory inventory) { - InventoryTranslator translator = Translators.getInventoryTranslators().get(inventory.getWindowType()); + InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()); if (translator != null) { session.getInventoryCache().setOpenInventory(inventory); translator.prepareInventory(session, inventory); @@ -64,27 +70,42 @@ public class InventoryUtils { public static void closeInventory(GeyserSession session, int windowId) { if (windowId != 0) { Inventory inventory = session.getInventoryCache().getInventories().get(windowId); - if (inventory != null) { - InventoryTranslator translator = Translators.getInventoryTranslators().get(inventory.getWindowType()); + Inventory openInventory = session.getInventoryCache().getOpenInventory(); + session.getInventoryCache().uncacheInventory(windowId); + if (inventory != null && openInventory != null && inventory.getId() == openInventory.getId()) { + InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()); translator.closeInventory(session, inventory); - session.getInventoryCache().uncacheInventory(windowId); session.getInventoryCache().setOpenInventory(null); + } else { + return; } } else { Inventory inventory = session.getInventory(); - InventoryTranslator translator = Translators.getInventoryTranslators().get(inventory.getWindowType()); + InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()); translator.updateInventory(session, inventory); } + session.setCraftSlot(0); session.getInventory().setCursor(null); + updateCursor(session); + } + + public static void closeWindow(GeyserSession session, int windowId) { + //Spamming close window packets can bug the client + if (System.currentTimeMillis() - session.getLastWindowCloseTime() > 500) { + ContainerClosePacket closePacket = new ContainerClosePacket(); + closePacket.setWindowId((byte) windowId); + session.sendUpstreamPacket(closePacket); + session.setLastWindowCloseTime(System.currentTimeMillis()); + } } public static void updateCursor(GeyserSession session) { InventorySlotPacket cursorPacket = new InventorySlotPacket(); cursorPacket.setContainerId(ContainerId.CURSOR); cursorPacket.setSlot(0); - cursorPacket.setItem(Translators.getItemTranslator().translateToBedrock(session, session.getInventory().getCursor())); - session.getUpstream().sendPacket(cursorPacket); + cursorPacket.setItem(ItemTranslator.translateToBedrock(session, session.getInventory().getCursor())); + session.sendUpstreamPacket(cursorPacket); } public static boolean canStack(ItemStack item1, ItemStack item2) { @@ -98,4 +119,19 @@ public class InventoryUtils { return false; return item1.equals(item2, false, true, true); } + + /** + * Returns a barrier block with custom name and lore to explain why + * part of the inventory is unusable. + */ + public static ItemData createUnusableSpaceBlock(String description) { + CompoundTagBuilder root = CompoundTagBuilder.builder(); + CompoundTagBuilder display = CompoundTagBuilder.builder(); + + display.stringTag("Name", ChatColor.RESET + "Unusable inventory space"); + display.listTag("Lore", StringTag.class, Collections.singletonList(new StringTag("", ChatColor.RESET + ChatColor.DARK_PURPLE + description))); + + root.tag(display.build("display")); + return ItemData.of(ItemRegistry.ITEM_ENTRIES.get(ItemRegistry.BARRIER_INDEX).getBedrockId(), (short) 0, 1, root.buildRootTag()); + } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/ItemUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ItemUtils.java index bb3cf0ed..b9600222 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ItemUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ItemUtils.java @@ -26,6 +26,12 @@ package org.geysermc.connector.utils; import com.github.steveice10.opennbt.tag.builtin.*; +import com.nukkitx.nbt.CompoundTagBuilder; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class ItemUtils { 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 726ad1c1..f55cb261 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -63,7 +63,7 @@ public class LocaleUtils { private static void generateAssetCache() { try { // Get the version manifest from Mojang - VersionManifest versionManifest = Toolbox.JSON_MAPPER.readValue(WebUtils.getBody("https://launchermeta.mojang.com/mc/game/version_manifest.json"), VersionManifest.class); + VersionManifest versionManifest = GeyserConnector.JSON_MAPPER.readValue(WebUtils.getBody("https://launchermeta.mojang.com/mc/game/version_manifest.json"), VersionManifest.class); // Get the url for the latest version of the games manifest String latestInfoURL = ""; @@ -80,7 +80,7 @@ public class LocaleUtils { } // Get the individual version manifest - VersionInfo versionInfo = Toolbox.JSON_MAPPER.readValue(WebUtils.getBody(latestInfoURL), VersionInfo.class); + VersionInfo versionInfo = GeyserConnector.JSON_MAPPER.readValue(WebUtils.getBody(latestInfoURL), VersionInfo.class); // Get the smallest jar for use when downloading the en_us locale, will be either the server or client int currentSize = Integer.MAX_VALUE; @@ -92,13 +92,13 @@ public class LocaleUtils { } // Get the assets list - JsonNode assets = Toolbox.JSON_MAPPER.readTree(WebUtils.getBody(versionInfo.getAssetIndex().getUrl())).get("objects"); + JsonNode assets = GeyserConnector.JSON_MAPPER.readTree(WebUtils.getBody(versionInfo.getAssetIndex().getUrl())).get("objects"); // Put each asset into an array for use later Iterator> assetIterator = assets.fields(); while (assetIterator.hasNext()) { Map.Entry entry = assetIterator.next(); - Asset asset = Toolbox.JSON_MAPPER.treeToValue(entry.getValue(), Asset.class); + Asset asset = GeyserConnector.JSON_MAPPER.treeToValue(entry.getValue(), Asset.class); ASSET_MAP.put(entry.getKey(), asset); } } catch (Exception e) { @@ -173,7 +173,7 @@ public class LocaleUtils { // Parse the file as json JsonNode localeObj; try { - localeObj = Toolbox.JSON_MAPPER.readTree(localeStream); + localeObj = GeyserConnector.JSON_MAPPER.readTree(localeStream); } catch (Exception e) { throw new AssertionError("Unable to load Java edition lang map for " + locale, e); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java index 300294a2..f9a7fec2 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -34,9 +34,6 @@ import com.nukkitx.network.util.Preconditions; import com.nukkitx.protocol.bedrock.packet.LoginPacket; import com.nukkitx.protocol.bedrock.packet.ServerToClientHandshakePacket; import com.nukkitx.protocol.bedrock.util.EncryptionUtils; - -import net.minidev.json.JSONObject; - import org.geysermc.common.window.CustomFormBuilder; import org.geysermc.common.window.CustomFormWindow; import org.geysermc.common.window.FormWindow; @@ -152,7 +149,7 @@ public class LoginEncryptionUtils { ServerToClientHandshakePacket packet = new ServerToClientHandshakePacket(); packet.setJwt(EncryptionUtils.createHandshakeJwt(serverKeyPair, token).serialize()); - session.getUpstream().sendPacketImmediately(packet); + session.sendUpstreamPacketImmediately(packet); } private static int AUTH_FORM_ID = 1336; @@ -195,18 +192,22 @@ public class LoginEncryptionUtils { String password = response.getInputResponses().get(2); session.authenticate(email, password); + } else { + showLoginDetailsWindow(session); } // Clear windows so authentication data isn't accidentally cached windowCache.getWindows().clear(); } else if (formId == AUTH_FORM_ID && window instanceof SimpleFormWindow) { SimpleFormResponse response = (SimpleFormResponse) window.getResponse(); - if(response != null) { - if(response.getClickedButtonId() == 0) { + if (response != null) { + if (response.getClickedButtonId() == 0) { showLoginDetailsWindow(session); - } else if(response.getClickedButtonId() == 1) { + } else if (response.getClickedButtonId() == 1) { session.disconnect("Login is required"); } + } else { + showLoginWindow(session); } } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/MapColor.java b/connector/src/main/java/org/geysermc/connector/utils/MapColor.java index b011edc7..4328d758 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MapColor.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MapColor.java @@ -1,3 +1,29 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + package org.geysermc.connector.utils; public enum MapColor { 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 27a3fdd3..050078e5 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java @@ -39,13 +39,28 @@ public class MathUtils { } public static double constrain(double num, double min, double max) { - if(num > max) { + if (num > max) { num = max; } - if(num < min) { + 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 + * or byte and bedrock only takes it as an byte + * + * @param value The value to convert + * @return The converted byte + */ + public static Byte convertByte(Object value) { + if (value instanceof Integer) { + return ((Integer) value).byteValue(); + } + return (Byte) value; + } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java index ac111c71..d79cdab8 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java @@ -27,17 +27,15 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; import com.github.steveice10.mc.protocol.data.message.*; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.JsonPrimitive; +import com.google.gson.*; import net.kyori.text.Component; import net.kyori.text.serializer.gson.GsonComponentSerializer; import net.kyori.text.serializer.legacy.LegacyComponentSerializer; import org.geysermc.connector.network.session.GeyserSession; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -110,9 +108,20 @@ public class MessageUtils { builder.append(getColorOrParent(msg.getStyle())); if (!(msg.getText() == null)) { boolean isTranslationMessage = (msg instanceof TranslationMessage); - builder.append(getTranslatedBedrockMessage(msg, locale, isTranslationMessage)); + String extraText = ""; + + if (isTranslationMessage) { + List paramsTranslated = getTranslationParams(((TranslationMessage) msg).getTranslationParams(), locale); + extraText = insertParams(getTranslatedBedrockMessage(msg, locale, isTranslationMessage), paramsTranslated); + } else { + extraText = getTranslatedBedrockMessage(msg, locale, isTranslationMessage); + } + + builder.append(extraText); + builder.append("\u00a7r"); } } + return builder.toString(); } @@ -121,20 +130,39 @@ public class MessageUtils { } public static String getBedrockMessage(Message message) { - Component component; if (isMessage(message.getText())) { - component = GsonComponentSerializer.INSTANCE.deserialize(message.getText()); + return getBedrockMessage(message.getText()); } else { - component = GsonComponentSerializer.INSTANCE.deserialize(message.toJsonString()); + return getBedrockMessage(message.toJsonString()); + } + } + + /** + * Verifies the message is valid JSON in case it's plaintext. Works around GsonComponentSeraializer not using lenient mode. + * See https://wiki.vg/Chat for messages sent in lenient mode, and for a description on leniency. + * + * @param message Potentially lenient JSON message + * @return Bedrock formatted message + */ + public static String getBedrockMessageLenient(String message) { + if (isMessage(message)) { + return getBedrockMessage(message); + } else { + final JsonObject obj = new JsonObject(); + obj.addProperty("text", message); + return getBedrockMessage(obj.toString()); } - return LegacyComponentSerializer.legacy().serialize(component); } public static String getBedrockMessage(String message) { - Component component = GsonComponentSerializer.INSTANCE.deserialize(message); + Component component = phraseJavaMessage(message); return LegacyComponentSerializer.legacy().serialize(component); } + public static Component phraseJavaMessage(String message) { + return GsonComponentSerializer.INSTANCE.deserialize(message); + } + public static String getJavaMessage(String message) { Component component = LegacyComponentSerializer.legacy().deserialize(message); return GsonComponentSerializer.INSTANCE.serialize(component); @@ -161,9 +189,11 @@ public class MessageUtils { } for (String text : params) { - newMessage = newMessage.replaceFirst("%s", text); + newMessage = newMessage.replaceFirst("%s", text.replaceAll("%s", "%r")); } + newMessage = newMessage.replaceAll("%r", "MISSING!"); + return newMessage; } diff --git a/connector/src/main/java/org/geysermc/connector/utils/PaintingType.java b/connector/src/main/java/org/geysermc/connector/utils/PaintingType.java index 00993522..63f1119c 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/PaintingType.java +++ b/connector/src/main/java/org/geysermc/connector/utils/PaintingType.java @@ -57,7 +57,7 @@ public enum PaintingType { DONKEY_KONG("DonkeyKong", 4, 3), POINTER("Pointer", 4, 4), PIG_SCENE("Pigscene", 4, 4), - BURNING_SKULL("Flaming Skull", 4, 4); // burning skull on java edition, flaming skull on bedrock + BURNING_SKULL("BurningSkull", 4, 4); private static final PaintingType[] VALUES = values(); private String bedrockName; 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 ade03c54..aea9ba18 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java @@ -25,16 +25,22 @@ package org.geysermc.connector.utils; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.AllArgsConstructor; import lombok.Getter; - +import lombok.NoArgsConstructor; import org.geysermc.connector.GeyserConnector; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; -import java.io.ByteArrayOutputStream; +import java.io.*; import java.net.URL; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Base64; import java.util.Map; import java.util.UUID; import java.util.concurrent.*; @@ -45,6 +51,8 @@ public class SkinProvider { public static final byte[] STEVE_SKIN = new ProvidedSkin("bedrock/skin/skin_steve.png").getSkin(); public static final Skin EMPTY_SKIN = new Skin(-1, "steve", STEVE_SKIN); + public static final byte[] ALEX_SKIN = new ProvidedSkin("bedrock/skin/skin_alex.png").getSkin(); + public static final Skin EMPTY_SKIN_ALEX = new Skin(-1, "alex", ALEX_SKIN); private static Map cachedSkins = new ConcurrentHashMap<>(); private static Map> requestedSkins = new ConcurrentHashMap<>(); @@ -52,8 +60,49 @@ public class SkinProvider { private static Map cachedCapes = new ConcurrentHashMap<>(); private static Map> requestedCapes = new ConcurrentHashMap<>(); + public static final SkinGeometry EMPTY_GEOMETRY = SkinProvider.SkinGeometry.getLegacy(false); + private static Map cachedGeometry = new ConcurrentHashMap<>(); + + public static final boolean ALLOW_THIRD_PARTY_EARS = GeyserConnector.getInstance().getConfig().isAllowThirdPartyEars(); + public static String EARS_GEOMETRY; + public static String EARS_GEOMETRY_SLIM; + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final int CACHE_INTERVAL = 8 * 60 * 1000; // 8 minutes + static { + /* Load in the normal ears geometry */ + InputStream earsStream = FileUtils.getResource("bedrock/skin/geometry.humanoid.ears.json"); + + StringBuilder earsDataBuilder = new StringBuilder(); + try (Reader reader = new BufferedReader(new InputStreamReader(earsStream, Charset.forName(StandardCharsets.UTF_8.name())))) { + int c = 0; + while ((c = reader.read()) != -1) { + earsDataBuilder.append((char) c); + } + } catch (IOException e) { + throw new AssertionError("Unable to load ears geometry", e); + } + + EARS_GEOMETRY = earsDataBuilder.toString(); + + + /* Load in the slim ears geometry */ + earsStream = FileUtils.getResource("bedrock/skin/geometry.humanoid.earsSlim.json"); + + earsDataBuilder = new StringBuilder(); + try (Reader reader = new BufferedReader(new InputStreamReader(earsStream, Charset.forName(StandardCharsets.UTF_8.name())))) { + int c = 0; + while ((c = reader.read()) != -1) { + earsDataBuilder.append((char) c); + } + } catch (IOException e) { + throw new AssertionError("Unable to load ears geometry", e); + } + + EARS_GEOMETRY_SLIM = earsDataBuilder.toString(); + } + public static boolean hasSkinCached(UUID uuid) { return cachedSkins.containsKey(uuid); } @@ -74,9 +123,10 @@ public class SkinProvider { return CompletableFuture.supplyAsync(() -> { long time = System.currentTimeMillis(); + CapeProvider provider = capeUrl != null ? CapeProvider.MINECRAFT : null; SkinAndCape skinAndCape = new SkinAndCape( getOrDefault(requestSkin(playerId, skinUrl, false), EMPTY_SKIN, 5), - getOrDefault(requestCape(capeUrl, false), EMPTY_CAPE, 5) + getOrDefault(requestCape(capeUrl, provider, false), EMPTY_CAPE, 5) ); GeyserConnector.getInstance().getLogger().debug("Took " + (System.currentTimeMillis() - time) + "ms for " + playerId); @@ -112,11 +162,11 @@ public class SkinProvider { return future; } - public static CompletableFuture requestCape(String capeUrl, boolean newThread) { + public static CompletableFuture requestCape(String capeUrl, CapeProvider provider, boolean newThread) { if (capeUrl == null || capeUrl.isEmpty()) return CompletableFuture.completedFuture(EMPTY_CAPE); if (requestedCapes.containsKey(capeUrl)) return requestedCapes.get(capeUrl); // already requested - boolean officialCape = capeUrl.startsWith("https://textures.minecraft.net"); + boolean officialCape = provider == CapeProvider.MINECRAFT; boolean validCache = (System.currentTimeMillis() - CACHE_INTERVAL) < cachedCapes.getOrDefault(capeUrl, EMPTY_CAPE).getRequestedOn(); if ((cachedCapes.containsKey(capeUrl) && officialCape) || validCache) { @@ -126,14 +176,14 @@ public class SkinProvider { CompletableFuture future; if (newThread) { - future = CompletableFuture.supplyAsync(() -> supplyCape(capeUrl), EXECUTOR_SERVICE) + future = CompletableFuture.supplyAsync(() -> supplyCape(capeUrl, provider), EXECUTOR_SERVICE) .whenCompleteAsync((cape, throwable) -> { cachedCapes.put(capeUrl, cape); requestedCapes.remove(capeUrl); }); requestedCapes.put(capeUrl, future); } else { - Cape cape = supplyCape(capeUrl); // blocking + Cape cape = supplyCape(capeUrl, provider); // blocking future = CompletableFuture.completedFuture(cape); cachedCapes.put(capeUrl, cape); } @@ -143,9 +193,9 @@ public class SkinProvider { public static CompletableFuture requestUnofficialCape(Cape officialCape, UUID playerId, String username, boolean newThread) { if (officialCape.isFailed() && ALLOW_THIRD_PARTY_CAPES) { - for (UnofficalCape cape : UnofficalCape.VALUES) { + for (CapeProvider provider : CapeProvider.VALUES) { Cape cape1 = getOrDefault( - requestCape(cape.getUrlFor(playerId, username), newThread), + requestCape(provider.getUrlFor(playerId, username), provider, newThread), EMPTY_CAPE, 4 ); if (!cape1.isFailed()) { @@ -156,18 +206,100 @@ public class SkinProvider { return CompletableFuture.completedFuture(officialCape); } + public static CompletableFuture requestEars(String earsUrl, EarsProvider provider, boolean newThread, Skin skin) { + if (earsUrl == null || earsUrl.isEmpty()) return CompletableFuture.completedFuture(skin); + + CompletableFuture future; + if (newThread) { + future = CompletableFuture.supplyAsync(() -> supplyEars(skin, earsUrl, provider), EXECUTOR_SERVICE) + .whenCompleteAsync((outSkin, throwable) -> { }); + } else { + Skin ears = supplyEars(skin, earsUrl, provider); // blocking + future = CompletableFuture.completedFuture(ears); + } + return future; + } + + /** + * Try and find an ear texture for a Java player + * + * @param officialSkin The current players skin + * @param playerId The players UUID + * @param username The players username + * @param newThread Should we start in a new thread + * @return The updated skin with ears + */ + public static CompletableFuture requestUnofficialEars(Skin officialSkin, UUID playerId, String username, boolean newThread) { + for (EarsProvider provider : EarsProvider.VALUES) { + Skin skin1 = getOrDefault( + requestEars(provider.getUrlFor(playerId, username), provider, newThread, officialSkin), + officialSkin, 4 + ); + if (skin1.isEars()) { + return CompletableFuture.completedFuture(skin1); + } + } + + return CompletableFuture.completedFuture(officialSkin); + } + + public static CompletableFuture requestBedrockCape(UUID playerID, boolean newThread) { + Cape bedrockCape = cachedCapes.getOrDefault(playerID.toString() + ".Bedrock", EMPTY_CAPE); + return CompletableFuture.completedFuture(bedrockCape); + } + + public static CompletableFuture requestBedrockGeometry(SkinGeometry currentGeometry, UUID playerID, boolean newThread) { + SkinGeometry bedrockGeometry = cachedGeometry.getOrDefault(playerID, currentGeometry); + return CompletableFuture.completedFuture(bedrockGeometry); + } + + public static void storeBedrockSkin(UUID playerID, String skinID, byte[] skinData) { + Skin skin = new Skin(playerID, skinID, skinData, System.currentTimeMillis(), true, false); + cachedSkins.put(playerID, skin); + } + + public static void storeBedrockCape(UUID playerID, byte[] capeData) { + Cape cape = new Cape(playerID.toString() + ".Bedrock", playerID.toString(), capeData, System.currentTimeMillis(), false); + cachedCapes.put(playerID.toString() + ".Bedrock", cape); + } + + public static void storeBedrockGeometry(UUID playerID, byte[] geometryName, byte[] geometryData) { + SkinGeometry geometry = new SkinGeometry(new String(geometryName), new String(geometryData), false); + cachedGeometry.put(playerID, geometry); + } + + /** + * Stores the ajusted skin with the ear texture to the cache + * + * @param playerID The UUID to cache it against + * @param skin The skin to cache + */ + public static void storeEarSkin(UUID playerID, Skin skin) { + cachedSkins.put(playerID, skin); + } + + /** + * Stores the geometry for a Java player with ears + * + * @param playerID The UUID to cache it against + * @param isSlim If the player is using an slim base + */ + public static void storeEarGeometry(UUID playerID, boolean isSlim) { + cachedGeometry.put(playerID, SkinGeometry.getEars(isSlim)); + } + private static Skin supplySkin(UUID uuid, String textureUrl) { byte[] skin = EMPTY_SKIN.getSkinData(); try { - skin = requestImage(textureUrl, false); + skin = requestImage(textureUrl, null); } catch (Exception ignored) {} // just ignore I guess - return new Skin(uuid, textureUrl, skin, System.currentTimeMillis(), false); + return new Skin(uuid, textureUrl, skin, System.currentTimeMillis(), false, false); } - private static Cape supplyCape(String capeUrl) { + private static Cape supplyCape(String capeUrl, CapeProvider provider) { byte[] cape = new byte[0]; try { - cape = requestImage(capeUrl, true); + cape = requestImage(capeUrl, provider); } catch (Exception ignored) {} // just ignore I guess String[] urlSection = capeUrl.split("/"); // A real url is expected at this stage @@ -181,36 +313,89 @@ public class SkinProvider { ); } - private static byte[] requestImage(String imageUrl, boolean cape) throws Exception { - BufferedImage image = ImageIO.read(new URL(imageUrl)); + /** + * Get the ears texture and place it on the skin from the given URL + * + * @param existingSkin The players current skin + * @param earsUrl The URL to get the ears texture from + * @param provider The ears texture provider + * @return The updated skin with ears + */ + private static Skin supplyEars(Skin existingSkin, String earsUrl, EarsProvider provider) { + try { + // Get the ears texture + BufferedImage ears = ImageIO.read(new URL(earsUrl)); + if (ears == null) throw new NullPointerException(); + + // Convert the skin data to a BufferedImage + int height = (existingSkin.getSkinData().length / 4 / 64); + BufferedImage skinImage = imageDataToBufferedImage(existingSkin.getSkinData(), 64, height); + + // Create a new image with the ears texture over it + BufferedImage newSkin = new BufferedImage(skinImage.getWidth(), skinImage.getHeight(), BufferedImage.TYPE_INT_ARGB); + Graphics2D g = (Graphics2D) newSkin.getGraphics(); + g.drawImage(skinImage, 0, 0, null); + g.drawImage(ears, 24, 0, null); + + // Turn the buffered image back into an array of bytes + byte[] data = bufferedImageToImageData(newSkin); + skinImage.flush(); + + // Create a new skin object with the new infomation + return new Skin( + existingSkin.getSkinOwner(), + existingSkin.getTextureUrl(), + data, + System.currentTimeMillis(), + true, + true + ); + } catch (Exception ignored) {} // just ignore I guess + + return existingSkin; + } + + private static byte[] requestImage(String imageUrl, CapeProvider provider) throws Exception { + BufferedImage image = downloadImage(imageUrl, provider); GeyserConnector.getInstance().getLogger().debug("Downloaded " + imageUrl); - if (cape) { - image = image.getWidth() > 64 ? scale(image) : image; - BufferedImage newImage = new BufferedImage(64, 32, BufferedImage.TYPE_INT_RGB); + // if the requested image is an cape + if (provider != null) { + while(image.getWidth() > 64) { + image = scale(image); + } + BufferedImage newImage = new BufferedImage(64, 32, BufferedImage.TYPE_INT_ARGB); Graphics g = newImage.createGraphics(); g.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null); g.dispose(); image = newImage; } - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(image.getWidth() * 4 + image.getHeight() * 4)) { - for (int y = 0; y < image.getHeight(); y++) { - for (int x = 0; x < image.getWidth(); x++) { - int rgba = image.getRGB(x, y); - outputStream.write((rgba >> 16) & 0xFF); - outputStream.write((rgba >> 8) & 0xFF); - outputStream.write(rgba & 0xFF); - outputStream.write((rgba >> 24) & 0xFF); - } - } - image.flush(); - return outputStream.toByteArray(); - } + byte[] data = bufferedImageToImageData(image); + image.flush(); + return data; } - private static BufferedImage scale (BufferedImage bufferedImage) { - BufferedImage resized = new BufferedImage(bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2, BufferedImage.TYPE_INT_RGB); + private static BufferedImage downloadImage(String imageUrl, CapeProvider provider) throws IOException { + if (provider == CapeProvider.FIVEZIG) + return readFiveZigCape(imageUrl); + BufferedImage image = ImageIO.read(new URL(imageUrl)); + if (image == null) throw new NullPointerException(); + return image; + } + + private static BufferedImage readFiveZigCape(String url) throws IOException { + JsonNode element = OBJECT_MAPPER.readTree(WebUtils.getBody(url)); + if (element != null && element.isObject()) { + JsonNode capeElement = element.get("d"); + if (capeElement == null || capeElement.isNull()) return null; + return ImageIO.read(new ByteArrayInputStream(Base64.getDecoder().decode(capeElement.textValue()))); + } + return null; + } + + private static BufferedImage scale(BufferedImage bufferedImage) { + BufferedImage resized = new BufferedImage(bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = resized.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g2.drawImage(bufferedImage, 0, 0, bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2, null); @@ -218,6 +403,60 @@ public class SkinProvider { return resized; } + /** + * Get the RGBA int for a given index in some image data + * + * @param index Index to get + * @param data Image data to find in + * @return An int representing RGBA + */ + private static int getRGBA(int index, byte[] data) { + return (data[index] & 0xFF) << 16 | (data[index + 1] & 0xFF) << 8 | + data[index + 2] & 0xFF | (data[index + 3] & 0xFF) << 24; + } + + /** + * Convert a byte[] to a BufferedImage + * + * @param imageData The byte[] to convert + * @param imageWidth The width of the target image + * @param imageHeight The height of the target image + * @return The converted BufferedImage + */ + public static BufferedImage imageDataToBufferedImage(byte[] imageData, int imageWidth, int imageHeight) { + BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB); + int index = 0; + for (int y = 0; y < imageHeight; y++) { + for (int x = 0; x < imageWidth; x++) { + image.setRGB(x, y, getRGBA(index, imageData)); + index += 4; + } + } + + return image; + } + + /** + * Convert a BufferedImage to a byte[] + * + * @param image The BufferedImage to convert + * @return The converted byte[] + */ + public static byte[] bufferedImageToImageData(BufferedImage image) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(image.getWidth() * 4 + image.getHeight() * 4); + for (int y = 0; y < image.getHeight(); y++) { + for (int x = 0; x < image.getWidth(); x++) { + int rgba = image.getRGB(x, y); + outputStream.write((rgba >> 16) & 0xFF); + outputStream.write((rgba >> 8) & 0xFF); + outputStream.write(rgba & 0xFF); + outputStream.write((rgba >> 24) & 0xFF); + } + } + + return outputStream.toByteArray(); + } + public static T getOrDefault(CompletableFuture future, T defaultValue, int timeoutInSeconds) { try { return future.get(timeoutInSeconds, TimeUnit.SECONDS); @@ -240,6 +479,7 @@ public class SkinProvider { private byte[] skinData; private long requestedOn; private boolean updated; + private boolean ears; private Skin(long requestedOn, String textureUrl, byte[] skinData) { this.requestedOn = requestedOn; @@ -258,18 +498,48 @@ public class SkinProvider { private boolean failed; } + @AllArgsConstructor + @Getter + public static class SkinGeometry { + private String geometryName; + private String geometryData; + private boolean failed; + + /** + * Generate generic geometry + * + * @param isSlim Should it be the alex model + * @return The generic geometry object + */ + public static SkinGeometry getLegacy(boolean isSlim) { + return new SkinProvider.SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.custom" + (isSlim ? "Slim" : "") + "\"}}", "", true); + } + + /** + * Generate basic geometry with ears + * + * @param isSlim Should it be the alex model + * @return The generated geometry for the ears model + */ + public static SkinGeometry getEars(boolean isSlim) { + return new SkinProvider.SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.ears" + (isSlim ? "Slim" : "") + "\"}}", (isSlim ? EARS_GEOMETRY_SLIM : EARS_GEOMETRY), false); + } + } + /* * Sorted by 'priority' */ @AllArgsConstructor + @NoArgsConstructor @Getter - public enum UnofficalCape { + public enum CapeProvider { + MINECRAFT, OPTIFINE("http://s.optifine.net/capes/%s.png", CapeUrlType.USERNAME), - LABYMOD("http://capes.labymod.net/capes/%s.png", CapeUrlType.UUID_DASHED), - FIVEZIG("http://textures.5zig.net/2/%s", CapeUrlType.UUID), + 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://www.minecraftcapes.co.uk/getCape/%s", CapeUrlType.UUID); - public static final UnofficalCape[] VALUES = values(); + public static final CapeProvider[] VALUES = Arrays.copyOfRange(values(), 1, 5); private String url; private CapeUrlType type; @@ -295,4 +565,34 @@ public class SkinProvider { UUID, UUID_DASHED } + + /* + * Sorted by 'priority' + */ + @AllArgsConstructor + @NoArgsConstructor + @Getter + public enum EarsProvider { + MINECRAFTCAPES("https://www.minecraftcapes.co.uk/getEars/%s", CapeUrlType.UUID); + + public static final EarsProvider[] VALUES = values(); + private String url; + private CapeUrlType type; + + public String getUrlFor(String type) { + return String.format(url, type); + } + + public String getUrlFor(UUID uuid, String username) { + return getUrlFor(toRequestedType(type, uuid, username)); + } + + public static String toRequestedType(CapeUrlType type, UUID uuid, String username) { + switch (type) { + case UUID: return uuid.toString().replace("-", ""); + case UUID_DASHED: return uuid.toString(); + default: return username; + } + } + } } 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 012bd73a..48e4c4c8 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java @@ -39,6 +39,7 @@ import org.geysermc.common.AuthType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.session.auth.BedrockClientData; import java.nio.charset.StandardCharsets; import java.util.Base64; @@ -52,6 +53,8 @@ public class SkinUtils { GameProfileData data = GameProfileData.from(profile); SkinProvider.Cape cape = SkinProvider.getCachedCape(data.getCapeUrl()); + SkinProvider.SkinGeometry geometry = SkinProvider.SkinGeometry.getLegacy(data.isAlex()); + return buildEntryManually( profile.getId(), profile.getName(), @@ -60,8 +63,8 @@ public class SkinUtils { SkinProvider.getCachedSkin(profile.getId()).getSkinData(), cape.getCapeId(), cape.getCapeData(), - getLegacySkinGeometry("geometry.humanoid.custom" + (data.isAlex() ? "Slim" : "")), - "" + geometry.getGeometryName(), + geometry.getGeometryData() ); } @@ -74,8 +77,8 @@ public class SkinUtils { SkinProvider.STEVE_SKIN, SkinProvider.EMPTY_CAPE.getCapeId(), SkinProvider.EMPTY_CAPE.getCapeData(), - getLegacySkinGeometry("geometry.humanoid"), - "" + SkinProvider.EMPTY_GEOMETRY.getGeometryName(), + SkinProvider.EMPTY_GEOMETRY.getGeometryData() ); } @@ -85,7 +88,7 @@ public class SkinUtils { String geometryName, String geometryData) { SerializedSkin serializedSkin = SerializedSkin.of( skinId, geometryName, ImageData.of(skinData), Collections.emptyList(), - ImageData.of(capeData), geometryData, "", true, false, false, capeId, uuid.toString() + ImageData.of(capeData), geometryData, "", true, false, !capeId.equals(SkinProvider.EMPTY_CAPE.getCapeId()), capeId, uuid.toString() ); PlayerListPacket.Entry entry = new PlayerListPacket.Entry(uuid); @@ -112,6 +115,9 @@ public class SkinUtils { * @return The built GameProfileData */ public static GameProfileData from(GameProfile profile) { + // Fallback to the offline mode of working it out + boolean isAlex = ((profile.getId().hashCode() % 2) == 1); + try { GameProfile.Property skinProperty = profile.getProperty("textures"); @@ -121,7 +127,7 @@ public class SkinUtils { JsonNode skinTexture = textures.get("SKIN"); String skinUrl = skinTexture.get("url").asText(); - boolean isAlex = skinTexture.has("metadata"); + isAlex = skinTexture.has("metadata"); String capeUrl = null; if (textures.has("CAPE")) { @@ -135,7 +141,7 @@ public class SkinUtils { GeyserConnector.getInstance().getLogger().debug("Got invalid texture data for " + profile.getName() + " " + exception.getMessage()); } // return default skin with default cape when texture data is invalid - return new GameProfileData(SkinProvider.EMPTY_SKIN.getTextureUrl(), SkinProvider.EMPTY_CAPE.getTextureUrl(), false); + return new GameProfileData((isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl()), SkinProvider.EMPTY_CAPE.getTextureUrl(), isAlex); } } } @@ -151,11 +157,49 @@ public class SkinUtils { SkinProvider.Skin skin = skinAndCape.getSkin(); SkinProvider.Cape cape = skinAndCape.getCape(); + if (cape.isFailed()) { + cape = SkinProvider.getOrDefault(SkinProvider.requestBedrockCape( + entity.getUuid(), false + ), SkinProvider.EMPTY_CAPE, 3); + } + if (cape.isFailed() && SkinProvider.ALLOW_THIRD_PARTY_CAPES) { cape = SkinProvider.getOrDefault(SkinProvider.requestUnofficialCape( cape, entity.getUuid(), entity.getUsername(), false - ), SkinProvider.EMPTY_CAPE, SkinProvider.UnofficalCape.VALUES.length * 3); + ), SkinProvider.EMPTY_CAPE, SkinProvider.CapeProvider.VALUES.length * 3); + } + + SkinProvider.SkinGeometry geometry = SkinProvider.SkinGeometry.getLegacy(data.isAlex()); + geometry = SkinProvider.getOrDefault(SkinProvider.requestBedrockGeometry( + geometry, entity.getUuid(), false + ), geometry, 3); + + // Not a bedrock player check for ears + if (geometry.isFailed() && SkinProvider.ALLOW_THIRD_PARTY_EARS) { + boolean isEars = false; + + // Its deadmau5, gotta support his skin :) + if (entity.getUuid().toString().equals("1e18d5ff-643d-45c8-b509-43b8461d8614")) { + isEars = true; + } else { + // Get the ears texture for the player + skin = SkinProvider.getOrDefault(SkinProvider.requestUnofficialEars( + skin, entity.getUuid(), entity.getUsername(), false + ), skin, 3); + + isEars = skin.isEars(); + } + + // Does the skin have an ears texture + if (isEars) { + // Get the new geometry + geometry = SkinProvider.SkinGeometry.getEars(data.isAlex()); + + // Store the skin and geometry for the ears + SkinProvider.storeEarSkin(entity.getUuid(), skin); + SkinProvider.storeEarGeometry(entity.getUuid(), data.isAlex()); + } } if (entity.getLastSkinUpdate() < skin.getRequestedOn()) { @@ -170,19 +214,36 @@ public class SkinUtils { skin.getSkinData(), cape.getCapeId(), cape.getCapeData(), - getLegacySkinGeometry("geometry.humanoid.custom" + (data.isAlex() ? "Slim" : "")), - "" + geometry.getGeometryName(), + geometry.getGeometryData() ); + // If it is our skin we replace the UUID with the authdata UUID + if (session.getPlayerEntity() == entity) { + // Copy the entry with our identity instead. + PlayerListPacket.Entry copy = new PlayerListPacket.Entry(session.getAuthData().getUUID()); + copy.setName(updatedEntry.getName()); + copy.setEntityId(updatedEntry.getEntityId()); + copy.setSkin(updatedEntry.getSkin()); + copy.setXuid(updatedEntry.getXuid()); + copy.setPlatformChatId(updatedEntry.getPlatformChatId()); + copy.setTeacher(updatedEntry.isTeacher()); + updatedEntry = copy; + } + PlayerListPacket playerRemovePacket = new PlayerListPacket(); playerRemovePacket.setAction(PlayerListPacket.Action.REMOVE); playerRemovePacket.getEntries().add(updatedEntry); - session.getUpstream().sendPacket(playerRemovePacket); + session.sendUpstreamPacket(playerRemovePacket); PlayerListPacket playerAddPacket = new PlayerListPacket(); playerAddPacket.setAction(PlayerListPacket.Action.ADD); playerAddPacket.getEntries().add(updatedEntry); - session.getUpstream().sendPacket(playerAddPacket); + session.sendUpstreamPacket(playerAddPacket); + + if(entity.getUuid().equals(session.getPlayerEntity().getUuid())) { + session.fetchOurSkin(updatedEntry); + } } } } catch (Exception e) { @@ -194,13 +255,31 @@ public class SkinUtils { }); } - /** - * Create a basic geometry json for the given name - * - * @param geometryName Geometry name to use - * @return Geometry data as a json string - */ - private static String getLegacySkinGeometry(String geometryName) { - return "{\"geometry\" :{\"default\" :\"" + geometryName + "\"}}"; + public static void handleBedrockSkin(PlayerEntity playerEntity, BedrockClientData clientData) { + GameProfileData data = GameProfileData.from(playerEntity.getProfile()); + + GeyserConnector.getInstance().getLogger().info("Registering bedrock skin for " + playerEntity.getUsername() + " (" + playerEntity.getUuid() + ")"); + + try { + byte[] skinBytes = com.github.steveice10.mc.auth.util.Base64.decode(clientData.getSkinData().getBytes("UTF-8")); + byte[] capeBytes = clientData.getCapeData(); + + byte[] geometryNameBytes = Base64.getDecoder().decode(clientData.getGeometryName().getBytes("UTF-8")); + byte[] geometryBytes = Base64.getDecoder().decode(clientData.getGeometryData().getBytes("UTF-8")); + + if (skinBytes.length <= (128 * 128 * 4) && !clientData.isPersonaSkin()) { + SkinProvider.storeBedrockSkin(playerEntity.getUuid(), data.getSkinUrl(), skinBytes); + SkinProvider.storeBedrockGeometry(playerEntity.getUuid(), geometryNameBytes, geometryBytes); + } else { + GeyserConnector.getInstance().getLogger().info("Unable to load bedrock skin for '" + playerEntity.getUsername() + "' as they are likely using a customised skin"); + GeyserConnector.getInstance().getLogger().debug("The size of '" + playerEntity.getUsername() + "' skin is: " + clientData.getSkinImageWidth() + "x" + clientData.getSkinImageHeight()); + } + + if (!clientData.getCapeId().equals("")) { + SkinProvider.storeBedrockCape(playerEntity.getUuid(), capeBytes); + } + } catch (Exception e) { + throw new AssertionError("Failed to cache skin for bedrock user (" + playerEntity.getUsername() + "): ", e); + } } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java b/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java deleted file mode 100644 index b17d4578..00000000 --- a/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.utils; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.nukkitx.nbt.NbtUtils; -import com.nukkitx.nbt.stream.NBTInputStream; -import com.nukkitx.nbt.tag.CompoundTag; -import com.nukkitx.protocol.bedrock.data.ItemData; -import com.nukkitx.protocol.bedrock.packet.StartGamePacket; - -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; - -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.ToolItemEntry; - -import java.io.*; -import java.util.*; - -public class Toolbox { - - public static final ObjectMapper JSON_MAPPER = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES); - public static final CompoundTag BIOMES; - public static final ItemData[] CREATIVE_ITEMS; - - public static final List ITEMS = new ArrayList<>(); - - public static final Int2ObjectMap ITEM_ENTRIES = new Int2ObjectOpenHashMap<>(); - - public static CompoundTag ENTITY_IDENTIFIERS; - - public static int BARRIER_INDEX = 0; - - static { - /* Load biomes */ - InputStream biomestream = GeyserConnector.class.getClassLoader().getResourceAsStream("bedrock/biome_definitions.dat"); - if (biomestream == null) { - throw new AssertionError("Unable to find bedrock/biome_definitions.dat"); - } - - CompoundTag biomesTag; - - try (NBTInputStream biomenbtInputStream = NbtUtils.createNetworkReader(biomestream)){ - biomesTag = (CompoundTag) biomenbtInputStream.readTag(); - BIOMES = biomesTag; - } catch (Exception ex) { - ex.printStackTrace(); - GeyserConnector.getInstance().getLogger().warning("Failed to get biomes from biome definitions, is there something wrong with the file?"); - throw new AssertionError(ex); - } - - /* Load item palette */ - InputStream stream = getResource("bedrock/items.json"); - - TypeReference> itemEntriesType = new TypeReference>() { - }; - - List itemEntries; - try { - itemEntries = JSON_MAPPER.readValue(stream, itemEntriesType); - } catch (Exception e) { - throw new AssertionError("Unable to load Bedrock runtime item IDs", e); - } - - for (JsonNode entry : itemEntries) { - ITEMS.add(new StartGamePacket.ItemEntry(entry.get("name").textValue(), (short) entry.get("id").intValue())); - } - - stream = getResource("mappings/items.json"); - - JsonNode items; - try { - items = JSON_MAPPER.readTree(stream); - } catch (Exception e) { - throw new AssertionError("Unable to load Java runtime item IDs", e); - } - - int itemIndex = 0; - Iterator> iterator = items.fields(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - if (entry.getValue().has("tool_type")) { - if (entry.getValue().has("tool_tier")) { - ITEM_ENTRIES.put(itemIndex, new ToolItemEntry( - entry.getKey(), itemIndex, - entry.getValue().get("bedrock_id").intValue(), - entry.getValue().get("bedrock_data").intValue(), - entry.getValue().get("tool_type").textValue(), - entry.getValue().get("tool_tier").textValue())); - } else { - ITEM_ENTRIES.put(itemIndex, new ToolItemEntry( - entry.getKey(), itemIndex, - entry.getValue().get("bedrock_id").intValue(), - entry.getValue().get("bedrock_data").intValue(), - entry.getValue().get("tool_type").textValue(), - "")); - } - } else { - ITEM_ENTRIES.put(itemIndex, new ItemEntry( - entry.getKey(), itemIndex, - entry.getValue().get("bedrock_id").intValue(), - entry.getValue().get("bedrock_data").intValue())); - } - if (entry.getKey().equals("minecraft:barrier")) { - BARRIER_INDEX = itemIndex; - } - - itemIndex++; - } - - // Load the locale data - LocaleUtils.init(); - - /* Load creative items */ - stream = getResource("bedrock/creative_items.json"); - - JsonNode creativeItemEntries; - try { - creativeItemEntries = JSON_MAPPER.readTree(stream).get("items"); - } catch (Exception e) { - throw new AssertionError("Unable to load creative items", e); - } - - List creativeItems = new ArrayList<>(); - for (JsonNode itemNode : creativeItemEntries) { - short damage = 0; - 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); - try { - com.nukkitx.nbt.tag.CompoundTag tag = (com.nukkitx.nbt.tag.CompoundTag) NbtUtils.createReaderLE(bais).readTag(); - creativeItems.add(ItemData.of(itemNode.get("id").asInt(), damage, 1, tag)); - } catch (IOException e) { - e.printStackTrace(); - } - } else { - creativeItems.add(ItemData.of(itemNode.get("id").asInt(), damage, 1)); - } - } - CREATIVE_ITEMS = creativeItems.toArray(new ItemData[0]); - - - /* Load entity identifiers */ - stream = Toolbox.getResource("bedrock/entity_identifiers.dat"); - - try (NBTInputStream nbtInputStream = NbtUtils.createNetworkReader(stream)) { - ENTITY_IDENTIFIERS = (CompoundTag) nbtInputStream.readTag(); - } catch (Exception e) { - throw new AssertionError("Unable to get entities from entity identifiers", e); - } - } - - /** - * Get an InputStream for the given resource path, throws AssertionError if resource is not found - * - * @param resource Resource to get - * @return InputStream of the given resource - */ - public static InputStream getResource(String resource) { - InputStream stream = Toolbox.class.getClassLoader().getResourceAsStream(resource); - if (stream == null) { - throw new AssertionError("Unable to find resource: " + resource); - } - return stream; - } - - public static void init() { - // no-op - } -} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/BitArray.java b/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/BitArray.java deleted file mode 100644 index 944cabd3..00000000 --- a/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/BitArray.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * This code in this file is derived from NukkitX and permission has - * been granted to us allowing the usage of it in Geyser. - * - * Copyright (C) 2020 The NukkitX Project - * https://github.com/NukkitX/Nukkit - */ - -package org.geysermc.connector.world.chunk.bitarray; - -public interface BitArray { - - void set(int index, int value); - - int get(int index); - - int size(); - - int[] getWords(); - - BitArrayVersion getVersion(); - - BitArray copy(); -} \ No newline at end of file diff --git a/connector/src/main/resources/bedrock/skin/geometry.humanoid.ears.json b/connector/src/main/resources/bedrock/skin/geometry.humanoid.ears.json new file mode 100644 index 00000000..5571655b --- /dev/null +++ b/connector/src/main/resources/bedrock/skin/geometry.humanoid.ears.json @@ -0,0 +1,249 @@ +{ + "format_version": "1.14.0", + "minecraft:geometry": [ + { + "bones": [ + { + "name" : "root", + "pivot" : [ 0.0, 0.0, 0.0 ] + }, + + { + "name" : "waist", + "parent" : "root", + "pivot" : [ 0.0, 12.0, 0.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes" : [] + }, + + + { + "name": "body", + "parent" : "waist", + "pivot": [ 0.0, 24.0, 0.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes": [ + { + "origin": [ -4.0, 12.0, -2.0 ], + "size": [ 8, 12, 4 ], + "uv": [ 16, 16 ] + } + ] + }, + + { + "name": "jacket", + "parent" : "body", + "pivot": [ 0.0, 24.0, 0.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes": [ + { + "origin": [ -4.0, 12.0, -2.0 ], + "size": [ 8, 12, 4 ], + "uv": [ 16, 32 ], + "inflate": 0.25 + } + ] + }, + + + { + "name": "head", + "parent" : "body", + "pivot": [ 0.0, 24.0, 0.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes": [ + { + "origin": [ -4.0, 24.0, -4.0 ], + "size": [ 8, 8, 8 ], + "uv": [ 0, 0 ] + } + ] + }, + + { + "name": "hat", + "parent" : "head", + "pivot": [ 0.0, 24.0, 0.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes": [ + { + "origin": [ -4.0, 24.0, -4.0 ], + "size": [ 8, 8, 8 ], + "uv": [ 32, 0 ], + "inflate": 0.5 + } + ] + }, + + + { + "name": "leftArm", + "parent" : "body", + "pivot": [ 5.0, 22.0, 0.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes": [ + { + "origin": [ 4.0, 12.0, -2.0 ], + "size": [ 4, 12, 4 ], + "uv": [ 32, 48 ] + } + ] + }, + { + "name": "rightArm", + "parent" : "body", + "pivot": [ -5.0, 22.0, 0.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes": [ + { + "origin": [ -8.0, 12.0, -2.0 ], + "size": [ 4, 12, 4 ], + "uv": [ 40, 16 ] + } + ] + }, + + { + "name": "leftSleeve", + "parent" : "leftArm", + "pivot": [ 5.0, 22.0, 0.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes": [ + { + "origin": [ 4.0, 12.0, -2.0 ], + "size": [ 4, 12, 4 ], + "uv": [ 48, 48 ], + "inflate": 0.25 + } + ] + }, + + { + "name": "rightSleeve", + "parent" : "rightArm", + "pivot": [ -5.0, 22.0, 0.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes": [ + { + "origin": [ -8.0, 12.0, -2.0 ], + "size": [ 4, 12, 4 ], + "uv": [ 40, 32 ], + "inflate": 0.25 + } + ] + }, + + + { + "name": "leftLeg", + "parent" : "root", + "pivot": [ 1.9, 12.0, 0.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes": [ + { + "origin": [ -0.1, 0.0, -2.0 ], + "size": [ 4, 12, 4 ], + "uv": [ 0, 16 ] + } + ] + }, + + { + "name": "rightLeg", + "parent" : "root", + "pivot": [ -1.9, 12.0, 0.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes": [ + { + "origin": [ -3.9, 0.0, -2.0 ], + "size": [ 4, 12, 4 ], + "uv": [ 0, 16 ] + } + ] + }, + + { + "name": "leftPants", + "parent" : "leftLeg", + "pivot": [1.9, 12.0, 0.0], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes": [ + { + "origin": [ -0.1, 0.0, -2.0 ], + "size": [ 4, 12, 4 ], + "uv": [ 0, 48 ], + "inflate": 0.25 + } + ] + }, + + { + "name": "rightPants", + "parent" : "rightLeg", + "pivot": [ -1.9, 12.0, 0.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes": [ + { + "origin": [ -3.9, 0.0, -2.0] , + "size": [ 4, 12, 4 ], + "uv": [ 0, 32], + "inflate": 0.25 + } + ] + }, + + + { + "name" : "rightItem", + "parent" : "rightArm", + "pivot" : [ -6.0, 15.0, 1.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes" : [] + }, + + { + "name" : "leftItem", + "parent" : "leftArm", + "pivot" : [ 6.0, 15.0, 1.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes" : [] + }, + + + { + "name": "leftEar", + "parent" : "head", + "pivot": [ -1.9, 12.0, 0.0 ], + "cubes": [ + { + "origin": [ 3.0, 31.0, -0.5 ], + "size": [ 6, 6, 1 ], + "uv": [ 24, 0 ], + "inflate": 0.5 + } + ] + }, + + { + "name": "rightEar", + "parent" : "head", + "pivot": [ -1.9, 12.0, 0.0 ], + "cubes": [ + { + "origin": [ -9.0, 31.0, -0.5 ], + "size": [ 6, 6, 1 ], + "uv": [ 24, 0 ], + "inflate": 0.5 + } + ] + } + ], + "description": { + "identifier": "geometry.humanoid.ears", + "texture_height": 64, + "texture_width": 64 + } + } + ] +} \ No newline at end of file diff --git a/connector/src/main/resources/bedrock/skin/geometry.humanoid.earsSlim.json b/connector/src/main/resources/bedrock/skin/geometry.humanoid.earsSlim.json new file mode 100644 index 00000000..70c44f85 --- /dev/null +++ b/connector/src/main/resources/bedrock/skin/geometry.humanoid.earsSlim.json @@ -0,0 +1,249 @@ +{ + "format_version": "1.14.0", + "minecraft:geometry": [ + { + "bones": [ + { + "name" : "root", + "pivot" : [ 0.0, 0.0, 0.0 ] + }, + + { + "name" : "waist", + "parent" : "root", + "pivot" : [ 0.0, 12.0, 0.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes" : [] + }, + + + { + "name": "body", + "parent" : "waist", + "pivot": [ 0.0, 24.0, 0.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes": [ + { + "origin": [ -4.0, 12.0, -2.0 ], + "size": [ 8, 12, 4 ], + "uv": [ 16, 16 ] + } + ] + }, + + { + "name": "jacket", + "parent" : "body", + "pivot": [ 0.0, 24.0, 0.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes": [ + { + "origin": [ -4.0, 12.0, -2.0 ], + "size": [ 8, 12, 4 ], + "uv": [ 16, 32 ], + "inflate": 0.25 + } + ] + }, + + + { + "name": "head", + "parent" : "body", + "pivot": [ 0.0, 24.0, 0.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes": [ + { + "origin": [ -4.0, 24.0, -4.0 ], + "size": [ 8, 8, 8 ], + "uv": [ 0, 0 ] + } + ] + }, + + { + "name": "hat", + "parent" : "head", + "pivot": [ 0.0, 24.0, 0.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes": [ + { + "origin": [ -4.0, 24.0, -4.0 ], + "size": [ 8, 8, 8 ], + "uv": [ 32, 0 ], + "inflate": 0.5 + } + ] + }, + + + { + "name": "leftArm", + "parent" : "body", + "pivot": [ 5.0, 21.5, 0.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes": [ + { + "origin": [ 4.0, 12, -2.0 ], + "size": [ 3, 12, 4 ], + "uv": [ 32, 48 ] + } + ] + }, + { + "name": "rightArm", + "parent" : "body", + "pivot": [ -5.0, 21.5, 0.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes": [ + { + "origin": [ -7.0, 12, -2.0 ], + "size": [ 3, 12, 4 ], + "uv": [ 40, 16 ] + } + ] + }, + + { + "name": "leftSleeve", + "parent" : "leftArm", + "pivot": [ 5.0, 21.5, 0.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes": [ + { + "origin": [ 4.0, 11.5, -2.0 ], + "size": [ 3, 12, 4 ], + "uv": [ 48, 48 ], + "inflate": 0.25 + } + ] + }, + + { + "name": "rightSleeve", + "parent" : "rightArm", + "pivot": [ -5.0, 21.5, 0.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes": [ + { + "origin": [ -7.0, 11.5, -2.0 ], + "size": [ 3, 12, 4 ], + "uv": [ 40, 32 ], + "inflate": 0.25 + } + ] + }, + + + { + "name": "leftLeg", + "parent" : "root", + "pivot": [ 1.9, 12.0, 0.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes": [ + { + "origin": [ -0.1, 0.0, -2.0 ], + "size": [ 4, 12, 4 ], + "uv": [ 0, 16 ] + } + ] + }, + + { + "name": "rightLeg", + "parent" : "root", + "pivot": [ -1.9, 12.0, 0.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes": [ + { + "origin": [ -3.9, 0.0, -2.0 ], + "size": [ 4, 12, 4 ], + "uv": [ 0, 16 ] + } + ] + }, + + { + "name": "leftPants", + "parent" : "leftLeg", + "pivot": [1.9, 12.0, 0.0], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes": [ + { + "origin": [ -0.1, 0.0, -2.0 ], + "size": [ 4, 12, 4 ], + "uv": [ 0, 48 ], + "inflate": 0.25 + } + ] + }, + + { + "name": "rightPants", + "parent" : "rightLeg", + "pivot": [ -1.9, 12.0, 0.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes": [ + { + "origin": [ -3.9, 0.0, -2.0] , + "size": [ 4, 12, 4 ], + "uv": [ 0, 32], + "inflate": 0.25 + } + ] + }, + + + { + "name" : "rightItem", + "parent" : "rightArm", + "pivot" : [ -6.0, 15.0, 1.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes" : [] + }, + + { + "name" : "leftItem", + "parent" : "leftArm", + "pivot" : [ 6.0, 15.0, 1.0 ], + "rotation" : [ 0.0, 0.0, 0.0 ], + "cubes" : [] + }, + + + { + "name": "leftEar", + "parent" : "head", + "pivot": [ -1.9, 12.0, 0.0 ], + "cubes": [ + { + "origin": [ 3.0, 31.0, -0.5 ], + "size": [ 6, 6, 1 ], + "uv": [ 24, 0 ], + "inflate": 0.5 + } + ] + }, + + { + "name": "rightEar", + "parent" : "head", + "pivot": [ -1.9, 12.0, 0.0 ], + "cubes": [ + { + "origin": [ -9.0, 31.0, -0.5 ], + "size": [ 6, 6, 1 ], + "uv": [ 24, 0 ], + "inflate": 0.5 + } + ] + } + ], + "description": { + "identifier": "geometry.humanoid.earsSlim", + "texture_height": 64, + "texture_width": 64 + } + } + ] +} \ No newline at end of file diff --git a/connector/src/main/resources/bedrock/skin/skin_alex.png b/connector/src/main/resources/bedrock/skin/skin_alex.png new file mode 100644 index 00000000..ffd8e071 Binary files /dev/null and b/connector/src/main/resources/bedrock/skin/skin_alex.png differ diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index ae0cbed8..931e0a8d 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -12,7 +12,7 @@ bedrock: address: 0.0.0.0 # The port that will listen for connections port: 19132 - # The MOTD that will be broadcasted to Minecraft: Bedrock Edition clients + # The MOTD that will be broadcasted to Minecraft: Bedrock Edition clients. Irrelevant if "passthrough-motd" is set to true motd1: "GeyserMC" motd2: "Another GeyserMC forced host." remote: @@ -41,8 +41,21 @@ floodgate-key-file: public-key.pem # email: herpderp@derpherp.com # password: dooooo -# Relay the MOTD, player count and max players from the remote server -ping-passthrough: false +# Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. +# Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. +command-suggestions: true + +# The following two options enable "ping passthrough" - the MOTD and/or player count gets retrieved from the Java server. +# Relay the MOTD from the remote server to Bedrock players. +passthrough-motd: false +# Relay the player count and max players from the remote server to Bedrock players. +passthrough-player-counts: false +# Enable LEGACY ping passthrough. There is no need to enable this unless your MOTD or player count does not appear properly. +# This option does nothing on standalone. +legacy-ping-passthrough: false +# How often to ping the remote server, in seconds. Only relevant for standalone or legacy ping passthrough. +# Increase if you are getting BrokenPipe errors. +ping-passthrough-interval: 3 # Maximum amount of players that can connect max-players: 100 @@ -57,9 +70,28 @@ general-thread-pool: 32 # OptiFine capes, LabyMod capes, 5Zig capes and MinecraftCapes allow-third-party-capes: true +# Allow third party deadmau5 ears to be visible. Currently allowing: +# MinecraftCapes +allow-third-party-ears: false + # The default locale if we dont have the one the client requested default-locale: en_us +# Configures if chunk caching should be enabled or not. This keeps an individual +# record of each block the client loads in. While this feature does allow for a few +# things such as block break animations to show up in creative mode and among others, +# it is HIGHLY recommended you disable this on a production environment as it can eat +# up a lot of RAM. However, when using the Bukkit version of Geyser, support for features +# or implementations this allows is automatically enabled without the additional caching as +# Geyser has direct access to the server itself. +cache-chunks: false + +# Bedrock prevents building and displaying blocks above Y127 in the Nether - +# enabling this config option works around that by changing the Nether dimension ID +# to the End ID. The main downside to this is that the sky will resemble that of +# the end sky in the nether, but ultimately it's the only way for this feature to work. +above-bedrock-nether-building: false + # 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/. @@ -69,3 +101,6 @@ metrics: enabled: true # UUID of server, don't change! uuid: generateduuid + +# DO NOT TOUCH! +config-version: 3 diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index 56e49014..a67cc940 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 56e4901437dde12458020e81ea76f6087aac7de8 +Subproject commit a67cc940c0d47874c833ffeb58f38e33eabfcc33 diff --git a/pom.xml b/pom.xml index fc1ead33..a82d6217 100644 --- a/pom.xml +++ b/pom.xml @@ -67,6 +67,10 @@ true + + viaversion-repo + https://repo.viaversion.com +