diff --git a/README.md b/README.md index 713e77b7..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 diff --git a/bootstrap/bukkit/pom.xml b/bootstrap/bukkit/pom.xml index 94fab83b..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 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 2213f90b..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 @@ -87,8 +87,28 @@ public class GeyserBukkitConfiguration implements GeyserConfiguration { } @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 @@ -111,6 +131,11 @@ public class GeyserBukkitConfiguration implements GeyserConfiguration { 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"); @@ -126,6 +151,11 @@ public class GeyserBukkitConfiguration implements GeyserConfiguration { 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 public IMetricsInfo getMetrics() { return metricsInfo; @@ -203,4 +233,9 @@ public class GeyserBukkitConfiguration implements GeyserConfiguration { 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/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 d6bd31ac..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,13 +28,18 @@ package org.geysermc.platform.bukkit; import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; import org.geysermc.common.PlatformType; +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; @@ -43,6 +48,8 @@ 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; @@ -59,7 +66,7 @@ public class GeyserBukkitPlugin extends JavaPlugin implements GeyserBootstrap { // 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()); } @@ -67,13 +74,39 @@ public class GeyserBukkitPlugin extends JavaPlugin implements GeyserBootstrap { 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); - this.geyserWorldManager = new GeyserBukkitWorldManager(); + + 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)); } @@ -98,8 +131,47 @@ public class GeyserBukkitPlugin extends JavaPlugin implements GeyserBootstrap { 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 index 07fc3136..fbdf2a47 100644 --- 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 @@ -28,18 +28,49 @@ 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 b9f06916..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 @@ -80,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 f8e40141..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 @@ -86,8 +86,28 @@ public class GeyserBungeeConfiguration implements GeyserConfiguration { } @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 @@ -110,6 +130,11 @@ public class GeyserBungeeConfiguration implements GeyserConfiguration { 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"); @@ -125,6 +150,11 @@ public class GeyserBungeeConfiguration implements GeyserConfiguration { return config.getBoolean("cache-chunks", false); } + @Override + public boolean isAboveBedrockNetherBuilding() { + return config.getBoolean("above-bedrock-nether-building", false); + } + @Override public BungeeMetricsInfo getMetrics() { return metricsInfo; @@ -202,4 +232,9 @@ public class GeyserBungeeConfiguration implements GeyserConfiguration { 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/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 bfd0cf49..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.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; @@ -50,6 +53,7 @@ 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 GeyserBootstrap { // 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,6 +120,7 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { } this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode()); + GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); geyserConfig.loadFloodgate(this); @@ -123,6 +128,12 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { 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)); } @@ -145,4 +156,9 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { public CommandManager getGeyserCommandManager() { return this.geyserCommandManager; } + + @Override + public IGeyserPingPassthrough getGeyserPingPassthrough() { + return geyserBungeePingPassthrough; + } } 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 e8459550..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 @@ -80,8 +80,28 @@ public class GeyserSpongeConfiguration implements GeyserConfiguration { } @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 @@ -104,6 +124,11 @@ public class GeyserSpongeConfiguration implements GeyserConfiguration { 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"); @@ -119,6 +144,11 @@ public class GeyserSpongeConfiguration implements GeyserConfiguration { 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; @@ -202,4 +232,9 @@ public class GeyserSpongeConfiguration implements GeyserConfiguration { 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/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 01d71f1c..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.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; @@ -62,6 +65,7 @@ public class GeyserSpongePlugin implements GeyserBootstrap { private GeyserSpongeCommandManager geyserCommandManager; private GeyserSpongeConfiguration geyserConfig; private GeyserSpongeLogger geyserLogger; + private IGeyserPingPassthrough geyserSpongePingPassthrough; private GeyserConnector connector; @@ -97,7 +101,7 @@ public class GeyserSpongePlugin implements GeyserBootstrap { // 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 GeyserBootstrap { } 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 GeyserBootstrap { return this.geyserCommandManager; } + @Override + public IGeyserPingPassthrough getGeyserPingPassthrough() { + return geyserSpongePingPassthrough; + } + @Listener public void onServerStart(GameStartedServerEvent event) { onEnable(); diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java index 9d249363..aa0d2392 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java @@ -26,10 +26,12 @@ package org.geysermc.platform.standalone; import org.geysermc.common.PlatformType; +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; @@ -40,8 +42,9 @@ import java.util.UUID; public class GeyserStandaloneBootstrap implements GeyserBootstrap { private GeyserCommandManager geyserCommandManager; - private GeyserConfiguration geyserConfig; + private GeyserStandaloneConfiguration geyserConfig; private GeyserStandaloneLogger geyserLogger; + private IGeyserPingPassthrough geyserPingPassthrough; private GeyserConnector connector; @@ -62,9 +65,13 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { 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(); } @@ -88,4 +95,9 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { public CommandManager getGeyserCommandManager() { return geyserCommandManager; } + + @Override + public IGeyserPingPassthrough getGeyserPingPassthrough() { + return geyserPingPassthrough; + } } diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneConfiguration.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneConfiguration.java index 8ae83d02..bd029204 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneConfiguration.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneConfiguration.java @@ -47,8 +47,20 @@ public class GeyserStandaloneConfiguration implements GeyserConfiguration { 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; @@ -62,12 +74,18 @@ public class GeyserStandaloneConfiguration implements GeyserConfiguration { @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 @@ -112,4 +130,7 @@ public class GeyserStandaloneConfiguration implements GeyserConfiguration { @JsonProperty("uuid") private String uniqueId; } + + @JsonProperty("config-version") + private int configVersion; } 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 ff99f8ab..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 @@ -52,8 +52,20 @@ public class GeyserVelocityConfiguration implements GeyserConfiguration { 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; @@ -67,19 +79,25 @@ public class GeyserVelocityConfiguration implements GeyserConfiguration { @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"); - floodgateKey = FloodgateKeyLoader.getKey(plugin.getGeyserLogger(), this, Paths.get(dataFolder.toString(), floodgateKeyFile.isEmpty() ? floodgateKeyFile : "public-key.pem"), floodgate.get(), Paths.get("plugins/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 @@ -127,4 +145,7 @@ public class GeyserVelocityConfiguration implements GeyserConfiguration { @JsonProperty("uuid") private String uniqueId; } + + @JsonProperty("config-version") + private int configVersion; } 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 bd892413..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.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; @@ -62,6 +65,7 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { private GeyserVelocityCommandManager geyserCommandManager; private GeyserVelocityConfiguration geyserConfig; private GeyserVelocityLogger geyserLogger; + private IGeyserPingPassthrough geyserPingPassthrough; private GeyserConnector connector; @@ -83,13 +87,14 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { // 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); @@ -97,6 +102,11 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { 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 @@ -119,6 +129,11 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { 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/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 bd8e4002..93d3c121 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/DeviceOS.java +++ b/common/src/main/java/org/geysermc/floodgate/util/DeviceOS.java @@ -43,7 +43,8 @@ public enum DeviceOS { ORBIS("PS4"), NX("Switch"), SWITCH("Switch"), - XBOX_ONE("Xbox One"); + XBOX_ONE("Xbox One"), + WIN_PHONE("Windows Phone"); private static final DeviceOS[] VALUES = values(); diff --git a/connector/pom.xml b/connector/pom.xml index 229761ad..91cf6f3d 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -98,14 +98,8 @@ com.github.steveice10 - opennbt - 1.4-SNAPSHOT - compile - - - com.github.steveice10 - packetlib - 1.5-SNAPSHOT + mcprotocollib + 4c315aa206 compile @@ -115,31 +109,11 @@ - com.github.steveice10 - mcauthlib - 1.3-SNAPSHOT + io.netty + netty-resolver-dns + 4.1.43.Final compile - - com.github.steveice10 - mcprotocollib - 1.15.2-1-SNAPSHOT - compile - - - com.github.steveice10 - opennbt - - - com.github.steveice10 - packetlib - - - com.github.steveice10 - mcauthlib - - - org.reflections reflections diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConfiguration.java b/connector/src/main/java/org/geysermc/connector/GeyserConfiguration.java index cbbd507a..8a39323b 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConfiguration.java @@ -31,13 +31,24 @@ 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 isPingPassthrough(); + boolean isCommandSuggestions(); + + boolean isPassthroughMotd(); + + boolean isPassthroughPlayerCounts(); + + boolean isLegacyPingPassthrough(); + + int getPingPassthroughInterval(); int getMaxPlayers(); @@ -47,10 +58,14 @@ public interface GeyserConfiguration { boolean isAllowThirdPartyCapes(); + boolean isAllowThirdPartyEars(); + String getDefaultLocale(); Path getFloodgateKeyFile(); + boolean isAboveBedrockNetherBuilding(); + boolean isCacheChunks(); IMetricsInfo getMetrics(); @@ -87,4 +102,14 @@ public interface GeyserConfiguration { 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 72995293..02e0c500 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -25,6 +25,8 @@ 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; @@ -37,10 +39,20 @@ 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.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.thread.PingPassthroughThread; -import org.geysermc.connector.utils.Toolbox; +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; @@ -54,6 +66,8 @@ 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"; @@ -69,7 +83,6 @@ public class GeyserConnector { private boolean shuttingDown = false; private final ScheduledExecutorService generalThreadPool; - private PingPassthroughThread passthroughThread; private BedrockServer bedrockServer; private PlatformType platformType; @@ -99,15 +112,29 @@ public class GeyserConnector { logger.setDebug(config.isDebugMode()); - Toolbox.init(); - Translators.start(); + 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)); diff --git a/connector/src/main/java/org/geysermc/connector/bootstrap/GeyserBootstrap.java b/connector/src/main/java/org/geysermc/connector/bootstrap/GeyserBootstrap.java index 24b338c8..24ce81cf 100644 --- a/connector/src/main/java/org/geysermc/connector/bootstrap/GeyserBootstrap.java +++ b/connector/src/main/java/org/geysermc/connector/bootstrap/GeyserBootstrap.java @@ -26,6 +26,7 @@ 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; @@ -67,6 +68,13 @@ public interface GeyserBootstrap { */ CommandManager getGeyserCommandManager(); + /** + * Returns the current PingPassthrough manager + * + * @return The current PingPassthrough manager + */ + IGeyserPingPassthrough getGeyserPingPassthrough(); + /** * Returns the current WorldManager * 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/DefaultBlockMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/DefaultBlockMinecartEntity.java new file mode 100644 index 00000000..b774af98 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/DefaultBlockMinecartEntity.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.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; + +/** + * This class is used as a base for minecarts with a default block to display like furnaces and spawners + */ +public class DefaultBlockMinecartEntity extends MinecartEntity { + + public int customBlock = 0; + public int customBlockOffset = 0; + public boolean showCustomBlock = false; + + public DefaultBlockMinecartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + + updateDefaultBlockMetadata(); + metadata.put(EntityData.HAS_DISPLAY, (byte) 1); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + + // Custom block + if (entityMetadata.getId() == 10) { + customBlock = (int) entityMetadata.getValue(); + + if (showCustomBlock) { + metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId(customBlock)); + } + } + + // Custom block offset + if (entityMetadata.getId() == 11) { + customBlockOffset = (int) entityMetadata.getValue(); + + if (showCustomBlock) { + metadata.put(EntityData.DISPLAY_OFFSET, customBlockOffset); + } + } + + // If the custom block should be enabled + if (entityMetadata.getId() == 12) { + if ((boolean) entityMetadata.getValue()) { + showCustomBlock = true; + metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId(customBlock)); + metadata.put(EntityData.DISPLAY_OFFSET, customBlockOffset); + } else { + showCustomBlock = false; + updateDefaultBlockMetadata(); + } + } + + super.updateBedrockMetadata(entityMetadata, session); + } + + public void updateDefaultBlockMetadata() { } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/EnderCrystalEntity.java b/connector/src/main/java/org/geysermc/connector/entity/EnderCrystalEntity.java index 31f7b8ed..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; 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 7e8a190d..16e9c0a1 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(); @@ -94,7 +102,7 @@ public class Entity { metadata.put(EntityData.SCALE, 1f); metadata.put(EntityData.COLOR, 0); - metadata.put(EntityData.MAX_AIR, (short) 400); + 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()); @@ -119,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 + ")"); } @@ -135,7 +143,7 @@ public class Entity { RemoveEntityPacket removeEntityPacket = new RemoveEntityPacket(); removeEntityPacket.setUniqueEntityId(geyserId); - session.getUpstream().sendPacket(removeEntityPacket); + session.sendUpstreamPacket(removeEntityPacket); valid = false; return true; @@ -156,7 +164,7 @@ 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, boolean teleported) { @@ -174,7 +182,7 @@ public class Entity { moveEntityPacket.setOnGround(isOnGround); moveEntityPacket.setTeleported(teleported); - session.getUpstream().sendPacket(moveEntityPacket); + session.sendUpstreamPacket(moveEntityPacket); } public void updateBedrockAttributes(GeyserSession session) { @@ -191,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) { @@ -205,38 +213,55 @@ public class Entity { metadata.getFlags().setFlag(EntityFlag.SWIMMING, (xd & 0x10) == 0x10); metadata.getFlags().setFlag(EntityFlag.GLIDING, (xd & 0x80) == 0x80); + if ((xd & 0x20) == 0x20) { + // Armour stands are handled in their own class + if (!this.is(ArmorStandEntity.class)) { + metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true); + } + } else { + metadata.getFlags().setFlag(EntityFlag.INVISIBLE, false); + } + // Shield code if (session.getPlayerEntity().getEntityId() == entityId && metadata.getFlags().getFlag(EntityFlag.SNEAKING)) { - if ((session.getInventory().getItemInHand() != null && session.getInventory().getItemInHand().getId() == 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)) @@ -248,6 +273,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(); @@ -265,11 +316,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 2fd06c23..bfd3e1ca 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ExpOrbEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ExpOrbEntity.java @@ -27,13 +27,9 @@ package org.geysermc.connector.entity; import com.nukkitx.math.vector.Vector3f; 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 java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - public class ExpOrbEntity extends Entity { private int amount; 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 e82a42a2..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,13 +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 updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 7) { + Entity entity = session.getEntityCache().getEntityByJavaId((Integer) entityMetadata.getValue() - 1); + if (entity == null && session.getPlayerEntity().getEntityId() == (Integer) entityMetadata.getValue() - 1) { + entity = session.getPlayerEntity(); + } + + if (entity != null) { + metadata.put(EntityData.TARGET_EID, entity.getGeyserId()); + } + } + + super.updateBedrockMetadata(entityMetadata, session); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.java new file mode 100644 index 00000000..29ade193 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.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.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 DefaultBlockMinecartEntity { + + private boolean hasFuel = false; + + public FurnaceMinecartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 13 && !showCustomBlock) { + hasFuel = (boolean) entityMetadata.getValue(); + updateDefaultBlockMetadata(); + } + + super.updateBedrockMetadata(entityMetadata, session); + } + + @Override + public void updateDefaultBlockMetadata() { + metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId(hasFuel ? BlockTranslator.JAVA_RUNTIME_FURNACE_LIT_ID : BlockTranslator.JAVA_RUNTIME_FURNACE_ID)); + metadata.put(EntityData.DISPLAY_OFFSET, 6); + } +} 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 index d49f6e17..3680945c 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java @@ -27,7 +27,7 @@ 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.type.object.HangingDirection; +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; @@ -38,10 +38,10 @@ 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.Translators; 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 org.geysermc.connector.utils.Toolbox; import java.util.concurrent.TimeUnit; @@ -87,20 +87,23 @@ public class ItemFrameEntity extends Entity { @Override public void spawnEntity(GeyserSession session) { session.getItemFrameCache().put(bedrockPosition, entityId); - updateBlock(session); + // 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; - session.getConnector().getLogger().debug("Spawned item frame at location " + bedrockPosition + " with java id " + entityId); } @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 7 && entityMetadata.getValue() != null) { - ItemData itemData = Translators.getItemTranslator().translateToBedrock(session, (ItemStack) entityMetadata.getValue()); - ItemEntry itemEntry = Translators.getItemTranslator().getItem((ItemStack) entityMetadata.getValue()); + 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: Toolbox.ITEMS) { + for (StartGamePacket.ItemEntry startGamePacketItemEntry : ItemRegistry.ITEMS) { if (startGamePacketItemEntry.getId() == (short) itemEntry.getBedrockId()) { blockName = startGamePacketItemEntry.getIdentifier(); break; @@ -149,7 +152,7 @@ public class ItemFrameEntity extends Entity { updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NONE); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); - session.getUpstream().sendPacket(updateBlockPacket); + session.sendUpstreamPacket(updateBlockPacket); session.getItemFrameCache().remove(position, entityId); valid = false; return true; @@ -170,27 +173,24 @@ public class ItemFrameEntity extends Entity { * @param session GeyserSession. */ public void updateBlock(GeyserSession session) { - // Delay is required, or else loading in frames on chunk load is sketchy at best - session.getConnector().getGeneralThreadPool().schedule(() -> { - 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.getUpstream().sendPacket(updateBlockPacket); + 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()); - } + BlockEntityDataPacket blockEntityDataPacket = new BlockEntityDataPacket(); + blockEntityDataPacket.setBlockPosition(bedrockPosition); + if (cachedTag != null) { + blockEntityDataPacket.setData(cachedTag); + } else { + blockEntityDataPacket.setData(getDefaultTag()); + } - session.getUpstream().sendPacket(blockEntityDataPacket); - }, 500, TimeUnit.MILLISECONDS); + session.sendUpstreamPacket(blockEntityDataPacket); } /** 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..a67c0be5 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 DefaultBlockMinecartEntity)) { // Handled in the DefaultBlockMinecartEntity class + // Custom block + if (entityMetadata.getId() == 10) { + metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId((int) entityMetadata.getValue())); + } + + // Custom block offset + if (entityMetadata.getId() == 11) { + metadata.put(EntityData.DISPLAY_OFFSET, entityMetadata.getValue()); + } + + // If the custom block should be enabled + if (entityMetadata.getId() == 12) { + // Needs a byte based off of Java's boolean + metadata.put(EntityData.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 d4732168..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); @@ -93,8 +98,13 @@ 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); @@ -108,7 +118,7 @@ public class PlayerEntity extends LivingEntity { PlayerListPacket playerList = new PlayerListPacket(); playerList.setAction(PlayerListPacket.Action.ADD); playerList.getEntries().add(SkinUtils.buildDefaultEntry(profile, geyserId)); - session.getUpstream().sendPacket(playerList); + session.sendUpstreamPacket(playerList); } } @@ -124,7 +134,7 @@ 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); }); } } @@ -134,6 +144,8 @@ public class PlayerEntity extends LivingEntity { setPosition(position); setRotation(rotation); + this.onGround = isOnGround; + MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); movePlayerPacket.setRuntimeEntityId(geyserId); movePlayerPacket.setPosition(this.position); @@ -145,7 +157,13 @@ public class PlayerEntity extends LivingEntity { movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.UNKNOWN); } - session.getUpstream().sendPacket(movePlayerPacket); + session.sendUpstreamPacket(movePlayerPacket); + if (leftParrot != null) { + leftParrot.moveAbsolute(session, position, rotation, true, teleported); + } + if (rightParrot != null) { + rightParrot.moveAbsolute(session, position, rotation, true, teleported); + } } @Override @@ -153,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 @@ -188,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/SpawnerMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/SpawnerMinecartEntity.java new file mode 100644 index 00000000..6be138c0 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/SpawnerMinecartEntity.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.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.world.block.BlockTranslator; + +public class SpawnerMinecartEntity extends DefaultBlockMinecartEntity { + + public SpawnerMinecartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateDefaultBlockMetadata() { + metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId(BlockTranslator.JAVA_RUNTIME_SPAWNER_ID)); + metadata.put(EntityData.DISPLAY_OFFSET, 6); + } +} 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..47faad36 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 @@ -35,16 +35,60 @@ import org.geysermc.connector.network.session.GeyserSession; public class ArmorStandEntity extends LivingEntity { + // These are used to store the state of the armour stand for use when handling invisibility + private boolean isMarker = false; + private boolean isInvisible = false; + private boolean isSmall = false; + public ArmorStandEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } + @Override + public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { + // Fake the height to be above where it is so the nametag appears in the right location for invisible non-marker armour stands + if (!isMarker && isInvisible) { + position = position.add(0d, entityType.getHeight() * (isSmall ? 0.55d : 1d), 0d); + } + + super.moveAbsolute(session, position, rotation, isOnGround, teleported); + } + @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getType() == MetadataType.BYTE) { + if (entityMetadata.getId() == 0 && 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); + + // Check if the armour stand is invisible and store accordingly + if ((xd & 0x20) == 0x20) { + metadata.put(EntityData.SCALE, 0.0f); + isInvisible = true; + } + } else if (entityMetadata.getId() == 14 && entityMetadata.getType() == MetadataType.BYTE) { + byte xd = (byte) entityMetadata.getValue(); + + // isSmall + if ((xd & 0x01) == 0x01) { + isSmall = true; + + if (metadata.getFloat(EntityData.SCALE) != 0.55f && metadata.getFloat(EntityData.SCALE) != 0.0f) { + metadata.put(EntityData.SCALE, 0.55f); + } + + if (metadata.get(EntityData.BOUNDING_BOX_WIDTH) != null && metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.5f)) { + metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.25f); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.9875f); + } + } else if (metadata.get(EntityData.BOUNDING_BOX_WIDTH) != null && metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.25f)) { + metadata.put(EntityData.BOUNDING_BOX_WIDTH, entityType.getWidth()); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, entityType.getHeight()); + } + + // setMarker + if ((xd & 0x10) == 0x10 && (metadata.get(EntityData.BOUNDING_BOX_WIDTH) == null || !metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.0f))) { + metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.0f); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.0f); + isMarker = true; } } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/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 d5e9a687..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; 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 c2dad7a5..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 @@ -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 08a71836..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 @@ -26,11 +26,19 @@ package org.geysermc.connector.entity.living.animal.horse; 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 TraderLlamaEntity extends LlamaEntity { public TraderLlamaEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } + + @Override + public void spawnEntity(GeyserSession session) { + this.metadata.put(EntityData.MARK_VARIANT, 1); + super.spawnEntity(session); + } } 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/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 98d836d2..3acc17c3 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 @@ -47,7 +47,7 @@ public enum EntityType { WOLF(WolfEntity.class, 14, 0.85f, 0.6f), 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), @@ -64,16 +64,17 @@ public enum EntityType { 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,50 +95,52 @@ 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(TNTEntity.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, 0f, 0f, "minecraft:xp_bottle"), + 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, 0f), - END_CRYSTAL(EnderCrystalEntity.class, 71, 0f, 0f, 0f, 0f, "minecraft:ender_crystal"), - FIREWORK_ROCKET(Entity.class, 72, 0f), - TRIDENT(ArrowEntity.class, 73, 0f), + 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), + 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_SPAWNER(SpawnerMinecartEntity.class, 98, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:minecart"), + MINECART_COMMAND_BLOCK(MinecartEntity.class, 100, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:command_block_minecart"), 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, 0.5f, 0f, "minecraft:evocation_fang"), + EVOKER(SpellcasterIllagerEntity.class, 104, 1.95f, 0.6f, 0.6f, 0f, "minecraft:evocation_illager"), + 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), @@ -152,7 +155,14 @@ public enum EntityType { /** * Item frames are handled differently since they are a block in Bedrock. */ - ITEM_FRAME(ItemFrameEntity.class, 0, 0, 0); + 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; @@ -163,7 +173,7 @@ public enum EntityType { 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) { @@ -189,4 +199,14 @@ public enum EntityType { 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 8810cffb..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,11 +25,12 @@ 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 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; @@ -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; @@ -56,29 +57,39 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler { connector.getLogger().debug(inetSocketAddress + " has pinged you!"); GeyserConfiguration config = connector.getConfig(); - ServerStatusInfo serverInfo = connector.getPassthroughThread().getInfo(); + + 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 @@ -105,4 +116,9 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler { }); 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 2839237e..67862d0e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -31,7 +31,7 @@ import org.geysermc.common.AuthType; 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.network.translators.PacketTranslatorRegistry; import org.geysermc.connector.utils.LoginEncryptionUtils; public class UpstreamPacketHandler extends LoggingPacketHandler { @@ -41,7 +41,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 @@ -58,10 +58,10 @@ 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(); - session.getUpstream().sendPacket(resourcePacksInfo); + session.sendUpstreamPacket(resourcePacksInfo); return true; } @@ -77,7 +77,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { stack.setExperimental(false); stack.setForcedToAccept(false); stack.setGameVersion("*"); - session.getUpstream().sendPacket(stack); + session.sendUpstreamPacket(stack); break; default: session.disconnect("disconnectionScreen.resourcePack"); @@ -110,15 +110,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 88fba8b0..0607bb3a 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,11 +29,13 @@ 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.packet.ingame.client.world.ClientTeleportConfirmPacket; import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket; 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; @@ -41,6 +43,7 @@ import com.github.steveice10.packetlib.tcp.TcpSessionFactory; import com.nukkitx.math.GenericMath; import com.nukkitx.math.TrigMath; 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; @@ -55,18 +58,22 @@ 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.world.WorldBorder; +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.world.WorldBorder; +import org.geysermc.connector.utils.SkinUtils; import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.EncryptionUtil; @@ -154,11 +161,18 @@ public class GeyserSession implements CommandSender { private boolean manyDimPackets = false; private ServerRespawnPacket lastDimPacket = null; + @Setter + private Entity ridingVehicleEntity; + @Setter private int craftSlot = 0; @Setter - private WorldBorder worldBorder; // Im just going to shove this here until i move some stuff around + private WorldBorder worldBorder; + @Setter + private long lastWindowCloseTime = 0; + + private MinecraftProtocol protocol; public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) { this.connector = connector; @@ -188,16 +202,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(); @@ -205,6 +219,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( @@ -231,7 +256,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 { @@ -329,13 +353,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(); @@ -480,7 +524,7 @@ 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.sendPacket(startGamePacket); @@ -496,8 +540,47 @@ public class GeyserSession implements CommandSender { int teleportId = teleportCache.getTeleportConfirmId(); teleportCache = null; ClientTeleportConfirmPacket teleportConfirmPacket = new ClientTeleportConfirmPacket(teleportId); - getDownstream().getSession().send(teleportConfirmPacket); + 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/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/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 423ffd39..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(), 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 03042e3a..00000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/Translators.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators; - -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 it.unimi.dsi.fastutil.objects.ObjectArrayList; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.network.translators.world.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<>(); - - @Getter - private static ObjectArrayList requiresBlockStateMap = new ObjectArrayList<>(); - - 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.world.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() + "."); - } - } - - for (Class clazz : ref.getSubTypesOf(RequiresBlockState.class)) { - - GeyserConnector.getInstance().getLogger().debug("Found block entity that requires block state: " + clazz.getCanonicalName()); - - try { - requiresBlockStateMap.add((RequiresBlockState) clazz.newInstance()); - } catch (InstantiationException | IllegalAccessException e) { - GeyserConnector.getInstance().getLogger().error("Could not instantiate required block state " + 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 d7248aa3..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 @@ -64,44 +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 { @@ -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 ed928959..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,6 +25,7 @@ package org.geysermc.connector.network.translators.bedrock; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; @@ -48,9 +49,9 @@ 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 org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.ItemTranslator; +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; @@ -64,12 +65,12 @@ public class BedrockInventoryTransactionTranslator 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 49b0b7fc..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 @@ -55,7 +55,7 @@ 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 { } ClientChatPacket chatPacket = new ClientChatPacket(message); - session.getDownstream().getSession().send(chatPacket); + session.sendDownstreamPacket(chatPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/EffectUtils.java b/connector/src/main/java/org/geysermc/connector/network/translators/effect/EffectRegistry.java similarity index 64% rename from connector/src/main/java/org/geysermc/connector/utils/EffectUtils.java rename to connector/src/main/java/org/geysermc/connector/network/translators/effect/EffectRegistry.java index 7c1690e4..50888090 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/EffectUtils.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/effect/EffectRegistry.java @@ -1,49 +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: + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to 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.utils; +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.network.translators.effect.Effect; +import org.geysermc.connector.utils.FileUtils; import java.io.InputStream; import java.util.HashMap; import java.util.Iterator; import java.util.Map; -public class EffectUtils { +/** + * Registry for particles and effects. + */ +public class EffectRegistry { public static final Map EFFECTS = new HashMap<>(); public static final Int2ObjectMap RECORDS = new Int2ObjectOpenHashMap<>(); @@ -57,10 +58,10 @@ public class EffectUtils { static { /* Load particles */ - InputStream particleStream = Toolbox.getResource("mappings/particles.json"); + InputStream particleStream = FileUtils.getResource("mappings/particles.json"); JsonNode particleEntries; try { - particleEntries = Toolbox.JSON_MAPPER.readTree(particleStream); + particleEntries = GeyserConnector.JSON_MAPPER.readTree(particleStream); } catch (Exception e) { throw new AssertionError("Unable to load particle map", e); } @@ -69,10 +70,10 @@ public class EffectUtils { while (particlesIterator.hasNext()) { Map.Entry entry = particlesIterator.next(); try { - setIdentifier(ParticleType.valueOf(entry.getKey().toUpperCase()), LevelEventType.valueOf(entry.getValue().asText().toUpperCase())); + particleTypeMap.put(ParticleType.valueOf(entry.getKey().toUpperCase()), LevelEventType.valueOf(entry.getValue().asText().toUpperCase())); } catch (IllegalArgumentException e1) { try { - setIdentifier(ParticleType.valueOf(entry.getKey().toUpperCase()), entry.getValue().asText()); + particleStringMap.put(ParticleType.valueOf(entry.getKey().toUpperCase()), entry.getValue().asText()); GeyserConnector.getInstance().getLogger().debug("Force to map particle " + entry.getKey() + "=>" @@ -85,10 +86,10 @@ public class EffectUtils { } /* Load effects */ - InputStream effectsStream = Toolbox.getResource("mappings/effects.json"); + InputStream effectsStream = FileUtils.getResource("mappings/effects.json"); JsonNode effects; try { - effects = Toolbox.JSON_MAPPER.readTree(effectsStream); + effects = GeyserConnector.JSON_MAPPER.readTree(effectsStream); } catch (Exception e) { throw new AssertionError("Unable to load effects mappings", e); } @@ -112,14 +113,6 @@ public class EffectUtils { } } - public static void setIdentifier(ParticleType type, LevelEventType identifier) { - particleTypeMap.put(type, identifier); - } - - public static void setIdentifier(ParticleType type, String identifier) { - particleStringMap.put(type, identifier); - } - public static LevelEventType getParticleLevelEventType(@NonNull ParticleType type) { return particleTypeMap.getOrDefault(type, null); } @@ -127,5 +120,4 @@ public class EffectUtils { 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/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 720280b4..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 @@ -39,15 +39,13 @@ 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,7 +103,7 @@ 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 @@ -117,7 +115,7 @@ public class DoubleChestInventoryTranslator extends BaseInventoryTranslator { 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()); @@ -126,16 +124,6 @@ public class DoubleChestInventoryTranslator extends BaseInventoryTranslator { 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 a6e5b2dd..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 @@ -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,7 +75,7 @@ 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 @@ -87,6 +87,6 @@ public class BlockInventoryHolder extends InventoryHolder { 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/utils/Toolbox.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java similarity index 52% rename from connector/src/main/java/org/geysermc/connector/utils/Toolbox.java rename to connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index 953b70a8..ed99ece3 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -1,91 +1,85 @@ /* * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/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.utils; +package org.geysermc.connector.network.translators.item; 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.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; 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 org.geysermc.connector.network.translators.sound.SoundHandlerRegistry; +import org.geysermc.connector.utils.FileUtils; -import java.io.*; -import java.util.*; +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; -public class Toolbox { +/** + * Registry for anything item related. + */ +public class ItemRegistry { + + private static final Map JAVA_IDENTIFIER_MAP = new HashMap<>(); - 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; + // 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 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) { - 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"); + InputStream stream = FileUtils.getResource("bedrock/items.json"); TypeReference> itemEntriesType = new TypeReference>() { }; List itemEntries; try { - itemEntries = JSON_MAPPER.readValue(stream, itemEntriesType); + itemEntries = GeyserConnector.JSON_MAPPER.readValue(stream, itemEntriesType); } catch (Exception e) { throw new AssertionError("Unable to load Bedrock runtime item IDs", e); } @@ -94,11 +88,11 @@ public class Toolbox { ITEMS.add(new StartGamePacket.ItemEntry(entry.get("name").textValue(), (short) entry.get("id").intValue())); } - stream = getResource("mappings/items.json"); + stream = FileUtils.getResource("mappings/items.json"); JsonNode items; try { - items = JSON_MAPPER.readTree(stream); + items = GeyserConnector.JSON_MAPPER.readTree(stream); } catch (Exception e) { throw new AssertionError("Unable to load Java runtime item IDs", e); } @@ -139,22 +133,12 @@ public class Toolbox { itemIndex++; } - // Load particle/effect mappings - EffectUtils.init(); - // Load sound mappings - SoundUtils.init(); - // Load the locale data - LocaleUtils.init(); - - // Load sound handlers - SoundHandlerRegistry.init(); - /* Load creative items */ - stream = getResource("bedrock/creative_items.json"); + stream = FileUtils.getResource("bedrock/creative_items.json"); JsonNode creativeItemEntries; try { - creativeItemEntries = JSON_MAPPER.readTree(stream).get("items"); + creativeItemEntries = GeyserConnector.JSON_MAPPER.readTree(stream).get("items"); } catch (Exception e) { throw new AssertionError("Unable to load creative items", e); } @@ -179,33 +163,51 @@ public class Toolbox { } } 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 + * Gets an {@link ItemEntry} from the given {@link ItemStack}. * - * @param resource Resource to get - * @return InputStream of the given resource + * @param stack the item stack + * @return an item entry from the given item stack */ - 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 ItemEntry getItem(ItemStack stack) { + return ITEM_ENTRIES.get(stack.getId()); } - public static void init() { - // no-op + /** + * 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; } -} \ No newline at end of file + + /** + * 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 f59b82ba..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<>(); + private static final Int2ObjectMap ITEM_STACK_TRANSLATORS = new Int2ObjectOpenHashMap<>(); + private static final List NBT_TRANSLATORS; - // Shield ID, used in Entity.java - public static final int SHIELD = 829; + protected ItemTranslator() { + } - public void init() { + 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,34 +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 { @@ -99,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); } @@ -108,62 +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); ItemStack itemStack = new ItemStack(stack.getId(), stack.getAmount(), stack.getNbt() != null ? stack.getNbt().clone() : null); if (itemStack.getNbt() != null) { - for (NbtItemStackTranslator translator : nbtItemTranslators) { + for (NbtItemStackTranslator translator : NBT_TRANSLATORS) { if (translator.acceptItem(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(itemStack, bedrockItem); + itemData = itemStackTranslator.translateToBedrock(itemStack, bedrockItem); } else { - return DEFAULT_TRANSLATOR.translateToBedrock(itemStack, 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/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 index 9458ec4f..776cec72 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java @@ -34,7 +34,7 @@ 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.NbtItemStackTranslator; +import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.utils.MessageUtils; @@ -46,49 +46,51 @@ public class BasicItemTranslator extends NbtItemStackTranslator { @Override public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) { - if (itemTag.contains("display")) { - CompoundTag displayTag = itemTag.get("display"); - if (displayTag.contains("Name")) { - StringTag nameTag = displayTag.get("Name"); + 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 { - displayTag.put(new StringTag("Name", toBedrockMessage(nameTag))); + lore.add(new StringTag("", toBedrockMessage((StringTag) tag))); } 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)); - } + displayTag.put(new ListTag("Lore", lore)); } } @Override public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { - if (itemTag.contains("display")) { - CompoundTag displayTag = itemTag.get("display"); - if (displayTag.contains("Name")) { - StringTag nameTag = displayTag.get("Name"); - displayTag.put(new StringTag("Name", toJavaMessage(nameTag))); - } + 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)); + 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)); } } 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 0e329669..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<>(); @@ -100,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 skinParts = Arrays.asList(SkinPart.values()); ClientSettingsPacket clientSettingsPacket = new ClientSettingsPacket(locale, (byte) session.getRenderDistance(), ChatVisibility.FULL, true, skinParts, Hand.MAIN_HAND); - session.getDownstream().getSession().send(clientSettingsPacket); + 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 31bfcf7c..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,6 +25,7 @@ package org.geysermc.connector.network.translators.java.entity.player; +import com.github.steveice10.mc.protocol.data.game.entity.player.PositionElement; import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerPositionRotationPacket; import com.nukkitx.math.vector.Vector3f; @@ -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) { - session.setTeleportCache(new TeleportCache(packet.getX(), packet.getY(), packet.getZ(), packet.getTeleportId())); - entity.moveAbsolute(session, Vector3f.from(packet.getX(), packet.getY(), packet.getZ()), packet.getYaw(), packet.getPitch(), true, true); - } else { + 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.getDownstream().getSession().send(teleportConfirmPacket); + session.sendDownstreamPacket(teleportConfirmPacket); + return; } } + + // If coordinates are relative, then add to the existing coordinate + double newX = packet.getX() + + (packet.getRelative().contains(PositionElement.X) ? entity.getPosition().getX() : 0); + double newY = packet.getY() + + (packet.getRelative().contains(PositionElement.Y) ? entity.getPosition().getY() - EntityType.PLAYER.getOffset() : 0); + double newZ = packet.getZ() + + (packet.getRelative().contains(PositionElement.Z) ? entity.getPosition().getZ() : 0); + + double newPitch = packet.getPitch() + + (packet.getRelative().contains(PositionElement.PITCH) ? entity.getBedrockRotation().getX() : 0); + double newYaw = packet.getYaw() + + (packet.getRelative().contains(PositionElement.YAW) ? entity.getBedrockRotation().getY() : 0); + + + session.setTeleportCache(new TeleportCache(newX, newY, newZ, packet.getTeleportId())); + entity.moveAbsolute(session, Vector3f.from(newX, newY, newZ), (float) newYaw, (float) newPitch, true, true); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerStopSoundTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerStopSoundTranslator.java index 3fc974c6..906c68db 100644 --- 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 @@ -32,7 +32,7 @@ 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.utils.SoundUtils; +import org.geysermc.connector.network.translators.sound.SoundRegistry; @Translator(packet = ServerStopSoundPacket.class) public class JavaPlayerStopSoundTranslator extends PacketTranslator { @@ -48,7 +48,7 @@ public class JavaPlayerStopSoundTranslator extends PacketTranslator " + soundMapping + (soundMapping == null ? "[not found]" : "") @@ -68,7 +68,7 @@ public class JavaPlayerStopSoundTranslator extends PacketTranslator " + 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 73% 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 d544e24b..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.type.object.HangingDirection; +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.FishingHookEntity; import org.geysermc.connector.entity.ItemFrameEntity; -import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.utils.EntityUtils; -import 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) { + 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,15 +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() == ObjectType.ITEM_FRAME) { + } 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/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,15 @@ public class JavaOpenWindowTranslator extends PacketTranslator InventoryUtils.openInventory(session, newInventory), 500, TimeUnit.MILLISECONDS); - 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 ea197097..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 @@ -29,6 +29,7 @@ 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; @@ -46,7 +47,7 @@ public class JavaBlockChangeTranslator 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()); @@ -77,15 +77,15 @@ public class JavaBlockValueTranslator extends PacketTranslator extendPiston(session, position, (progress >= 1.0f) ? 1.0f : progress + 0.5f, progress), @@ -121,7 +121,7 @@ public class JavaBlockValueTranslator extends PacketTranslator retractPiston(session, position, (progress <= 0.0f) ? 0.0f : progress - 0.5f, progress), 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 9e2d2ae7..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 @@ -98,7 +98,7 @@ public class JavaChunkDataTranslator extends PacketTranslator blockEntityEntry : chunkData.getLoadBlockEntitiesLater().object2IntEntrySet()) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaCollectItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaCollectItemTranslator.java index b4287b08..31379bd2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaCollectItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaCollectItemTranslator.java @@ -50,6 +50,6 @@ public class JavaCollectItemTranslator extends PacketTranslator= 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); @@ -62,6 +64,6 @@ public class JavaExplosionTranslator 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 index aa4ade4a..f51e35fe 100644 --- 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 @@ -35,7 +35,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.utils.SoundUtils; +import org.geysermc.connector.network.translators.sound.SoundRegistry; @Translator(packet = ServerPlayBuiltinSoundPacket.class) public class JavaPlayBuiltinSoundTranslator extends PacketTranslator { @@ -44,11 +44,9 @@ public class JavaPlayBuiltinSoundTranslator extends PacketTranslator " - + soundMapping + (soundMapping == null ? "[not found]" : "") - + " - " + packet.toString()); + SoundRegistry.SoundMapping soundMapping = SoundRegistry.fromJava(packetSound); if (soundMapping == null) { + session.getConnector().getLogger().debug("[Builtin] Sound mapping " + packetSound + " not found - " + packet.toString()); return; } @@ -57,16 +55,16 @@ public class JavaPlayBuiltinSoundTranslator extends PacketTranslator " + soundPacket.toString()); + 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 index e22db97e..da23adb5 100644 --- 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 @@ -33,13 +33,18 @@ 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.utils.EffectUtils; +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 { @@ -50,7 +55,7 @@ public class JavaPlayEffectTranslator extends PacketTranslator 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.getUpstream().sendPacket(soundEvent); + session.sendUpstreamPacket(soundEvent); } } else { GeyserConnector.getInstance().getLogger().debug("No effect handling for sound effect: " + packet.getEffect()); @@ -139,7 +159,7 @@ public class JavaPlayEffectTranslator extends PacketTranslator { @@ -51,17 +51,13 @@ public class JavaPlayerPlaySoundTranslator extends PacketTranslator " - + soundMapping + (soundMapping == null ? "[not found]" : "") - + " - " + packet.toString()); + 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."); - playsound = packetSound; + .debug("[PlaySound] Defaulting to sound server gave us for " + packet.toString()); + playsound = packetSound.replace("minecraft:", ""); } else { playsound = soundMapping.getPlaysound(); } @@ -72,7 +68,6 @@ public class JavaPlayerPlaySoundTranslator extends PacketTranslator " + playSoundPacket); + 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 index 1f9bcba3..63512047 100644 --- 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 @@ -34,12 +34,12 @@ 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.Translators; +import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.utils.EffectUtils; 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 { @@ -52,7 +52,7 @@ public class JavaSpawnParticleTranslator 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/java/world/JavaWorldBorderTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaWorldBorderTranslator.java index 6795e78f..a58650eb 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaWorldBorderTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaWorldBorderTranslator.java @@ -32,7 +32,7 @@ import com.nukkitx.math.vector.Vector2f; 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.world.WorldBorder; +import org.geysermc.connector.network.translators.world.WorldBorder; @Translator(packet = ServerWorldBorderPacket.class) public class JavaWorldBorderTranslator extends PacketTranslator { @@ -42,28 +42,30 @@ public class JavaWorldBorderTranslator extends PacketTranslator SOUNDS; + private SoundRegistry() { + } + public static void init() { // no-op } static { /* Load sound mappings */ - InputStream stream = Toolbox.getResource("mappings/sounds.json"); + InputStream stream = FileUtils.getResource("mappings/sounds.json"); JsonNode soundsTree; try { - soundsTree = Toolbox.JSON_MAPPER.readTree(stream); + soundsTree = GeyserConnector.JSON_MAPPER.readTree(stream); } catch (IOException e) { throw new AssertionError("Unable to load sound mappings", e); } @@ -83,6 +87,13 @@ public class SoundUtils { 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("\\.", "_")); 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 index 00269cc7..367f9beb 100644 --- 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 @@ -30,7 +30,7 @@ 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.Translators; +import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.sound.BlockSoundInteractionHandler; import org.geysermc.connector.network.translators.sound.SoundHandler; @@ -39,7 +39,7 @@ public class BucketSoundInteractionHandler implements BlockSoundInteractionHandl @Override public void handleInteraction(GeyserSession session, Vector3f position, String identifier) { - String handItemIdentifier = Translators.getItemTranslator().getItem(session.getInventory().getItemInHand()).getJavaIdentifier(); + String handItemIdentifier = ItemRegistry.getItem(session.getInventory().getItemInHand()).getJavaIdentifier(); LevelSoundEventPacket soundEventPacket = new LevelSoundEventPacket(); soundEventPacket.setPosition(position); soundEventPacket.setIdentifier(":"); @@ -67,7 +67,7 @@ public class BucketSoundInteractionHandler implements BlockSoundInteractionHandl } if (soundEvent != null) { soundEventPacket.setSound(soundEvent); - session.getUpstream().sendPacket(soundEventPacket); + 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 index 260ad814..4b74a678 100644 --- 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 @@ -43,6 +43,6 @@ public class ComparatorSoundInteractHandler implements BlockSoundInteractionHand levelEventPacket.setPosition(position); levelEventPacket.setType(LevelEventType.REDSTONE_TRIGGER); levelEventPacket.setData(powered ? 500 : 550); - session.getUpstream().sendPacket(levelEventPacket); + 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 index b8e2854d..39a07c3a 100644 --- 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 @@ -42,6 +42,6 @@ public class DoorSoundInteractionHandler implements BlockSoundInteractionHandler levelEventPacket.setType(LevelEventType.SOUND_DOOR); levelEventPacket.setPosition(position); levelEventPacket.setData(0); - session.getUpstream().sendPacket(levelEventPacket); + 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 index b28133b3..290aa7bd 100644 --- 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 @@ -45,6 +45,6 @@ public class FlintAndSteelInteractionHandler implements BlockSoundInteractionHan levelSoundEventPacket.setIdentifier(":"); levelSoundEventPacket.setSound(SoundEvent.IGNITE); levelSoundEventPacket.setExtraData(-1); - session.getUpstream().sendPacket(levelSoundEventPacket); + 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 index e2957103..e5445e9d 100644 --- 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 @@ -46,6 +46,6 @@ public class GrassPathInteractionHandler implements BlockSoundInteractionHandler levelSoundEventPacket.setIdentifier(":"); levelSoundEventPacket.setSound(SoundEvent.ITEM_USE_ON); levelSoundEventPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaBlockState(identifier))); - session.getUpstream().sendPacket(levelSoundEventPacket); + 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 index c9d4299b..17d346ae 100644 --- 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 @@ -46,6 +46,6 @@ public class HoeInteractionHandler implements BlockSoundInteractionHandler { levelSoundEventPacket.setIdentifier(":"); levelSoundEventPacket.setSound(SoundEvent.ITEM_USE_ON); levelSoundEventPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaBlockState(identifier))); - session.getUpstream().sendPacket(levelSoundEventPacket); + 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 index cfbe7727..fb39d4ac 100644 --- 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 @@ -43,6 +43,6 @@ public class LeverSoundInteractionHandler implements BlockSoundInteractionHandle levelEventPacket.setPosition(position); levelEventPacket.setType(LevelEventType.REDSTONE_TRIGGER); levelEventPacket.setData(powered ? 600 : 500); - session.getUpstream().sendPacket(levelEventPacket); + 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 index d4046eea..d33d68af 100644 --- 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 @@ -31,7 +31,7 @@ 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.Translators; +import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.sound.EntitySoundInteractionHandler; import org.geysermc.connector.network.translators.sound.SoundHandler; @@ -40,7 +40,7 @@ public class MilkCowSoundInteractionHandler implements EntitySoundInteractionHan @Override public void handleInteraction(GeyserSession session, Vector3f position, Entity value) { - if (!Translators.getItemTranslator().getItem(session.getInventory().getItemInHand()).getJavaIdentifier().equals("minecraft:bucket")) { + if (!ItemRegistry.getItem(session.getInventory().getItemInHand()).getJavaIdentifier().equals("minecraft:bucket")) { return; } LevelSoundEventPacket levelSoundEventPacket = new LevelSoundEventPacket(); @@ -50,6 +50,6 @@ public class MilkCowSoundInteractionHandler implements EntitySoundInteractionHan levelSoundEventPacket.setIdentifier(":"); levelSoundEventPacket.setSound(SoundEvent.MILK); levelSoundEventPacket.setExtraData(-1); - session.getUpstream().sendPacket(levelSoundEventPacket); + session.sendUpstreamPacket(levelSoundEventPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/world/WorldBorder.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldBorder.java similarity index 81% rename from connector/src/main/java/org/geysermc/connector/world/WorldBorder.java rename to connector/src/main/java/org/geysermc/connector/network/translators/world/WorldBorder.java index f7ea173b..a06dd0fd 100644 --- a/connector/src/main/java/org/geysermc/connector/world/WorldBorder.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldBorder.java @@ -24,11 +24,10 @@ * */ -package org.geysermc.connector.world; +package org.geysermc.connector.network.translators.world; import com.nukkitx.math.vector.Vector2f; import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.packet.TextPacket; import lombok.Getter; import lombok.NonNull; import lombok.RequiredArgsConstructor; @@ -38,18 +37,23 @@ import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + @Getter @Setter @RequiredArgsConstructor public class WorldBorder { private @NonNull Vector2f center; - private @NonNull double radius; private @NonNull double oldRadius; private @NonNull double newRadius; private @NonNull long speed; private @NonNull int warningTime; private @NonNull int warningBlocks; + // Runs the onTick method + private ScheduledFuture worldBorderTask; + private double minX; private double minZ; private double maxX; @@ -64,13 +68,24 @@ public class WorldBorder { /** * Updates the min and max positions of the world border. - * This should be called every time there is a modifcation to either the center coordinates or the radius. + * This should be called every time there is a modification to either the center coordinates or the radius. */ - public void update() { + public void update(GeyserSession session) { this.minX = Math.max(center.getX() - newRadius / 2.0D, -newRadius); this.minZ = Math.max(center.getY() - newRadius / 2.0D, -newRadius); this.maxX = Math.min(center.getX() + newRadius / 2.0D, newRadius); this.maxZ = Math.min(center.getY() + newRadius / 2.0D, newRadius); + if (worldBorderTask != null) { + worldBorderTask.cancel(false); + } + + // If world border is at that number then it's 'disabled', no need to run tasks for it + // https://minecraft.gamepedia.com/World_border#Commands + if (!(newRadius >= 59999967)) { + worldBorderTask = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> + onTick(session), 1, 50, TimeUnit.MILLISECONDS + ); + } } /** @@ -82,10 +97,7 @@ public class WorldBorder { float entityDistance = (float) getDistanceToEdge(entity); - if ((double) entityDistance < distance) { - return true; - } - return false; + return (double) entityDistance < distance; } /** @@ -113,4 +125,4 @@ public class WorldBorder { return Math.min(Math.min(Math.min(minPosX, maxPosX), minPosZ), maxPosZ); } -} \ No newline at end of file +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java index 0890aed6..d92d8454 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java @@ -26,8 +26,10 @@ 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; /** @@ -39,6 +41,28 @@ import org.geysermc.connector.network.session.GeyserSession; */ 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 * diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java index de08b7e8..070a0592 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java @@ -47,6 +47,7 @@ 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<>(); @@ -74,6 +75,15 @@ public class BlockStateValues { 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; @@ -136,6 +146,15 @@ public class BlockStateValues { 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 diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java index e614900c..d6f446f0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java @@ -39,7 +39,7 @@ 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.world.block.entity.BlockEntity; -import org.geysermc.connector.utils.Toolbox; +import org.geysermc.connector.utils.FileUtils; import org.reflections.Reflections; import java.io.InputStream; @@ -69,11 +69,16 @@ 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; + + public static final int JAVA_RUNTIME_SPAWNER_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)) { @@ -90,10 +95,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); } @@ -108,6 +113,9 @@ public class BlockTranslator { int javaRuntimeId = -1; int bedrockRuntimeId = 0; int cobwebRuntimeId = -1; + int furnaceRuntimeId = -1; + int furnaceLitRuntimeId = -1; + int spawnerRuntimeId = -1; Iterator> blocksIterator = blocks.fields(); while (blocksIterator.hasNext()) { javaRuntimeId++; @@ -186,6 +194,18 @@ 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; + } + } + + if (javaId.startsWith("minecraft:spawner")) { + spawnerRuntimeId = javaRuntimeId; + } + bedrockRuntimeId++; } @@ -194,6 +214,21 @@ 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 (spawnerRuntimeId == -1) { + throw new AssertionError("Unable to find spawner in palette"); + } + JAVA_RUNTIME_SPAWNER_ID = spawnerRuntimeId; + if (waterRuntimeId == -1) { throw new AssertionError("Unable to find water in palette"); } 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/world/block/entity/BannerBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java index 2034b3d5..3e2c0a95 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java @@ -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.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/world/block/entity/BedBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java index 543828e8..5f0b1cc0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java @@ -35,7 +35,7 @@ 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/BlockEntity.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntity.java index d08ab561..11bfe0ea 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntity.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntity.java @@ -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/world/block/entity/BlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java index 4545aed5..3d663926 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java @@ -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/world/block/entity/CampfireBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java index d91e47c2..cd31636c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java @@ -30,15 +30,14 @@ 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/world/block/entity/EmptyBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java index f95cb89e..d1068277 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java @@ -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/world/block/entity/EndGatewayBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java index 10de9d32..4cd2eaa9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java @@ -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 index 6861a953..c4748c82 100644 --- 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 @@ -53,7 +53,7 @@ public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity, R updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NONE); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); - session.getUpstream().sendPacket(updateBlockPacket); + session.sendUpstreamPacket(updateBlockPacket); } /** 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 index c538f09e..168015f6 100644 --- 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 @@ -50,7 +50,7 @@ public class NoteblockBlockEntityTranslator implements RequiresBlockState { blockEventPacket.setBlockPosition(Vector3i.from(position.getX(), position.getY(), position.getZ())); blockEventPacket.setEventType(0); blockEventPacket.setEventData(BlockStateValues.getNoteblockPitch(blockState)); - session.getUpstream().sendPacket(blockEventPacket); + session.sendUpstreamPacket(blockEventPacket); ChunkUtils.CACHED_BLOCK_ENTITIES.remove(position); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java index b0b8fa3d..373b963e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java @@ -36,7 +36,7 @@ import org.geysermc.connector.network.translators.world.block.BlockStateValues; import java.util.ArrayList; import java.util.List; -@BlockEntity(name = "ShulkerBox", delay = false, regex = "shulker_box") +@BlockEntity(name = "ShulkerBox", regex = "shulker_box") public class ShulkerBoxBlockEntityTranslator extends BlockEntityTranslator { @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java index 0dde3307..6c170462 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java @@ -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/world/block/entity/SkullBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java index 7e73c846..9393f7bb 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java @@ -1,71 +1,71 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.world.block.entity; - -import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; -import com.nukkitx.nbt.CompoundTagBuilder; -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.world.block.BlockStateValues; - -import java.util.ArrayList; -import java.util.List; - -@BlockEntity(name = "Skull", delay = false, regex = "skull") -public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { - - @Override - public boolean isBlock(BlockState blockState) { - return BlockStateValues.getSkullVariant(blockState) != -1; - } - - @Override - public List> translateTag(com.github.steveice10.opennbt.tag.builtin.CompoundTag tag, BlockState blockState) { - List> tags = new ArrayList<>(); - byte skullVariant = BlockStateValues.getSkullVariant(blockState); - float rotation = BlockStateValues.getSkullRotation(blockState) * 22.5f; - // Just in case... - if (skullVariant == -1) skullVariant = 0; - tags.add(new FloatTag("Rotation", rotation)); - tags.add(new ByteTag("SkullType", skullVariant)); - return tags; - } - - @Override - public com.github.steveice10.opennbt.tag.builtin.CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { - return null; - } - - @Override - public CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder(); - tagBuilder.floatTag("Rotation", 0); - tagBuilder.byteTag("SkullType", (byte) 0); - return tagBuilder.buildRootTag(); - } -} +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN 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.nbt.CompoundTagBuilder; +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.world.block.BlockStateValues; + +import java.util.ArrayList; +import java.util.List; + +@BlockEntity(name = "Skull", regex = "skull") +public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { + + @Override + public boolean isBlock(BlockState blockState) { + return BlockStateValues.getSkullVariant(blockState) != -1; + } + + @Override + public List> translateTag(com.github.steveice10.opennbt.tag.builtin.CompoundTag tag, BlockState blockState) { + List> tags = new ArrayList<>(); + byte skullVariant = BlockStateValues.getSkullVariant(blockState); + float rotation = BlockStateValues.getSkullRotation(blockState) * 22.5f; + // Just in case... + if (skullVariant == -1) skullVariant = 0; + tags.add(new FloatTag("Rotation", rotation)); + tags.add(new ByteTag("SkullType", skullVariant)); + return tags; + } + + @Override + public com.github.steveice10.opennbt.tag.builtin.CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { + return null; + } + + @Override + public CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder(); + tagBuilder.floatTag("Rotation", 0); + tagBuilder.byteTag("SkullType", (byte) 0); + return tagBuilder.buildRootTag(); + } +} 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/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 b0794e20..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,23 +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.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("_", " "); @@ -33,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; } @@ -49,6 +54,6 @@ public class BlockEntityUtils { BlockEntityDataPacket blockEntityPacket = new BlockEntityDataPacket(); 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 3a9ecb86..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.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 fcc596eb..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,6 +32,9 @@ 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.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; @@ -43,11 +46,12 @@ import org.geysermc.connector.entity.ItemFrameEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockStateValues; import org.geysermc.connector.network.translators.world.block.entity.*; -import org.geysermc.connector.network.translators.Translators; 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; @@ -61,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(); @@ -89,21 +110,12 @@ 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())) { @@ -156,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); } @@ -188,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); @@ -198,12 +210,12 @@ 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 (RequiresBlockState requiresBlockState : Translators.getRequiresBlockStateMap()) { + 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) { @@ -226,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); @@ -236,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 6dd182a7..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(); @@ -49,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); } /** @@ -71,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 dea42728..04b6ecc4 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java @@ -125,4 +125,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..ded47723 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,16 +50,22 @@ 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); + //Ensure at least half a second passes between closing and opening a new window + //The client will not open the new window if it is still closing the old one + long delay = 500 - (System.currentTimeMillis() - session.getLastWindowCloseTime()); //TODO: find better way to handle double chest delay if (translator instanceof DoubleChestInventoryTranslator) { + delay = Math.max(delay, 200); + } + if (delay > 0) { GeyserConnector.getInstance().getGeneralThreadPool().schedule(() -> { translator.openInventory(session, inventory); translator.updateInventory(session, inventory); - }, 200, TimeUnit.MILLISECONDS); + }, delay, TimeUnit.MILLISECONDS); } else { translator.openInventory(session, inventory); translator.updateInventory(session, inventory); @@ -64,27 +76,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 +125,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 4e8497c6..f66e869d 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java @@ -37,4 +37,19 @@ public class MathUtils { int truncated = (int) floatNumber; return floatNumber > truncated ? truncated + 1 : truncated; } + + /** + * 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 3c924912..d79cdab8 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java @@ -137,6 +137,23 @@ public class MessageUtils { } } + /** + * 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()); + } + } + public static String getBedrockMessage(String message) { Component component = phraseJavaMessage(message); return LegacyComponentSerializer.legacy().serialize(component); 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 7c2b7fc0..aea9ba18 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java @@ -35,10 +35,10 @@ import org.geysermc.connector.GeyserConnector; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; +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; @@ -51,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<>(); @@ -58,9 +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); } @@ -164,12 +206,94 @@ 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, 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, CapeProvider provider) { @@ -189,33 +313,67 @@ public class SkinProvider { ); } + /** + * 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 the requested image is an cape if (provider != null) { - image = image.getWidth() > 64 ? scale(image) : image; - BufferedImage newImage = new BufferedImage(64, 32, BufferedImage.TYPE_INT_RGB); + 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 downloadImage(String imageUrl, CapeProvider provider) throws IOException { @@ -237,7 +395,7 @@ public class SkinProvider { } private static BufferedImage scale(BufferedImage bufferedImage) { - BufferedImage resized = new BufferedImage(bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2, BufferedImage.TYPE_INT_RGB); + 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); @@ -245,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); @@ -267,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; @@ -285,6 +498,34 @@ 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' */ @@ -324,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 9ce025e7..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,6 +157,12 @@ 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(), @@ -158,6 +170,38 @@ public class SkinUtils { ), 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()) { entity.setLastSkinUpdate(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/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 b1e1e2f9..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,6 +70,10 @@ 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 @@ -69,6 +86,12 @@ default-locale: en_us # 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/. @@ -78,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 5b3a9ad1..a67cc940 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 5b3a9ad1d2ef76105fb318e63126a096844b3195 +Subproject commit a67cc940c0d47874c833ffeb58f38e33eabfcc33 diff --git a/pom.xml b/pom.xml index fc1ead33..3e119eb1 100644 --- a/pom.xml +++ b/pom.xml @@ -38,14 +38,8 @@ - CodeMC-repo - https://repo.codemc.org/repository/maven-public - - true - - - true - + jitpack.io + https://jitpack.io nukkitx-release-repo @@ -67,6 +61,10 @@ true + + viaversion-repo + https://repo.viaversion.com +