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 dda804b7..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,10 +28,13 @@ 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; @@ -45,6 +48,7 @@ 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; @@ -70,11 +74,18 @@ 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); boolean isViaVersion = false; @@ -120,6 +131,11 @@ public class GeyserBukkitPlugin extends JavaPlugin implements GeyserBootstrap { return this.geyserCommandManager; } + @Override + public IGeyserPingPassthrough getGeyserPingPassthrough() { + return geyserBukkitPingPassthrough; + } + @Override public WorldManager getWorldManager() { return this.geyserWorldManager; diff --git a/bootstrap/bukkit/src/main/resources/plugin.yml b/bootstrap/bukkit/src/main/resources/plugin.yml index 7e32f0e1..89c90789 100644 --- a/bootstrap/bukkit/src/main/resources/plugin.yml +++ b/bootstrap/bukkit/src/main/resources/plugin.yml @@ -4,6 +4,7 @@ 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 488854c9..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; @@ -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 0c323a5e..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; @@ -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 b9b3b379..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,12 +79,18 @@ 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; @@ -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 0269d933..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; @@ -90,6 +94,7 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { 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/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 5392baed..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 @@ -56,6 +56,16 @@ public class OffhandCommand extends GeyserCommand { ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.SWAP_HANDS, new Position(0,0,0), BlockFace.DOWN); 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 index 2808b093..79e67f34 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java @@ -31,7 +31,7 @@ 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.utils.EffectUtils; +import org.geysermc.connector.network.translators.effect.EffectRegistry; public class AreaEffectCloudEntity extends Entity { @@ -52,7 +52,7 @@ public class AreaEffectCloudEntity extends Entity { 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, EffectUtils.getParticleString(particle.getType())); + metadata.put(EntityData.AREA_EFFECT_CLOUD_PARTICLE_ID, EffectRegistry.getParticleString(particle.getType())); } else if (entityMetadata.getId() == 8) { metadata.put(EntityData.POTION_COLOR, entityMetadata.getValue()); } 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/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index 2fe830af..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,21 +27,24 @@ 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.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; @@ -49,8 +52,9 @@ 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.ArrayList; @@ -80,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(); @@ -98,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()); @@ -210,9 +214,8 @@ public class Entity { metadata.getFlags().setFlag(EntityFlag.GLIDING, (xd & 0x80) == 0x80); if ((xd & 0x20) == 0x20) { - if (this.is(ArmorStandEntity.class)) { - metadata.put(EntityData.SCALE, 0.0f); - } else { + // Armour stands are handled in their own class + if (!this.is(ArmorStandEntity.class)) { metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true); } } else { @@ -221,11 +224,11 @@ public class Entity { // 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 @@ -242,10 +245,23 @@ public class Entity { } } 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)) @@ -257,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(); 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 8d8d5ef2..1b648f7c 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java @@ -26,7 +26,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.type.object.ProjectileData; +import com.github.steveice10.mc.protocol.data.game.entity.object.ProjectileData; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.EntityData; import org.geysermc.connector.GeyserConnector; @@ -37,10 +37,14 @@ public class FishingHookEntity extends Entity { public FishingHookEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, ProjectileData data) { super(entityId, geyserId, entityType, position, motion, rotation); - // TODO: Find a better way to do this for (GeyserSession session : GeyserConnector.getInstance().getPlayers().values()) { - if (session.getPlayerEntity().getEntityId() == data.getOwnerId()) { - this.metadata.put(EntityData.OWNER_EID, session.getPlayerEntity().getGeyserId()); + 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; } } 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 b321213e..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,7 +49,7 @@ public class ItemEntity extends Entity { itemPacket.setUniqueEntityId(geyserId); itemPacket.setFromFishing(false); itemPacket.getMetadata().putAll(metadata); - itemPacket.setItemInHand(Translators.getItemTranslator().translateToBedrock(session, (ItemStack) entityMetadata.getValue())); + itemPacket.setItemInHand(ItemTranslator.translateToBedrock(session, (ItemStack) entityMetadata.getValue())); session.sendUpstreamPacket(itemPacket); } 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 16942898..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; @@ -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.sendUpstreamPacket(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.sendUpstreamPacket(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/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 56e07733..1711fd38 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java @@ -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 43891a7e..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,14 +26,12 @@ 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.EntityLink; -import com.nukkitx.protocol.bedrock.data.PlayerPermission; +import com.nukkitx.protocol.bedrock.data.*; import com.nukkitx.protocol.bedrock.packet.*; import lombok.Getter; @@ -47,6 +45,8 @@ 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; @@ -57,6 +57,7 @@ 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; @@ -97,6 +98,11 @@ 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.sendUpstreamPacket(addPlayerPacket); @@ -138,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); @@ -163,6 +171,8 @@ 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); @@ -205,6 +215,17 @@ public class PlayerEntity extends LivingEntity { } } + // 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(); 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/entity/ArrowEntity.java b/connector/src/main/java/org/geysermc/connector/entity/TippedArrowEntity.java similarity index 87% rename from connector/src/main/java/org/geysermc/connector/entity/ArrowEntity.java rename to connector/src/main/java/org/geysermc/connector/entity/TippedArrowEntity.java index 961908a2..def5715c 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ArrowEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/TippedArrowEntity.java @@ -28,9 +28,9 @@ package org.geysermc.connector.entity; import com.nukkitx.math.vector.Vector3f; import org.geysermc.connector.entity.type.EntityType; -public class ArrowEntity extends Entity { +public class TippedArrowEntity extends AbstractArrowEntity { - public ArrowEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + public TippedArrowEntity(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/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/ArmorStandEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java index 8d3c8f8f..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,17 +35,42 @@ 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(); + + // 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); } @@ -60,9 +85,10 @@ public class ArmorStandEntity extends LivingEntity { } // setMarker - if ((xd & 0x10) == 0x10 && (metadata.get(EntityData.BOUNDING_BOX_WIDTH) != null && !metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.0f))) { + if ((xd & 0x10) == 0x10 && (metadata.get(EntityData.BOUNDING_BOX_WIDTH) == null || !metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.0f))) { metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.0f); metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.0f); + isMarker = true; } } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/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/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/network/translators/NbtItemStackTranslator.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/GiantEntity.java similarity index 70% rename from connector/src/main/java/org/geysermc/connector/network/translators/NbtItemStackTranslator.java rename to connector/src/main/java/org/geysermc/connector/entity/living/monster/GiantEntity.java index 56c780f4..b9dc9e66 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/NbtItemStackTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/GiantEntity.java @@ -23,23 +23,17 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators; +package org.geysermc.connector.entity.living.monster; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import org.geysermc.connector.network.translators.item.ItemEntry; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityData; +import org.geysermc.connector.entity.type.EntityType; -public class NbtItemStackTranslator { +public class GiantEntity extends MonsterEntity { - public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) { + 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); } - - 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/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index c77baf0e..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 @@ -64,6 +64,7 @@ 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), @@ -81,7 +82,7 @@ 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(WitherEntity.class, 52, 3.5f, 0.9f), ENDER_DRAGON(EnderDragonEntity.class, 53, 4f, 13f), @@ -98,45 +99,47 @@ public enum EntityType { 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, 0.25f), - END_CRYSTAL(EnderCrystalEntity.class, 71, 0f, 0f, 0f, 0f, "minecraft:ender_crystal"), - FIREWORK_ROCKET(Entity.class, 72, 0.25f), - 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, 0.3125f), FISHING_BOBBER(FishingHookEntity.class, 77, 0f, 0f, 0f, 0f, "minecraft:fishing_hook"), CHALKBOARD(Entity.class, 78, 0f), DRAGON_FIREBALL(ItemedFireballEntity.class, 79, 1.0f), - ARROW(ArrowEntity.class, 80, 0.25f, 0.25f), + 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), - EGG(ThrowableEntity.class, 82, 0.25f), + THROWN_EGG(ThrowableEntity.class, 82, 0.25f, 0.25f, 0.25f, 0f, "minecraft:egg"), PAINTING(PaintingEntity.class, 83, 0f), - MINECART(MinecartEntity.class, 84, 0.7f, 0.98f), + MINECART(MinecartEntity.class, 84, 0.7f, 0.98f, 0.98f, 0.35f), FIREBALL(ItemedFireballEntity.class, 85, 1.0f), - POTION(ThrowableEntity.class, 86, 0.25f), - ENDER_PEARL(ThrowableEntity.class, 87, 0.25f), - LEASH_KNOT(Entity.class, 88, 0.5f, 0.375f), + 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(Entity.class, 90, 0.7f, 1.6f, 1.6f, 0.35f), + 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, 0.3125f), AREA_EFFECT_CLOUD(AreaEffectCloudEntity.class, 95, 0.5f, 1.0f), - HOPPER_MINECART(MinecartEntity.class, 96, 0.7f, 0.98f), - TNT_MINECART(MinecartEntity.class, 97, 0.7f, 0.98f), - CHEST_MINECART(MinecartEntity.class, 98, 0.7f, 0.98f), - - COMMAND_BLOCK_MINECART(MinecartEntity.class, 100, 0.7f, 0.98f), + 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, 0.25f), - EVOKER_FANGS(Entity.class, 103, 0.8f, 0.5f), - EVOKER(SpellcasterIllagerEntity.class, 104, 1.95f, 0.5f), + 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 @@ -159,6 +162,8 @@ public enum EntityType { */ 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; private final float height; @@ -194,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..6ad206b3 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION 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() == 0xFEFD : 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) 0x80); + query.write((byte) 0x00); + + // Fills the game data + for(Map.Entry entry : gameData.entrySet()) { + query.write(entry.getKey().getBytes()); + query.write((byte) 0x00); + query.write(entry.getValue().getBytes()); + query.write((byte) 0x00); + } + + // 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]; + } + } + + /** + * Generate a byte[] storing the player names + * + * @return The byte[] representation of players + */ + 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 { + // Generate an MD5 hash from the address + MessageDigest digest = MessageDigest.getInstance("MD5"); + digest.update(address.toString().getBytes(StandardCharsets.UTF_8)); + digest.update(token); + + // Get the first 4 bytes of the digest + byte[] digestBytes = Arrays.copyOf(digest.digest(), 4); + + // Convert the bytes to a buffer + ByteBuffer byteBuffer = ByteBuffer.wrap(digestBytes); + + // Turn the number into a null terminated string + return (byteBuffer.getInt() + "\0").getBytes(); + } catch (NoSuchAlgorithmException e) { + return (ByteBuffer.allocate(4).putInt(ThreadLocalRandom.current().nextInt()).getInt() + "\0").getBytes(); + } + } +} 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 dfc3112b..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 @@ -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 e66564f6..7597e59f 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 @@ -60,18 +60,21 @@ 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.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.SkinUtils; -import org.geysermc.connector.utils.Toolbox; import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.EncryptionUtil; @@ -159,9 +162,15 @@ public class GeyserSession implements CommandSender { private boolean manyDimPackets = false; private ServerRespawnPacket lastDimPacket = null; + @Setter + private Entity ridingVehicleEntity; + @Setter private int craftSlot = 0; + @Setter + private long lastWindowCloseTime = 0; + @Setter private VillagerTrade[] villagerTrades; @@ -195,16 +204,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(); @@ -212,6 +221,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( @@ -335,7 +355,7 @@ 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; } @@ -351,9 +371,17 @@ public class GeyserSession implements CommandSender { } } - Registry.JAVA.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this); + 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(); @@ -487,7 +515,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); 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 1fde179d..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 @@ -104,10 +104,13 @@ 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.sendUpstreamPacket(addEntityPacket); } 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/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 376d0f43..00000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/Translators.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators; - -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.MERCHANT, new MerchantInventoryTranslator()); - //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/BedrockAdventureSettingsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockAdventureSettingsTranslator.java new file mode 100644 index 00000000..75a1547f --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockAdventureSettingsTranslator.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.network.translators.bedrock; + +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerAbilitiesPacket; +import com.nukkitx.protocol.bedrock.packet.AdventureSettingsPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +@Translator(packet = AdventureSettingsPacket.class) +public class BedrockAdventureSettingsTranslator 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 35f710bd..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 @@ -54,6 +58,18 @@ public class BedrockAnimateTranslator extends PacketTranslator { 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/BedrockBlockPickRequestPacketTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestPacketTranslator.java index 288b99d3..04fe8dbf 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestPacketTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestPacketTranslator.java @@ -31,15 +31,13 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientMoveI import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.packet.BlockPickRequestPacket; import com.nukkitx.protocol.bedrock.packet.PlayerHotbarPacket; -import org.geysermc.connector.entity.Entity; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.geysermc.connector.network.translators.Translators; @Translator(packet = BlockPickRequestPacket.class) public class BedrockBlockPickRequestPacketTranslator extends PacketTranslator { @@ -61,14 +59,13 @@ public class BedrockBlockPickRequestPacketTranslator extends PacketTranslator { @@ -47,7 +51,7 @@ 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(), @@ -59,6 +63,58 @@ public class BedrockInteractTranslator extends PacketTranslator InteractAction.ATTACK, Hand.MAIN_HAND); 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 b067273d..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; @@ -44,14 +45,13 @@ import com.nukkitx.protocol.bedrock.packet.InventoryTransactionPacket; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.ItemFrameEntity; -import org.geysermc.connector.entity.living.merchant.VillagerEntity; 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; @@ -65,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 d61461cc..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 @@ -85,6 +85,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/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/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 3f887001..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 @@ -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 8c5b2cf2..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 @@ -128,14 +126,4 @@ public class DoubleChestInventoryTranslator extends BaseInventoryTranslator { blockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(realBlock)); session.sendUpstreamPacket(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); - } } 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..97594907 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,51 @@ 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.MERCHANT, new MerchantInventoryTranslator()); + //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 5beea2a2..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 @@ -36,10 +36,9 @@ 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.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; @@ -60,11 +59,11 @@ 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.sendUpstreamPacket(inventoryContentPacket); @@ -74,7 +73,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator { 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.sendUpstreamPacket(armorContentPacket); @@ -82,7 +81,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator { // Offhand InventoryContentPacket offhandPacket = new InventoryContentPacket(); offhandPacket.setContainerId(ContainerId.OFFHAND); - offhandPacket.setContents(new ItemData[]{Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(45))}); + offhandPacket.setContents(new ItemData[]{ItemTranslator.translateToBedrock(session, inventory.getItem(45))}); session.sendUpstreamPacket(offhandPacket); } @@ -101,7 +100,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator { if (session.getGameMode() == GameMode.CREATIVE) { 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.sendUpstreamPacket(slotPacket); @@ -126,12 +125,12 @@ public class PlayerInventoryTranslator extends InventoryTranslator { slotPacket.setContainerId(ContainerId.CURSOR); slotPacket.setSlot(slot + 27); } - slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(slot))); + 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))}); + offhandPacket.setContents(new ItemData[]{ItemTranslator.translateToBedrock(session, inventory.getItem(slot))}); session.sendUpstreamPacket(offhandPacket); } } @@ -202,7 +201,7 @@ 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.sendDownstreamPacket(creativePacket); @@ -210,13 +209,13 @@ public class PlayerInventoryTranslator extends InventoryTranslator { 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.sendDownstreamPacket(creativeDropPacket); } 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/InventoryActionDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/InventoryActionDataTranslator.java index 0c267775..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; } @@ -187,11 +187,12 @@ public class InventoryActionDataTranslator { } 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; } } @@ -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/updater/ChestInventoryUpdater.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java index e9541bd1..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,8 +31,8 @@ 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 @@ -48,8 +48,8 @@ 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] = UNUSUABLE_SPACE_BLOCK; } @@ -69,7 +69,7 @@ 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))); + 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 d25705e2..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,7 +40,7 @@ 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(); @@ -57,7 +57,7 @@ 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))); + 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 6a93ecd3..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,7 +44,7 @@ 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))); + slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(i))); session.sendUpstreamPacket(slotPacket); } } @@ -57,7 +57,7 @@ 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))); + 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 ebca038a..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,15 +31,15 @@ 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); @@ -52,7 +52,7 @@ 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))); + slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(javaSlot))); session.sendUpstreamPacket(slotPacket); return true; } 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 28c5345a..cb8613c8 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,56 +119,270 @@ 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 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; } /** @@ -205,10 +430,4 @@ public class ItemTranslator { return true; } - private static final ItemStackTranslator DEFAULT_TRANSLATOR = new ItemStackTranslator() { - @Override - public List getAppliedItems() { - 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/JavaServerDeclareCommandsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareCommandsTranslator.java similarity index 97% 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 6f16f8b4..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<>(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java index b6a25e54..b0637722 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java @@ -42,9 +42,9 @@ import lombok.EqualsAndHashCode; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.Translators; import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.utils.Toolbox; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.item.ItemTranslator; import java.util.*; import java.util.stream.Collectors; @@ -64,7 +64,7 @@ public class JavaDeclareRecipesTranslator 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/entity/JavaEntityAttachTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAttachTranslator.java new file mode 100644 index 00000000..6e53df27 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAttachTranslator.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.network.translators.java.entity; + +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityAttachPacket; +import com.nukkitx.protocol.bedrock.data.EntityData; +import com.nukkitx.protocol.bedrock.data.EntityEventType; +import com.nukkitx.protocol.bedrock.data.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; +import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +/** + * Called when a leash is attached, removed or updated from an entity + */ +@Translator(packet = ServerEntityAttachPacket.class) +public class JavaEntityAttachTranslator 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/JavaEntityEquipmentTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEquipmentTranslator.java index a010752d..8918217a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEquipmentTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEquipmentTranslator.java @@ -30,10 +30,10 @@ import org.geysermc.connector.entity.LivingEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.Translators; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityEquipmentPacket; import com.nukkitx.protocol.bedrock.data.ItemData; +import org.geysermc.connector.network.translators.item.ItemTranslator; @Translator(packet = ServerEntityEquipmentPacket.class) public class JavaEntityEquipmentTranslator 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 06d06695..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 { @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()); + } - playerEntity.setPlayerList(true); - playerEntity.setValid(true); - session.getEntityCache().addPlayerEntity(playerEntity); + 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; } } 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 ff6d41a1..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.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 9475d8a8..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]" : "") 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 80% 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 419f1e46..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,17 +25,16 @@ package org.geysermc.connector.network.translators.java.entity.spawn; -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.type.object.ObjectType; -import com.github.steveice10.mc.protocol.data.game.entity.type.object.ProjectileData; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnObjectPacket; +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; @@ -44,17 +43,17 @@ import org.geysermc.connector.utils.EntityUtils; 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; @@ -63,19 +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() == ObjectType.FISHING_BOBBER) { + } 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 7360cbb2..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 { @@ -49,14 +46,12 @@ 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= 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); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java index 78681f8f..c8be3a56 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java @@ -26,11 +26,15 @@ package org.geysermc.connector.network.translators.java.world; import com.github.steveice10.mc.protocol.data.game.world.map.MapData; +import com.github.steveice10.mc.protocol.data.game.world.map.MapIcon; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerMapDataPacket; +import com.nukkitx.protocol.bedrock.data.MapDecoration; +import com.nukkitx.protocol.bedrock.data.MapTrackedObject; import com.nukkitx.protocol.bedrock.packet.ClientboundMapItemDataPacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.utils.BedrockMapIcon; import org.geysermc.connector.utils.MapColor; @Translator(packet = ServerMapDataPacket.class) @@ -62,6 +66,16 @@ public class JavaMapDataTranslator extends PacketTranslator mapItemDataPacket.setColors(colors); } + // Bedrock needs an entity id to display an icon + int id = 0; + for (MapIcon icon : packet.getIcons()) { + BedrockMapIcon bedrockMapIcon = BedrockMapIcon.fromType(icon.getIconType()); + + mapItemDataPacket.getTrackedObjects().add(new MapTrackedObject(id)); + mapItemDataPacket.getDecorations().add(new MapDecoration(bedrockMapIcon.getIconID(), icon.getIconRotation(), icon.getCenterX(), icon.getCenterZ(), "", bedrockMapIcon.toARGB())); + id++; + } + session.getUpstream().getSession().sendPacket(mapItemDataPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java index 0db343b2..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 { @@ -83,6 +81,7 @@ 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()); @@ -108,7 +111,6 @@ public class JavaNotifyClientTranslator 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; } @@ -61,12 +59,12 @@ public class JavaPlayBuiltinSoundTranslator extends PacketTranslator " + soundPacket.toString()); } } 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 3afb79bd..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 @@ -40,7 +40,7 @@ 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; @@ -55,7 +55,7 @@ public class JavaPlayEffectTranslator extends PacketTranslator params = new ArrayList<>(); - // Couldn't figure out how to set this to Bedrock translation so it just uses the Java translation - String recordString = "item.minecraft." + EffectUtils.RECORDS.get(recordEffectData.getRecordId()).name().toLowerCase().replace("record_", "music_disc_") + ".desc"; + 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); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlayerPlaySoundTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlayerPlaySoundTranslator.java index 97274a72..c99de3e3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlayerPlaySoundTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlayerPlaySoundTranslator.java @@ -34,7 +34,7 @@ import com.nukkitx.protocol.bedrock.packet.*; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.SoundUtils; +import org.geysermc.connector.network.translators.sound.SoundRegistry; @Translator(packet = ServerPlaySoundPacket.class) public class JavaPlayerPlaySoundTranslator 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(); } @@ -73,6 +69,5 @@ public class JavaPlayerPlaySoundTranslator extends PacketTranslator " + 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 d6da1c81..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 { @@ -64,7 +64,7 @@ public class JavaSpawnParticleTranslator extends PacketTranslator { + + @Override + public void translate(ServerVehicleMovePacket packet, GeyserSession session) { + Entity entity = session.getRidingVehicleEntity(); + if (entity == null) return; + + entity.moveAbsolute(session, Vector3f.from(packet.getX(), packet.getY(), packet.getZ()), packet.getYaw(), packet.getPitch(), false, false); + + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/BlockSoundInteractionHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/BlockSoundInteractionHandler.java index 579539a1..5a03b218 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/sound/BlockSoundInteractionHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/BlockSoundInteractionHandler.java @@ -29,7 +29,7 @@ package org.geysermc.connector.network.translators.sound; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.nukkitx.math.vector.Vector3f; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.Translators; +import org.geysermc.connector.network.translators.item.ItemRegistry; import java.util.Map; @@ -66,7 +66,7 @@ public interface BlockSoundInteractionHandler extends SoundInteractionHandler 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 031782c1..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(":"); 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 42e2e601..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(); 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/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/entity/BannerBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java index eefd8402..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,6 +32,7 @@ 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; @@ -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/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 eac87b04..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,9 +30,8 @@ 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; @@ -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/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/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 9423a4f8..038084c3 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java @@ -4,12 +4,11 @@ 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) { // These are the only exceptions when it comes to block entity ids @@ -22,6 +21,10 @@ public class BlockEntityUtils { if (id.contains("EnderChest")) return "EnderChest"; + if (id.contains("enchanting_table")) { + return "EnchantTable"; + } + id = id.toLowerCase() .replace("minecraft:", "") .replace("_", " "); @@ -35,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; } 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 deddce58..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, ""); @@ -117,16 +120,22 @@ public class BlockUtils { int hasteLevel = 0; int miningFatigueLevel = 0; - if (player != null) { - hasteLevel = player.getEffectCache().getEffectLevel(Effect.FASTER_DIG); - miningFatigueLevel = player.getEffectCache().getEffectLevel(Effect.SLOWER_DIG); + if (session == null) { + return calculateBreakTime(blockHardness, toolTier, canHarvestWithHand, correctTool, toolType, isWoolBlock, isCobweb, toolEfficiencyLevel, hasteLevel, miningFatigueLevel, false, false, false); } - // 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); + + 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 41d0dc2d..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(); @@ -194,7 +215,7 @@ public class ChunkUtils { // 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) { @@ -217,7 +238,7 @@ 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.sendUpstreamPacket(data); 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 2c3933e2..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(); @@ -55,6 +60,16 @@ public class DimensionUtils { 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); @@ -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 c0311b64..ded47723 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java @@ -31,14 +31,16 @@ 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; @@ -48,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); @@ -68,26 +76,41 @@ 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())); + cursorPacket.setItem(ItemTranslator.translateToBedrock(session, session.getInventory().getCursor())); session.sendUpstreamPacket(cursorPacket); } @@ -115,6 +138,6 @@ public class InventoryUtils { display.listTag("Lore", StringTag.class, Collections.singletonList(new StringTag("", ChatColor.RESET + ChatColor.DARK_PURPLE + description))); root.tag(display.build("display")); - return ItemData.of(Toolbox.ITEM_ENTRIES.get(Toolbox.BARRIER_INDEX).getBedrockId(), (short) 0, 1, root.buildRootTag()); + 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 7f6c4760..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,7 +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 org.geysermc.common.window.CustomFormBuilder; import org.geysermc.common.window.CustomFormWindow; import org.geysermc.common.window.FormWindow; @@ -193,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 469ae758..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,12 +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("geometry.humanoid"); + 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); } @@ -167,6 +206,43 @@ 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); @@ -178,7 +254,7 @@ public class SkinProvider { } public static void storeBedrockSkin(UUID playerID, String skinID, byte[] skinData) { - Skin skin = new Skin(playerID, skinID, skinData, System.currentTimeMillis(), true); + Skin skin = new Skin(playerID, skinID, skinData, System.currentTimeMillis(), true, false); cachedSkins.put(playerID, skin); } @@ -188,16 +264,36 @@ public class SkinProvider { } public static void storeBedrockGeometry(UUID playerID, byte[] geometryName, byte[] geometryData) { - SkinGeometry geometry = new SkinGeometry(new String(geometryName), new String(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) { @@ -217,6 +313,48 @@ 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); @@ -226,26 +364,16 @@ public class SkinProvider { while(image.getWidth() > 64) { image = scale(image); } - BufferedImage newImage = new BufferedImage(64, 32, BufferedImage.TYPE_INT_RGB); + 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 { @@ -267,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); @@ -275,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); @@ -297,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; @@ -320,9 +503,26 @@ public class SkinProvider { public static class SkinGeometry { private String geometryName; private String geometryData; + private boolean failed; - public static SkinGeometry getLegacy(String name) { - return new SkinProvider.SkinGeometry("{\"geometry\" :{\"default\" :\"" + name + "\"}}", ""); + /** + * 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); } } @@ -365,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 8e68d3e1..48e4c4c8 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java @@ -53,7 +53,7 @@ public class SkinUtils { GameProfileData data = GameProfileData.from(profile); SkinProvider.Cape cape = SkinProvider.getCachedCape(data.getCapeUrl()); - SkinProvider.SkinGeometry geometry = SkinProvider.SkinGeometry.getLegacy("geometry.humanoid.custom" + (data.isAlex() ? "Slim" : "")); + SkinProvider.SkinGeometry geometry = SkinProvider.SkinGeometry.getLegacy(data.isAlex()); return buildEntryManually( profile.getId(), @@ -88,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); @@ -115,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"); @@ -124,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")) { @@ -138,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); } } } @@ -167,11 +170,38 @@ public class SkinUtils { ), SkinProvider.EMPTY_CAPE, SkinProvider.CapeProvider.VALUES.length * 3); } - SkinProvider.SkinGeometry geometry = SkinProvider.SkinGeometry.getLegacy("geometry.humanoid.custom" + (data.isAlex() ? "Slim" : "")); + 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()); @@ -188,6 +218,19 @@ public class SkinUtils { 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); @@ -197,6 +240,10 @@ public class SkinUtils { playerAddPacket.setAction(PlayerListPacket.Action.ADD); playerAddPacket.getEntries().add(updatedEntry); session.sendUpstreamPacket(playerAddPacket); + + if(entity.getUuid().equals(session.getPlayerEntity().getUuid())) { + session.fetchOurSkin(updatedEntry); + } } } } catch (Exception e) { @@ -217,8 +264,8 @@ public class SkinUtils { byte[] skinBytes = com.github.steveice10.mc.auth.util.Base64.decode(clientData.getSkinData().getBytes("UTF-8")); byte[] capeBytes = clientData.getCapeData(); - byte[] geometryNameBytes = com.github.steveice10.mc.auth.util.Base64.decode(clientData.getGeometryName().getBytes("UTF-8")); - byte[] geometryBytes = com.github.steveice10.mc.auth.util.Base64.decode(clientData.getGeometryData().getBytes("UTF-8")); + 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); @@ -235,14 +282,4 @@ public class SkinUtils { throw new AssertionError("Failed to cache skin for bedrock user (" + playerEntity.getUsername() + "): ", e); } } - - /** - * 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 + "\"}}"; - } } 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 a7963d0a..a67cc940 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit a7963d0a0236b1c47eea21718ac50706139d90cc +Subproject commit a67cc940c0d47874c833ffeb58f38e33eabfcc33 diff --git a/pom.xml b/pom.xml index a82d6217..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