diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 8001187ca..7a1fdba58 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -31,10 +31,10 @@ assignees: '' **Geyser Version** - + **Minecraft: Bedrock Edition Version** **Additional Context** - + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..92085a35b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: GeyserMC Discord + url: http://discord.geysermc.org/ + about: If your issue seems like it could possibly be an easy fix due to configuration, please hop on our Discord. diff --git a/.idea/copyright/Geyser.xml b/.idea/copyright/Geyser.xml new file mode 100644 index 000000000..568fa7b84 --- /dev/null +++ b/.idea/copyright/Geyser.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 000000000..f2d5911c9 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 8a0197972..f11b9bfb3 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Geyser is a bridge between Minecraft: Bedrock Edition and Minecraft: Java Editio Geyser is a proxy, bridging the gap between Minecraft: Bedrock Edition and Minecraft: Java Edition servers. The ultimate goal of this project is to allow Minecraft: Bedrock Edition users to join Minecraft: Java Edition servers as seamlessly as possible. **Please note, this project is still a work in progress and should not be used on production. Expect bugs!** -### Currently supporting Minecraft Bedrock v1.14.X and Minecraft Java v1.15.2. +### Currently supporting Minecraft Bedrock v1.14.6(0) and Minecraft Java v1.15.2. ## Setting Up Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser. @@ -28,12 +28,14 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set - Donate: https://patreon.com/GeyserMC ## What's Left to be Added/Fixed -- Inventories ([`inventory`](https://github.com/GeyserMC/Geyser/tree/inventory)) -- Crafting ([`inventory`](https://github.com/GeyserMC/Geyser/tree/inventory)) -- Creative Mode ([`inventory`](https://github.com/GeyserMC/Geyser/tree/inventory)) +- The Following Inventories + - [ ] Enchantment Table + - [ ] Beacon + - [ ] Cartography Table + - [ ] Stonecutter + - [ ] Villager Trading - Sounds - Block Particles -- Block Entities ([`inventory`](https://github.com/GeyserMC/Geyser/tree/inventory)) - Some Entity Flags ## Compiling 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 1ddda6528..5b8842b4e 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 diff --git a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitLogger.java b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitLogger.java index 05fd2c6c8..454ec9f6e 100644 --- a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitLogger.java +++ b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitLogger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 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 3c7ae8391..fa58f66c5 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 @@ -25,16 +25,20 @@ package org.geysermc.platform.bukkit; +import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; import org.geysermc.common.PlatformType; -import org.geysermc.connector.GeyserConnector; import org.geysermc.common.bootstrap.IGeyserBootstrap; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.CommandManager; import org.geysermc.platform.bukkit.command.GeyserBukkitCommandExecutor; +import org.geysermc.platform.bukkit.command.GeyserBukkitCommandManager; import java.util.UUID; public class GeyserBukkitPlugin extends JavaPlugin implements IGeyserBootstrap { + private GeyserBukkitCommandManager geyserCommandManager; private GeyserBukkitConfiguration geyserConfig; private GeyserBukkitLogger geyserLogger; @@ -50,9 +54,20 @@ public class GeyserBukkitPlugin extends JavaPlugin implements IGeyserBootstrap { saveConfig(); } + // Don't change the ip if its listening on all interfaces + // By default this should be 127.0.0.1 but may need to be changed in some circumstances + if (!Bukkit.getIp().equals("0.0.0.0")) { + getConfig().set("remote.address", Bukkit.getIp()); + } + + getConfig().set("remote.port", Bukkit.getPort()); + saveConfig(); + this.geyserLogger = new GeyserBukkitLogger(getLogger(), geyserConfig.isDebugMode()); this.connector = GeyserConnector.start(PlatformType.BUKKIT, this); + this.geyserCommandManager = new GeyserBukkitCommandManager(this, connector); + this.getCommand("geyser").setExecutor(new GeyserBukkitCommandExecutor(connector)); } @@ -70,4 +85,9 @@ public class GeyserBukkitPlugin extends JavaPlugin implements IGeyserBootstrap { public GeyserBukkitLogger getGeyserLogger() { return geyserLogger; } + + @Override + public CommandManager getGeyserCommandManager() { + return this.geyserCommandManager; + } } diff --git a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/command/GeyserBukkitCommandExecutor.java b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/command/GeyserBukkitCommandExecutor.java index 84920db72..d2603f7c5 100644 --- a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/command/GeyserBukkitCommandExecutor.java +++ b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/command/GeyserBukkitCommandExecutor.java @@ -70,6 +70,6 @@ public class GeyserBukkitCommandExecutor implements TabExecutor { } private GeyserCommand getCommand(String label) { - return connector.getCommandMap().getCommands().get(label); + return connector.getCommandManager().getCommands().get(label); } } diff --git a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/command/GeyserBukkitCommandManager.java b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/command/GeyserBukkitCommandManager.java new file mode 100644 index 000000000..b826ab1f5 --- /dev/null +++ b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/command/GeyserBukkitCommandManager.java @@ -0,0 +1,64 @@ +/* + * 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.command; + +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandMap; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.CommandManager; +import org.geysermc.platform.bukkit.GeyserBukkitPlugin; + +import java.lang.reflect.Field; + +public class GeyserBukkitCommandManager extends CommandManager { + + private static CommandMap COMMAND_MAP; + + static { + try { + Field cmdMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap"); + cmdMapField.setAccessible(true); + COMMAND_MAP = (CommandMap) cmdMapField.get(Bukkit.getServer()); + } catch (NoSuchFieldException | IllegalAccessException ex) { + ex.printStackTrace(); + } + } + + private GeyserBukkitPlugin plugin; + + public GeyserBukkitCommandManager(GeyserBukkitPlugin plugin, GeyserConnector connector) { + super(connector); + + this.plugin = plugin; + } + + @Override + public String getDescription(String command) { + Command cmd = COMMAND_MAP.getCommand(command.replace("/", "")); + return cmd != null ? cmd.getDescription() : ""; + } +} 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 420f8347e..e0f6a6eff 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeLogger.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeLogger.java index 2ebc069b1..7aba88bcd 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeLogger.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeLogger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 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 d6974dafa..4c5953900 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 @@ -25,25 +25,29 @@ package org.geysermc.platform.bungeecord; +import net.md_5.bungee.api.config.ListenerInfo; import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.config.Configuration; import net.md_5.bungee.config.ConfigurationProvider; import net.md_5.bungee.config.YamlConfiguration; - import org.geysermc.common.PlatformType; -import org.geysermc.connector.GeyserConnector; import org.geysermc.common.bootstrap.IGeyserBootstrap; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.CommandManager; import org.geysermc.platform.bungeecord.command.GeyserBungeeCommandExecutor; +import org.geysermc.platform.bungeecord.command.GeyserBungeeCommandManager; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.net.InetSocketAddress; import java.nio.file.Files; import java.util.UUID; import java.util.logging.Level; public class GeyserBungeePlugin extends Plugin implements IGeyserBootstrap { + private GeyserBungeeCommandManager geyserCommandManager; private GeyserBungeeConfiguration geyserConfig; private GeyserBungeeLogger geyserLogger; @@ -78,8 +82,31 @@ public class GeyserBungeePlugin extends Plugin implements IGeyserBootstrap { this.geyserConfig = new GeyserBungeeConfiguration(getDataFolder(), configuration); + boolean configHasChanged = false; + + if (getProxy().getConfig().getListeners().size() == 1) { + ListenerInfo listener = getProxy().getConfig().getListeners().toArray(new ListenerInfo[0])[0]; + + InetSocketAddress javaAddr = listener.getHost(); + + // Don't change the ip if its listening on all interfaces + // By default this should be 127.0.0.1 but may need to be changed in some circumstances + if (!javaAddr.getHostString().equals("0.0.0.0")) { + configuration.set("remote.address", javaAddr.getHostString()); + } + + configuration.set("remote.port", javaAddr.getPort()); + + configHasChanged = true; + } + if (geyserConfig.getMetrics().getUniqueId().equals("generateduuid")) { configuration.set("metrics.uuid", UUID.randomUUID().toString()); + + configHasChanged = true; + } + + if (configHasChanged) { try { ConfigurationProvider.getProvider(YamlConfiguration.class).save(configuration, new File(getDataFolder(), "config.yml")); } catch (IOException ex) { @@ -91,6 +118,8 @@ public class GeyserBungeePlugin extends Plugin implements IGeyserBootstrap { this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode()); this.connector = GeyserConnector.start(PlatformType.BUNGEECORD, this); + this.geyserCommandManager = new GeyserBungeeCommandManager(connector); + this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor(connector)); } @@ -108,4 +137,9 @@ public class GeyserBungeePlugin extends Plugin implements IGeyserBootstrap { public GeyserBungeeLogger getGeyserLogger() { return geyserLogger; } + + @Override + public CommandManager getGeyserCommandManager() { + return this.geyserCommandManager; + } } diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java index 8149d2917..d1c8473bd 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java @@ -71,6 +71,6 @@ public class GeyserBungeeCommandExecutor extends Command implements TabExecutor } private GeyserCommand getCommand(String label) { - return connector.getCommandMap().getCommands().get(label); + return connector.getCommandManager().getCommands().get(label); } } diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandManager.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandManager.java new file mode 100644 index 000000000..bb79c5779 --- /dev/null +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandManager.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.platform.bungeecord.command; + +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.CommandManager; + +public class GeyserBungeeCommandManager extends CommandManager { + + public GeyserBungeeCommandManager(GeyserConnector connector) { + super(connector); + } + + @Override + public String getDescription(String command) { + return ""; // no support for command descriptions in bungee + } +} 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 7b6a89f15..e60a5c3ef 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 @@ -26,17 +26,13 @@ package org.geysermc.platform.sponge; import lombok.AllArgsConstructor; - import ninja.leaping.configurate.ConfigurationNode; - import org.geysermc.common.IGeyserConfiguration; import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; public class GeyserSpongeConfiguration implements IGeyserConfiguration { @@ -60,7 +56,8 @@ public class GeyserSpongeConfiguration implements IGeyserConfiguration { if (node.getNode("userAuths").getValue() == null) return; - for (String key : (List) node.getNode("userAuths").getValue()) { + List userAuths = new ArrayList(((LinkedHashMap)node.getNode("userAuths").getValue()).keySet()); + for (String key : userAuths) { userAuthInfo.put(key, new SpongeUserAuthenticationInfo(key)); } } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeLogger.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeLogger.java index 1df7fbf9b..758ac98d3 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeLogger.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeLogger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 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 a2074b158..cc26abb9c 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 @@ -26,15 +26,16 @@ package org.geysermc.platform.sponge; import com.google.inject.Inject; - +import ninja.leaping.configurate.ConfigurationNode; import ninja.leaping.configurate.loader.ConfigurationLoader; import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; - import org.geysermc.common.PlatformType; import org.geysermc.common.bootstrap.IGeyserBootstrap; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.CommandManager; import org.geysermc.connector.utils.FileUtils; import org.geysermc.platform.sponge.command.GeyserSpongeCommandExecutor; +import org.geysermc.platform.sponge.command.GeyserSpongeCommandManager; import org.slf4j.Logger; import org.spongepowered.api.Sponge; import org.spongepowered.api.config.ConfigDir; @@ -45,6 +46,7 @@ import org.spongepowered.api.plugin.Plugin; import java.io.File; import java.io.IOException; +import java.net.InetSocketAddress; import java.util.UUID; @Plugin(id = "geyser", name = GeyserConnector.NAME + "-Sponge", version = GeyserConnector.VERSION, url = "https://geysermc.org", authors = "GeyserMC") @@ -57,6 +59,7 @@ public class GeyserSpongePlugin implements IGeyserBootstrap { @ConfigDir(sharedRoot = false) private File configDir; + private GeyserSpongeCommandManager geyserCommandManager; private GeyserSpongeConfiguration geyserConfig; private GeyserSpongeLogger geyserLogger; @@ -76,16 +79,34 @@ public class GeyserSpongePlugin implements IGeyserBootstrap { } ConfigurationLoader loader = YAMLConfigurationLoader.builder().setPath(configFile.toPath()).build(); + ConfigurationNode config; try { - this.geyserConfig = new GeyserSpongeConfiguration(configDir, loader.load()); + config = loader.load(); + this.geyserConfig = new GeyserSpongeConfiguration(configDir, config); } catch (IOException ex) { logger.warn("Failed to load config.yml!"); ex.printStackTrace(); return; } + ConfigurationNode serverIP = config.getNode("remote").getNode("address"); + ConfigurationNode serverPort = config.getNode("remote").getNode("port"); + + if (Sponge.getServer().getBoundAddress().isPresent()) { + InetSocketAddress javaAddr = Sponge.getServer().getBoundAddress().get(); + + // Don't change the ip if its listening on all interfaces + // By default this should be 127.0.0.1 but may need to be changed in some circumstances + if (!javaAddr.getHostString().equals("0.0.0.0")) { + serverIP.setValue("127.0.0.1"); + } + + serverPort.setValue(javaAddr.getPort()); + } + this.geyserLogger = new GeyserSpongeLogger(logger, geyserConfig.isDebugMode()); this.connector = GeyserConnector.start(PlatformType.SPONGE, this); + this.geyserCommandManager = new GeyserSpongeCommandManager(Sponge.getCommandManager(), connector); Sponge.getCommandManager().register(this, new GeyserSpongeCommandExecutor(connector), "geyser"); } @@ -105,6 +126,11 @@ public class GeyserSpongePlugin implements IGeyserBootstrap { return geyserLogger; } + @Override + public CommandManager getGeyserCommandManager() { + return this.geyserCommandManager; + } + @Listener public void onServerStart(GameStartedServerEvent event) { onEnable(); diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java index c68ff4e47..91cb59b0f 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java @@ -95,6 +95,6 @@ public class GeyserSpongeCommandExecutor implements CommandCallable { } private GeyserCommand getCommand(String label) { - return connector.getCommandMap().getCommands().get(label); + return connector.getCommandManager().getCommands().get(label); } } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandManager.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandManager.java new file mode 100644 index 000000000..c36511a4c --- /dev/null +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandManager.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.platform.sponge.command; + +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.CommandManager; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.command.CommandMapping; +import org.spongepowered.api.text.Text; + +public class GeyserSpongeCommandManager extends CommandManager { + + private org.spongepowered.api.command.CommandManager handle; + + public GeyserSpongeCommandManager(org.spongepowered.api.command.CommandManager handle, GeyserConnector connector) { + super(connector); + + this.handle = handle; + } + + @Override + public String getDescription(String command) { + return handle.get(command).map(CommandMapping::getCallable).map(callable -> callable.getShortDescription(Sponge.getServer().getConsole()).orElse(Text.EMPTY)).orElse(Text.EMPTY).toPlain(); + } +} diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserBootstrap.java index 284d94072..20b7f5ea7 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserBootstrap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 @@ -25,18 +25,21 @@ package org.geysermc.platform.standalone; +import org.geysermc.common.PlatformType; +import org.geysermc.common.bootstrap.IGeyserBootstrap; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.CommandManager; +import org.geysermc.connector.utils.FileUtils; +import org.geysermc.platform.standalone.command.GeyserCommandManager; +import org.geysermc.platform.standalone.console.GeyserLogger; + import java.io.File; import java.io.IOException; import java.util.UUID; -import org.geysermc.common.PlatformType; -import org.geysermc.common.bootstrap.IGeyserBootstrap; -import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.utils.FileUtils; -import org.geysermc.platform.standalone.console.GeyserLogger; - public class GeyserBootstrap implements IGeyserBootstrap { - + + private GeyserCommandManager geyserCommandManager; private GeyserConfiguration geyserConfig; private GeyserLogger geyserLogger; @@ -49,7 +52,7 @@ public class GeyserBootstrap implements IGeyserBootstrap { @Override public void onEnable() { geyserLogger = new GeyserLogger(); - + LoopbackUtil.checkLoopback(geyserLogger); try { @@ -61,6 +64,7 @@ public class GeyserBootstrap implements IGeyserBootstrap { } connector = GeyserConnector.start(PlatformType.STANDALONE, this); + geyserCommandManager = new GeyserCommandManager(connector); geyserLogger.start(); } @@ -79,4 +83,9 @@ public class GeyserBootstrap implements IGeyserBootstrap { public GeyserLogger getGeyserLogger() { return geyserLogger; } + + @Override + public CommandManager getGeyserCommandManager() { + return geyserCommandManager; + } } diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserConfiguration.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserConfiguration.java index a1362c7a7..afd6179e8 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserConfiguration.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/command/GeyserCommandManager.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/command/GeyserCommandManager.java new file mode 100644 index 000000000..41bf61c12 --- /dev/null +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/command/GeyserCommandManager.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.platform.standalone.command; + +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.CommandManager; + +public class GeyserCommandManager extends CommandManager { + + public GeyserCommandManager(GeyserConnector connector) { + super(connector); + } + + @Override + public String getDescription(String command) { + return ""; // this is not sent over the protocol, so we return none + } +} diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/console/GeyserLogger.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/console/GeyserLogger.java index ac21215cb..631de9052 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/console/GeyserLogger.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/console/GeyserLogger.java @@ -47,7 +47,7 @@ public class GeyserLogger extends SimpleTerminalConsole implements IGeyserLogger @Override protected void runCommand(String line) { - GeyserConnector.getInstance().getCommandMap().runCommand(this, line); + GeyserConnector.getInstance().getCommandManager().runCommand(this, line); } @Override 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 920c65379..03f7cbeeb 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java @@ -27,9 +27,8 @@ package org.geysermc.platform.velocity; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; - import lombok.Getter; - +import lombok.Setter; import org.geysermc.common.IGeyserConfiguration; import java.nio.file.Path; @@ -86,7 +85,10 @@ public class GeyserVelocityConfiguration implements IGeyserConfiguration { @Getter public static class RemoteConfiguration implements IRemoteConfiguration { + @Setter private String address; + + @Setter private int port; private String motd1; 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 5ab411f3e..38378f01c 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 @@ -33,15 +33,18 @@ import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.proxy.ProxyServer; import org.geysermc.common.PlatformType; import org.geysermc.common.bootstrap.IGeyserBootstrap; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.utils.FileUtils; import org.geysermc.platform.velocity.command.GeyserVelocityCommandExecutor; +import org.geysermc.platform.velocity.command.GeyserVelocityCommandManager; import org.slf4j.Logger; import java.io.File; import java.io.IOException; +import java.net.InetSocketAddress; import java.util.UUID; @Plugin(id = "geyser", name = GeyserConnector.NAME + "-Velocity", version = GeyserConnector.VERSION, url = "https://geysermc.org", authors = "GeyserMC") @@ -50,9 +53,13 @@ public class GeyserVelocityPlugin implements IGeyserBootstrap { @Inject private Logger logger; + @Inject + private ProxyServer server; + @Inject private CommandManager commandManager; + private GeyserVelocityCommandManager geyserCommandManager; private GeyserVelocityConfiguration geyserConfig; private GeyserVelocityLogger geyserLogger; @@ -71,9 +78,20 @@ public class GeyserVelocityPlugin implements IGeyserBootstrap { ex.printStackTrace(); } + InetSocketAddress javaAddr = server.getBoundAddress(); + + // Don't change the ip if its listening on all interfaces + // By default this should be 127.0.0.1 but may need to be changed in some circumstances + if (!javaAddr.getHostString().equals("0.0.0.0")) { + geyserConfig.getRemote().setAddress(javaAddr.getHostString()); + } + + geyserConfig.getRemote().setPort(javaAddr.getPort()); + this.geyserLogger = new GeyserVelocityLogger(logger, geyserConfig.isDebugMode()); this.connector = GeyserConnector.start(PlatformType.VELOCITY, this); + this.geyserCommandManager = new GeyserVelocityCommandManager(connector); this.commandManager.register(new GeyserVelocityCommandExecutor(connector), "geyser"); } @@ -92,6 +110,11 @@ public class GeyserVelocityPlugin implements IGeyserBootstrap { return geyserLogger; } + @Override + public org.geysermc.connector.command.CommandManager getGeyserCommandManager() { + return this.geyserCommandManager; + } + @Subscribe public void onInit(ProxyInitializeEvent event) { onEnable(); diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java index 39a0af97c..940c52244 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java @@ -57,6 +57,6 @@ public class GeyserVelocityCommandExecutor implements Command { } private GeyserCommand getCommand(String label) { - return connector.getCommandMap().getCommands().get(label); + return connector.getCommandManager().getCommands().get(label); } } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandManager.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandManager.java new file mode 100644 index 000000000..76655d0ac --- /dev/null +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandManager.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.platform.velocity.command; + +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.CommandManager; + +public class GeyserVelocityCommandManager extends CommandManager { + + public GeyserVelocityCommandManager(GeyserConnector connector) { + super(connector); + } + + @Override + public String getDescription(String command) { + return ""; // no support for command descriptions in velocity + } +} diff --git a/common/src/main/java/org/geysermc/common/AuthType.java b/common/src/main/java/org/geysermc/common/AuthType.java index bec515600..8edbc4d55 100644 --- a/common/src/main/java/org/geysermc/common/AuthType.java +++ b/common/src/main/java/org/geysermc/common/AuthType.java @@ -14,6 +14,13 @@ public enum AuthType { return id < VALUES.length ? VALUES[id] : OFFLINE; } + /** + * Convert the AuthType string (from config) to the enum, OFFLINE on fail + * + * @param name AuthType string + * + * @return The converted AuthType + */ public static AuthType getByName(String name) { String upperCase = name.toUpperCase(); for (AuthType type : VALUES) { diff --git a/common/src/main/java/org/geysermc/common/ChatColor.java b/common/src/main/java/org/geysermc/common/ChatColor.java index 20632770c..8868b063c 100644 --- a/common/src/main/java/org/geysermc/common/ChatColor.java +++ b/common/src/main/java/org/geysermc/common/ChatColor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 @@ -51,6 +51,13 @@ public class ChatColor { public static final String ITALIC = ESCAPE + "o"; public static final String RESET = ESCAPE + "r"; + /** + * Convert chat colour codes to terminal colours + * + * @param string The text to replace colours for + * + * @return A string ready for terminal printing + */ public static String toANSI(String string) { string = string.replace(BOLD, (char) 0x1b + "[1m"); string = string.replace(OBFUSCATED, (char) 0x1b + "[5m"); @@ -81,6 +88,13 @@ public class ChatColor { return message.replace(color, ESCAPE); } + /** + * Remove all colour formatting tags from a message + * + * @param message Message to remove colour tags from + * + * @return The sanitised message + */ public static String stripColors(String message) { return message = message.replaceAll("(&([a-fk-or0-9]))","").replaceAll("(ยง([a-fk-or0-9]))","").replaceAll("s/\\x1b\\[[0-9;]*[a-zA-Z]//g",""); } diff --git a/common/src/main/java/org/geysermc/common/IGeyserConfiguration.java b/common/src/main/java/org/geysermc/common/IGeyserConfiguration.java index 41534457e..774e3394d 100644 --- a/common/src/main/java/org/geysermc/common/IGeyserConfiguration.java +++ b/common/src/main/java/org/geysermc/common/IGeyserConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 diff --git a/common/src/main/java/org/geysermc/common/bootstrap/IGeyserBootstrap.java b/common/src/main/java/org/geysermc/common/bootstrap/IGeyserBootstrap.java index cf4b3991e..5df61953f 100644 --- a/common/src/main/java/org/geysermc/common/bootstrap/IGeyserBootstrap.java +++ b/common/src/main/java/org/geysermc/common/bootstrap/IGeyserBootstrap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 @@ -26,15 +26,39 @@ package org.geysermc.common.bootstrap; import org.geysermc.common.IGeyserConfiguration; +import org.geysermc.common.command.ICommandManager; import org.geysermc.common.logger.IGeyserLogger; public interface IGeyserBootstrap { + /** + * Called when the GeyserBootstrap is enabled + */ void onEnable(); + /** + * Called when the GeyserBootstrap is disabled + */ void onDisable(); + /** + * Returns the current GeyserConfig + * + * @return The current GeyserConfig + */ IGeyserConfiguration getGeyserConfig(); + /** + * Returns the current GeyserLogger + * + * @return The current GeyserLogger + */ IGeyserLogger getGeyserLogger(); + + /** + * Returns the current GeyserCommandManager + * + * @return The current GeyserCommandManager + */ + ICommandManager getGeyserCommandManager(); } diff --git a/common/src/main/java/org/geysermc/common/command/ICommandManager.java b/common/src/main/java/org/geysermc/common/command/ICommandManager.java new file mode 100644 index 000000000..f46dfafcd --- /dev/null +++ b/common/src/main/java/org/geysermc/common/command/ICommandManager.java @@ -0,0 +1,38 @@ +/* + * 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.command; + +public interface ICommandManager { + + /** + * Returns the description of the given command + * + * @param command Command to get the description for + * + * @return Command description + */ + String getDescription(String command); +} diff --git a/common/src/main/java/org/geysermc/common/logger/IGeyserLogger.java b/common/src/main/java/org/geysermc/common/logger/IGeyserLogger.java index b4d143e43..ad571ebbb 100644 --- a/common/src/main/java/org/geysermc/common/logger/IGeyserLogger.java +++ b/common/src/main/java/org/geysermc/common/logger/IGeyserLogger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 diff --git a/common/src/main/java/org/geysermc/common/window/CustomFormBuilder.java b/common/src/main/java/org/geysermc/common/window/CustomFormBuilder.java index fb6794120..004b00a96 100644 --- a/common/src/main/java/org/geysermc/common/window/CustomFormBuilder.java +++ b/common/src/main/java/org/geysermc/common/window/CustomFormBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 diff --git a/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java b/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java index a6bc72781..efc71ae8d 100644 --- a/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java +++ b/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 @@ -25,8 +25,9 @@ package org.geysermc.common.window; -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; import lombok.Setter; import org.geysermc.common.window.button.FormImage; @@ -34,6 +35,7 @@ import org.geysermc.common.window.component.*; import org.geysermc.common.window.response.CustomFormResponse; import org.geysermc.common.window.response.FormResponseData; +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -77,7 +79,11 @@ public class CustomFormWindow extends FormWindow { } public String getJSONData() { - String toModify = new Gson().toJson(this); + String toModify = ""; + try { + toModify = new ObjectMapper().writeValueAsString(this); + } catch (JsonProcessingException e) { } + //We need to replace this due to Java not supporting declaring class field 'default' return toModify.replace("defaultOptionIndex", "default") .replace("defaultText", "default") @@ -100,7 +106,11 @@ public class CustomFormWindow extends FormWindow { Map responses = new HashMap(); Map labelResponses = new HashMap(); - List componentResponses = new Gson().fromJson(data, new TypeToken>() { }.getType()); + List componentResponses = new ArrayList<>(); + try { + componentResponses = new ObjectMapper().readValue(data, new TypeReference>(){}); + } catch (IOException e) { } + for (String response : componentResponses) { if (i >= content.size()) { break; diff --git a/common/src/main/java/org/geysermc/common/window/FormWindow.java b/common/src/main/java/org/geysermc/common/window/FormWindow.java index 968d9349d..c3cc4258b 100644 --- a/common/src/main/java/org/geysermc/common/window/FormWindow.java +++ b/common/src/main/java/org/geysermc/common/window/FormWindow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 @@ -25,6 +25,7 @@ package org.geysermc.common.window; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Getter; import lombok.Setter; import org.geysermc.common.window.response.FormResponse; @@ -50,6 +51,7 @@ public abstract class FormWindow { this.response = response; } + @JsonIgnore public abstract String getJSONData(); public abstract void setResponse(String response); diff --git a/common/src/main/java/org/geysermc/common/window/ModalFormWindow.java b/common/src/main/java/org/geysermc/common/window/ModalFormWindow.java index 934660395..bfeafa1b0 100644 --- a/common/src/main/java/org/geysermc/common/window/ModalFormWindow.java +++ b/common/src/main/java/org/geysermc/common/window/ModalFormWindow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 @@ -25,7 +25,8 @@ package org.geysermc.common.window; -import com.google.gson.Gson; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; import lombok.Setter; import org.geysermc.common.window.response.ModalFormResponse; @@ -59,7 +60,11 @@ public class ModalFormWindow extends FormWindow { @Override public String getJSONData() { - return new Gson().toJson(this); + try { + return new ObjectMapper().writeValueAsString(this); + } catch (JsonProcessingException e) { + return ""; + } } public void setResponse(String data) { diff --git a/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java b/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java index cc31f061a..7c1acc26f 100644 --- a/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java +++ b/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 @@ -25,7 +25,8 @@ package org.geysermc.common.window; -import com.google.gson.Gson; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; import lombok.Setter; import org.geysermc.common.window.button.FormButton; @@ -63,7 +64,11 @@ public class SimpleFormWindow extends FormWindow { @Override public String getJSONData() { - return new Gson().toJson(this); + try { + return new ObjectMapper().writeValueAsString(this); + } catch (JsonProcessingException e) { + return ""; + } } public void setResponse(String data) { diff --git a/common/src/main/java/org/geysermc/common/window/button/FormButton.java b/common/src/main/java/org/geysermc/common/window/button/FormButton.java index 4f710d483..6daa2feae 100644 --- a/common/src/main/java/org/geysermc/common/window/button/FormButton.java +++ b/common/src/main/java/org/geysermc/common/window/button/FormButton.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 @@ -37,6 +37,10 @@ public class FormButton { @Getter private FormImage image; + public FormButton(String text) { + this.text = text; + } + public FormButton(String text, FormImage image) { this.text = text; diff --git a/common/src/main/java/org/geysermc/common/window/button/FormImage.java b/common/src/main/java/org/geysermc/common/window/button/FormImage.java index a3f83a0c5..b700b046b 100644 --- a/common/src/main/java/org/geysermc/common/window/button/FormImage.java +++ b/common/src/main/java/org/geysermc/common/window/button/FormImage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 diff --git a/common/src/main/java/org/geysermc/common/window/component/DropdownComponent.java b/common/src/main/java/org/geysermc/common/window/component/DropdownComponent.java index 8abe72ff8..4dac6b043 100644 --- a/common/src/main/java/org/geysermc/common/window/component/DropdownComponent.java +++ b/common/src/main/java/org/geysermc/common/window/component/DropdownComponent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 diff --git a/common/src/main/java/org/geysermc/common/window/component/FormComponent.java b/common/src/main/java/org/geysermc/common/window/component/FormComponent.java index fb5b9d18c..5a56ae0cc 100644 --- a/common/src/main/java/org/geysermc/common/window/component/FormComponent.java +++ b/common/src/main/java/org/geysermc/common/window/component/FormComponent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 diff --git a/common/src/main/java/org/geysermc/common/window/component/InputComponent.java b/common/src/main/java/org/geysermc/common/window/component/InputComponent.java index 53ec2b5eb..fad6a0fed 100644 --- a/common/src/main/java/org/geysermc/common/window/component/InputComponent.java +++ b/common/src/main/java/org/geysermc/common/window/component/InputComponent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 diff --git a/common/src/main/java/org/geysermc/common/window/component/LabelComponent.java b/common/src/main/java/org/geysermc/common/window/component/LabelComponent.java index 7d2aaa420..a76b313fa 100644 --- a/common/src/main/java/org/geysermc/common/window/component/LabelComponent.java +++ b/common/src/main/java/org/geysermc/common/window/component/LabelComponent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 diff --git a/common/src/main/java/org/geysermc/common/window/component/SliderComponent.java b/common/src/main/java/org/geysermc/common/window/component/SliderComponent.java index fd82b3e26..a7a78362e 100644 --- a/common/src/main/java/org/geysermc/common/window/component/SliderComponent.java +++ b/common/src/main/java/org/geysermc/common/window/component/SliderComponent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 diff --git a/common/src/main/java/org/geysermc/common/window/component/StepSliderComponent.java b/common/src/main/java/org/geysermc/common/window/component/StepSliderComponent.java index b61e416d0..8f128d1a4 100644 --- a/common/src/main/java/org/geysermc/common/window/component/StepSliderComponent.java +++ b/common/src/main/java/org/geysermc/common/window/component/StepSliderComponent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 diff --git a/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java b/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java index 614ecf8fb..50a5c631a 100644 --- a/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java +++ b/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 diff --git a/common/src/main/java/org/geysermc/common/window/response/CustomFormResponse.java b/common/src/main/java/org/geysermc/common/window/response/CustomFormResponse.java index 36b2922f9..6cdd70978 100644 --- a/common/src/main/java/org/geysermc/common/window/response/CustomFormResponse.java +++ b/common/src/main/java/org/geysermc/common/window/response/CustomFormResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 diff --git a/common/src/main/java/org/geysermc/common/window/response/FormResponse.java b/common/src/main/java/org/geysermc/common/window/response/FormResponse.java index 58c8c8e87..2be646837 100644 --- a/common/src/main/java/org/geysermc/common/window/response/FormResponse.java +++ b/common/src/main/java/org/geysermc/common/window/response/FormResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 diff --git a/common/src/main/java/org/geysermc/common/window/response/FormResponseData.java b/common/src/main/java/org/geysermc/common/window/response/FormResponseData.java index 826aba208..fd40be0fb 100644 --- a/common/src/main/java/org/geysermc/common/window/response/FormResponseData.java +++ b/common/src/main/java/org/geysermc/common/window/response/FormResponseData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 diff --git a/common/src/main/java/org/geysermc/common/window/response/ModalFormResponse.java b/common/src/main/java/org/geysermc/common/window/response/ModalFormResponse.java index 844c9a5b8..e1a14039d 100644 --- a/common/src/main/java/org/geysermc/common/window/response/ModalFormResponse.java +++ b/common/src/main/java/org/geysermc/common/window/response/ModalFormResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 diff --git a/common/src/main/java/org/geysermc/common/window/response/SimpleFormResponse.java b/common/src/main/java/org/geysermc/common/window/response/SimpleFormResponse.java index 5a53d0423..e80d58e78 100644 --- a/common/src/main/java/org/geysermc/common/window/response/SimpleFormResponse.java +++ b/common/src/main/java/org/geysermc/common/window/response/SimpleFormResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 diff --git a/connector/pom.xml b/connector/pom.xml index 25ef50730..9de533b6a 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -30,16 +30,10 @@ 2.9.8 compile - - io.sentry - sentry - 1.7.0 - compile - com.nukkitx.protocol - bedrock-v389 - 2.5.4 + bedrock-v390 + 2.5.6-SNAPSHOT compile @@ -72,10 +66,34 @@ 8.3.1 compile + + com.nukkitx.fastutil + fastutil-int-double-maps + 8.3.1 + compile + + + com.nukkitx.fastutil + fastutil-int-boolean-maps + 8.3.1 + compile + + + com.nukkitx.fastutil + fastutil-object-int-maps + 8.3.1 + compile + + + com.nukkitx.fastutil + fastutil-object-byte-maps + 8.3.1 + compile + com.github.steveice10 opennbt - 1.3-SNAPSHOT + 1.4-SNAPSHOT compile @@ -93,7 +111,7 @@ com.github.steveice10 mcauthlib - 1.1-SNAPSHOT + 1.3-SNAPSHOT compile @@ -121,5 +139,23 @@ reflections 0.9.12 + + net.kyori + text-api + 3.0.3 + compile + + + net.kyori + text-serializer-gson + 3.0.3 + compile + + + net.kyori + text-serializer-legacy + 3.0.3 + compile + diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index b839c1af7..101b66cf4 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -27,15 +27,16 @@ package org.geysermc.connector; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; import com.nukkitx.protocol.bedrock.BedrockServer; -import com.nukkitx.protocol.bedrock.v389.Bedrock_v389; +import com.nukkitx.protocol.bedrock.v390.Bedrock_v390; import lombok.Getter; import org.geysermc.common.AuthType; +import org.geysermc.common.IGeyserConfiguration; import org.geysermc.common.PlatformType; import org.geysermc.common.bootstrap.IGeyserBootstrap; import org.geysermc.common.logger.IGeyserLogger; -import org.geysermc.connector.command.GeyserCommandMap; +import org.geysermc.connector.command.CommandManager; import org.geysermc.connector.metrics.Metrics; import org.geysermc.connector.network.ConnectorServerEventHandler; import org.geysermc.connector.network.remote.RemoteServer; @@ -44,14 +45,12 @@ import org.geysermc.connector.network.translators.Translators; import org.geysermc.connector.thread.PingPassthroughThread; import org.geysermc.connector.utils.ResourcePack; import org.geysermc.connector.utils.Toolbox; -import org.geysermc.common.IGeyserConfiguration; import java.net.InetSocketAddress; import java.text.DecimalFormat; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; -import java.util.UUID; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -59,7 +58,7 @@ import java.util.concurrent.TimeUnit; @Getter public class GeyserConnector { - public static final BedrockPacketCodec BEDROCK_PACKET_CODEC = Bedrock_v389.V389_CODEC; + public static final BedrockPacketCodec BEDROCK_PACKET_CODEC = Bedrock_v390.V390_CODEC; public static final String NAME = "Geyser"; public static final String VERSION = "1.0-SNAPSHOT"; @@ -71,8 +70,6 @@ public class GeyserConnector { private RemoteServer remoteServer; private AuthType authType; - private GeyserCommandMap commandMap; - private boolean shuttingDown = false; private final ScheduledExecutorService generalThreadPool; @@ -110,7 +107,6 @@ public class GeyserConnector { Toolbox.init(); ResourcePack.loadPacks(); - commandMap = new GeyserCommandMap(this); remoteServer = new RemoteServer(config.getRemote().getAddress(), config.getRemote().getPort()); authType = AuthType.getByName(config.getRemote().getAuthType()); @@ -184,8 +180,7 @@ public class GeyserConnector { players.clear(); remoteServer = null; authType = null; - commandMap.getCommands().clear(); - commandMap = null; + this.getCommandManager().getCommands().clear(); bootstrap.getGeyserLogger().info("Geyser shutdown successfully."); } @@ -215,6 +210,10 @@ public class GeyserConnector { return bootstrap.getGeyserConfig(); } + public CommandManager getCommandManager() { + return (CommandManager) bootstrap.getGeyserCommandManager(); + } + public static GeyserConnector getInstance() { return instance; } diff --git a/connector/src/main/java/org/geysermc/connector/command/GeyserCommandMap.java b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java similarity index 86% rename from connector/src/main/java/org/geysermc/connector/command/GeyserCommandMap.java rename to connector/src/main/java/org/geysermc/connector/command/CommandManager.java index c4bf488ee..7b1b4d69b 100644 --- a/connector/src/main/java/org/geysermc/connector/command/GeyserCommandMap.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java @@ -26,28 +26,29 @@ package org.geysermc.connector.command; import lombok.Getter; +import org.geysermc.common.command.ICommandManager; import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.defaults.HelpCommand; -import org.geysermc.connector.command.defaults.ReloadCommand; -import org.geysermc.connector.command.defaults.StopCommand; +import org.geysermc.connector.command.defaults.*; import java.util.Collections; import java.util.HashMap; import java.util.Map; -public class GeyserCommandMap { +public abstract class CommandManager implements ICommandManager { @Getter private final Map commands = Collections.synchronizedMap(new HashMap<>()); private GeyserConnector connector; - public GeyserCommandMap(GeyserConnector connector) { + public CommandManager(GeyserConnector connector) { this.connector = connector; registerCommand(new HelpCommand(connector, "help", "Shows help for all registered commands.", "geyser.command.help")); + registerCommand(new ListCommand(connector, "list", "List all players connected through Geyser.", "geyser.command.list")); registerCommand(new ReloadCommand(connector, "reload", "Reloads the Geyser configurations. Kicks all players when used!", "geyser.command.reload")); registerCommand(new StopCommand(connector, "stop", "Shuts down Geyser.", "geyser.command.stop")); + registerCommand(new OffhandCommand(connector, "offhand", "Puts an items in your offhand.", "geyser.command.offhand")); } public void registerCommand(GeyserCommand command) { diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java index cdfb612a6..6acb7822b 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java @@ -49,8 +49,8 @@ public class HelpCommand extends GeyserCommand { @Override public void execute(CommandSender sender, String[] args) { sender.sendMessage("---- Showing Help For: Geyser (Page 1/1) ----"); - Map cmds = connector.getCommandMap().getCommands(); - List commands = connector.getCommandMap().getCommands().keySet().stream().sorted().collect(Collectors.toList()); + Map cmds = connector.getCommandManager().getCommands(); + List commands = connector.getCommandManager().getCommands().keySet().stream().sorted().collect(Collectors.toList()); commands.forEach(cmd -> sender.sendMessage(ChatColor.YELLOW + "/geyser " + cmd + ChatColor.WHITE + ": " + cmds.get(cmd).getDescription())); } } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/ListCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/ListCommand.java new file mode 100644 index 000000000..21fa42535 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/ListCommand.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.command.defaults; + +import org.geysermc.common.ChatColor; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.CommandSender; +import org.geysermc.connector.command.GeyserCommand; +import org.geysermc.connector.network.session.GeyserSession; + +import java.util.stream.Collectors; + +public class ListCommand extends GeyserCommand { + + private GeyserConnector connector; + + public ListCommand(GeyserConnector connector, String name, String description, String permission) { + super(name, description, permission); + + this.connector = connector; + } + + @Override + public void execute(CommandSender sender, String[] args) { + sender.sendMessage(ChatColor.YELLOW + "Online Players (" + connector.getPlayers().size() + "): " + ChatColor.WHITE + connector.getPlayers().values().stream().map(GeyserSession::getName).collect(Collectors.joining(" "))); + } +} 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 new file mode 100644 index 000000000..d181d07e2 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.command.defaults; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; +import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.CommandSender; +import org.geysermc.connector.command.GeyserCommand; +import org.geysermc.connector.network.session.GeyserSession; + +public class OffhandCommand extends GeyserCommand { + + private GeyserConnector connector; + + public OffhandCommand(GeyserConnector connector, String name, String description, String permission) { + super(name, description, permission); + + this.connector = connector; + } + + @Override + public void execute(CommandSender sender, String[] args) { + if (sender.isConsole()) { + return; + } + + // Make sure the sender is a Bedrock edition client + if (sender instanceof GeyserSession) { + GeyserSession session = (GeyserSession) sender; + ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.SWAP_HANDS, new Position(0,0,0), + BlockFace.DOWN); + session.getDownstream().getSession().send(releaseItemPacket); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java index bd8444c79..c38a0c23d 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java @@ -48,7 +48,7 @@ public class ReloadCommand extends GeyserCommand { } sender.sendMessage(ChatColor.YELLOW + "Reloading Geyser configurations... all connected bedrock clients will be kicked."); for (GeyserSession session : connector.getPlayers().values()) { - session.getUpstream().disconnect("Geyser has been reloaded... sorry for the inconvenience!"); + session.disconnect("Geyser has been reloaded... sorry for the inconvenience!"); } connector.reload(); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/EnderCrystalEntity.java b/connector/src/main/java/org/geysermc/connector/entity/EnderCrystalEntity.java new file mode 100644 index 000000000..b3ce22783 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/EnderCrystalEntity.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.data.EntityData; +import com.nukkitx.protocol.bedrock.data.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class EnderCrystalEntity extends Entity { + + public EnderCrystalEntity(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) { + // Show beam + // Usually performed client-side on Bedrock except for Ender Dragon respawn event + if (entityMetadata.getId() == 7) { + if (entityMetadata.getValue() instanceof Position) { + Position pos = (Position) entityMetadata.getValue(); + metadata.put(EntityData.BLOCK_TARGET, Vector3i.from(pos.getX(), pos.getY(), pos.getZ())); + } else { + metadata.put(EntityData.BLOCK_TARGET, Vector3i.ZERO); + } + } + // There is a base located on the ender crystal + if (entityMetadata.getId() == 8) { + metadata.getFlags().setFlag(EntityFlag.SHOW_BOTTOM, (boolean) entityMetadata.getValue()); + } + super.updateBedrockMetadata(entityMetadata, session); + } + + @Override + public void spawnEntity(GeyserSession session) { + AddEntityPacket addEntityPacket = new AddEntityPacket(); + // Not end crystal but ender crystal + addEntityPacket.setIdentifier("minecraft:ender_crystal"); + addEntityPacket.setRuntimeEntityId(geyserId); + addEntityPacket.setUniqueEntityId(geyserId); + addEntityPacket.setPosition(position); + addEntityPacket.setMotion(motion); + addEntityPacket.setRotation(getBedrockRotation()); + addEntityPacket.setEntityType(entityType.getType()); + addEntityPacket.getMetadata().putAll(metadata); + + valid = true; + session.getUpstream().sendPacket(addEntityPacket); + + session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index 177d0d7a9..5596ca7ae 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -27,12 +27,15 @@ 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.Position; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; +import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; import com.github.steveice10.mc.protocol.data.message.TextMessage; +import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerUseItemPacket; import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.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.data.*; import com.nukkitx.protocol.bedrock.packet.*; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; @@ -45,6 +48,7 @@ import org.geysermc.connector.entity.attribute.Attribute; import org.geysermc.connector.entity.attribute.AttributeType; 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.utils.AttributeUtils; import org.geysermc.connector.utils.MessageUtils; @@ -120,6 +124,9 @@ public class Entity { } /** + * Despawns the entity + * + * @param session The GeyserSession * @return can be deleted */ public boolean despawnEntity(GeyserSession session) { @@ -196,6 +203,28 @@ public class Entity { metadata.getFlags().setFlag(EntityFlag.SPRINTING, (xd & 0x08) == 0x08); metadata.getFlags().setFlag(EntityFlag.SWIMMING, (xd & 0x10) == 0x10); metadata.getFlags().setFlag(EntityFlag.GLIDING, (xd & 0x80) == 0x80); + + // 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)) { + ClientPlayerUseItemPacket useItemPacket; + metadata.getFlags().setFlag(EntityFlag.BLOCKING, true); + if (session.getInventory().getItemInHand() != null && session.getInventory().getItemInHand().getId() == ItemTranslator.SHIELD) { + useItemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND); + } + // Else we just assume it's the offhand, to simplify logic and to assure the packet gets sent + else { + useItemPacket = new ClientPlayerUseItemPacket(Hand.OFF_HAND); + } + session.getDownstream().getSession().send(useItemPacket); + } + } else if (session.getPlayerEntity().getEntityId() == entityId && !metadata.getFlags().getFlag(EntityFlag.SNEAKING) && metadata.getFlags().getFlag(EntityFlag.BLOCKING)) { + metadata.getFlags().setFlag(EntityFlag.BLOCKING, false); + metadata.getFlags().setFlag(EntityFlag.DISABLE_BLOCKING, true); + ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, new Position(0,0,0), BlockFace.DOWN); + session.getDownstream().getSession().send(releaseItemPacket); + } // metadata.getFlags().setFlag(EntityFlag.INVISIBLE, (xd & 0x20) == 0x20); if ((xd & 0x20) == 0x20) metadata.put(EntityData.SCALE, 0.0f); @@ -218,6 +247,12 @@ public class Entity { case 5: // no gravity metadata.getFlags().setFlag(EntityFlag.HAS_GRAVITY, !(boolean) entityMetadata.getValue()); break; + case 7: // blocking + if (entityMetadata.getType() == MetadataType.BYTE) { + byte xd = (byte) entityMetadata.getValue(); + metadata.getFlags().setFlag(EntityFlag.BLOCKING, (xd & 0x01) == 0x01); + } + break; } updateBedrockMetadata(session); @@ -234,6 +269,7 @@ public class Entity { /** * x = Pitch, y = HeadYaw, z = Yaw + * @return the bedrock rotation */ public Vector3f getBedrockRotation() { return Vector3f.from(rotation.getY(), rotation.getZ(), rotation.getX()); diff --git a/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java new file mode 100644 index 000000000..3a77292fe --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java @@ -0,0 +1,56 @@ +/* + * 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.packet.AddEntityPacket; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class FishingHookEntity extends Entity { + public FishingHookEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void spawnEntity(GeyserSession session) { + AddEntityPacket addEntityPacket = new AddEntityPacket(); + // Different ID in Bedrock + addEntityPacket.setIdentifier("minecraft:fishing_hook"); + addEntityPacket.setRuntimeEntityId(geyserId); + addEntityPacket.setUniqueEntityId(geyserId); + addEntityPacket.setPosition(position); + addEntityPacket.setMotion(motion); + addEntityPacket.setRotation(getBedrockRotation()); + addEntityPacket.setEntityType(entityType.getType()); + addEntityPacket.getMetadata().putAll(metadata); + + valid = true; + session.getUpstream().sendPacket(addEntityPacket); + + session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemEntity.java index a80323076..a28b563b4 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemEntity.java @@ -49,7 +49,7 @@ public class ItemEntity extends Entity { itemPacket.setUniqueEntityId(geyserId); itemPacket.setFromFishing(false); itemPacket.getMetadata().putAll(metadata); - itemPacket.setItemInHand(Translators.getItemTranslator().translateToBedrock((ItemStack) entityMetadata.getValue())); + itemPacket.setItemInHand(Translators.getItemTranslator().translateToBedrock(session, (ItemStack) entityMetadata.getValue())); session.getUpstream().sendPacket(itemPacket); } 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 fb48871b3..d0ac2aeb8 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -44,6 +44,7 @@ import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.scoreboard.Team; import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.session.cache.EntityEffectCache; import org.geysermc.connector.utils.SkinUtils; import java.util.UUID; @@ -55,6 +56,7 @@ public class PlayerEntity extends LivingEntity { private String username; private long lastSkinUpdate = -1; private boolean playerList = true; + private final EntityEffectCache effectCache; public PlayerEntity(GameProfile gameProfile, long entityId, long geyserId, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, EntityType.PLAYER, position, motion, rotation); @@ -62,6 +64,7 @@ public class PlayerEntity extends LivingEntity { profile = gameProfile; uuid = gameProfile.getId(); username = gameProfile.getName(); + effectCache = new EntityEffectCache(); if (geyserId == 1) valid = true; } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/AbstractFishEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/AbstractFishEntity.java index 92acb1179..54443825a 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/AbstractFishEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/AbstractFishEntity.java @@ -25,12 +25,28 @@ package org.geysermc.connector.entity.living; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityData; +import com.nukkitx.protocol.bedrock.data.EntityFlag; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; public class AbstractFishEntity extends WaterEntity { public AbstractFishEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + metadata.getFlags().setFlag(EntityFlag.CAN_SWIM, true); + metadata.getFlags().setFlag(EntityFlag.BREATHING, true); + metadata.getFlags().setFlag(EntityFlag.CAN_CLIMB, false); + metadata.getFlags().setFlag(EntityFlag.HAS_GRAVITY, false); + + metadata.put(EntityData.AIR, (short) 400); + + super.updateBedrockMetadata(entityMetadata, session); + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/AgeableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/AgeableEntity.java index a9c60e457..634f06743 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/AgeableEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/AgeableEntity.java @@ -26,7 +26,6 @@ package org.geysermc.connector.entity.living; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.EntityData; import com.nukkitx.protocol.bedrock.data.EntityFlag; @@ -43,10 +42,8 @@ public class AgeableEntity extends CreatureEntity { public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 15) { boolean isBaby = (boolean) entityMetadata.getValue(); - if (isBaby) { - metadata.put(EntityData.SCALE, .55f); - metadata.getFlags().setFlag(EntityFlag.BABY, true); - } + metadata.put(EntityData.SCALE, isBaby ? .55f : 1f); + metadata.getFlags().setFlag(EntityFlag.BABY, isBaby); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PufferFishEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PufferFishEntity.java new file mode 100644 index 000000000..d5503dc06 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PufferFishEntity.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.entity.living.animal; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityData; +import org.geysermc.connector.entity.living.AbstractFishEntity; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class PufferFishEntity extends AbstractFishEntity { + + public PufferFishEntity(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() == 16) { + // Transfers correctly but doesn't apply on the client + int puffsize = (int) entityMetadata.getValue(); + metadata.put(EntityData.PUFFERFISH_SIZE, puffsize); + metadata.put(EntityData.VARIANT, puffsize); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/TropicalFishEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/TropicalFishEntity.java new file mode 100644 index 000000000..3ded4cbfe --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/TropicalFishEntity.java @@ -0,0 +1,94 @@ +/* + * 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.animal; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityData; +import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.geysermc.connector.entity.living.AbstractFishEntity; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class TropicalFishEntity extends AbstractFishEntity { + + public TropicalFishEntity(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() == 16) { + TropicalFishVariant variant = TropicalFishVariant.fromVariantNumber((int) entityMetadata.getValue()); + + metadata.put(EntityData.VARIANT, variant.getShape()); // Shape 0-1 + metadata.put(EntityData.MARK_VARIANT, variant.getPattern()); // Pattern 0-5 + metadata.put(EntityData.COLOR, variant.getBaseColor()); // Base color 0-15 + metadata.put(EntityData.COLOR_2, variant.getPatternColor()); // Pattern color 0-15 + } + super.updateBedrockMetadata(entityMetadata, session); + } + + @Override + public void spawnEntity(GeyserSession session) { + AddEntityPacket addEntityPacket = new AddEntityPacket(); + addEntityPacket.setIdentifier("minecraft:tropicalfish"); + addEntityPacket.setRuntimeEntityId(geyserId); + addEntityPacket.setUniqueEntityId(geyserId); + addEntityPacket.setPosition(position); + addEntityPacket.setMotion(motion); + addEntityPacket.setRotation(getBedrockRotation()); + addEntityPacket.setEntityType(entityType.getType()); + addEntityPacket.getMetadata().putAll(metadata); + + valid = true; + session.getUpstream().sendPacket(addEntityPacket); + + session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); + } + + @Getter + @AllArgsConstructor + private static class TropicalFishVariant { + private int shape; + private int pattern; + private byte baseColor; + private byte patternColor; + + /** + * Convert the variant number from Java into separate values + * + * @param varNumber Variant number from Java edition + * + * @return The variant converted into TropicalFishVariant + */ + public static TropicalFishVariant fromVariantNumber(int varNumber) { + return new TropicalFishVariant((varNumber & 0xFF), ((varNumber >> 8) & 0xFF), (byte) ((varNumber >> 16) & 0xFF), (byte) ((varNumber >> 24) & 0xFF)); + } + } +} 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 95d91ab80..b6a50520a 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 @@ -45,4 +45,4 @@ public class HorseEntity extends AbstractHorseEntity { super.updateBedrockMetadata(entityMetadata, session); } -} +} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java index 0ac49d55d..118262dcd 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java @@ -44,9 +44,19 @@ public class WolfEntity extends TameableEntity { if (entityMetadata.getId() == 18) { metadata.getFlags().setFlag(EntityFlag.INTERESTED, (boolean) entityMetadata.getValue()); } + + //Reset wolf color + if (entityMetadata.getId() == 16) { + byte xd = (byte) entityMetadata.getValue(); + boolean angry = (xd & 0x02) == 0x02; + if (angry) { + metadata.put(EntityData.COLOR, (byte) 0); + } + } + // Wolf collar color // Relies on EntityData.OWNER_EID being set in TameableEntity.java - if (entityMetadata.getId() == 19) { + if (entityMetadata.getId() == 19 && !metadata.getFlags().getFlag(EntityFlag.ANGRY)) { metadata.put(EntityData.COLOR, (byte) (int) entityMetadata.getValue()); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java new file mode 100644 index 000000000..b07377389 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living.monster; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.Attribute; +import com.nukkitx.protocol.bedrock.data.EntityEventType; +import com.nukkitx.protocol.bedrock.data.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; +import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; +import org.geysermc.connector.entity.living.InsentientEntity; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class EnderDragonEntity extends InsentientEntity { + + public EnderDragonEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 15) { + metadata.getFlags().setFlag(EntityFlag.FIRE_IMMUNE, true); + switch ((int) entityMetadata.getValue()) { + // Performing breath attack + case 5: + EntityEventPacket entityEventPacket = new EntityEventPacket(); + entityEventPacket.setType(EntityEventType.DRAGON_FLAMING); + entityEventPacket.setRuntimeEntityId(geyserId); + entityEventPacket.setData(0); + session.getUpstream().sendPacket(entityEventPacket); + case 6: + case 7: + metadata.getFlags().setFlag(EntityFlag.SITTING, true); + break; + } + } + super.updateBedrockMetadata(entityMetadata, session); + } + + @Override + public void spawnEntity(GeyserSession session) { + AddEntityPacket addEntityPacket = new AddEntityPacket(); + addEntityPacket.setIdentifier("minecraft:" + entityType.name().toLowerCase()); + addEntityPacket.setRuntimeEntityId(geyserId); + addEntityPacket.setUniqueEntityId(geyserId); + addEntityPacket.setPosition(position); + addEntityPacket.setMotion(motion); + addEntityPacket.setRotation(getBedrockRotation()); + addEntityPacket.setEntityType(entityType.getType()); + addEntityPacket.getMetadata().putAll(metadata); + + // Otherwise dragon is always 'dying' + addEntityPacket.getAttributes().add(new Attribute("minecraft:health", 0.0f, 200f, 200f, 200f)); + + valid = true; + session.getUpstream().sendPacket(addEntityPacket); + + session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java new file mode 100644 index 000000000..a423013cb --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living.monster; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityData; +import com.nukkitx.protocol.bedrock.data.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.block.BlockTranslator; + +public class EndermanEntity extends MonsterEntity { + + public EndermanEntity(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) { + // Held block + if (entityMetadata.getId() == 15) { + metadata.put(EntityData.ENDERMAN_HELD_ITEM_ID, BlockTranslator.getBedrockBlockId((BlockState) entityMetadata.getValue())); + } + // 'Angry' - mouth open + if (entityMetadata.getId() == 16) { + metadata.getFlags().setFlag(EntityFlag.ANGRY, (boolean) entityMetadata.getValue()); + } + // TODO: ID 17 is stared at but I don't believe it's used - maybe only for the sound effect. Check after particle merge + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java new file mode 100644 index 000000000..bca9e6891 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living.monster; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.data.EntityData; +import org.geysermc.connector.entity.living.GolemEntity; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class ShulkerEntity extends GolemEntity { + + public ShulkerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 15) { + BlockFace blockFace = (BlockFace) entityMetadata.getValue(); + metadata.put(EntityData.SHULKER_ATTACH_FACE, (byte) blockFace.ordinal()); + } + if (entityMetadata.getId() == 16) { + Position position = (Position) entityMetadata.getValue(); + if (position != null) { + metadata.put(EntityData.SHULKER_ATTACH_POS, Vector3i.from(position.getX(), position.getY(), position.getZ())); + } + } + //TODO Outdated metadata flag SHULKER_PEAK_HEIGHT +// if (entityMetadata.getId() == 17) { +// int height = (byte) entityMetadata.getValue(); +// metadata.put(EntityData.SHULKER_PEAK_HEIGHT, height); +// } + if (entityMetadata.getId() == 18) { + int color = Math.abs((byte) entityMetadata.getValue() - 15); + metadata.put(EntityData.VARIANT, color); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index 38f99abcf..7a7e13c06 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 @@ -69,7 +69,7 @@ public enum EntityType { SPIDER(SpiderEntity.class, 35, 0.9f, 1.4f, 1.4f, 1f), ZOMBIE_PIGMAN(MonsterEntity.class, 36, 1.8f, 0.6f, 0.6f, 1.62f), SLIME(InsentientEntity.class, 37, 0.51f), - ENDERMAN(MonsterEntity.class, 38, 2.9f, 0.6f), + ENDERMAN(EndermanEntity.class, 38, 2.9f, 0.6f), SILVERFISH(MonsterEntity.class, 39, 0.3f, 0.4f), CAVE_SPIDER(MonsterEntity.class, 40, 0.5f, 0.7f), GHAST(FlyingEntity.class, 41, 4.0f), @@ -84,8 +84,8 @@ public enum EntityType { ELDER_GUARDIAN(GuardianEntity.class, 50, 1.9975f), NPC(PlayerEntity.class, 51, 1.8f, 0.6f, 0.6f, 1.62f), WITHER(MonsterEntity.class, 52, 3.5f, 0.9f), - ENDER_DRAGON(InsentientEntity.class, 53, 4f, 13f), - SHULKER(GolemEntity.class, 54, 1f, 1f), + ENDER_DRAGON(EnderDragonEntity.class, 53, 4f, 13f), + SHULKER(ShulkerEntity.class, 54, 1f, 1f), ENDERMITE(MonsterEntity.class, 55, 0.3f, 0.4f), AGENT(Entity.class, 56, 0f), VINDICATOR(AbstractIllagerEntity.class, 57, 1.8f, 0.6f, 0.6f, 1.62f), @@ -104,13 +104,13 @@ public enum EntityType { EXPERIENCE_BOTTLE(ThrowableEntity.class, 68, 0.25f, 0.25f), EXPERIENCE_ORB(ExpOrbEntity.class, 69, 0f), EYE_OF_ENDER(Entity.class, 70, 0f), - END_CRYSTAL(Entity.class, 71, 0f), + END_CRYSTAL(EnderCrystalEntity.class, 71, 0f), FIREWORK_ROCKET(Entity.class, 72, 0f), TRIDENT(ArrowEntity.class, 73, 0f), TURTLE(AnimalEntity.class, 74, 0.4f, 1.2f), CAT(CatEntity.class, 75, 0.35f, 0.3f), SHULKER_BULLET(Entity.class, 76, 0f), - FISHING_BOBBER(Entity.class, 77, 0f), + FISHING_BOBBER(FishingHookEntity.class, 77, 0f), CHALKBOARD(Entity.class, 78, 0f), DRAGON_FIREBALL(ItemedFireballEntity.class, 79, 0f), ARROW(ArrowEntity.class, 80, 0.25f, 0.25f), @@ -140,10 +140,10 @@ public enum EntityType { VEX(MonsterEntity.class, 105, 0f), ICE_BOMB(Entity.class, 106, 0f), BALLOON(Entity.class, 107, 0f), //TODO - PUFFERFISH(AbstractFishEntity.class, 108, 0.7f, 0.7f), + PUFFERFISH(PufferFishEntity.class, 108, 0.7f, 0.7f), SALMON(AbstractFishEntity.class, 109, 0.5f, 0.7f), DROWNED(ZombieEntity.class, 110, 1.95f, 0.6f), - TROPICAL_FISH(AbstractFishEntity.class, 111, 0.6f, 0.6f), + TROPICAL_FISH(TropicalFishEntity.class, 111, 0.6f, 0.6f), COD(AbstractFishEntity.class, 112, 0.25f, 0.5f), PANDA(PandaEntity.class, 113, 1.25f, 1.125f, 1.825f), FOX(FoxEntity.class, 121, 0.5f, 1.25f), diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java index 801f670cd..539fe1e26 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 @@ -27,9 +27,12 @@ package org.geysermc.connector.inventory; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.window.WindowType; +import com.nukkitx.math.vector.Vector3i; import lombok.Getter; import lombok.Setter; +import java.util.concurrent.atomic.AtomicInteger; + public class Inventory { @Getter @@ -43,16 +46,26 @@ public class Inventory { protected WindowType windowType; @Getter - protected int size; + protected final int size; @Getter @Setter protected String title; - @Getter @Setter protected ItemStack[] items; + @Getter + @Setter + protected Vector3i holderPosition = Vector3i.ZERO; + + @Getter + @Setter + protected long holderId = -1; + + @Getter + protected AtomicInteger transactionId = new AtomicInteger(1); + public Inventory(int id, WindowType windowType, int size) { this("Inventory", id, windowType, size); } @@ -62,11 +75,16 @@ public class Inventory { this.id = id; this.windowType = windowType; this.size = size; - this.items = new ItemStack[size]; } public ItemStack getItem(int slot) { return items[slot]; } + + public void setItem(int slot, ItemStack item) { + if (item != null && (item.getId() == 0 || item.getAmount() < 1)) + item = null; + items[slot] = item; + } } diff --git a/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java b/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java index 424570b9e..432ca8270 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * 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 @@ -35,13 +35,21 @@ public class PlayerInventory extends Inventory { @Setter private int heldItemSlot; - public PlayerInventory() { - super(0, null, 45); + @Getter + private ItemStack cursor; + public PlayerInventory() { + super(0, null, 46); heldItemSlot = 0; } + public void setCursor(ItemStack stack) { + if (stack != null && (stack.getId() == 0 || stack.getAmount() < 1)) + stack = null; + cursor = stack; + } + public ItemStack getItemInHand() { - return items[heldItemSlot]; + return items[36 + heldItemSlot]; } } diff --git a/connector/src/main/java/org/geysermc/connector/metrics/Metrics.java b/connector/src/main/java/org/geysermc/connector/metrics/Metrics.java index ff2cd097c..36aa32c36 100644 --- a/connector/src/main/java/org/geysermc/connector/metrics/Metrics.java +++ b/connector/src/main/java/org/geysermc/connector/metrics/Metrics.java @@ -25,8 +25,10 @@ package org.geysermc.connector.metrics; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.geysermc.connector.GeyserConnector; import javax.net.ssl.HttpsURLConnection; @@ -71,6 +73,8 @@ public class Metrics { // A list with all custom charts private final List charts = new ArrayList<>(); + private final static ObjectMapper mapper = new ObjectMapper(); + private GeyserConnector connector; /** @@ -110,7 +114,7 @@ public class Metrics { */ private void startSubmitting() { connector.getGeneralThreadPool().scheduleAtFixedRate(this::submitData, 1, 30, TimeUnit.MINUTES); - // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start + // Submit the data every 30 minutes, first time after 1 minutes to give other plugins enough time to start // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted! // WARNING: Just don't do it! } @@ -120,22 +124,22 @@ public class Metrics { * * @return The plugin specific data. */ - private JsonObject getPluginData() { - JsonObject data = new JsonObject(); + private ObjectNode getPluginData() { + ObjectNode data = mapper.createObjectNode(); - data.addProperty("pluginName", name); // Append the name of the server software - data.addProperty("pluginVersion", GeyserConnector.VERSION); // Append the name of the server software + data.put("pluginName", name); // Append the name of the server software + data.put("pluginVersion", GeyserConnector.VERSION); // Append the name of the server software - JsonArray customCharts = new JsonArray(); + ArrayNode customCharts = mapper.createArrayNode(); for (CustomChart customChart : charts) { // Add the data of the custom charts - JsonObject chart = customChart.getRequestJsonObject(); + JsonNode chart = customChart.getRequestJsonNode(); if (chart == null) { // If the chart is null, we skip it continue; } customCharts.add(chart); } - data.add("customCharts", customCharts); + data.put("customCharts", customCharts); return data; } @@ -145,7 +149,7 @@ public class Metrics { * * @return The server specific data. */ - private JsonObject getServerData() { + private ObjectNode getServerData() { // OS specific data int playerAmount = connector.getPlayers().size(); @@ -154,15 +158,15 @@ public class Metrics { String osVersion = System.getProperty("os.version"); int coreCount = Runtime.getRuntime().availableProcessors(); - JsonObject data = new JsonObject(); + ObjectNode data = mapper.createObjectNode(); - data.addProperty("serverUUID", serverUUID); + data.put("serverUUID", serverUUID); - data.addProperty("playerAmount", playerAmount); - data.addProperty("osName", osName); - data.addProperty("osArch", osArch); - data.addProperty("osVersion", osVersion); - data.addProperty("coreCount", coreCount); + data.put("playerAmount", playerAmount); + data.put("osName", osName); + data.put("osArch", osArch); + data.put("osVersion", osVersion); + data.put("coreCount", coreCount); return data; } @@ -171,11 +175,11 @@ public class Metrics { * Collects the data and sends it afterwards. */ private void submitData() { - final JsonObject data = getServerData(); + final ObjectNode data = getServerData(); - JsonArray pluginData = new JsonArray(); + ArrayNode pluginData = mapper.createArrayNode(); pluginData.add(getPluginData()); - data.add("plugins", pluginData); + data.putPOJO("plugins", pluginData); new Thread(() -> { try { @@ -196,7 +200,7 @@ public class Metrics { * @param data The data to send. * @throws Exception If the request failed. */ - private static void sendData(JsonObject data) throws Exception { + private static void sendData(ObjectNode data) throws Exception { if (data == null) { throw new IllegalArgumentException("Data cannot be null!"); } @@ -262,16 +266,16 @@ public class Metrics { this.chartId = chartId; } - private JsonObject getRequestJsonObject() { - JsonObject chart = new JsonObject(); - chart.addProperty("chartId", chartId); + private ObjectNode getRequestJsonNode() { + ObjectNode chart = new ObjectMapper().createObjectNode(); + chart.put("chartId", chartId); try { - JsonObject data = getChartData(); + ObjectNode data = getChartData(); if (data == null) { // If the data is null we don't send the chart. return null; } - chart.add("data", data); + chart.putPOJO("data", data); } catch (Throwable t) { if (logFailedRequests) { logger.log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t); @@ -281,7 +285,9 @@ public class Metrics { return chart; } - protected abstract JsonObject getChartData() throws Exception; + + + protected abstract ObjectNode getChartData() throws Exception; } @@ -304,14 +310,14 @@ public class Metrics { } @Override - protected JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); + protected ObjectNode getChartData() throws Exception { + ObjectNode data = mapper.createObjectNode(); String value = callable.call(); if (value == null || value.isEmpty()) { // Null = skip the chart return null; } - data.addProperty("value", value); + data.put("value", value); return data; } } @@ -335,9 +341,9 @@ public class Metrics { } @Override - protected JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - JsonObject values = new JsonObject(); + protected ObjectNode getChartData() throws Exception { + ObjectNode data = mapper.createObjectNode(); + ObjectNode values = mapper.createObjectNode(); Map map = callable.call(); if (map == null || map.isEmpty()) { // Null = skip the chart @@ -349,13 +355,13 @@ public class Metrics { continue; // Skip this invalid } allSkipped = false; - values.addProperty(entry.getKey(), entry.getValue()); + values.put(entry.getKey(), entry.getValue()); } if (allSkipped) { // Null = skip the chart return null; } - data.add("values", values); + data.putPOJO("values", values); return data; } } @@ -379,9 +385,9 @@ public class Metrics { } @Override - public JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - JsonObject values = new JsonObject(); + public ObjectNode getChartData() throws Exception { + ObjectNode data = mapper.createObjectNode(); + ObjectNode values = mapper.createObjectNode(); Map> map = callable.call(); if (map == null || map.isEmpty()) { // Null = skip the chart @@ -389,22 +395,22 @@ public class Metrics { } boolean reallyAllSkipped = true; for (Map.Entry> entryValues : map.entrySet()) { - JsonObject value = new JsonObject(); + ObjectNode value = mapper.createObjectNode(); boolean allSkipped = true; for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { - value.addProperty(valueEntry.getKey(), valueEntry.getValue()); + value.put(valueEntry.getKey(), valueEntry.getValue()); allSkipped = false; } if (!allSkipped) { reallyAllSkipped = false; - values.add(entryValues.getKey(), value); + values.putPOJO(entryValues.getKey(), value); } } if (reallyAllSkipped) { // Null = skip the chart return null; } - data.add("values", values); + data.putPOJO("values", values); return data; } } @@ -428,14 +434,14 @@ public class Metrics { } @Override - protected JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); + protected ObjectNode getChartData() throws Exception { + ObjectNode data = mapper.createObjectNode(); int value = callable.call(); if (value == 0) { // Null = skip the chart return null; } - data.addProperty("value", value); + data.put("value", value); return data; } @@ -460,9 +466,9 @@ public class Metrics { } @Override - protected JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - JsonObject values = new JsonObject(); + protected ObjectNode getChartData() throws Exception { + ObjectNode data = mapper.createObjectNode(); + ObjectNode values = mapper.createObjectNode(); Map map = callable.call(); if (map == null || map.isEmpty()) { // Null = skip the chart @@ -474,13 +480,13 @@ public class Metrics { continue; // Skip this invalid } allSkipped = false; - values.addProperty(entry.getKey(), entry.getValue()); + values.put(entry.getKey(), entry.getValue()); } if (allSkipped) { // Null = skip the chart return null; } - data.add("values", values); + data.putPOJO("values", values); return data; } @@ -505,20 +511,20 @@ public class Metrics { } @Override - protected JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - JsonObject values = new JsonObject(); + protected ObjectNode getChartData() throws Exception { + ObjectNode data = mapper.createObjectNode(); + ObjectNode values = mapper.createObjectNode(); Map map = callable.call(); if (map == null || map.isEmpty()) { // Null = skip the chart return null; } for (Map.Entry entry : map.entrySet()) { - JsonArray categoryValues = new JsonArray(); + ArrayNode categoryValues = mapper.createArrayNode(); categoryValues.add(entry.getValue()); - values.add(entry.getKey(), categoryValues); + values.putPOJO(entry.getKey(), categoryValues); } - data.add("values", values); + data.putPOJO("values", values); return data; } @@ -543,9 +549,9 @@ public class Metrics { } @Override - protected JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - JsonObject values = new JsonObject(); + protected ObjectNode getChartData() throws Exception { + ObjectNode data = mapper.createObjectNode(); + ObjectNode values = mapper.createObjectNode(); Map map = callable.call(); if (map == null || map.isEmpty()) { // Null = skip the chart @@ -557,17 +563,17 @@ public class Metrics { continue; // Skip this invalid } allSkipped = false; - JsonArray categoryValues = new JsonArray(); + ArrayNode categoryValues = mapper.createArrayNode(); for (int categoryValue : entry.getValue()) { categoryValues.add(categoryValue); } - values.add(entry.getKey(), categoryValues); + values.putPOJO(entry.getKey(), categoryValues); } if (allSkipped) { // Null = skip the chart return null; } - data.add("values", values); + data.putPOJO("values", values); return data; } diff --git a/connector/src/main/java/org/geysermc/connector/metrics/SentryMetrics.java b/connector/src/main/java/org/geysermc/connector/metrics/SentryMetrics.java deleted file mode 100644 index 88926fd5e..000000000 --- a/connector/src/main/java/org/geysermc/connector/metrics/SentryMetrics.java +++ /dev/null @@ -1,107 +0,0 @@ -package org.geysermc.connector.metrics; - -import io.sentry.Sentry; -import io.sentry.SentryClient; -import io.sentry.SentryClientFactory; -import io.sentry.context.Context; -import io.sentry.event.BreadcrumbBuilder; -import io.sentry.event.UserBuilder; - -public class SentryMetrics { - private static SentryClient sentry; - - public static void init() { - /* - It is recommended that you use the DSN detection system, which - will check the environment variable "SENTRY_DSN", the Java - System Property "sentry.dsn", or the "sentry.properties" file - in your classpath. This makes it easier to provide and adjust - your DSN without needing to change your code. See the configuration - page for more information. - */ - Sentry.init(); - - // You can also manually provide the DSN to the ``init`` method. - Sentry.init(); - - /* - It is possible to go around the static ``Sentry`` API, which means - you are responsible for making the SentryClient instance available - to your code. - */ - sentry = SentryClientFactory.sentryClient(); - - SentryMetrics metrics = new SentryMetrics(); - metrics.logWithStaticAPI(); - metrics.logWithInstanceAPI(); - } - - /** - * An example method that throws an exception. - */ - void unsafeMethod() { - throw new UnsupportedOperationException("You shouldn't call this!"); - } - - /** - * Examples using the (recommended) static API. - */ - void logWithStaticAPI() { - // Note that all fields set on the context are optional. Context data is copied onto - // all future events in the current context (until the context is cleared). - - // Record a breadcrumb in the current context. By default the last 100 breadcrumbs are kept. - Sentry.getContext().recordBreadcrumb( - new BreadcrumbBuilder().setMessage("User made an action").build() - ); - - // Set the user in the current context. - Sentry.getContext().setUser( - new UserBuilder().setEmail("hello@sentry.io").build() - ); - - // Add extra data to future events in this context. - Sentry.getContext().addExtra("extra", "thing"); - - // Add an additional tag to future events in this context. - Sentry.getContext().addTag("tagName", "tagValue"); - - /* - This sends a simple event to Sentry using the statically stored instance - that was created in the ``main`` method. - */ - Sentry.capture("This is a test"); - - try { - unsafeMethod(); - } catch (Exception e) { - // This sends an exception event to Sentry using the statically stored instance - // that was created in the ``main`` method. - Sentry.capture(e); - } - } - - /** - * Examples that use the SentryClient instance directly. - */ - void logWithInstanceAPI() { - // Retrieve the current context. - Context context = sentry.getContext(); - - // Record a breadcrumb in the current context. By default the last 100 breadcrumbs are kept. - context.recordBreadcrumb(new BreadcrumbBuilder().setMessage("User made an action").build()); - - // Set the user in the current context. - context.setUser(new UserBuilder().setEmail("geyser.project@gmail.com").build()); - - // This sends a simple event to Sentry. - sentry.sendMessage("This is a test"); - - try { - unsafeMethod(); - } catch (Exception e) { - // This sends an exception event to Sentry. - sentry.sendException(e); - } - } -} 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 f0ae14a5c..60ad28d47 100644 --- a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java @@ -80,6 +80,13 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler { 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 + //so we have to fake it not being full + if (pong.getPlayerCount() >= pong.getMaximumPlayerCount()) { + pong.setMaximumPlayerCount(pong.getPlayerCount() + 1); + } + return pong; } 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 56f6f8e1a..553f90f9e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -50,8 +50,11 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public boolean handle(LoginPacket loginPacket) { - if (loginPacket.getProtocolVersion() != GeyserConnector.BEDROCK_PACKET_CODEC.getProtocolVersion()) { - session.getUpstream().disconnect("Unsupported Bedrock version. Are you running an outdated version?"); + if (loginPacket.getProtocolVersion() > GeyserConnector.BEDROCK_PACKET_CODEC.getProtocolVersion()) { + session.disconnect("Outdated Geyser proxy! I'm still on " + GeyserConnector.BEDROCK_PACKET_CODEC.getMinecraftVersion()); + return true; + } else if (loginPacket.getProtocolVersion() < GeyserConnector.BEDROCK_PACKET_CODEC.getProtocolVersion()) { + session.disconnect("Outdated Bedrock client! Please use " + GeyserConnector.BEDROCK_PACKET_CODEC.getMinecraftVersion()); return true; } @@ -111,7 +114,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { break; default: - session.getUpstream().disconnect("disconnectionScreen.resourcePack"); + session.disconnect("disconnectionScreen.resourcePack"); break; } @@ -120,7 +123,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public boolean handle(ModalFormResponsePacket packet) { - return LoginEncryptionUtils.authenticateFromForm(session, connector, packet.getFormData()); + return LoginEncryptionUtils.authenticateFromForm(session, connector, packet.getFormId(), packet.getFormData()); } private boolean couldLoginUserByName(String bedrockUsername) { 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 02427042b..16c7e7fed 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 @@ -30,8 +30,8 @@ import com.github.steveice10.mc.auth.exception.request.InvalidCredentialsExcepti import com.github.steveice10.mc.auth.exception.request.RequestException; import com.github.steveice10.mc.protocol.MinecraftProtocol; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket; import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket; import com.github.steveice10.packetlib.Client; import com.github.steveice10.packetlib.event.session.*; import com.github.steveice10.packetlib.packet.Packet; @@ -42,16 +42,14 @@ import com.nukkitx.math.vector.Vector2f; import com.nukkitx.math.vector.Vector2i; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.nbt.tag.CompoundTag; import com.nukkitx.protocol.bedrock.BedrockServerSession; +import com.nukkitx.protocol.bedrock.data.ContainerId; import com.nukkitx.protocol.bedrock.data.GamePublishSetting; import com.nukkitx.protocol.bedrock.data.GameRuleData; import com.nukkitx.protocol.bedrock.data.PlayerPermission; import com.nukkitx.protocol.bedrock.packet.*; - import lombok.Getter; import lombok.Setter; - import org.geysermc.common.AuthType; import org.geysermc.common.window.FormWindow; import org.geysermc.connector.GeyserConnector; @@ -125,6 +123,9 @@ public class GeyserSession implements CommandSender { private boolean manyDimPackets = false; private ServerRespawnPacket lastDimPacket = null; + @Setter + private int craftSlot = 0; + public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) { this.connector = connector; this.upstream = new UpstreamSession(bedrockServerSession); @@ -157,9 +158,14 @@ public class GeyserSession implements CommandSender { upstream.sendPacket(biomeDefinitionListPacket); AvailableEntityIdentifiersPacket entityPacket = new AvailableEntityIdentifiersPacket(); - entityPacket.setTag(CompoundTag.EMPTY); + entityPacket.setTag(Toolbox.ENTITY_IDENTIFIERS); upstream.sendPacket(entityPacket); + InventoryContentPacket creativePacket = new InventoryContentPacket(); + creativePacket.setContainerId(ContainerId.CREATIVE); + creativePacket.setContents(Toolbox.CREATIVE_ITEMS); + upstream.sendPacket(creativePacket); + PlayStatusPacket playStatusPacket = new PlayStatusPacket(); playStatusPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN); upstream.sendPacket(playStatusPacket); @@ -274,6 +280,9 @@ public class GeyserSession implements CommandSender { loggingIn = false; loggedIn = false; connector.getLogger().info(authData.getName() + " has disconnected from remote java server on address " + remoteServer.getAddress() + " because of " + event.getReason()); + if (event.getCause() != null) { + event.getCause().printStackTrace(); + } upstream.disconnect(event.getReason()); } @@ -297,7 +306,7 @@ public class GeyserSession implements CommandSender { downstream.getSession().connect(); connector.addPlayer(this); - } catch (InvalidCredentialsException e) { + } catch (InvalidCredentialsException | IllegalArgumentException e) { connector.getLogger().info("User '" + username + "' entered invalid login info, kicking."); disconnect("Invalid/incorrect login info"); } catch (RequestException ex) { @@ -313,10 +322,16 @@ public class GeyserSession implements CommandSender { downstream.getSession().disconnect(reason); } if (upstream != null && !upstream.isClosed()) { + connector.getPlayers().remove(this.upstream.getAddress()); upstream.disconnect(reason); } } + this.entityCache.getEntities().clear(); + this.scoreboardCache.removeScoreboard(); + this.inventoryCache.getInventories().clear(); + this.windowCache.getWindows().clear(); + closed = true; } 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 new file mode 100644 index 000000000..abb9016a2 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java @@ -0,0 +1,121 @@ +/* + * 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.session.cache; + +import com.github.steveice10.mc.protocol.data.message.Message; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityData; +import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; +import com.nukkitx.protocol.bedrock.packet.BossEventPacket; +import com.nukkitx.protocol.bedrock.packet.RemoveEntityPacket; +import lombok.AllArgsConstructor; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.MessageUtils; + +@AllArgsConstructor +public class BossBar { + + private GeyserSession session; + + private long entityId; + private Message title; + private float health; + private int color; + private int overlay; + private int darkenSky; + + public void addBossBar() { + addBossEntity(); + updateBossBar(); + } + + public void updateBossBar() { + BossEventPacket bossEventPacket = new BossEventPacket(); + bossEventPacket.setBossUniqueEntityId(entityId); + bossEventPacket.setAction(BossEventPacket.Action.SHOW); + bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getClientData().getLanguageCode())); + bossEventPacket.setHealthPercentage(health); + bossEventPacket.setColor(color); //ignored by client + bossEventPacket.setOverlay(overlay); + bossEventPacket.setDarkenSky(darkenSky); + + session.getUpstream().sendPacket(bossEventPacket); + } + + public void updateTitle(Message title) { + this.title = title; + BossEventPacket bossEventPacket = new BossEventPacket(); + bossEventPacket.setBossUniqueEntityId(entityId); + bossEventPacket.setAction(BossEventPacket.Action.TITLE); + bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getClientData().getLanguageCode())); + + session.getUpstream().sendPacket(bossEventPacket); + } + + public void updateHealth(float health) { + this.health = health; + BossEventPacket bossEventPacket = new BossEventPacket(); + bossEventPacket.setBossUniqueEntityId(entityId); + bossEventPacket.setAction(BossEventPacket.Action.HEALTH_PERCENTAGE); + bossEventPacket.setHealthPercentage(health); + + session.getUpstream().sendPacket(bossEventPacket); + } + + public void removeBossBar() { + BossEventPacket bossEventPacket = new BossEventPacket(); + bossEventPacket.setBossUniqueEntityId(entityId); + bossEventPacket.setAction(BossEventPacket.Action.HIDE); + + session.getUpstream().sendPacket(bossEventPacket); + removeBossEntity(); + } + + /** + * Bedrock still needs an entity to display the BossBar.
+ * Just like 1.8 but it doesn't care about which entity + */ + private void addBossEntity() { + AddEntityPacket addEntityPacket = new AddEntityPacket(); + addEntityPacket.setUniqueEntityId(entityId); + addEntityPacket.setRuntimeEntityId(entityId); + addEntityPacket.setIdentifier("minecraft:creeper"); + addEntityPacket.setEntityType(33); + addEntityPacket.setPosition(session.getPlayerEntity().getPosition()); + addEntityPacket.setRotation(Vector3f.ZERO); + addEntityPacket.setMotion(Vector3f.ZERO); + addEntityPacket.getMetadata().put(EntityData.SCALE, 0.01F); // scale = 0 doesn't work? + + session.getUpstream().sendPacket(addEntityPacket); + } + + private void removeBossEntity() { + RemoveEntityPacket removeEntityPacket = new RemoveEntityPacket(); + removeEntityPacket.setUniqueEntityId(entityId); + + session.getUpstream().sendPacket(removeEntityPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java index f32ee2a5c..80d10b1a0 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 @@ -48,7 +48,7 @@ public class EntityCache { private Long2ObjectMap entities = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); private Long2LongMap entityIdTranslations = Long2LongMaps.synchronize(new Long2LongOpenHashMap()); private Map playerEntities = Collections.synchronizedMap(new HashMap<>()); - private Object2LongMap bossbars = new Object2LongOpenHashMap<>(); + private Map bossBars = Collections.synchronizedMap(new HashMap<>()); @Getter private AtomicLong nextEntityId = new AtomicLong(2L); @@ -58,13 +58,19 @@ public class EntityCache { } public void spawnEntity(Entity entity) { - cacheEntity(entity); - entity.spawnEntity(session); + if (cacheEntity(entity)) { + entity.spawnEntity(session); + } } - public void cacheEntity(Entity entity) { - entityIdTranslations.put(entity.getEntityId(), entity.getGeyserId()); - entities.put(entity.getGeyserId(), entity); + public boolean cacheEntity(Entity entity) { + // Check to see if the entity exists, otherwise we can end up with duplicated mobs + if (!entityIdTranslations.containsKey(entity.getEntityId())) { + entityIdTranslations.put(entity.getEntityId(), entity.getGeyserId()); + entities.put(entity.getGeyserId(), entity); + return true; + } + return false; } public boolean removeEntity(Entity entity, boolean force) { @@ -116,24 +122,30 @@ public class EntityCache { playerEntities.remove(uuid); } - public long addBossBar(UUID uuid) { - long entityId = getNextEntityId().incrementAndGet(); - bossbars.put(uuid, entityId); - return entityId; + public void addBossBar(UUID uuid, BossBar bossBar) { + bossBars.put(uuid, bossBar); + bossBar.addBossBar(); } - public long getBossBar(UUID uuid) { - return bossbars.containsKey(uuid) ? bossbars.get(uuid) : -1; + public BossBar getBossBar(UUID uuid) { + return bossBars.get(uuid); } - public long removeBossBar(UUID uuid) { - return bossbars.remove(uuid); + public void removeBossBar(UUID uuid) { + BossBar bossBar = bossBars.remove(uuid); + if (bossBar != null) { + bossBar.removeBossBar(); + } + } + + public void updateBossBars() { + bossBars.values().forEach(BossBar::updateBossBar); } public void clear() { entities = null; entityIdTranslations = null; playerEntities = null; - bossbars = null; + bossBars = null; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityEffectCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityEffectCache.java new file mode 100644 index 000000000..a16ef6902 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityEffectCache.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.session.cache; + +import com.github.steveice10.mc.protocol.data.game.entity.Effect; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import lombok.Getter; + +public class EntityEffectCache { + + @Getter + private final Object2IntMap entityEffects = new Object2IntOpenHashMap<>(); + + public void addEffect(Effect effect, int effectAmplifier) { + if (effect != null) { + entityEffects.putIfAbsent(effect, effectAmplifier + 1); + } + } + + public void removeEffect(Effect effect) { + if (entityEffects.containsKey(effect)) { + int effectLevel = entityEffects.getInt(effect); + entityEffects.remove(effect, effectLevel); + } + } + + public int getEffectLevel(Effect effect) { + return entityEffects.getOrDefault(effect, 0); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/InventoryCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/InventoryCache.java index 7a505878e..032f64024 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/InventoryCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/InventoryCache.java @@ -25,17 +25,13 @@ package org.geysermc.connector.network.session.cache; -import com.github.steveice10.packetlib.packet.Packet; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import lombok.Getter; import lombok.Setter; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - public class InventoryCache { private GeyserSession session; @@ -45,10 +41,7 @@ public class InventoryCache { private Inventory openInventory; @Getter - private Map inventories = new HashMap(); - - @Getter - private Map> cachedPackets = new HashMap>(); + private Int2ObjectMap inventories = new Int2ObjectOpenHashMap<>(); public InventoryCache(GeyserSession session) { this.session = session; @@ -65,10 +58,4 @@ public class InventoryCache { public void uncacheInventory(int id) { inventories.remove(id); } - - public void cachePacket(int id, Packet packet) { - List packets = cachedPackets.getOrDefault(id, new ArrayList()); - packets.add(packet); - cachedPackets.put(id, packets); - } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/ItemRemapper.java b/connector/src/main/java/org/geysermc/connector/network/translators/ItemRemapper.java new file mode 100644 index 000000000..6c286da2f --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/ItemRemapper.java @@ -0,0 +1,34 @@ +/* + * 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.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(value = RetentionPolicy.RUNTIME) +public @interface ItemRemapper { + int priority() default 0; +} 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 new file mode 100644 index 000000000..356dcf982 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/ItemStackTranslator.java @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.message.Message; +import com.github.steveice10.opennbt.tag.builtin.*; +import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.nbt.tag.Tag; +import com.nukkitx.protocol.bedrock.data.ItemData; +import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.utils.MessageUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public abstract class ItemStackTranslator { + + public ItemData translateToBedrock(ItemStack itemStack, ItemEntry itemEntry) { + if (itemStack == null) { + return ItemData.AIR; + } + if (itemStack.getNbt() == null) { + return ItemData.of(itemEntry.getBedrockId(), (short) itemEntry.getBedrockData(), itemStack.getAmount()); + } + return ItemData.of(itemEntry.getBedrockId(), (short) itemEntry.getBedrockData(), itemStack.getAmount(), this.translateNbtToBedrock(itemStack.getNbt())); + } + + public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { + if (itemData == null) return null; + if (itemData.getTag() == null) { + return new ItemStack(itemEntry.getJavaId(), itemData.getCount(), new com.github.steveice10.opennbt.tag.builtin.CompoundTag("")); + } + return new ItemStack(itemEntry.getJavaId(), itemData.getCount(), this.translateToJavaNBT(itemData.getTag())); + } + + public abstract List getAppliedItems(); + + public CompoundTag translateNbtToBedrock(com.github.steveice10.opennbt.tag.builtin.CompoundTag tag) { + Map> javaValue = new HashMap>(); + if (tag.getValue() != null && !tag.getValue().isEmpty()) { + for (String str : tag.getValue().keySet()) { + com.github.steveice10.opennbt.tag.builtin.Tag javaTag = tag.get(str); + com.nukkitx.nbt.tag.Tag translatedTag = translateToBedrockNBT(javaTag); + if (translatedTag == null) + continue; + + javaValue.put(translatedTag.getName(), translatedTag); + } + } + + com.nukkitx.nbt.tag.CompoundTag bedrockTag = new com.nukkitx.nbt.tag.CompoundTag(tag.getName(), javaValue); + return bedrockTag; + } + + private com.nukkitx.nbt.tag.Tag translateToBedrockNBT(com.github.steveice10.opennbt.tag.builtin.Tag tag) { + if (tag instanceof ByteArrayTag) { + ByteArrayTag byteArrayTag = (ByteArrayTag) tag; + return new com.nukkitx.nbt.tag.ByteArrayTag(byteArrayTag.getName(), byteArrayTag.getValue()); + } + + if (tag instanceof ByteTag) { + ByteTag byteTag = (ByteTag) tag; + return new com.nukkitx.nbt.tag.ByteTag(byteTag.getName(), byteTag.getValue()); + } + + if (tag instanceof DoubleTag) { + DoubleTag doubleTag = (DoubleTag) tag; + return new com.nukkitx.nbt.tag.DoubleTag(doubleTag.getName(), doubleTag.getValue()); + } + + if (tag instanceof FloatTag) { + FloatTag floatTag = (FloatTag) tag; + return new com.nukkitx.nbt.tag.FloatTag(floatTag.getName(), floatTag.getValue()); + } + + if (tag instanceof IntArrayTag) { + IntArrayTag intArrayTag = (IntArrayTag) tag; + return new com.nukkitx.nbt.tag.IntArrayTag(intArrayTag.getName(), intArrayTag.getValue()); + } + + if (tag instanceof IntTag) { + IntTag intTag = (IntTag) tag; + return new com.nukkitx.nbt.tag.IntTag(intTag.getName(), intTag.getValue()); + } + + if (tag instanceof LongArrayTag) { + LongArrayTag longArrayTag = (LongArrayTag) tag; + return new com.nukkitx.nbt.tag.LongArrayTag(longArrayTag.getName(), longArrayTag.getValue()); + } + + if (tag instanceof LongTag) { + LongTag longTag = (LongTag) tag; + return new com.nukkitx.nbt.tag.LongTag(longTag.getName(), longTag.getValue()); + } + + if (tag instanceof ShortTag) { + ShortTag shortTag = (ShortTag) tag; + return new com.nukkitx.nbt.tag.ShortTag(shortTag.getName(), shortTag.getValue()); + } + + if (tag instanceof StringTag) { + StringTag stringTag = (StringTag) tag; + return new com.nukkitx.nbt.tag.StringTag(stringTag.getName(), MessageUtils.getBedrockMessage(Message.fromString(stringTag.getValue()))); + } + + if (tag instanceof ListTag) { + ListTag listTag = (ListTag) tag; + + List tagList = new ArrayList<>(); + for (com.github.steveice10.opennbt.tag.builtin.Tag value : listTag) { + tagList.add(translateToBedrockNBT(value)); + } + Class clazz = CompoundTag.class; + if (!tagList.isEmpty()) { + clazz = tagList.get(0).getClass(); + } + return new com.nukkitx.nbt.tag.ListTag(listTag.getName(), clazz, tagList); + } + + if (tag instanceof com.github.steveice10.opennbt.tag.builtin.CompoundTag) { + com.github.steveice10.opennbt.tag.builtin.CompoundTag compoundTag = (com.github.steveice10.opennbt.tag.builtin.CompoundTag) tag; + + return translateNbtToBedrock(compoundTag); + } + + return null; + } + + public com.github.steveice10.opennbt.tag.builtin.CompoundTag translateToJavaNBT(com.nukkitx.nbt.tag.CompoundTag tag) { + com.github.steveice10.opennbt.tag.builtin.CompoundTag javaTag = new com.github.steveice10.opennbt.tag.builtin.CompoundTag(tag.getName()); + Map javaValue = javaTag.getValue(); + if (tag.getValue() != null && !tag.getValue().isEmpty()) { + for (String str : tag.getValue().keySet()) { + com.nukkitx.nbt.tag.Tag bedrockTag = tag.get(str); + com.github.steveice10.opennbt.tag.builtin.Tag translatedTag = translateToJavaNBT(bedrockTag); + if (translatedTag == null) + continue; + + javaValue.put(translatedTag.getName(), translatedTag); + } + } + + javaTag.setValue(javaValue); + return javaTag; + } + + private com.github.steveice10.opennbt.tag.builtin.Tag translateToJavaNBT(com.nukkitx.nbt.tag.Tag tag) { + if (tag instanceof com.nukkitx.nbt.tag.ByteArrayTag) { + com.nukkitx.nbt.tag.ByteArrayTag byteArrayTag = (com.nukkitx.nbt.tag.ByteArrayTag) tag; + return new ByteArrayTag(byteArrayTag.getName(), byteArrayTag.getValue()); + } + + if (tag instanceof com.nukkitx.nbt.tag.ByteTag) { + com.nukkitx.nbt.tag.ByteTag byteTag = (com.nukkitx.nbt.tag.ByteTag) tag; + return new ByteTag(byteTag.getName(), byteTag.getValue()); + } + + if (tag instanceof com.nukkitx.nbt.tag.DoubleTag) { + com.nukkitx.nbt.tag.DoubleTag doubleTag = (com.nukkitx.nbt.tag.DoubleTag) tag; + return new DoubleTag(doubleTag.getName(), doubleTag.getValue()); + } + + if (tag instanceof com.nukkitx.nbt.tag.FloatTag) { + com.nukkitx.nbt.tag.FloatTag floatTag = (com.nukkitx.nbt.tag.FloatTag) tag; + return new FloatTag(floatTag.getName(), floatTag.getValue()); + } + + if (tag instanceof com.nukkitx.nbt.tag.IntArrayTag) { + com.nukkitx.nbt.tag.IntArrayTag intArrayTag = (com.nukkitx.nbt.tag.IntArrayTag) tag; + return new IntArrayTag(intArrayTag.getName(), intArrayTag.getValue()); + } + + if (tag instanceof com.nukkitx.nbt.tag.IntTag) { + com.nukkitx.nbt.tag.IntTag intTag = (com.nukkitx.nbt.tag.IntTag) tag; + return new IntTag(intTag.getName(), intTag.getValue()); + } + + if (tag instanceof com.nukkitx.nbt.tag.LongArrayTag) { + com.nukkitx.nbt.tag.LongArrayTag longArrayTag = (com.nukkitx.nbt.tag.LongArrayTag) tag; + return new LongArrayTag(longArrayTag.getName(), longArrayTag.getValue()); + } + + if (tag instanceof com.nukkitx.nbt.tag.LongTag) { + com.nukkitx.nbt.tag.LongTag longTag = (com.nukkitx.nbt.tag.LongTag) tag; + return new LongTag(longTag.getName(), longTag.getValue()); + } + + if (tag instanceof com.nukkitx.nbt.tag.ShortTag) { + com.nukkitx.nbt.tag.ShortTag shortTag = (com.nukkitx.nbt.tag.ShortTag) tag; + return new ShortTag(shortTag.getName(), shortTag.getValue()); + } + + if (tag instanceof com.nukkitx.nbt.tag.StringTag) { + com.nukkitx.nbt.tag.StringTag stringTag = (com.nukkitx.nbt.tag.StringTag) tag; + return new StringTag(stringTag.getName(), stringTag.getValue()); + } + + if (tag instanceof com.nukkitx.nbt.tag.ListTag) { + com.nukkitx.nbt.tag.ListTag listTag = (com.nukkitx.nbt.tag.ListTag) tag; + + List tags = new ArrayList<>(); + + for (Object value : listTag.getValue()) { + if (!(value instanceof com.nukkitx.nbt.tag.Tag)) + continue; + + com.nukkitx.nbt.tag.Tag tagValue = (com.nukkitx.nbt.tag.Tag) value; + com.github.steveice10.opennbt.tag.builtin.Tag javaTag = translateToJavaNBT(tagValue); + if (javaTag != null) + tags.add(javaTag); + } + return new ListTag(listTag.getName(), tags); + } + + if (tag instanceof com.nukkitx.nbt.tag.CompoundTag) { + com.nukkitx.nbt.tag.CompoundTag compoundTag = (com.nukkitx.nbt.tag.CompoundTag) tag; + return translateToJavaNBT(compoundTag); + } + + return null; + } + + +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/NbtItemStackTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/NbtItemStackTranslator.java new file mode 100644 index 000000000..56c780f44 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/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; + +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import org.geysermc.connector.network.translators.item.ItemEntry; + +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/Translators.java b/connector/src/main/java/org/geysermc/connector/network/translators/Translators.java index d4e5dae8b..f0a3fd28c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/Translators.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/Translators.java @@ -27,11 +27,17 @@ package org.geysermc.connector.network.translators; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import com.github.steveice10.mc.protocol.data.game.window.WindowType; +import com.nukkitx.protocol.bedrock.data.ContainerType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.translators.block.BlockTranslator; -import org.geysermc.connector.network.translators.inventory.GenericInventoryTranslator; -import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +import org.geysermc.connector.network.translators.block.entity.*; +import org.geysermc.connector.network.translators.inventory.*; +import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater; +import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; import org.geysermc.connector.network.translators.item.ItemTranslator; import org.reflections.Reflections; @@ -50,7 +56,10 @@ public class Translators { private static ItemTranslator itemTranslator; @Getter - private static InventoryTranslator inventoryTranslator = new GenericInventoryTranslator(); + private static Map inventoryTranslators = new HashMap<>(); + + @Getter + private static Map blockEntityTranslators = new HashMap<>(); private static final CompoundTag EMPTY_TAG = CompoundTagBuilder.builder().buildRootTag(); public static final byte[] EMPTY_LEVEL_CHUNK_DATA; @@ -82,15 +91,15 @@ public class Translators { 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."); } @@ -100,17 +109,51 @@ public class Translators { } itemTranslator = new ItemTranslator(); + itemTranslator.init(); BlockTranslator.init(); + registerBlockEntityTranslators(); registerInventoryTranslators(); } + private static void registerBlockEntityTranslators() { + Reflections ref = new Reflections("org.geysermc.connector.network.translators.block.entity"); + + for (Class clazz : ref.getTypesAnnotatedWith(BlockEntity.class)) { + + GeyserConnector.getInstance().getLogger().debug("Found annotated block entity: " + clazz.getCanonicalName()); + + try { + blockEntityTranslators.put(clazz.getAnnotation(BlockEntity.class).name(), (BlockEntityTranslator) clazz.newInstance()); + } catch (InstantiationException | IllegalAccessException e) { + GeyserConnector.getInstance().getLogger().error("Could not instantiate annotated block entity " + clazz.getCanonicalName() + "."); + } + } + } + private static void registerInventoryTranslators() { - /*inventoryTranslators.put(WindowType.GENERIC_9X1, new GenericInventoryTranslator()); - inventoryTranslators.put(WindowType.GENERIC_9X2, new GenericInventoryTranslator()); - inventoryTranslators.put(WindowType.GENERIC_9X3, new GenericInventoryTranslator()); - inventoryTranslators.put(WindowType.GENERIC_9X4, new GenericInventoryTranslator()); - inventoryTranslators.put(WindowType.GENERIC_9X5, new GenericInventoryTranslator()); - inventoryTranslators.put(WindowType.GENERIC_9X6, new GenericInventoryTranslator());*/ + inventoryTranslators.put(null, new PlayerInventoryTranslator()); //player inventory + inventoryTranslators.put(WindowType.GENERIC_9X1, new SingleChestInventoryTranslator(9)); + inventoryTranslators.put(WindowType.GENERIC_9X2, new SingleChestInventoryTranslator(18)); + inventoryTranslators.put(WindowType.GENERIC_9X3, new SingleChestInventoryTranslator(27)); + inventoryTranslators.put(WindowType.GENERIC_9X4, new DoubleChestInventoryTranslator(36)); + inventoryTranslators.put(WindowType.GENERIC_9X5, new DoubleChestInventoryTranslator(45)); + inventoryTranslators.put(WindowType.GENERIC_9X6, new DoubleChestInventoryTranslator(54)); + inventoryTranslators.put(WindowType.BREWING_STAND, new BrewingInventoryTranslator()); + inventoryTranslators.put(WindowType.ANVIL, new AnvilInventoryTranslator()); + inventoryTranslators.put(WindowType.CRAFTING, new CraftingInventoryTranslator()); + inventoryTranslators.put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator()); + //inventoryTranslators.put(WindowType.ENCHANTMENT, new EnchantmentInventoryTranslator()); //TODO + + InventoryTranslator furnace = new FurnaceInventoryTranslator(); + inventoryTranslators.put(WindowType.FURNACE, furnace); + inventoryTranslators.put(WindowType.BLAST_FURNACE, furnace); + inventoryTranslators.put(WindowType.SMOKER, furnace); + + InventoryUpdater containerUpdater = new ContainerInventoryUpdater(); + inventoryTranslators.put(WindowType.GENERIC_3X3, new BlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER, containerUpdater)); + inventoryTranslators.put(WindowType.HOPPER, new BlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER, containerUpdater)); + inventoryTranslators.put(WindowType.SHULKER_BOX, new BlockInventoryTranslator(27, "minecraft:shulker_box[facing=north]", ContainerType.CONTAINER, containerUpdater)); + //inventoryTranslators.put(WindowType.BEACON, new BlockInventoryTranslator(1, "minecraft:beacon", ContainerType.BEACON)); //TODO } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java index 206f42d1f..7ab713893 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java @@ -123,6 +123,7 @@ public class BedrockActionTranslator extends PacketTranslator { @Override public void translate(AnimatePacket packet, GeyserSession session) { + // Stop the player sending animations before they have fully spawned into the server + if (!session.isSpawned()) { + return; + } + switch (packet.getAction()) { case SWING_ARM: // Delay so entity damage can be processed first diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java new file mode 100644 index 000000000..dc076a910 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.bedrock; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientUpdateSignPacket; +import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +import java.util.HashMap; +import java.util.Map; + +@Translator(packet = BlockEntityDataPacket.class) +public class BedrockBlockEntityDataTranslator extends PacketTranslator { + + // In case two people are editing signs at the same time this array holds the temporary messages to be sent + // Position -> Message being held + protected static Map lastMessages = new HashMap<>(); + + @Override + public void translate(BlockEntityDataPacket packet, GeyserSession session) { + if (packet.getData() instanceof CompoundTag) { + CompoundTag tag = (CompoundTag) packet.getData(); + if (tag.getString("id").equals("Sign")) { + // This is the reason why this all works - Bedrock sends packets every time you update the sign, Java only wants the final packet + // But Bedrock sends one final packet when you're done editing the sign, which should be equal to the last message since there's no edits + // So if the latest update does not match the last cached update then it's still being edited + Position pos = new Position(tag.getInt("x"), tag.getInt("y"), tag.getInt("z")); + if (!tag.getString("Text").equals(lastMessages.get(pos))) { + lastMessages.put(pos, tag.getString("Text")); + return; + } + // Otherwise the two messages are identical and we can get to work deconstructing + StringBuilder newMessage = new StringBuilder(); + // While Bedrock's sign lines are one string, Java's is an array of each line + // (Initialized all with empty strings because it complains about null) + String[] lines = new String[] {"", "", "", ""}; + int iterator = 0; + // This converts the message into the array'd message Java wants + for (char character : tag.getString("Text").toCharArray()) { + // If we get a return in Bedrock, that signals to use the next line. + if (character == '\n') { + lines[iterator] = newMessage.toString(); + iterator++; + // Bedrock, for whatever reason, can hold a message out of bounds + // We don't care about that so we discard that + if (iterator > lines.length - 1) { + break; + } + newMessage = new StringBuilder(); + } else newMessage.append(character); + } + // Put the final line on since it isn't done in the for loop + if (iterator < lines.length) lines[iterator] = newMessage.toString(); + ClientUpdateSignPacket clientUpdateSignPacket = new ClientUpdateSignPacket(pos, lines); + session.getDownstream().getSession().send(clientUpdateSignPacket); + //TODO (potentially): originally I was going to update the sign blocks so Bedrock and Java users would match visually + // However Java can still store a lot per-line and visuals are still messed up so that doesn't work + + // We remove the sign position from map to indicate there is no work-in-progress sign + lastMessages.remove(pos); + } + } + + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java index 28cbf4c41..c8e117410 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java @@ -27,13 +27,14 @@ package org.geysermc.connector.network.translators.bedrock; import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.command.GeyserCommandMap; +import org.geysermc.connector.command.CommandManager; 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.client.ClientChatPacket; import com.nukkitx.protocol.bedrock.packet.CommandRequestPacket; +import org.geysermc.connector.utils.MessageUtils; @Translator(packet = CommandRequestPacket.class) public class BedrockCommandRequestTranslator extends PacketTranslator { @@ -41,11 +42,17 @@ public class BedrockCommandRequestTranslator extends PacketTranslator { + + @Override + public void translate(ContainerClosePacket packet, GeyserSession session) { + byte windowId = packet.getWindowId(); + if (windowId == -1) { //player inventory or crafting table + Inventory openInventory = session.getInventoryCache().getOpenInventory(); + if (openInventory != null) { + windowId = (byte) openInventory.getId(); + } else { + windowId = 0; + } + } + ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(windowId); + session.getDownstream().getSession().send(closeWindowPacket); + InventoryUtils.closeInventory(session, windowId); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInteractTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInteractTranslator.java index 012ef90ea..0d1c08b22 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInteractTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInteractTranslator.java @@ -34,6 +34,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.github.steveice10.mc.protocol.data.game.entity.player.InteractAction; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerInteractEntityPacket; import com.nukkitx.protocol.bedrock.packet.InteractPacket; +import org.geysermc.connector.network.translators.item.ItemTranslator; @Translator(packet = InteractPacket.class) public class BedrockInteractTranslator extends PacketTranslator { @@ -46,6 +47,9 @@ public class BedrockInteractTranslator extends PacketTranslator switch (packet.getAction()) { case INTERACT: + if (session.getInventory().getItem(session.getInventory().getHeldItemSlot() + 36).getId() == ItemTranslator.SHIELD) { + break; + } ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(), InteractAction.INTERACT, Hand.MAIN_HAND); session.getDownstream().getSession().send(interactPacket); 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 94903246b..3f6eba55d 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 @@ -27,10 +27,15 @@ package org.geysermc.connector.network.translators.bedrock; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPlaceBlockPacket; import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.Translators; +import org.geysermc.connector.network.translators.item.ItemTranslator; +import org.geysermc.connector.utils.InventoryUtils; +import com.nukkitx.math.vector.Vector3f; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; @@ -40,7 +45,6 @@ import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerInteractEntityPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerUseItemPacket; -import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.packet.InventoryTransactionPacket; @Translator(packet = InventoryTransactionPacket.class) @@ -49,6 +53,17 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator { @@ -38,12 +39,24 @@ public class BedrockTextTranslator extends PacketTranslator { @Override public void translate(TextPacket packet, GeyserSession session) { if (packet.getMessage().charAt(0) == '.') { - ClientChatPacket chatPacket = new ClientChatPacket(packet.getMessage().replace(".", "/")); + String message = packet.getMessage().replace(".", "/").trim(); + + if (MessageUtils.isTooLong(message, session)) { + return; + } + + ClientChatPacket chatPacket = new ClientChatPacket(message); session.getDownstream().getSession().send(chatPacket); return; } - ClientChatPacket chatPacket = new ClientChatPacket(packet.getMessage()); + String message = packet.getMessage().trim(); + + if (MessageUtils.isTooLong(message, session)) { + return; + } + + ClientChatPacket chatPacket = new ClientChatPacket(message); session.getDownstream().getSession().send(chatPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockStateValues.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockStateValues.java new file mode 100644 index 000000000..25d6070fe --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockStateValues.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.block; + +import com.fasterxml.jackson.databind.JsonNode; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import it.unimi.dsi.fastutil.objects.Object2ByteMap; +import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; + +import java.util.Map; + +/** + * Used for block entities if the Java block state contains Bedrock block information. + */ +public class BlockStateValues { + + private static final Object2IntMap BANNER_COLORS = new Object2IntOpenHashMap<>(); + private static final Object2ByteMap BED_COLORS = new Object2ByteOpenHashMap<>(); + private static final Object2ByteMap SKULL_VARIANTS = new Object2ByteOpenHashMap<>(); + private static final Object2ByteMap SKULL_ROTATIONS = new Object2ByteOpenHashMap<>(); + + /** + * Determines if the block state contains Bedrock block information + * @param entry The String to JsonNode map used in BlockTranslator + * @param javaBlockState the Java Block State of the block + */ + public static void storeBlockStateValues(Map.Entry entry, BlockState javaBlockState) { + JsonNode bannerColor = entry.getValue().get("banner_color"); + if (bannerColor != null) { + BlockStateValues.BANNER_COLORS.put(javaBlockState, (byte) bannerColor.intValue()); + return; // There will never be a banner color and a skull variant + } + + JsonNode bedColor = entry.getValue().get("bed_color"); + if (bedColor != null) { + BlockStateValues.BED_COLORS.put(javaBlockState, (byte) bedColor.intValue()); + return; + } + + JsonNode skullVariation = entry.getValue().get("variation"); + if(skullVariation != null) { + BlockStateValues.SKULL_VARIANTS.put(javaBlockState, (byte) skullVariation.intValue()); + } + + JsonNode skullRotation = entry.getValue().get("skull_rotation"); + if (skullRotation != null) { + BlockStateValues.SKULL_ROTATIONS.put(javaBlockState, (byte) skullRotation.intValue()); + } + } + + /** + * Banner colors are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. + * This gives an integer color that Bedrock can use. + * @param state BlockState of the block + * @return banner color integer or -1 if no color + */ + public static int getBannerColor(BlockState state) { + if (BANNER_COLORS.containsKey(state)) { + return BANNER_COLORS.getInt(state); + } + return -1; + } + + /** + * Bed colors are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. + * This gives a byte color that Bedrock can use - Bedrock needs a byte in the final tag. + * @param state BlockState of the block + * @return bed color byte or -1 if no color + */ + public static byte getBedColor(BlockState state) { + if (BED_COLORS.containsKey(state)) { + return BED_COLORS.getByte(state); + } + return -1; + } + + /** + * Skull variations are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. + * This gives a byte variant ID that Bedrock can use. + * @param state BlockState of the block + * @return skull variant byte or -1 if no variant + */ + public static byte getSkullVariant(BlockState state) { + if (SKULL_VARIANTS.containsKey(state)) { + return SKULL_VARIANTS.getByte(state); + } + return -1; + } + + /** + * + * @param state BlockState of the block + * @return skull rotation value or -1 if no value + */ + public static byte getSkullRotation(BlockState state) { + if (SKULL_ROTATIONS.containsKey(state)) { + return SKULL_ROTATIONS.getByte(state); + } + return -1; + } + +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java index 1ac9a9fe1..cf0da17b8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java @@ -32,19 +32,14 @@ import com.nukkitx.nbt.NbtUtils; import com.nukkitx.nbt.stream.NBTInputStream; import com.nukkitx.nbt.tag.CompoundTag; import com.nukkitx.nbt.tag.ListTag; -import it.unimi.dsi.fastutil.ints.Int2IntMap; -import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.ints.*; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import org.geysermc.connector.GeyserConnector; -//import org.geysermc.connector.network.translators.forge.Blocks; +import org.geysermc.connector.network.translators.block.entity.BlockEntity; import org.geysermc.connector.utils.Toolbox; +import org.reflections.Reflections; -import java.io.File; import java.io.InputStream; import java.util.*; @@ -55,11 +50,22 @@ public class BlockTranslator { private static final Int2IntMap JAVA_TO_BEDROCK_BLOCK_MAP = new Int2IntOpenHashMap(); private static final Int2ObjectMap BEDROCK_TO_JAVA_BLOCK_MAP = new Int2ObjectOpenHashMap<>(); + private static final Map JAVA_ID_BLOCK_MAP = new HashMap<>(); private static final IntSet WATERLOGGED = new IntOpenHashSet(); // Bedrock carpet ID, used in LlamaEntity.java for decoration public static final int CARPET = 171; + private static final Map JAVA_ID_TO_BLOCK_ENTITY_MAP = new HashMap<>(); + + public static final Int2DoubleMap JAVA_RUNTIME_ID_TO_HARDNESS = new Int2DoubleOpenHashMap(); + public static final Int2BooleanMap JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND = new Int2BooleanOpenHashMap(); + public static final Int2ObjectMap JAVA_RUNTIME_ID_TO_TOOL_TYPE = new Int2ObjectOpenHashMap<>(); + + // For block breaking animation math + public static final IntSet JAVA_RUNTIME_WOOL_IDS = new IntOpenHashSet(); + public static final int JAVA_RUNTIME_COBWEB_ID; + private static final int BLOCK_STATE_VERSION = 17760256; static { @@ -93,16 +99,58 @@ public class BlockTranslator { addedStatesMap.defaultReturnValue(-1); List paletteList = new ArrayList<>(); + Reflections ref = new Reflections("org.geysermc.connector.network.translators.block.entity"); + ref.getTypesAnnotatedWith(BlockEntity.class); + int waterRuntimeId = -1; int javaRuntimeId = -1; int bedrockRuntimeId = 0; + int cobwebRuntimeId = -1; Iterator> blocksIterator = blocks.fields(); while (blocksIterator.hasNext()) { javaRuntimeId++; Map.Entry entry = blocksIterator.next(); String javaId = entry.getKey(); + BlockState javaBlockState = new BlockState(javaRuntimeId); CompoundTag blockTag = buildBedrockState(entry.getValue()); + // TODO fix this, (no block should have a null hardness) + JsonNode hardnessNode = entry.getValue().get("block_hardness"); + if (hardnessNode != null) { + JAVA_RUNTIME_ID_TO_HARDNESS.put(javaRuntimeId, hardnessNode.doubleValue()); + } + + JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND.put(javaRuntimeId, entry.getValue().get("can_break_with_hand").booleanValue()); + + JsonNode toolTypeNode = entry.getValue().get("tool_type"); + if (toolTypeNode != null) { + JAVA_RUNTIME_ID_TO_TOOL_TYPE.put(javaRuntimeId, toolTypeNode.textValue()); + } + + if (javaId.contains("wool")) { + JAVA_RUNTIME_WOOL_IDS.add(javaRuntimeId); + } + + if (javaId.contains("cobweb")) { + cobwebRuntimeId = javaRuntimeId; + } + + JAVA_ID_BLOCK_MAP.put(javaId, javaBlockState); + + // Used for adding all "special" Java block states to block state map + String identifier; + String bedrock_identifer = entry.getValue().get("bedrock_identifier").asText(); + for (Class clazz : ref.getTypesAnnotatedWith(BlockEntity.class)) { + identifier = clazz.getAnnotation(BlockEntity.class).regex(); + // Endswith, or else the block bedrock gets picked up for bed + if (bedrock_identifer.endsWith(identifier) && !identifier.equals("")) { + JAVA_ID_TO_BLOCK_ENTITY_MAP.put(javaBlockState, clazz.getAnnotation(BlockEntity.class).name()); + break; + } + } + + BlockStateValues.storeBlockStateValues(entry, javaBlockState); + if ("minecraft:water[level=0]".equals(javaId)) { waterRuntimeId = bedrockRuntimeId; } @@ -110,10 +158,10 @@ public class BlockTranslator { || javaId.contains("minecraft:bubble_column") || javaId.contains("minecraft:kelp") || javaId.contains("seagrass"); if (waterlogged) { - BEDROCK_TO_JAVA_BLOCK_MAP.putIfAbsent(bedrockRuntimeId | 1 << 31, new BlockState(javaRuntimeId)); + BEDROCK_TO_JAVA_BLOCK_MAP.putIfAbsent(bedrockRuntimeId | 1 << 31, javaBlockState); WATERLOGGED.add(javaRuntimeId); } else { - BEDROCK_TO_JAVA_BLOCK_MAP.putIfAbsent(bedrockRuntimeId, new BlockState(javaRuntimeId)); + BEDROCK_TO_JAVA_BLOCK_MAP.putIfAbsent(bedrockRuntimeId, javaBlockState); } CompoundTag runtimeTag = blockStateMap.remove(blockTag); @@ -121,7 +169,7 @@ public class BlockTranslator { addedStatesMap.put(blockTag, bedrockRuntimeId); paletteList.add(runtimeTag); } else { - int duplicateRuntimeId = addedStatesMap.get(blockTag); + int duplicateRuntimeId = addedStatesMap.getOrDefault(blockTag, -1); if (duplicateRuntimeId == -1) { GeyserConnector.getInstance().getLogger().debug("Mapping " + javaId + " was not found for bedrock edition!"); } else { @@ -134,6 +182,11 @@ public class BlockTranslator { bedrockRuntimeId++; } + if (cobwebRuntimeId == -1) { + throw new AssertionError("Unable to find cobwebs in palette"); + } + JAVA_RUNTIME_COBWEB_ID = cobwebRuntimeId; + if (waterRuntimeId == -1) { throw new AssertionError("Unable to find water in palette"); } @@ -192,6 +245,14 @@ public class BlockTranslator { return BEDROCK_TO_JAVA_BLOCK_MAP.get(bedrockId); } + public static BlockState getJavaBlockState(String javaId) { + return JAVA_ID_BLOCK_MAP.get(javaId); + } + + public static String getBlockEntityString(BlockState javaId) { + return JAVA_ID_TO_BLOCK_ENTITY_MAP.get(javaId); + } + public static boolean isWaterlogged(BlockState state) { return WATERLOGGED.contains(state.getId()); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BannerBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BannerBlockEntityTranslator.java new file mode 100644 index 000000000..e81191a39 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BannerBlockEntityTranslator.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.block.entity; + +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.IntTag; +import com.nukkitx.nbt.tag.StringTag; +import com.nukkitx.nbt.tag.Tag; +import org.geysermc.connector.network.translators.block.BlockStateValues; + +import java.util.ArrayList; +import java.util.List; + +@BlockEntity(name = "Banner", delay = false, regex = "banner") +public class BannerBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { + + @Override + public boolean isBlock(BlockState blockState) { + return BlockStateValues.getBannerColor(blockState) != -1; + } + + @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); + } + if (tag.contains("CustomName")) { + tags.add(new StringTag("CustomName", (String) tag.get("CustomName").getValue())); + } + return tags; + } + + @Override + public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { + CompoundTag tag = getConstantJavaTag(javaId, x, y, z); + tag.put(new ListTag("Patterns")); + return tag; + } + + @Override + public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder(); + tagBuilder.listTag("Patterns", com.nukkitx.nbt.tag.CompoundTag.class, new ArrayList<>()); + return tagBuilder.buildRootTag(); + } + + /** + * Convert the Java edition pattern nbt to Bedrock edition, null if the pattern doesn't exist + * + * @param pattern Java edition pattern nbt + * @return The Bedrock edition format pattern nbt + */ + protected com.nukkitx.nbt.tag.CompoundTag getPattern(CompoundTag pattern) { + String patternName = (String) pattern.get("Pattern").getValue(); + + // Return null if its the globe pattern as it doesn't exist on bedrock + if (patternName.equals("glb")) { + return null; + } + + return CompoundTagBuilder.builder() + .intTag("Color", 15 - (int) pattern.get("Color").getValue()) + .stringTag("Pattern", (String) pattern.get("Pattern").getValue()) + .stringTag("Pattern", patternName) + .buildRootTag(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BedBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BedBlockEntityTranslator.java new file mode 100644 index 000000000..a8dae2532 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BedBlockEntityTranslator.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.block.entity; + +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.ByteTag; +import com.nukkitx.nbt.tag.Tag; +import org.geysermc.connector.network.translators.block.BlockStateValues; + +import java.util.ArrayList; +import java.util.List; + +@BlockEntity(name = "Bed", delay = false, regex = "bed") +public class BedBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { + + @Override + public boolean isBlock(BlockState blockState) { + return BlockStateValues.getBedColor(blockState) != -1; + } + + @Override + public List> translateTag(CompoundTag tag, BlockState blockState) { + List> tags = new ArrayList<>(); + byte bedcolor = BlockStateValues.getBedColor(blockState); + // Just in case... + if (bedcolor == -1) bedcolor = 0; + tags.add(new ByteTag("color", bedcolor)); + 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("color", (byte) 0); + return tagBuilder.buildRootTag(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BlockEntity.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BlockEntity.java new file mode 100644 index 000000000..47cbbaf30 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BlockEntity.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.block.entity; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(value = RetentionPolicy.RUNTIME) +public @interface BlockEntity { + + /** + * Whether to delay the sending of the block entity + * @return the delay for when sending the block entity + */ + boolean delay(); + + /** + * The block entity name + * @return the name of the block entity + */ + String name(); + + /** + * The search term used in BlockTranslator + * @return the search term used in BlockTranslator + */ + String regex(); +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BlockEntityTranslator.java new file mode 100644 index 000000000..f28257898 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BlockEntityTranslator.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.connector.network.translators.block.entity; + +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.IntTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.Tag; + +import org.geysermc.connector.utils.BlockEntityUtils; + +import java.util.List; + +public abstract class BlockEntityTranslator { + + public abstract List> translateTag(CompoundTag tag, BlockState blockState); + + public abstract CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z); + + public abstract com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z); + + public com.nukkitx.nbt.tag.CompoundTag getBlockEntityTag(String id, CompoundTag tag, BlockState blockState) { + int x = Integer.parseInt(String.valueOf(tag.getValue().get("x").getValue())); + int y = Integer.parseInt(String.valueOf(tag.getValue().get("y").getValue())); + int z = Integer.parseInt(String.valueOf(tag.getValue().get("z").getValue())); + + CompoundTagBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(id), x, y, z).toBuilder(); + translateTag(tag, blockState).forEach(tagBuilder::tag); + return tagBuilder.buildRootTag(); + } + + protected CompoundTag getConstantJavaTag(String javaId, int x, int y, int z) { + CompoundTag tag = new CompoundTag(""); + tag.put(new IntTag("x", x)); + tag.put(new IntTag("y", y)); + tag.put(new IntTag("z", z)); + tag.put(new StringTag("id", javaId)); + return tag; + } + + protected com.nukkitx.nbt.tag.CompoundTag getConstantBedrockTag(String bedrockId, int x, int y, int z) { + CompoundTagBuilder tagBuilder = CompoundTagBuilder.builder() + .intTag("x", x) + .intTag("y", y) + .intTag("z", z) + .stringTag("id", bedrockId); + return tagBuilder.buildRootTag(); + } + + @SuppressWarnings("unchecked") + protected T getOrDefault(com.github.steveice10.opennbt.tag.builtin.Tag tag, T defaultValue) { + return (tag != null && tag.getValue() != null) ? (T) tag.getValue() : defaultValue; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/CampfireBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/CampfireBlockEntityTranslator.java new file mode 100644 index 000000000..11b7e8649 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/CampfireBlockEntityTranslator.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.block.entity; + +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.Tag; + +import org.geysermc.connector.network.translators.Translators; +import org.geysermc.connector.network.translators.item.ItemEntry; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +@BlockEntity(name = "Campfire", delay = false, regex = "campfire") +public class CampfireBlockEntityTranslator extends BlockEntityTranslator { + + @Override + public List> translateTag(CompoundTag tag, BlockState blockState) { + List> tags = new ArrayList<>(); + ListTag items = tag.get("Items"); + int i = 1; + for (com.github.steveice10.opennbt.tag.builtin.Tag itemTag : items.getValue()) { + tags.add(getItem((CompoundTag) itemTag).toBuilder().build("Item" + i)); + i++; + } + return tags; + } + + @Override + public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { + CompoundTag tag = getConstantJavaTag(javaId, x, y, z); + tag.put(new ListTag("Items")); + return tag; + } + + @Override + public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder(); + tagBuilder.tag(new com.nukkitx.nbt.tag.CompoundTag("Item1", new HashMap<>())); + tagBuilder.tag(new com.nukkitx.nbt.tag.CompoundTag("Item2", new HashMap<>())); + tagBuilder.tag(new com.nukkitx.nbt.tag.CompoundTag("Item3", new HashMap<>())); + tagBuilder.tag(new com.nukkitx.nbt.tag.CompoundTag("Item4", new HashMap<>())); + return tagBuilder.buildRootTag(); + } + + protected com.nukkitx.nbt.tag.CompoundTag getItem(CompoundTag tag) { + ItemEntry entry = Translators.getItemTranslator().getItemEntry((String) tag.get("id").getValue()); + CompoundTagBuilder tagBuilder = CompoundTagBuilder.builder() + .shortTag("id", (short) entry.getBedrockId()) + .byteTag("Count", (byte) tag.get("Count").getValue()) + .shortTag("Damage", (short) entry.getBedrockData()) + .tag(CompoundTagBuilder.builder().build("tag")); + return tagBuilder.buildRootTag(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/EmptyBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/EmptyBlockEntityTranslator.java new file mode 100644 index 000000000..e46014008 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/EmptyBlockEntityTranslator.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.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.tag.Tag; + +import java.util.ArrayList; +import java.util.List; + +@BlockEntity(name = "Empty", delay = false, regex = "") +public class EmptyBlockEntityTranslator extends BlockEntityTranslator { + + @Override + public List> translateTag(CompoundTag tag, BlockState blockState) { + return new ArrayList<>(); + } + + @Override + public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { + return getConstantJavaTag(javaId, x, y, z); + } + + @Override + public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + return getConstantBedrockTag(bedrockId, x, y, z); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/EndGatewayBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/EndGatewayBlockEntityTranslator.java new file mode 100644 index 000000000..de5868a4b --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/EndGatewayBlockEntityTranslator.java @@ -0,0 +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: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.block.entity; + +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.LongTag; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.IntTag; +import com.nukkitx.nbt.tag.Tag; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; + +@BlockEntity(name = "EndGateway", delay = true, regex = "end_gateway") +public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator { + + @Override + public List> translateTag(CompoundTag tag, BlockState blockState) { + List> tags = new ArrayList<>(); + tags.add(new IntTag("Age", (int) (long) tag.get("Age").getValue())); + // Java sometimes does not provide this tag, but Bedrock crashes if it doesn't exist + // Linked coordinates + List tagsList = new ArrayList<>(); + // Yes, the axis letters are capitalized + tagsList.add(new IntTag("", getExitPortalCoordinate(tag, "X"))); + tagsList.add(new IntTag("", getExitPortalCoordinate(tag, "Y"))); + tagsList.add(new IntTag("", getExitPortalCoordinate(tag, "Z"))); + com.nukkitx.nbt.tag.ListTag exitPortal = + new com.nukkitx.nbt.tag.ListTag<>("ExitPortal", IntTag.class, tagsList); + tags.add(exitPortal); + return tags; + } + + @Override + public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { + CompoundTag tag = getConstantJavaTag(javaId, x, y, z); + tag.put(new LongTag("Age")); + return tag; + } + + @Override + public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder(); + List tagsList = new ArrayList<>(); + tagsList.add(new IntTag("", 0)); + tagsList.add(new IntTag("", 0)); + tagsList.add(new IntTag("", 0)); + tagBuilder.listTag("ExitPortal", IntTag.class, tagsList); + return tagBuilder.buildRootTag(); + } + + private int getExitPortalCoordinate(CompoundTag tag, String axis) { + // Return 0 if it doesn't exist, otherwise give proper value + if (tag.get("ExitPortal") != null) { + LinkedHashMap compoundTag = (LinkedHashMap) tag.get("ExitPortal").getValue(); + com.github.steveice10.opennbt.tag.builtin.IntTag intTag = (com.github.steveice10.opennbt.tag.builtin.IntTag) compoundTag.get(axis); + return intTag.getValue(); + } return 0; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/RequiresBlockState.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/RequiresBlockState.java new file mode 100644 index 000000000..ed8e6ede9 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/RequiresBlockState.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.block.entity; + +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; + +/** + * Implemented in block entities if their Java block state is required for additional values in Bedrock + */ +public interface RequiresBlockState { + + /** + * Determines if block is part of class + * @param blockState BlockState to be compared + * @return true if part of the class + */ + boolean isBlock(BlockState blockState); + +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/SignBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/SignBlockEntityTranslator.java new file mode 100644 index 000000000..74dcdd13e --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/SignBlockEntityTranslator.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.block.entity; + +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.github.steveice10.mc.protocol.data.message.Message; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.StringTag; +import com.nukkitx.nbt.tag.Tag; +import org.geysermc.connector.utils.MessageUtils; + +import java.util.ArrayList; +import java.util.List; + +@BlockEntity(name = "Sign", delay = true, regex = "sign") +public class SignBlockEntityTranslator extends BlockEntityTranslator { + + @Override + public List> translateTag(CompoundTag tag, BlockState blockState) { + List> tags = new ArrayList<>(); + + String line1 = getOrDefault(tag.getValue().get("Text1"), ""); + String line2 = getOrDefault(tag.getValue().get("Text2"), ""); + String line3 = getOrDefault(tag.getValue().get("Text3"), ""); + String line4 = getOrDefault(tag.getValue().get("Text4"), ""); + + tags.add(new StringTag("Text", MessageUtils.getBedrockMessage(Message.fromString(line1)) + + "\n" + MessageUtils.getBedrockMessage(Message.fromString(line2)) + + "\n" + MessageUtils.getBedrockMessage(Message.fromString(line3)) + + "\n" + MessageUtils.getBedrockMessage(Message.fromString(line4)) + )); + + return tags; + } + + @Override + public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { + CompoundTag tag = getConstantJavaTag(javaId, x, y, z); + tag.put(new com.github.steveice10.opennbt.tag.builtin.StringTag("Text1", "{\"text\":\"\"}")); + tag.put(new com.github.steveice10.opennbt.tag.builtin.StringTag("Text2", "{\"text\":\"\"}")); + tag.put(new com.github.steveice10.opennbt.tag.builtin.StringTag("Text3", "{\"text\":\"\"}")); + tag.put(new com.github.steveice10.opennbt.tag.builtin.StringTag("Text4", "{\"text\":\"\"}")); + return tag; + } + + @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.stringTag("Text", ""); + return tagBuilder.buildRootTag(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/SkullBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/SkullBlockEntityTranslator.java new file mode 100644 index 000000000..2380663b1 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/SkullBlockEntityTranslator.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.block.entity; + +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.ByteTag; +import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.nbt.tag.FloatTag; +import com.nukkitx.nbt.tag.Tag; +import org.geysermc.connector.network.translators.block.BlockStateValues; + +import java.util.ArrayList; +import java.util.List; + +@BlockEntity(name = "Skull", delay = false, regex = "skull") +public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { + + @Override + public boolean isBlock(BlockState blockState) { + return BlockStateValues.getSkullVariant(blockState) != -1; + } + + @Override + public List> translateTag(com.github.steveice10.opennbt.tag.builtin.CompoundTag tag, BlockState blockState) { + List> tags = new ArrayList<>(); + byte skullVariant = BlockStateValues.getSkullVariant(blockState); + float rotation = BlockStateValues.getSkullRotation(blockState) * 22.5f; + // Just in case... + if (skullVariant == -1) skullVariant = 0; + tags.add(new FloatTag("Rotation", rotation)); + tags.add(new ByteTag("SkullType", skullVariant)); + return tags; + } + + @Override + public com.github.steveice10.opennbt.tag.builtin.CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { + return null; + } + + @Override + public CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder(); + tagBuilder.floatTag("Rotation", 0); + tagBuilder.byteTag("SkullType", (byte) 0); + return tagBuilder.buildRootTag(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java new file mode 100644 index 000000000..8df72d362 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java @@ -0,0 +1,146 @@ +/* + * 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.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.message.Message; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientRenameItemPacket; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.protocol.bedrock.data.ContainerId; +import com.nukkitx.protocol.bedrock.data.ContainerType; +import com.nukkitx.protocol.bedrock.data.InventoryActionData; +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.inventory.updater.CursorInventoryUpdater; + +import java.util.List; + +public class AnvilInventoryTranslator extends BlockInventoryTranslator { + public AnvilInventoryTranslator() { + super(3, "minecraft:anvil[facing=north]", ContainerType.ANVIL, new CursorInventoryUpdater()); + } + + @Override + public int bedrockSlotToJava(InventoryActionData action) { + if (action.getSource().getContainerId() == ContainerId.CURSOR) { + switch (action.getSlot()) { + case 1: + return 0; + case 2: + return 1; + case 50: + return 2; + } + } + return super.bedrockSlotToJava(action); + } + + @Override + public int javaSlotToBedrock(int slot) { + switch (slot) { + case 0: + return 1; + case 1: + return 2; + case 2: + return 50; + } + return super.javaSlotToBedrock(slot); + } + + @Override + public SlotType getSlotType(int javaSlot) { + if (javaSlot == 2) + return SlotType.OUTPUT; + return SlotType.NORMAL; + } + + @Override + public void translateActions(GeyserSession session, Inventory inventory, List actions) { + InventoryActionData anvilResult = null; + InventoryActionData anvilInput = null; + for (InventoryActionData action : actions) { + if (action.getSource().getContainerId() == ContainerId.ANVIL_MATERIAL) { + //useless packet + return; + } else if (action.getSource().getContainerId() == ContainerId.ANVIL_RESULT) { + anvilResult = action; + } else if (bedrockSlotToJava(action) == 0) { + anvilInput = action; + } + } + ItemData itemName = null; + if (anvilResult != null) { + itemName = anvilResult.getFromItem(); + } else if (anvilInput != null) { + itemName = anvilInput.getToItem(); + } + if (itemName != null) { + String rename; + com.nukkitx.nbt.tag.CompoundTag tag = itemName.getTag(); + if (tag != null) { + rename = tag.getCompound("display").getString("Name"); + } else { + rename = ""; + } + ClientRenameItemPacket renameItemPacket = new ClientRenameItemPacket(rename); + session.getDownstream().getSession().send(renameItemPacket); + } + if (anvilResult != null) { + //client will send another packet to grab anvil output + return; + } + + super.translateActions(session, inventory, actions); + } + + @Override + public void updateSlot(GeyserSession session, Inventory inventory, int slot) { + if (slot >= 0 && slot <= 2) { + ItemStack item = inventory.getItem(slot); + if (item != null) { + String rename; + CompoundTag tag = item.getNbt(); + if (tag != null) { + CompoundTag displayTag = tag.get("display"); + if (displayTag != null) { + String itemName = displayTag.get("Name").getValue().toString(); + Message message = Message.fromString(itemName); + rename = message.getText(); + } else { + rename = ""; + } + } else { + rename = ""; + } + ClientRenameItemPacket renameItemPacket = new ClientRenameItemPacket(rename); + session.getDownstream().getSession().send(renameItemPacket); + } + } + super.updateSlot(session, inventory, slot); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BaseInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BaseInventoryTranslator.java new file mode 100644 index 000000000..5deb0370a --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BaseInventoryTranslator.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory; + +import com.nukkitx.protocol.bedrock.data.ContainerId; +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.action.InventoryActionDataTranslator; + +import java.util.List; + +public abstract class BaseInventoryTranslator extends InventoryTranslator{ + BaseInventoryTranslator(int size) { + super(size); + } + + @Override + public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { + // + } + + @Override + public int bedrockSlotToJava(InventoryActionData action) { + int slotnum = action.getSlot(); + if (action.getSource().getContainerId() == ContainerId.INVENTORY) { + //hotbar + if (slotnum >= 9) { + return slotnum + this.size - 9; + } else { + return slotnum + this.size + 27; + } + } + return slotnum; + } + + @Override + public int javaSlotToBedrock(int slot) { + if (slot >= this.size) { + final int tmp = slot - this.size; + if (tmp < 27) { + return tmp + 9; + } else { + return tmp - 27; + } + } + return slot; + } + + @Override + public SlotType getSlotType(int javaSlot) { + return SlotType.NORMAL; + } + + @Override + public void translateActions(GeyserSession session, Inventory inventory, List actions) { + InventoryActionDataTranslator.translate(this, session, inventory, actions); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BlockInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BlockInventoryTranslator.java new file mode 100644 index 000000000..5f6274f0a --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BlockInventoryTranslator.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory; + +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.nukkitx.protocol.bedrock.data.ContainerType; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.block.BlockTranslator; +import org.geysermc.connector.network.translators.inventory.holder.BlockInventoryHolder; +import org.geysermc.connector.network.translators.inventory.holder.InventoryHolder; +import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; + +public class BlockInventoryTranslator extends BaseInventoryTranslator { + private final InventoryHolder holder; + private final InventoryUpdater updater; + + public BlockInventoryTranslator(int size, String javaBlockIdentifier, ContainerType containerType, InventoryUpdater updater) { + super(size); + BlockState javaBlockState = BlockTranslator.getJavaBlockState(javaBlockIdentifier); + int blockId = BlockTranslator.getBedrockBlockId(javaBlockState); + this.holder = new BlockInventoryHolder(blockId, containerType); + this.updater = updater; + } + + @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); + } + + @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/BrewingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BrewingInventoryTranslator.java new file mode 100644 index 000000000..bd143698f --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BrewingInventoryTranslator.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory; + +import com.nukkitx.protocol.bedrock.data.ContainerType; +import com.nukkitx.protocol.bedrock.data.InventoryActionData; +import com.nukkitx.protocol.bedrock.packet.ContainerSetDataPacket; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater; + +public class BrewingInventoryTranslator extends BlockInventoryTranslator { + public BrewingInventoryTranslator() { + super(5, "minecraft:brewing_stand[has_bottle_0=false,has_bottle_1=false,has_bottle_2=false]", ContainerType.BREWING_STAND, new ContainerInventoryUpdater()); + } + + @Override + public void openInventory(GeyserSession session, Inventory inventory) { + super.openInventory(session, inventory); + ContainerSetDataPacket dataPacket = new ContainerSetDataPacket(); + dataPacket.setWindowId((byte) inventory.getId()); + dataPacket.setProperty(ContainerSetDataPacket.BREWING_STAND_FUEL_TOTAL); + dataPacket.setValue(20); + session.getUpstream().sendPacket(dataPacket); + } + + @Override + public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { + ContainerSetDataPacket dataPacket = new ContainerSetDataPacket(); + dataPacket.setWindowId((byte) inventory.getId()); + switch (key) { + case 0: + dataPacket.setProperty(ContainerSetDataPacket.BREWING_STAND_BREW_TIME); + break; + case 1: + dataPacket.setProperty(ContainerSetDataPacket.BREWING_STAND_FUEL_AMOUNT); + break; + default: + return; + } + dataPacket.setValue(value); + session.getUpstream().sendPacket(dataPacket); + } + + @Override + public int bedrockSlotToJava(InventoryActionData action) { + final int slot = super.bedrockSlotToJava(action); + switch (slot) { + case 0: + return 3; + case 1: + return 0; + case 2: + return 1; + case 3: + return 2; + default: + return slot; + } + } + + @Override + public int javaSlotToBedrock(int slot) { + switch (slot) { + case 0: + return 1; + case 1: + return 2; + case 2: + return 3; + case 3: + return 0; + } + return super.javaSlotToBedrock(slot); + } +} 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 new file mode 100644 index 000000000..92a1d90ec --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.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.network.translators.inventory; + +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.nukkitx.protocol.bedrock.data.ContainerId; +import com.nukkitx.protocol.bedrock.data.ContainerType; +import com.nukkitx.protocol.bedrock.data.InventoryActionData; +import com.nukkitx.protocol.bedrock.data.InventorySource; +import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater; +import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; +import org.geysermc.connector.utils.InventoryUtils; + +import java.util.List; + +public class CraftingInventoryTranslator extends BaseInventoryTranslator { + private final InventoryUpdater updater; + + public CraftingInventoryTranslator() { + super(10); + this.updater = new CursorInventoryUpdater(); + } + + @Override + public void prepareInventory(GeyserSession session, Inventory inventory) { + // + } + + @Override + public void openInventory(GeyserSession session, Inventory inventory) { + ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket(); + containerOpenPacket.setWindowId((byte) inventory.getId()); + containerOpenPacket.setType((byte) ContainerType.WORKBENCH.id()); + containerOpenPacket.setBlockPosition(inventory.getHolderPosition()); + containerOpenPacket.setUniqueEntityId(inventory.getHolderId()); + session.getUpstream().sendPacket(containerOpenPacket); + } + + @Override + public void closeInventory(GeyserSession session, Inventory inventory) { + // + } + + @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 int bedrockSlotToJava(InventoryActionData action) { + if (action.getSource().getContainerId() == ContainerId.CURSOR) { + int slotnum = action.getSlot(); + if (slotnum >= 32 && 42 >= slotnum) { + return slotnum - 31; + } else if (slotnum == 50) { + return 0; + } + } + return super.bedrockSlotToJava(action); + } + + @Override + public int javaSlotToBedrock(int slot) { + return slot == 0 ? 50 : slot + 31; + } + + @Override + public SlotType getSlotType(int javaSlot) { + if (javaSlot == 0) + return SlotType.OUTPUT; + return SlotType.NORMAL; + } + + @Override + public void translateActions(GeyserSession session, Inventory inventory, List actions) { + if (session.getGameMode() == GameMode.CREATIVE) { + for (InventoryActionData action : actions) { + if (action.getSource().getType() == InventorySource.Type.CREATIVE) { + 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/DoubleChestInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/DoubleChestInventoryTranslator.java new file mode 100644 index 000000000..c70a89955 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/DoubleChestInventoryTranslator.java @@ -0,0 +1,141 @@ +/* + * 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.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.protocol.bedrock.data.ContainerType; +import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; +import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; +import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.block.BlockTranslator; +import org.geysermc.connector.network.translators.inventory.updater.ChestInventoryUpdater; +import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; + +public class DoubleChestInventoryTranslator extends BaseInventoryTranslator { + private final int blockId; + private final InventoryUpdater updater; + + public DoubleChestInventoryTranslator(int size) { + super(size); + BlockState javaBlockState = BlockTranslator.getJavaBlockState("minecraft:chest[facing=north,type=single,waterlogged=false]"); + this.blockId = BlockTranslator.getBedrockBlockId(javaBlockState); + this.updater = new ChestInventoryUpdater(54); + } + + @Override + public void prepareInventory(GeyserSession session, Inventory inventory) { + Vector3i position = session.getPlayerEntity().getPosition().toInt().add(Vector3i.UP); + Vector3i pairPosition = position.add(Vector3i.UNIT_X); + + UpdateBlockPacket blockPacket = new UpdateBlockPacket(); + blockPacket.setDataLayer(0); + blockPacket.setBlockPosition(position); + blockPacket.setRuntimeId(blockId); + blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); + session.getUpstream().sendPacket(blockPacket); + + CompoundTag tag = CompoundTag.builder() + .stringTag("id", "Chest") + .intTag("x", position.getX()) + .intTag("y", position.getY()) + .intTag("z", position.getZ()) + .intTag("pairx", pairPosition.getX()) + .intTag("pairz", pairPosition.getZ()) + .stringTag("CustomName", inventory.getTitle()).buildRootTag(); + BlockEntityDataPacket dataPacket = new BlockEntityDataPacket(); + dataPacket.setData(tag); + dataPacket.setBlockPosition(position); + session.getUpstream().sendPacket(dataPacket); + + blockPacket = new UpdateBlockPacket(); + blockPacket.setDataLayer(0); + blockPacket.setBlockPosition(pairPosition); + blockPacket.setRuntimeId(blockId); + blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); + session.getUpstream().sendPacket(blockPacket); + + tag = CompoundTag.builder() + .stringTag("id", "Chest") + .intTag("x", pairPosition.getX()) + .intTag("y", pairPosition.getY()) + .intTag("z", pairPosition.getZ()) + .intTag("pairx", position.getX()) + .intTag("pairz", position.getZ()) + .stringTag("CustomName", inventory.getTitle()).buildRootTag(); + dataPacket = new BlockEntityDataPacket(); + dataPacket.setData(tag); + dataPacket.setBlockPosition(pairPosition); + session.getUpstream().sendPacket(dataPacket); + + inventory.setHolderPosition(position); + } + + @Override + public void openInventory(GeyserSession session, Inventory inventory) { + ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket(); + containerOpenPacket.setWindowId((byte) inventory.getId()); + containerOpenPacket.setType((byte) ContainerType.CONTAINER.id()); + containerOpenPacket.setBlockPosition(inventory.getHolderPosition()); + containerOpenPacket.setUniqueEntityId(inventory.getHolderId()); + session.getUpstream().sendPacket(containerOpenPacket); + } + + @Override + public void closeInventory(GeyserSession session, Inventory inventory) { + Vector3i holderPos = inventory.getHolderPosition(); + Position pos = new Position(holderPos.getX(), holderPos.getY(), holderPos.getZ()); + BlockState realBlock = session.getChunkCache().getBlockAt(pos); + UpdateBlockPacket blockPacket = new UpdateBlockPacket(); + blockPacket.setDataLayer(0); + blockPacket.setBlockPosition(holderPos); + blockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(realBlock)); + session.getUpstream().sendPacket(blockPacket); + + holderPos = holderPos.add(Vector3i.UNIT_X); + pos = new Position(holderPos.getX(), holderPos.getY(), holderPos.getZ()); + realBlock = session.getChunkCache().getBlockAt(pos); + blockPacket = new UpdateBlockPacket(); + blockPacket.setDataLayer(0); + blockPacket.setBlockPosition(holderPos); + blockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(realBlock)); + session.getUpstream().sendPacket(blockPacket); + } + + @Override + public void updateInventory(GeyserSession session, Inventory inventory) { + updater.updateInventory(this, session, inventory); + } + + @Override + public void updateSlot(GeyserSession session, Inventory inventory, int slot) { + updater.updateSlot(this, session, inventory, slot); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java new file mode 100644 index 000000000..ba7f8cc7a --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory; + +import com.nukkitx.protocol.bedrock.data.ContainerType; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater; + +public class EnchantmentInventoryTranslator extends BlockInventoryTranslator { + public EnchantmentInventoryTranslator() { + super(2, "minecraft:enchanting_table", ContainerType.ENCHANTMENT, new ContainerInventoryUpdater()); + } + + @Override + public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { + + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/FurnaceInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/FurnaceInventoryTranslator.java new file mode 100644 index 000000000..9b45201ed --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/FurnaceInventoryTranslator.java @@ -0,0 +1,70 @@ +/* + * 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.github.steveice10.mc.protocol.data.game.window.WindowType; +import com.nukkitx.protocol.bedrock.data.ContainerType; +import com.nukkitx.protocol.bedrock.packet.ContainerSetDataPacket; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater; + +public class FurnaceInventoryTranslator extends BlockInventoryTranslator { + public FurnaceInventoryTranslator() { + super(3, "minecraft:furnace[facing=north,lit=false]", ContainerType.FURNACE, new ContainerInventoryUpdater()); + } + + @Override + public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { + ContainerSetDataPacket dataPacket = new ContainerSetDataPacket(); + dataPacket.setWindowId((byte) inventory.getId()); + switch (key) { + case 0: + dataPacket.setProperty(ContainerSetDataPacket.FURNACE_LIT_TIME); + break; + case 1: + dataPacket.setProperty(ContainerSetDataPacket.FURNACE_LIT_DURATION); + break; + case 2: + dataPacket.setProperty(ContainerSetDataPacket.FURNACE_TICK_COUNT); + if (inventory.getWindowType() == WindowType.BLAST_FURNACE || inventory.getWindowType() == WindowType.SMOKER) { + value *= 2; + } + break; + default: + return; + } + dataPacket.setValue(value); + session.getUpstream().sendPacket(dataPacket); + } + + @Override + public SlotType getSlotType(int javaSlot) { + if (javaSlot == 2) + return SlotType.FURNACE_OUTPUT; + return SlotType.NORMAL; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/GrindstoneInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/GrindstoneInventoryTranslator.java new file mode 100644 index 000000000..174cfbc11 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/GrindstoneInventoryTranslator.java @@ -0,0 +1,68 @@ +/* + * 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.ContainerType; +import com.nukkitx.protocol.bedrock.data.InventoryActionData; +import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater; + +public class GrindstoneInventoryTranslator extends BlockInventoryTranslator { + + public GrindstoneInventoryTranslator() { + super(3, "minecraft:grindstone[face=floor,facing=north]", ContainerType.GRINDSTONE, new CursorInventoryUpdater()); + } + + @Override + public int bedrockSlotToJava(InventoryActionData action) { + final int slot = super.bedrockSlotToJava(action); + if (action.getSource().getContainerId() == 124) { + switch (slot) { + case 16: + return 0; + case 17: + return 1; + case 50: + return 2; + default: + return slot; + } + } return slot; + } + + @Override + public int javaSlotToBedrock(int slot) { + switch (slot) { + case 0: + return 16; + case 1: + return 17; + case 2: + return 50; + } + return super.javaSlotToBedrock(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 f17aba57e..2a5afb8c0 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,14 +25,25 @@ package org.geysermc.connector.network.translators.inventory; +import com.nukkitx.protocol.bedrock.data.InventoryActionData; +import lombok.AllArgsConstructor; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; +import java.util.List; + +@AllArgsConstructor public abstract class InventoryTranslator { + public final int size; public abstract void prepareInventory(GeyserSession session, Inventory inventory); public abstract void openInventory(GeyserSession session, Inventory inventory); + public abstract void closeInventory(GeyserSession session, Inventory inventory); + public abstract void updateProperty(GeyserSession session, Inventory inventory, int key, int value); public abstract void updateInventory(GeyserSession session, Inventory inventory); public abstract void updateSlot(GeyserSession session, Inventory inventory, int slot); - + public abstract int bedrockSlotToJava(InventoryActionData action); + public abstract int javaSlotToBedrock(int slot); + public abstract SlotType getSlotType(int javaSlot); + public abstract void translateActions(GeyserSession session, Inventory inventory, List actions); } 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 new file mode 100644 index 000000000..668612c53 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/PlayerInventoryTranslator.java @@ -0,0 +1,246 @@ +/* + * 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.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCreativeInventoryActionPacket; +import com.nukkitx.protocol.bedrock.data.ContainerId; +import com.nukkitx.protocol.bedrock.data.InventoryActionData; +import com.nukkitx.protocol.bedrock.data.InventorySource; +import com.nukkitx.protocol.bedrock.data.ItemData; +import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; +import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; +import it.unimi.dsi.fastutil.longs.LongArraySet; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.Translators; +import org.geysermc.connector.network.translators.inventory.action.InventoryActionDataTranslator; +import org.geysermc.connector.utils.InventoryUtils; +import org.geysermc.connector.utils.Toolbox; + +import java.util.List; + +public class PlayerInventoryTranslator extends InventoryTranslator { + + public PlayerInventoryTranslator() { + super(46); + } + + @Override + public void updateInventory(GeyserSession session, Inventory inventory) { + updateCraftingGrid(session, inventory); + + InventoryContentPacket inventoryContentPacket = new InventoryContentPacket(); + inventoryContentPacket.setContainerId(ContainerId.INVENTORY); + ItemData[] contents = new ItemData[36]; + // Inventory + for (int i = 9; i < 36; i++) { + contents[i] = Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(i)); + } + // Hotbar + for (int i = 36; i < 45; i++) { + contents[i - 36] = Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(i)); + } + inventoryContentPacket.setContents(contents); + session.getUpstream().sendPacket(inventoryContentPacket); + + // Armor + InventoryContentPacket armorContentPacket = new InventoryContentPacket(); + armorContentPacket.setContainerId(ContainerId.ARMOR); + contents = new ItemData[4]; + for (int i = 5; i < 9; i++) { + contents[i - 5] = Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(i)); + } + armorContentPacket.setContents(contents); + session.getUpstream().sendPacket(armorContentPacket); + + // Offhand + InventoryContentPacket offhandPacket = new InventoryContentPacket(); + offhandPacket.setContainerId(ContainerId.OFFHAND); + offhandPacket.setContents(new ItemData[]{Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(45))}); + session.getUpstream().sendPacket(offhandPacket); + } + + /** + * Update the crafting grid for the player to hide/show the barriers in the creative inventory + * @param session Session of the player + * @param inventory Inventory of the player + */ + public static void updateCraftingGrid(GeyserSession session, Inventory inventory) { + // Crafting grid + for (int i = 1; i < 5; i++) { + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.CURSOR); + slotPacket.setSlot(i + 27); + + if (session.getGameMode() == GameMode.CREATIVE) { + slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(session, new ItemStack(Toolbox.BARRIER_INDEX))); + }else{ + slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(i))); + } + + session.getUpstream().sendPacket(slotPacket); + } + } + + @Override + public void updateSlot(GeyserSession session, Inventory inventory, int slot) { + if (slot >= 1 && slot <= 44) { + InventorySlotPacket slotPacket = new InventorySlotPacket(); + if (slot >= 9) { + slotPacket.setContainerId(ContainerId.INVENTORY); + if (slot >= 36) { + slotPacket.setSlot(slot - 36); + } else { + slotPacket.setSlot(slot); + } + } else if (slot >= 5) { + slotPacket.setContainerId(ContainerId.ARMOR); + slotPacket.setSlot(slot - 5); + } else { + slotPacket.setContainerId(ContainerId.CURSOR); + slotPacket.setSlot(slot + 27); + } + slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(slot))); + session.getUpstream().sendPacket(slotPacket); + } else if (slot == 45) { + InventoryContentPacket offhandPacket = new InventoryContentPacket(); + offhandPacket.setContainerId(ContainerId.OFFHAND); + offhandPacket.setContents(new ItemData[]{Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(slot))}); + session.getUpstream().sendPacket(offhandPacket); + } + } + + @Override + public int bedrockSlotToJava(InventoryActionData action) { + int slotnum = action.getSlot(); + switch (action.getSource().getContainerId()) { + case ContainerId.INVENTORY: + // Inventory + if (slotnum >= 9 && slotnum <= 35) { + return slotnum; + } + // Hotbar + if (slotnum >= 0 && slotnum <= 8) { + return slotnum + 36; + } + break; + case ContainerId.ARMOR: + if (slotnum >= 0 && slotnum <= 3) { + return slotnum + 5; + } + break; + case ContainerId.OFFHAND: + return 45; + case ContainerId.CURSOR: + if (slotnum >= 28 && 31 >= slotnum) { + return slotnum - 27; + } else if (slotnum == 50) { + return 0; + } + break; + } + return slotnum; + } + + @Override + public int javaSlotToBedrock(int slot) { + return slot; + } + + @Override + public SlotType getSlotType(int javaSlot) { + if (javaSlot == 0) + return SlotType.OUTPUT; + return SlotType.NORMAL; + } + + @Override + public void translateActions(GeyserSession session, Inventory inventory, List actions) { + if (session.getGameMode() == GameMode.CREATIVE) { + //crafting grid is not visible in creative mode in java edition + for (InventoryActionData action : actions) { + if (action.getSource().getContainerId() == ContainerId.CURSOR && (action.getSlot() >= 28 && 31 >= action.getSlot())) { + updateInventory(session, inventory); + InventoryUtils.updateCursor(session); + return; + } + } + + ItemStack javaItem; + for (InventoryActionData action : actions) { + switch (action.getSource().getContainerId()) { + case ContainerId.INVENTORY: + case ContainerId.ARMOR: + case ContainerId.OFFHAND: + int javaSlot = bedrockSlotToJava(action); + if (action.getToItem().getId() == 0) { + javaItem = new ItemStack(-1, 0, null); + } else { + javaItem = Translators.getItemTranslator().translateToJava(session, action.getToItem()); + } + ClientCreativeInventoryActionPacket creativePacket = new ClientCreativeInventoryActionPacket(javaSlot, javaItem); + session.getDownstream().getSession().send(creativePacket); + inventory.setItem(javaSlot, javaItem); + break; + case ContainerId.CURSOR: + if (action.getSlot() == 0) { + session.getInventory().setCursor(Translators.getItemTranslator().translateToJava(session, 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()); + ClientCreativeInventoryActionPacket creativeDropPacket = new ClientCreativeInventoryActionPacket(-1, javaItem); + session.getDownstream().getSession().send(creativeDropPacket); + } + break; + } + } + return; + } + + InventoryActionDataTranslator.translate(this, session, inventory, actions); + } + + @Override + public void prepareInventory(GeyserSession session, Inventory inventory) { + } + + @Override + public void openInventory(GeyserSession session, Inventory inventory) { + } + + @Override + public void closeInventory(GeyserSession session, Inventory inventory) { + } + + @Override + public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { + } +} 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 new file mode 100644 index 000000000..5c99b0126 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SingleChestInventoryTranslator.java @@ -0,0 +1,35 @@ +/* + * 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.ContainerType; +import org.geysermc.connector.network.translators.inventory.updater.ChestInventoryUpdater; + +public class SingleChestInventoryTranslator extends BlockInventoryTranslator { + public SingleChestInventoryTranslator(int size) { + super(size, "minecraft:chest[facing=north,type=single,waterlogged=false]", ContainerType.CONTAINER, new ChestInventoryUpdater(27)); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SlotType.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SlotType.java new file mode 100644 index 000000000..045adbd32 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SlotType.java @@ -0,0 +1,32 @@ +/* + * 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; + +public enum SlotType { + NORMAL, + OUTPUT, + FURNACE_OUTPUT +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/Click.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/Click.java new file mode 100644 index 000000000..1fdfa3640 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/Click.java @@ -0,0 +1,38 @@ +/* + * 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.action; + +import com.github.steveice10.mc.protocol.data.game.window.ClickItemParam; +import com.github.steveice10.mc.protocol.data.game.window.WindowActionParam; +import lombok.AllArgsConstructor; + +@AllArgsConstructor +enum Click { + LEFT(ClickItemParam.LEFT_CLICK), + RIGHT(ClickItemParam.RIGHT_CLICK); + + public final WindowActionParam actionParam; +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/ClickPlan.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/ClickPlan.java new file mode 100644 index 000000000..cdc42f961 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/ClickPlan.java @@ -0,0 +1,125 @@ +/* + * 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.action; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.window.WindowAction; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientConfirmTransactionPacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientWindowActionPacket; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.inventory.PlayerInventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +import org.geysermc.connector.network.translators.inventory.SlotType; +import org.geysermc.connector.utils.InventoryUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; + +class ClickPlan { + private final List plan = new ArrayList<>(); + + public void add(Click click, int slot) { + plan.add(new ClickAction(click, slot)); + } + + public void execute(GeyserSession session, InventoryTranslator translator, Inventory inventory, boolean refresh) { + PlayerInventory playerInventory = session.getInventory(); + ListIterator planIter = plan.listIterator(); + while (planIter.hasNext()) { + final ClickAction action = planIter.next(); + final ItemStack cursorItem = playerInventory.getCursor(); + final ItemStack clickedItem = inventory.getItem(action.slot); + final short actionId = (short) inventory.getTransactionId().getAndIncrement(); + + //TODO: stop relying on refreshing the inventory for crafting to work properly + if (translator.getSlotType(action.slot) != SlotType.NORMAL) + refresh = true; + + ClientWindowActionPacket clickPacket = new ClientWindowActionPacket(inventory.getId(), + actionId, action.slot, !planIter.hasNext() && refresh ? InventoryUtils.REFRESH_ITEM : clickedItem, + WindowAction.CLICK_ITEM, action.click.actionParam); + + if (translator.getSlotType(action.slot) == SlotType.OUTPUT) { + if (cursorItem == null && clickedItem != null) { + playerInventory.setCursor(clickedItem); + } else if (InventoryUtils.canStack(cursorItem, clickedItem)) { + playerInventory.setCursor(new ItemStack(cursorItem.getId(), + cursorItem.getAmount() + clickedItem.getAmount(), cursorItem.getNbt())); + } + } else { + switch (action.click) { + case LEFT: + if (!InventoryUtils.canStack(cursorItem, clickedItem)) { + playerInventory.setCursor(clickedItem); + inventory.setItem(action.slot, cursorItem); + } else { + playerInventory.setCursor(null); + inventory.setItem(action.slot, new ItemStack(clickedItem.getId(), + clickedItem.getAmount() + cursorItem.getAmount(), clickedItem.getNbt())); + } + break; + case RIGHT: + if (cursorItem == null && clickedItem != null) { + ItemStack halfItem = new ItemStack(clickedItem.getId(), + clickedItem.getAmount() / 2, clickedItem.getNbt()); + inventory.setItem(action.slot, halfItem); + playerInventory.setCursor(new ItemStack(clickedItem.getId(), + clickedItem.getAmount() - halfItem.getAmount(), clickedItem.getNbt())); + } else if (cursorItem != null && clickedItem == null) { + playerInventory.setCursor(new ItemStack(cursorItem.getId(), + cursorItem.getAmount() - 1, cursorItem.getNbt())); + inventory.setItem(action.slot, new ItemStack(cursorItem.getId(), + 1, cursorItem.getNbt())); + } else if (InventoryUtils.canStack(cursorItem, clickedItem)) { + playerInventory.setCursor(new ItemStack(cursorItem.getId(), + cursorItem.getAmount() - 1, cursorItem.getNbt())); + inventory.setItem(action.slot, new ItemStack(clickedItem.getId(), + clickedItem.getAmount() + 1, clickedItem.getNbt())); + } + break; + } + } + session.getDownstream().getSession().send(clickPacket); + session.getDownstream().getSession().send(new ClientConfirmTransactionPacket(inventory.getId(), actionId, true)); + } + + /*if (refresh) { + translator.updateInventory(session, inventory); + InventoryUtils.updateCursor(session); + }*/ + } + + private static class ClickAction { + final Click click; + final int slot; + ClickAction(Click click, int slot) { + this.click = click; + this.slot = slot; + } + } +} 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 new file mode 100644 index 000000000..370d41777 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/InventoryActionDataTranslator.java @@ -0,0 +1,330 @@ +/* + * 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.action; + +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.PlayerAction; +import com.github.steveice10.mc.protocol.data.game.window.*; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; +import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientWindowActionPacket; +import com.nukkitx.protocol.bedrock.data.ContainerId; +import com.nukkitx.protocol.bedrock.data.InventoryActionData; +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.utils.InventoryUtils; + +import java.util.*; + +public class InventoryActionDataTranslator { + public static void translate(InventoryTranslator translator, GeyserSession session, Inventory inventory, List actions) { + if (actions.size() != 2) + return; + + InventoryActionData worldAction = null; + InventoryActionData cursorAction = null; + InventoryActionData containerAction = null; + boolean refresh = false; + for (InventoryActionData action : actions) { + if (action.getSource().getContainerId() == ContainerId.CRAFTING_USE_INGREDIENT || action.getSource().getContainerId() == ContainerId.CRAFTING_RESULT) { + return; + } else if (action.getSource().getType() == InventorySource.Type.WORLD_INTERACTION) { + worldAction = action; + } else if (action.getSource().getContainerId() == ContainerId.CURSOR && action.getSlot() == 0) { + cursorAction = action; + ItemData translatedCursor = Translators.getItemTranslator().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))); + if (!translatedItem.equals(action.getFromItem())) { + refresh = true; + } + } + } + + final int craftSlot = session.getCraftSlot(); + session.setCraftSlot(0); + + if (worldAction != null) { + InventoryActionData sourceAction; + if (cursorAction != null) { + sourceAction = cursorAction; + } else { + sourceAction = containerAction; + } + + if (sourceAction != null) { + if (worldAction.getSource().getFlag() == InventorySource.Flag.DROP_ITEM) { + //quick dropping from hotbar? + if (session.getInventoryCache().getOpenInventory() == null && sourceAction.getSource().getContainerId() == ContainerId.INVENTORY) { + int heldSlot = session.getInventory().getHeldItemSlot(); + if (sourceAction.getSlot() == heldSlot) { + ClientPlayerActionPacket actionPacket = new ClientPlayerActionPacket( + sourceAction.getToItem().getCount() == 0 ? PlayerAction.DROP_ITEM_STACK : PlayerAction.DROP_ITEM, + new Position(0, 0, 0), BlockFace.DOWN); + session.getDownstream().getSession().send(actionPacket); + ItemStack item = session.getInventory().getItem(heldSlot); + if (item != null) { + session.getInventory().setItem(heldSlot, new ItemStack(item.getId(), item.getAmount() - 1, item.getNbt())); + } + return; + } + } + int dropAmount = sourceAction.getFromItem().getCount() - sourceAction.getToItem().getCount(); + if (sourceAction != cursorAction) { //dropping directly from inventory + int javaSlot = translator.bedrockSlotToJava(sourceAction); + if (dropAmount == sourceAction.getFromItem().getCount()) { + ClientWindowActionPacket dropPacket = new ClientWindowActionPacket(inventory.getId(), + inventory.getTransactionId().getAndIncrement(), + javaSlot, null, WindowAction.DROP_ITEM, + DropItemParam.DROP_SELECTED_STACK); + session.getDownstream().getSession().send(dropPacket); + } else { + for (int i = 0; i < dropAmount; i++) { + ClientWindowActionPacket dropPacket = new ClientWindowActionPacket(inventory.getId(), + inventory.getTransactionId().getAndIncrement(), + javaSlot, null, WindowAction.DROP_ITEM, + DropItemParam.DROP_FROM_SELECTED); + session.getDownstream().getSession().send(dropPacket); + } + } + ItemStack item = session.getInventory().getItem(javaSlot); + if (item != null) { + session.getInventory().setItem(javaSlot, new ItemStack(item.getId(), item.getAmount() - dropAmount, item.getNbt())); + } + return; + } else { //clicking outside of inventory + ClientWindowActionPacket dropPacket = new ClientWindowActionPacket(inventory.getId(), inventory.getTransactionId().getAndIncrement(), + -999, null, WindowAction.CLICK_ITEM, + dropAmount > 1 ? ClickItemParam.LEFT_CLICK : ClickItemParam.RIGHT_CLICK); + session.getDownstream().getSession().send(dropPacket); + ItemStack cursor = session.getInventory().getCursor(); + if (cursor != null) { + session.getInventory().setCursor(new ItemStack(cursor.getId(), dropAmount > 1 ? 0 : cursor.getAmount() - 1, cursor.getNbt())); + } + return; + } + } + } + } else if (cursorAction != null && containerAction != null) { + //left/right click + ClickPlan plan = new ClickPlan(); + int javaSlot = translator.bedrockSlotToJava(containerAction); + if (cursorAction.getFromItem().equals(containerAction.getToItem()) + && containerAction.getFromItem().equals(cursorAction.getToItem()) + && !InventoryUtils.canStack(cursorAction.getFromItem(), containerAction.getFromItem())) { //simple swap + plan.add(Click.LEFT, javaSlot); + } else if (cursorAction.getFromItem().getCount() > cursorAction.getToItem().getCount()) { //release + if (cursorAction.getToItem().getCount() == 0) { + plan.add(Click.LEFT, javaSlot); + } else { + int difference = cursorAction.getFromItem().getCount() - cursorAction.getToItem().getCount(); + for (int i = 0; i < difference; i++) { + plan.add(Click.RIGHT, javaSlot); + } + } + } else { //pickup + if (cursorAction.getFromItem().getCount() == 0) { + if (containerAction.getToItem().getCount() == 0) { //pickup all + plan.add(Click.LEFT, javaSlot); + } else { //pickup some + if (translator.getSlotType(javaSlot) == SlotType.FURNACE_OUTPUT + || containerAction.getToItem().getCount() == containerAction.getFromItem().getCount() / 2) { //right click + plan.add(Click.RIGHT, javaSlot); + } else { + plan.add(Click.LEFT, javaSlot); + int difference = containerAction.getFromItem().getCount() - cursorAction.getToItem().getCount(); + for (int i = 0; i < difference; i++) { + plan.add(Click.RIGHT, javaSlot); + } + } + } + } else { //pickup into non-empty cursor + if (translator.getSlotType(javaSlot) == SlotType.FURNACE_OUTPUT) { + if (containerAction.getToItem().getCount() == 0) { + plan.add(Click.LEFT, javaSlot); + } else { + ClientWindowActionPacket shiftClickPacket = new ClientWindowActionPacket(inventory.getId(), + inventory.getTransactionId().getAndIncrement(), + javaSlot, InventoryUtils.REFRESH_ITEM, WindowAction.SHIFT_CLICK_ITEM, + ShiftClickItemParam.LEFT_CLICK); + session.getDownstream().getSession().send(shiftClickPacket); + translator.updateInventory(session, inventory); + return; + } + } else if (translator.getSlotType(javaSlot) == SlotType.OUTPUT) { + plan.add(Click.LEFT, javaSlot); + } else { + int cursorSlot = findTempSlot(inventory, session.getInventory().getCursor(), Collections.singletonList(javaSlot)); + if (cursorSlot != -1) { + plan.add(Click.LEFT, cursorSlot); + } else { + translator.updateInventory(session, inventory); + return; + } + plan.add(Click.LEFT, javaSlot); + int difference = cursorAction.getToItem().getCount() - cursorAction.getFromItem().getCount(); + for (int i = 0; i < difference; i++) { + plan.add(Click.RIGHT, cursorSlot); + } + plan.add(Click.LEFT, javaSlot); + plan.add(Click.LEFT, cursorSlot); + } + } + } + plan.execute(session, translator, inventory, refresh); + return; + } else { + ClickPlan plan = new ClickPlan(); + InventoryActionData fromAction; + InventoryActionData toAction; + if (actions.get(0).getFromItem().getCount() >= actions.get(0).getToItem().getCount()) { + fromAction = actions.get(0); + toAction = actions.get(1); + } else { + fromAction = actions.get(1); + toAction = actions.get(0); + } + int fromSlot = translator.bedrockSlotToJava(fromAction); + int toSlot = translator.bedrockSlotToJava(toAction); + + if (translator.getSlotType(fromSlot) == SlotType.OUTPUT) { + if ((craftSlot != 0 && craftSlot != -2) && (inventory.getItem(toSlot) == null + || InventoryUtils.canStack(session.getInventory().getCursor(), inventory.getItem(toSlot)))) { + if (fromAction.getToItem().getCount() == 0) { + refresh = true; + plan.add(Click.LEFT, toSlot); + if (craftSlot != -1) { + plan.add(Click.LEFT, craftSlot); + } + } else { + int difference = toAction.getToItem().getCount() - toAction.getFromItem().getCount(); + for (int i = 0; i < difference; i++) { + plan.add(Click.RIGHT, toSlot); + } + session.setCraftSlot(craftSlot); + } + plan.execute(session, translator, inventory, refresh); + return; + } else { + session.setCraftSlot(-2); + } + } + + 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)); + if (cursorSlot != -1) { + plan.add(Click.LEFT, cursorSlot); + } else { + translator.updateInventory(session, inventory); + return; + } + } + if ((fromAction.getFromItem().equals(toAction.getToItem()) && !InventoryUtils.canStack(fromAction.getFromItem(), toAction.getFromItem())) + || fromAction.getToItem().getId() == 0) { //slot swap + plan.add(Click.LEFT, fromSlot); + plan.add(Click.LEFT, toSlot); + if (fromAction.getToItem().getId() != 0) { + plan.add(Click.LEFT, fromSlot); + } + } else if (InventoryUtils.canStack(fromAction.getFromItem(), toAction.getToItem())) { //partial item move + if (translator.getSlotType(fromSlot) == SlotType.FURNACE_OUTPUT) { + ClientWindowActionPacket shiftClickPacket = new ClientWindowActionPacket(inventory.getId(), + inventory.getTransactionId().getAndIncrement(), + fromSlot, InventoryUtils.REFRESH_ITEM, WindowAction.SHIFT_CLICK_ITEM, + ShiftClickItemParam.LEFT_CLICK); + session.getDownstream().getSession().send(shiftClickPacket); + translator.updateInventory(session, inventory); + return; + } else if (translator.getSlotType(fromSlot) == SlotType.OUTPUT) { + session.setCraftSlot(cursorSlot); + plan.add(Click.LEFT, fromSlot); + int difference = toAction.getToItem().getCount() - toAction.getFromItem().getCount(); + for (int i = 0; i < difference; i++) { + plan.add(Click.RIGHT, toSlot); + } + //client will send additional packets later to finish transferring crafting output + //translator will know how to handle this using the craftSlot variable + } else { + plan.add(Click.LEFT, fromSlot); + int difference = toAction.getToItem().getCount() - toAction.getFromItem().getCount(); + for (int i = 0; i < difference; i++) { + plan.add(Click.RIGHT, toSlot); + } + plan.add(Click.LEFT, fromSlot); + } + } + if (cursorSlot != -1) { + plan.add(Click.LEFT, cursorSlot); + } + plan.execute(session, translator, inventory, refresh); + return; + } + + translator.updateInventory(session, inventory); + InventoryUtils.updateCursor(session); + } + + private static int findTempSlot(Inventory inventory, ItemStack item, List slotBlacklist) { + /*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*/ + int offset = inventory.getId() == 0 ? 1 : 0; //offhand is not a viable slot (some servers disable it) + List itemBlacklist = new ArrayList<>(slotBlacklist.size() + 1); + itemBlacklist.add(item); + for (int slot : slotBlacklist) { + ItemStack blacklistItem = inventory.getItem(slot); + if (blacklistItem != null) + itemBlacklist.add(blacklistItem); + } + for (int i = inventory.getSize() - (36 + offset); i < inventory.getSize() - offset; i++) { + ItemStack testItem = inventory.getItem(i); + boolean acceptable = true; + if (testItem != null) { + for (ItemStack blacklistItem : itemBlacklist) { + if (InventoryUtils.canStack(testItem, blacklistItem)) { + acceptable = false; + break; + } + } + } + if (acceptable && !slotBlacklist.contains(i)) + return i; + } + //could not find a viable temp slot + return -1; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java new file mode 100644 index 000000000..a32354453 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java @@ -0,0 +1,92 @@ +/* + * 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.holder; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.protocol.bedrock.data.ContainerType; +import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; +import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; +import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; +import lombok.AllArgsConstructor; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.block.BlockTranslator; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +import org.geysermc.connector.utils.LocaleUtils; + +@AllArgsConstructor +public class BlockInventoryHolder extends InventoryHolder { + private final int blockId; + private final ContainerType containerType; + + @Override + public void prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { + Vector3i position = session.getPlayerEntity().getPosition().toInt(); + position = position.add(Vector3i.UP); + UpdateBlockPacket blockPacket = new UpdateBlockPacket(); + blockPacket.setDataLayer(0); + blockPacket.setBlockPosition(position); + blockPacket.setRuntimeId(blockId); + blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); + session.getUpstream().sendPacket(blockPacket); + inventory.setHolderPosition(position); + + CompoundTag tag = CompoundTag.builder() + .intTag("x", position.getX()) + .intTag("y", position.getY()) + .intTag("z", position.getZ()) + .stringTag("CustomName", LocaleUtils.getLocaleString(inventory.getTitle(), session.getClientData().getLanguageCode())).buildRootTag(); + BlockEntityDataPacket dataPacket = new BlockEntityDataPacket(); + dataPacket.setData(tag); + dataPacket.setBlockPosition(position); + session.getUpstream().sendPacket(dataPacket); + } + + @Override + public void openInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { + ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket(); + containerOpenPacket.setWindowId((byte) inventory.getId()); + containerOpenPacket.setType((byte) containerType.id()); + containerOpenPacket.setBlockPosition(inventory.getHolderPosition()); + containerOpenPacket.setUniqueEntityId(inventory.getHolderId()); + session.getUpstream().sendPacket(containerOpenPacket); + } + + @Override + public void closeInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { + Vector3i holderPos = inventory.getHolderPosition(); + Position pos = new Position(holderPos.getX(), holderPos.getY(), holderPos.getZ()); + BlockState realBlock = session.getChunkCache().getBlockAt(pos); + UpdateBlockPacket blockPacket = new UpdateBlockPacket(); + blockPacket.setDataLayer(0); + blockPacket.setBlockPosition(holderPos); + blockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(realBlock)); + session.getUpstream().sendPacket(blockPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/InventoryHolder.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/InventoryHolder.java new file mode 100644 index 000000000..5a9e736e9 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/InventoryHolder.java @@ -0,0 +1,36 @@ +/* + * 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.holder; + +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; + +public abstract class InventoryHolder { + public abstract void prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory); + public abstract void openInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory); + public abstract void closeInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory); +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/GenericInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java similarity index 64% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/GenericInventoryTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java index f7be13c39..e8e0fc455 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/GenericInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java @@ -23,38 +23,32 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.inventory; +package org.geysermc.connector.network.translators.inventory.updater; -import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.ItemData; -import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; 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; -public class GenericInventoryTranslator extends InventoryTranslator { +@AllArgsConstructor +public class ChestInventoryUpdater extends InventoryUpdater { + private final int paddedSize; @Override - public void prepareInventory(GeyserSession session, Inventory inventory) { - // TODO: Add code here - } + public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { + super.updateInventory(translator, session, inventory); - @Override - public void openInventory(GeyserSession session, Inventory inventory) { - ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket(); - containerOpenPacket.setWindowId((byte) inventory.getId()); - containerOpenPacket.setType((byte) 0); - containerOpenPacket.setBlockPosition(Vector3i.ZERO); - session.getUpstream().sendPacket(containerOpenPacket); - } - - @Override - public void updateInventory(GeyserSession session, Inventory inventory) { - ItemData[] bedrockItems = new ItemData[inventory.getItems().length]; + ItemData[] bedrockItems = new ItemData[paddedSize]; for (int i = 0; i < bedrockItems.length; i++) { - bedrockItems[i] = Translators.getItemTranslator().translateToBedrock(inventory.getItems()[i]); + if (i <= translator.size) { + bedrockItems[i] = Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(i)); + } else { + bedrockItems[i] = ItemData.AIR; + } } InventoryContentPacket contentPacket = new InventoryContentPacket(); @@ -64,11 +58,15 @@ public class GenericInventoryTranslator extends InventoryTranslator { } @Override - public void updateSlot(GeyserSession session, Inventory inventory, int slot) { + public boolean updateSlot(InventoryTranslator translator, GeyserSession session, Inventory inventory, int javaSlot) { + if (super.updateSlot(translator, session, inventory, javaSlot)) + return true; + InventorySlotPacket slotPacket = new InventorySlotPacket(); slotPacket.setContainerId(inventory.getId()); - slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(inventory.getItems()[slot])); - slotPacket.setSlot(slot); + slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); + slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(javaSlot))); session.getUpstream().sendPacket(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 new file mode 100644 index 000000000..d187ffd95 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ContainerInventoryUpdater.java @@ -0,0 +1,64 @@ +/* + * 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.updater; + +import com.nukkitx.protocol.bedrock.data.ItemData; +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; + +public class ContainerInventoryUpdater extends InventoryUpdater { + @Override + public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { + super.updateInventory(translator, session, inventory); + + 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)); + } + + InventoryContentPacket contentPacket = new InventoryContentPacket(); + contentPacket.setContainerId(inventory.getId()); + contentPacket.setContents(bedrockItems); + session.getUpstream().sendPacket(contentPacket); + } + + @Override + public boolean updateSlot(InventoryTranslator translator, GeyserSession session, Inventory inventory, int javaSlot) { + if (super.updateSlot(translator, session, inventory, javaSlot)) + return true; + + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(inventory.getId()); + slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); + slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(javaSlot))); + session.getUpstream().sendPacket(slotPacket); + 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 new file mode 100644 index 000000000..e3dc18648 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/CursorInventoryUpdater.java @@ -0,0 +1,64 @@ +/* + * 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.updater; + +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; + +public class CursorInventoryUpdater extends InventoryUpdater { + @Override + public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { + super.updateInventory(translator, session, inventory); + + for (int i = 0; i < translator.size; i++) { + final int bedrockSlot = translator.javaSlotToBedrock(i); + if (bedrockSlot == 50) + continue; + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.CURSOR); + slotPacket.setSlot(bedrockSlot); + slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(i))); + session.getUpstream().sendPacket(slotPacket); + } + } + + @Override + public boolean updateSlot(InventoryTranslator translator, GeyserSession session, Inventory inventory, int javaSlot) { + if (super.updateSlot(translator, session, inventory, javaSlot)) + return true; + + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.CURSOR); + slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); + slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(javaSlot))); + session.getUpstream().sendPacket(slotPacket); + 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 new file mode 100644 index 000000000..2f139e27a --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.updater; + +import com.nukkitx.protocol.bedrock.data.ContainerId; +import com.nukkitx.protocol.bedrock.data.ItemData; +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; + +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)); + } + InventoryContentPacket contentPacket = new InventoryContentPacket(); + contentPacket.setContainerId(ContainerId.INVENTORY); + contentPacket.setContents(bedrockItems); + session.getUpstream().sendPacket(contentPacket); + } + + public boolean updateSlot(InventoryTranslator translator, GeyserSession session, Inventory inventory, int javaSlot) { + if (javaSlot >= translator.size) { + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.INVENTORY); + slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); + slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(session, inventory.getItem(javaSlot))); + session.getUpstream().sendPacket(slotPacket); + return true; + } + return false; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/Enchantment.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/Enchantment.java new file mode 100644 index 000000000..b9348e25f --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/Enchantment.java @@ -0,0 +1,92 @@ +/* + * 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 lombok.Getter; + +import java.util.Locale; + +@Getter +public enum Enchantment { + PROTECTION, + FIRE_PROTECTION, + FEATHER_FALLING, + BLAST_PROTECTION, + PROJECTILE_PROTECTION, + THORNS, + RESPIRATION, + DEPTH_STRIDER, + AQUA_AFFINITY, + SHARPNESS, + SMITE, + BANE_OF_ARTHROPODS, + KNOCKBACK, + FIRE_ASPECT, + LOOTING, + EFFICIENCY, + SILK_TOUCH, + UNBREAKING, + FORTUNE, + POWER, + PUNCH, + FLAME, + INFINITY, + LUCK_OF_THE_SEA, + LURE, + FROST_WALKER, + MENDING, + BINDING_CURSE, + VANISHING_CURSE, + IMPALING, + RIPTIDE, + LOYALTY, + CHANNELING, + MULTISHOT, + PIERCING, + QUICK_CHARGE; + + private final String javaIdentifier; + + Enchantment() { + this.javaIdentifier = "minecraft:" + this.name().toLowerCase(Locale.ENGLISH); + } + + public static Enchantment getByJavaIdentifier(String javaIdentifier) { + for (Enchantment enchantment : Enchantment.values()) { + if (enchantment.javaIdentifier.equals(javaIdentifier)) { + return enchantment; + } + } + return null; + } + + public static Enchantment getByBedrockId(int bedrockId) { + if (bedrockId >= 0 && bedrockId < Enchantment.values().length) { + return Enchantment.values()[bedrockId]; + } + return null; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java index fd4f0b020..e579c20ee 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java @@ -34,11 +34,11 @@ public class ItemEntry { public static ItemEntry AIR = new ItemEntry("minecraft:air", 0, 0, 0); - private String javaIdentifier; - private int javaId; + private final String javaIdentifier; + private final int javaId; - private int bedrockId; - private int bedrockData; + private final int bedrockId; + private final int bedrockData; @Override public boolean equals(Object obj) { 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 68f88bb21..5e0361c06 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 @@ -26,64 +26,110 @@ 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.github.steveice10.opennbt.tag.builtin.ByteArrayTag; -import com.github.steveice10.opennbt.tag.builtin.ByteTag; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.github.steveice10.opennbt.tag.builtin.DoubleTag; -import com.github.steveice10.opennbt.tag.builtin.FloatTag; -import com.github.steveice10.opennbt.tag.builtin.IntArrayTag; -import com.github.steveice10.opennbt.tag.builtin.IntTag; -import com.github.steveice10.opennbt.tag.builtin.ListTag; -import com.github.steveice10.opennbt.tag.builtin.LongArrayTag; -import com.github.steveice10.opennbt.tag.builtin.LongTag; -import com.github.steveice10.opennbt.tag.builtin.ShortTag; -import com.github.steveice10.opennbt.tag.builtin.StringTag; -import com.github.steveice10.opennbt.tag.builtin.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.utils.MessageUtils; -import org.geysermc.connector.utils.Toolbox; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.*; +import org.geysermc.connector.utils.Toolbox; +import org.reflections.Reflections; + +import java.util.*; +import java.util.stream.Collectors; public class ItemTranslator { - public ItemStack translateToJava(ItemData data) { - ItemEntry javaItem = getItem(data); + private Int2ObjectMap itemTranslators = new Int2ObjectOpenHashMap(); + private List nbtItemTranslators; + private Map javaIdentifierMap = new HashMap<>(); + + // Shield ID, used in Entity.java + public static final int SHIELD = 829; - if (data.getTag() == null) { - return new ItemStack(javaItem.getJavaId(), data.getCount()); + public void init() { + Reflections ref = new Reflections("org.geysermc.connector.network.translators.item"); + + Map loadedNbtItemTranslators = new HashMap<>(); + for (Class clazz : ref.getTypesAnnotatedWith(ItemRemapper.class)) { + int priority = clazz.getAnnotation(ItemRemapper.class).priority(); + + GeyserConnector.getInstance().getLogger().debug("Found annotated item translator: " + clazz.getCanonicalName()); + + try { + if (NbtItemStackTranslator.class.isAssignableFrom(clazz)) { + NbtItemStackTranslator nbtItemTranslator = (NbtItemStackTranslator) clazz.newInstance(); + loadedNbtItemTranslators.put(nbtItemTranslator, priority); + continue; + } + ItemStackTranslator itemStackTranslator = (ItemStackTranslator) clazz.newInstance(); + List appliedItems = itemStackTranslator.getAppliedItems(); + for (ItemEntry item : appliedItems) { + ItemStackTranslator registered = itemTranslators.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); + } + } catch (InstantiationException | IllegalAccessException e) { + GeyserConnector.getInstance().getLogger().error("Could not instantiate annotated item translator " + clazz.getCanonicalName() + "."); + } } - return new ItemStack(javaItem.getJavaId(), data.getCount(), translateToJavaNBT(data.getTag())); + + nbtItemTranslators = loadedNbtItemTranslators.keySet().stream() + .sorted(Comparator.comparingInt(value -> loadedNbtItemTranslators.get(value))).collect(Collectors.toList()); } - public ItemData translateToBedrock(ItemStack stack) { - // Most likely dirt if null + public ItemStack translateToJava(GeyserSession session, ItemData data) { + if (data == null) { + return new ItemStack(0); + } + ItemEntry javaItem = getItem(data); + + ItemStack itemStack; + + ItemStackTranslator itemStackTranslator = itemTranslators.get(javaItem.getJavaId()); + if (itemStackTranslator != null) { + itemStack = itemStackTranslator.translateToJava(data, javaItem); + } else { + itemStack = DEFAULT_TRANSLATOR.translateToJava(data, javaItem); + } + + if (itemStack != null && itemStack.getNbt() != null) { + for (NbtItemStackTranslator translator : nbtItemTranslators) { + if (translator.acceptItem(javaItem)) { + translator.translateToJava(itemStack.getNbt(), javaItem); + } + } + } + return itemStack; + } + + public ItemData translateToBedrock(GeyserSession session, ItemStack stack) { if (stack == null) { - return ItemData.of(3, (short)0, 0); + return ItemData.AIR; } ItemEntry bedrockItem = getItem(stack); - if (stack.getNbt() == null) { - return ItemData.of(bedrockItem.getBedrockId(), (short) bedrockItem.getBedrockData(), stack.getAmount()); + + if (stack != null && stack.getNbt() != null) { + for (NbtItemStackTranslator translator : nbtItemTranslators) { + if (translator.acceptItem(bedrockItem)) { + translator.translateToBedrock(stack.getNbt(), bedrockItem); + } + } } - // TODO: Create proper transformers instead of shoving everything here - CompoundTag tag = stack.getNbt(); - IntTag mapId = tag.get("map"); - - if (mapId != null) { - tag.put(new StringTag("map_uuid", mapId.getValue().toString())); - tag.put(new IntTag("map_name_index", mapId.getValue())); + ItemStackTranslator itemStackTranslator = itemTranslators.get(bedrockItem.getJavaId()); + if (itemStackTranslator != null) { + return itemStackTranslator.translateToBedrock(stack, bedrockItem); + } else { + return DEFAULT_TRANSLATOR.translateToBedrock(stack, bedrockItem); } - - - return ItemData.of(bedrockItem.getBedrockId(), (short) bedrockItem.getBedrockData(), stack.getAmount(), translateToBedrockNBT(tag)); } public ItemEntry getItem(ItemStack stack) { @@ -92,7 +138,14 @@ public class ItemTranslator { public ItemEntry getItem(ItemData data) { for (ItemEntry itemEntry : Toolbox.ITEM_ENTRIES.values()) { - if (itemEntry.getBedrockId() == data.getId() && itemEntry.getBedrockData() == data.getDamage()) { + 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; } } @@ -101,185 +154,15 @@ public class ItemTranslator { return ItemEntry.AIR; } - private CompoundTag translateToJavaNBT(com.nukkitx.nbt.tag.CompoundTag tag) { - CompoundTag javaTag = new 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); - Tag translatedTag = translateToJavaNBT(bedrockTag); - if (translatedTag == null) - continue; - - javaValue.put(str, translatedTag); - } - } - - return javaTag; + 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 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()); + private static final ItemStackTranslator DEFAULT_TRANSLATOR = new ItemStackTranslator() { + @Override + public List getAppliedItems() { + return null; } - - 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; - Tag javaTag = translateToJavaNBT(tagValue); - if (javaTag != null) - tags.add(javaTag); - } - return new ListTag(listTag.getName(), tags); - } - - if (tag instanceof com.nukkitx.nbt.tag.CompoundTag) { - return translateToJavaNBT((com.nukkitx.nbt.tag.CompoundTag) tag); - } - - return null; - } - - private com.nukkitx.nbt.tag.CompoundTag translateToBedrockNBT(CompoundTag tag) { - Map> javaValue = new HashMap>(); - if (tag.getValue() != null && !tag.getValue().isEmpty()) { - for (String str : tag.getValue().keySet()) { - Tag javaTag = tag.get(str); - com.nukkitx.nbt.tag.Tag translatedTag = translateToBedrockNBT(javaTag); - if (translatedTag == null) - continue; - - javaValue.put(str, 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(Tag tag) { - if (tag instanceof ByteArrayTag) { - ByteArrayTag byteArrayTag = (ByteArrayTag) tag; - return new com.nukkitx.nbt.tag.ByteArrayTag(byteArrayTag.getName(), byteArrayTag.getValue()); - } - - if (tag instanceof ByteTag) { - ByteTag byteTag = (ByteTag) tag; - return new com.nukkitx.nbt.tag.ByteTag(byteTag.getName(), byteTag.getValue()); - } - - if (tag instanceof DoubleTag) { - DoubleTag doubleTag = (DoubleTag) tag; - return new com.nukkitx.nbt.tag.DoubleTag(doubleTag.getName(), doubleTag.getValue()); - } - - if (tag instanceof FloatTag) { - FloatTag floatTag = (FloatTag) tag; - return new com.nukkitx.nbt.tag.FloatTag(floatTag.getName(), floatTag.getValue()); - } - - if (tag instanceof IntArrayTag) { - IntArrayTag intArrayTag = (IntArrayTag) tag; - return new com.nukkitx.nbt.tag.IntArrayTag(intArrayTag.getName(), intArrayTag.getValue()); - } - - if (tag instanceof IntTag) { - IntTag intTag = (IntTag) tag; - return new com.nukkitx.nbt.tag.IntTag(intTag.getName(), intTag.getValue()); - } - - if (tag instanceof LongArrayTag) { - LongArrayTag longArrayTag = (LongArrayTag) tag; - return new com.nukkitx.nbt.tag.LongArrayTag(longArrayTag.getName(), longArrayTag.getValue()); - } - - if (tag instanceof LongTag) { - LongTag longTag = (LongTag) tag; - return new com.nukkitx.nbt.tag.LongTag(longTag.getName(), longTag.getValue()); - } - - if (tag instanceof ShortTag) { - ShortTag shortTag = (ShortTag) tag; - return new com.nukkitx.nbt.tag.ShortTag(shortTag.getName(), shortTag.getValue()); - } - - if (tag instanceof StringTag) { - StringTag stringTag = (StringTag) tag; - return new com.nukkitx.nbt.tag.StringTag(stringTag.getName(), MessageUtils.getBedrockMessage(Message.fromString(stringTag.getValue()))); - } - - if (tag instanceof ListTag) { - ListTag listTag = (ListTag) tag; - if (listTag.getName().equalsIgnoreCase("Lore")) { - List tags = new ArrayList<>(); - for (Object value : listTag.getValue()) { - if (!(value instanceof Tag)) - continue; - - com.nukkitx.nbt.tag.StringTag bedrockTag = (com.nukkitx.nbt.tag.StringTag) translateToBedrockNBT((Tag) value); - if (bedrockTag != null) - tags.add(bedrockTag); - } - return new com.nukkitx.nbt.tag.ListTag<>(listTag.getName(), com.nukkitx.nbt.tag.StringTag.class, tags); - } - } - - if (tag instanceof CompoundTag) { - return translateToBedrockNBT((CompoundTag) tag); - } - - return null; - } + }; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/Potion.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/Potion.java new file mode 100644 index 000000000..51ae36e49 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/Potion.java @@ -0,0 +1,102 @@ +/* + * 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 lombok.Getter; + +import java.util.Locale; + +@Getter +public enum Potion { + WATER(0), + MUNDANE(1), + THICK(3), + AWKWARD(4), + NIGHT_VISION(5), + LONG_NIGHT_VISION(6), + INVISIBILITY(7), + LONG_INVISIBILITY(8), + LEAPING(9), + STRONG_LEAPING(11), + LONG_LEAPING(10), + FIRE_RESISTANCE(12), + LONG_FIRE_RESISTANCE(13), + SWIFTNESS(14), + STRONG_SWIFTNESS(16), + LONG_SWIFTNESS(15), + SLOWNESS(17), + STRONG_SLOWNESS(18), //does not exist + LONG_SLOWNESS(18), + WATER_BREATHING(19), + LONG_WATER_BREATHING(20), + HEALING(21), + STRONG_HEALING(22), + HARMING(23), + STRONG_HARMING(24), + POISON(25), + STRONG_POISON(27), + LONG_POISON(26), + REGENERATION(28), + STRONG_REGENERATION(30), + LONG_REGENERATION(29), + STRENGTH(31), + STRONG_STRENGTH(33), + LONG_STRENGTH(32), + WEAKNESS(34), + LONG_WEAKNESS(35), + LUCK(2), //does not exist + TURTLE_MASTER(37), + STRONG_TURTLE_MASTER(39), + LONG_TURTLE_MASTER(38), + SLOW_FALLING(40), + LONG_SLOW_FALLING(41); + + private final String javaIdentifier; + private final short bedrockId; + + Potion(int bedrockId) { + this.javaIdentifier = "minecraft:" + this.name().toLowerCase(Locale.ENGLISH); + this.bedrockId = (short) bedrockId; + } + + public static Potion getByJavaIdentifier(String javaIdentifier) { + for (Potion potion : Potion.values()) { + if (potion.javaIdentifier.equals(javaIdentifier)) { + return potion; + } + } + return null; + } + + public static Potion getByBedrockId(short bedrockId) { + for (Potion potion : Potion.values()) { + if (potion.bedrockId == bedrockId) { + return potion; + } + } + return null; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java new file mode 100644 index 000000000..5d1ddd262 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java @@ -0,0 +1,15 @@ +package org.geysermc.connector.network.translators.item; + +import lombok.Getter; + +@Getter +public class ToolItemEntry extends ItemEntry { + private final String toolType; + private final String toolTier; + + public ToolItemEntry(String javaIdentifier, int javaId, int bedrockId, int bedrockData, String toolType, String toolTier) { + super(javaIdentifier, javaId, bedrockId, bedrockData); + this.toolType = toolType; + this.toolTier = toolTier; + } +} 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 new file mode 100644 index 000000000..e528b448e --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/PotionTranslator.java @@ -0,0 +1,80 @@ +/* + * 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.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.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 { + + private List appliedItems; + + public PotionTranslator() { + appliedItems = Toolbox.ITEM_ENTRIES.values().stream().filter(entry -> entry.getJavaIdentifier().endsWith("potion")).collect(Collectors.toList()); + } + + @Override + public ItemData translateToBedrock(ItemStack itemStack, ItemEntry itemEntry) { + if (itemStack.getNbt() == null) return super.translateToBedrock(itemStack, itemEntry); + Tag potionTag = itemStack.getNbt().get("Potion"); + if (potionTag instanceof StringTag) { + Potion potion = Potion.getByJavaIdentifier(((StringTag) potionTag).getValue()); + if (potion != null) { + return ItemData.of(itemEntry.getBedrockId(), potion.getBedrockId(), itemStack.getAmount(), translateNbtToBedrock(itemStack.getNbt())); + } + GeyserConnector.getInstance().getLogger().debug("Unknown java potion: " + potionTag.getValue()); + } + return super.translateToBedrock(itemStack, itemEntry); + } + + @Override + public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { + Potion potion = Potion.getByBedrockId(itemData.getDamage()); + ItemStack itemStack = super.translateToJava(itemData, itemEntry); + if (potion != null) { + StringTag potionTag = new StringTag("Potion", potion.getJavaIdentifier()); + itemStack.getNbt().put(potionTag); + } + return itemStack; + } + + @Override + public List getAppliedItems() { + return appliedItems; + } +} 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 new file mode 100644 index 000000000..f29f16fea --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.item.translators.nbt; + +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.NbtItemStackTranslator; +import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.utils.MessageUtils; + +import java.util.ArrayList; +import java.util.List; + +@ItemRemapper +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)); + } + } + + @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)); + } + } +} 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 new file mode 100644 index 000000000..2ca6e35cc --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantedBookTranslator.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.item.translators.nbt; + +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.NbtItemStackTranslator; +import org.geysermc.connector.network.translators.item.ItemEntry; + +@ItemRemapper(priority = 1) +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); + } + } + } + + @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); + } + } + } + + @Override + public boolean acceptItem(ItemEntry itemEntry) { + return "minecraft:enchanted_book".equals(itemEntry.getJavaIdentifier()); + } +} 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 new file mode 100644 index 000000000..41e1ae36a --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantmentTranslator.java @@ -0,0 +1,142 @@ +/* + * 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.GeyserConnector; +import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.NbtItemStackTranslator; +import org.geysermc.connector.network.translators.item.Enchantment; +import org.geysermc.connector.network.translators.item.ItemEntry; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@ItemRemapper +public class EnchantmentTranslator extends NbtItemStackTranslator { + + @Override + public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) { + List newTags = new ArrayList<>(); + if (itemTag.contains("Enchantments")) { + ListTag enchantmentTag = itemTag.get("Enchantments"); + for (Tag tag : enchantmentTag.getValue()) { + if (!(tag instanceof CompoundTag)) continue; + + CompoundTag bedrockTag = remapEnchantment((CompoundTag) tag); + newTags.add(bedrockTag); + } + itemTag.remove("Enchantments"); + } + if (itemTag.contains("StoredEnchantments")) { + ListTag enchantmentTag = itemTag.get("StoredEnchantments"); + for (Tag tag : enchantmentTag.getValue()) { + if (!(tag instanceof CompoundTag)) continue; + + CompoundTag bedrockTag = remapEnchantment((CompoundTag) tag); + if (bedrockTag != null) { + bedrockTag.put(new ShortTag("GeyserStoredEnchantment", (short) 0)); + newTags.add(bedrockTag); + } + } + itemTag.remove("StoredEnchantments"); + } + + if (!newTags.isEmpty()) { + itemTag.put(new ListTag("ench", newTags)); + } + } + + @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"); + } + } + + + private CompoundTag remapEnchantment(CompoundTag tag) { + Tag javaEnchLvl = tag.get("lvl"); + if (!(javaEnchLvl instanceof ShortTag)) + return null; + + Tag javaEnchId = tag.get("id"); + if (!(javaEnchId instanceof StringTag)) + return null; + + Enchantment enchantment = Enchantment.getByJavaIdentifier(((StringTag) javaEnchId).getValue()); + if (enchantment == null) { + GeyserConnector.getInstance().getLogger().debug("Unknown java enchantment: " + javaEnchId.getValue()); + return null; + } + + CompoundTag bedrockTag = new CompoundTag(""); + bedrockTag.put(new ShortTag("id", (short) enchantment.ordinal())); + bedrockTag.put(new ShortTag("lvl", ((ShortTag) javaEnchLvl).getValue())); + return bedrockTag; + } + +} 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 new file mode 100644 index 000000000..4b01d912d --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/LeatherArmorTranslator.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.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.ItemEntry; + +@ItemRemapper +public class LeatherArmorTranslator extends NbtItemStackTranslator { + + private static final String[] ITEMS = new String[]{"minecraft:leather_helmet", "minecraft:leather_chestplate", "minecraft:leather_leggings", "minecraft:leather_boots"}; + + @Override + public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) { + 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"); + } + } + } + } + + @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"); + } + } + + @Override + public boolean acceptItem(ItemEntry itemEntry) { + for (String item : ITEMS) { + if (itemEntry.getJavaIdentifier().equals(item)) return true; + } + return false; + } +} 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 new file mode 100644 index 000000000..cdf272ece --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.item.translators.nbt; + +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.ItemEntry; + +@ItemRemapper +public class MapItemTranslator extends NbtItemStackTranslator { + + @Override + public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) { + IntTag mapId = itemTag.get("map"); + + if (mapId != null) { + itemTag.put(new StringTag("map_uuid", mapId.getValue().toString())); + itemTag.put(new IntTag("map_name_index", mapId.getValue())); + itemTag.remove("map"); + } + } + + @Override + public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { + IntTag tag = itemTag.get("map_name_index"); + if (tag != null) { + itemTag.put(new IntTag("map", tag.getValue())); + itemTag.remove("map_name_index"); + itemTag.remove("map_uuid"); + } + } +} 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 582f0b5aa..2c32ef6fe 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 @@ -26,80 +26,38 @@ package org.geysermc.connector.network.translators.java; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.session.cache.BossBar; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.MessageUtils; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerBossBarPacket; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.EntityData; -import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; -import com.nukkitx.protocol.bedrock.packet.BossEventPacket; -import com.nukkitx.protocol.bedrock.packet.RemoveEntityPacket; + +import java.awt.*; @Translator(packet = ServerBossBarPacket.class) public class JavaBossBarTranslator extends PacketTranslator { @Override public void translate(ServerBossBarPacket packet, GeyserSession session) { - BossEventPacket bossEventPacket = new BossEventPacket(); - bossEventPacket.setBossUniqueEntityId(session.getEntityCache().getBossBar(packet.getUuid())); - + BossBar bossBar = session.getEntityCache().getBossBar(packet.getUuid()); switch (packet.getAction()) { case ADD: - long entityId = session.getEntityCache().addBossBar(packet.getUuid()); - addBossEntity(session, entityId); - - bossEventPacket.setAction(BossEventPacket.Action.SHOW); - bossEventPacket.setBossUniqueEntityId(entityId); - bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(packet.getTitle(), session.getClientData().getLanguageCode())); - bossEventPacket.setHealthPercentage(packet.getHealth()); - bossEventPacket.setColor(0); //ignored by client - bossEventPacket.setOverlay(1); - bossEventPacket.setDarkenSky(0); + long entityId = session.getEntityCache().getNextEntityId().incrementAndGet(); + bossBar = new BossBar(session, entityId, packet.getTitle(), packet.getHealth(), 0, 1, 0); + session.getEntityCache().addBossBar(packet.getUuid(), bossBar); break; case UPDATE_TITLE: - bossEventPacket.setAction(BossEventPacket.Action.TITLE); - bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(packet.getTitle(), session.getClientData().getLanguageCode())); + if (bossBar != null) bossBar.updateTitle(packet.getTitle()); break; case UPDATE_HEALTH: - bossEventPacket.setAction(BossEventPacket.Action.HEALTH_PERCENTAGE); - bossEventPacket.setHealthPercentage(packet.getHealth()); + if (bossBar != null) bossBar.updateHealth(packet.getHealth()); break; case REMOVE: - bossEventPacket.setAction(BossEventPacket.Action.HIDE); - removeBossEntity(session, session.getEntityCache().removeBossBar(packet.getUuid())); + session.getEntityCache().removeBossBar(packet.getUuid()); break; case UPDATE_STYLE: case UPDATE_FLAGS: //todo return; } - - session.getUpstream().sendPacket(bossEventPacket); - } - - /** - * Bedrock still needs an entity to display the BossBar.
- * Just like 1.8 but it doesn't care about which entity - */ - private void addBossEntity(GeyserSession session, long entityId) { - AddEntityPacket addEntityPacket = new AddEntityPacket(); - addEntityPacket.setUniqueEntityId(entityId); - addEntityPacket.setRuntimeEntityId(entityId); - addEntityPacket.setIdentifier("minecraft:creeper"); - addEntityPacket.setEntityType(33); - addEntityPacket.setPosition(session.getPlayerEntity().getPosition()); - addEntityPacket.setRotation(Vector3f.ZERO); - addEntityPacket.setMotion(Vector3f.ZERO); - addEntityPacket.getMetadata().put(EntityData.SCALE, 0.01F); // scale = 0 doesn't work? - - session.getUpstream().sendPacket(addEntityPacket); - } - - private void removeBossEntity(GeyserSession session, long entityId) { - RemoveEntityPacket removeEntityPacket = new RemoveEntityPacket(); - removeEntityPacket.setUniqueEntityId(entityId); - - session.getUpstream().sendPacket(removeEntityPacket); } } 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 new file mode 100644 index 000000000..c78493a36 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java @@ -0,0 +1,174 @@ +/* + * 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; + +import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; +import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; +import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; +import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerDeclareRecipesPacket; +import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.protocol.bedrock.data.CraftingData; +import com.nukkitx.protocol.bedrock.data.ItemData; +import com.nukkitx.protocol.bedrock.data.PotionMixData; +import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import lombok.AllArgsConstructor; +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 java.util.*; +import java.util.stream.Collectors; + +@Translator(packet = ServerDeclareRecipesPacket.class) +public class JavaDeclareRecipesTranslator extends PacketTranslator { + private static final Collection POTION_MIXES = + Arrays.stream(new int[]{372, 331, 348, 376, 289, 437, 353, 414, 382, 375, 462, 378, 396, 377, 370, 469, 470}) + .mapToObj(ingredient -> new PotionMixData(0, ingredient, 0)) + .collect(Collectors.toList()); + + @Override + public void translate(ServerDeclareRecipesPacket packet, GeyserSession session) { + CraftingDataPacket craftingDataPacket = new CraftingDataPacket(); + craftingDataPacket.setCleanRecipes(true); + for (Recipe recipe : packet.getRecipes()) { + switch (recipe.getType()) { + case CRAFTING_SHAPELESS: { + ShapelessRecipeData shapelessRecipeData = (ShapelessRecipeData) recipe.getData(); + ItemData output = Translators.getItemTranslator().translateToBedrock(session, shapelessRecipeData.getResult()); + output = ItemData.of(output.getId(), output.getDamage(), output.getCount()); //strip NBT + ItemData[][] inputCombinations = combinations(session, shapelessRecipeData.getIngredients()); + for (ItemData[] inputs : inputCombinations) { + UUID uuid = UUID.randomUUID(); + craftingDataPacket.getCraftingData().add(CraftingData.fromShapeless(uuid.toString(), + inputs, new ItemData[]{output}, uuid, "crafting_table", 0)); + } + break; + } + case CRAFTING_SHAPED: { + ShapedRecipeData shapedRecipeData = (ShapedRecipeData) recipe.getData(); + ItemData output = Translators.getItemTranslator().translateToBedrock(session, shapedRecipeData.getResult()); + output = ItemData.of(output.getId(), output.getDamage(), output.getCount()); //strip NBT + ItemData[][] inputCombinations = combinations(session, shapedRecipeData.getIngredients()); + for (ItemData[] inputs : inputCombinations) { + UUID uuid = UUID.randomUUID(); + craftingDataPacket.getCraftingData().add(CraftingData.fromShaped(uuid.toString(), + shapedRecipeData.getWidth(), shapedRecipeData.getHeight(), inputs, + new ItemData[]{output}, uuid, "crafting_table", 0)); + } + break; + } + } + } + craftingDataPacket.getPotionMixData().addAll(POTION_MIXES); + session.getUpstream().sendPacket(craftingDataPacket); + } + + //TODO: rewrite + private ItemData[][] combinations(GeyserSession session, Ingredient[] ingredients) { + Map, IntSet> squashedOptions = new HashMap<>(); + for (int i = 0; i < ingredients.length; i++) { + if (ingredients[i].getOptions().length == 0) { + squashedOptions.computeIfAbsent(Collections.singleton(ItemData.AIR), k -> new IntOpenHashSet()).add(i); + continue; + } + Ingredient ingredient = ingredients[i]; + Map> groupedByIds = Arrays.stream(ingredient.getOptions()) + .map(item -> Translators.getItemTranslator().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()) { + if (entry.getValue().size() > 1) { + GroupedItem groupedItem = entry.getKey(); + int idCount = 0; + //not optimal + for (ItemEntry itemEntry : Toolbox.ITEM_ENTRIES.values()) { + if (itemEntry.getBedrockId() == groupedItem.id) { + idCount++; + } + } + if (entry.getValue().size() < idCount) { + optionSet.addAll(entry.getValue()); + } else { + optionSet.add(ItemData.of(groupedItem.id, (short) -1, groupedItem.count, groupedItem.tag)); + } + } else { + ItemData item = entry.getValue().get(0); + optionSet.add(item); + } + } + squashedOptions.computeIfAbsent(optionSet, k -> new IntOpenHashSet()).add(i); + } + int totalCombinations = 1; + for (Set optionSet : squashedOptions.keySet()) { + totalCombinations *= optionSet.size(); + } + if (totalCombinations > 500) { + ItemData[] translatedItems = new ItemData[ingredients.length]; + for (int i = 0; i < ingredients.length; i++) { + if (ingredients[i].getOptions().length > 0) { + translatedItems[i] = Translators.getItemTranslator().translateToBedrock(session, ingredients[i].getOptions()[0]); + } else { + translatedItems[i] = ItemData.AIR; + } + } + return new ItemData[][]{translatedItems}; + } + List> sortedSets = new ArrayList<>(squashedOptions.keySet()); + sortedSets.sort(Comparator.comparing(Set::size, Comparator.reverseOrder())); + ItemData[][] combinations = new ItemData[totalCombinations][ingredients.length]; + int x = 1; + for (Set set : sortedSets) { + IntSet slotSet = squashedOptions.get(set); + int i = 0; + for (ItemData item : set) { + for (int j = 0; j < totalCombinations / set.size(); j++) { + final int comboIndex = (i * x) + (j % x) + ((j / x) * set.size() * x); + for (int slot : slotSet) { + combinations[comboIndex][slot] = item; + } + } + i++; + } + x *= set.size(); + } + return combinations; + } + + @EqualsAndHashCode + @AllArgsConstructor + private static class GroupedItem { + int id; + int count; + CompoundTag tag; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java index 60227aa4a..cd0161dbc 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java @@ -33,14 +33,56 @@ import org.geysermc.connector.network.translators.Translator; import com.github.steveice10.mc.protocol.packet.ingame.client.ClientPluginMessagePacket; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPluginMessagePacket; +import java.nio.charset.StandardCharsets; + @Translator(packet = ServerPluginMessagePacket.class) public class JavaPluginMessageTranslator extends PacketTranslator { + + private static byte[] brandData; + + static { + byte[] data = GeyserConnector.NAME.getBytes(StandardCharsets.UTF_8); + byte[] varInt = writeVarInt(data.length); + brandData = new byte[varInt.length + data.length]; + System.arraycopy(varInt, 0, brandData, 0, varInt.length); + System.arraycopy(data, 0, brandData, varInt.length, data.length); + } + + @Override public void translate(ServerPluginMessagePacket packet, GeyserSession session) { if (packet.getChannel().equals("minecraft:brand")) { session.getDownstream().getSession().send( - new ClientPluginMessagePacket(packet.getChannel(), GeyserConnector.NAME.getBytes()) + new ClientPluginMessagePacket(packet.getChannel(), brandData) ); } } + + private static byte[] writeVarInt(int value) { + byte[] data = new byte[getVarIntLength(value)]; + int index = 0; + do { + byte temp = (byte)(value & 0b01111111); + value >>>= 7; + if (value != 0) { + temp |= 0b10000000; + } + data[index] = temp; + index++; + } while (value != 0); + return data; + } + + private static int getVarIntLength(int number) { + if ((number & 0xFFFFFF80) == 0) { + return 1; + } else if ((number & 0xFFFFC000) == 0) { + return 2; + } else if ((number & 0xFFE00000) == 0) { + return 3; + } else if ((number & 0xF0000000) == 0) { + return 4; + } + return 5; + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java index 185aab540..cc2c540f6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java @@ -25,6 +25,9 @@ package org.geysermc.connector.network.translators.java; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.attribute.AttributeType; import org.geysermc.connector.network.session.GeyserSession; @@ -35,6 +38,8 @@ import org.geysermc.connector.utils.DimensionUtils; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket; import com.nukkitx.protocol.bedrock.packet.SetPlayerGameTypePacket; +import java.util.concurrent.ThreadLocalRandom; + @Translator(packet = ServerRespawnPacket.class) public class JavaRespawnTranslator extends PacketTranslator { @@ -48,11 +53,19 @@ public class JavaRespawnTranslator extends PacketTranslator // Max health must be divisible by two in bedrock entity.getAttributes().put(AttributeType.HEALTH, AttributeType.HEALTH.getAttribute(maxHealth, (maxHealth % 2 == 1 ? maxHealth + 1 : maxHealth))); + session.getInventoryCache().setOpenInventory(null); + SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket(); playerGameTypePacket.setGamemode(packet.getGamemode().ordinal()); session.getUpstream().sendPacket(playerGameTypePacket); session.setGameMode(packet.getGamemode()); + LevelEventPacket stopRainPacket = new LevelEventPacket(); + stopRainPacket.setType(LevelEventType.STOP_RAIN); + stopRainPacket.setData(ThreadLocalRandom.current().nextInt(50000) + 10000); + stopRainPacket.setPosition(Vector3f.ZERO); + session.getUpstream().sendPacket(stopRainPacket); + if (entity.getDimension() != DimensionUtils.javaToBedrock(packet.getDimension())) { DimensionUtils.switchDimension(session, packet.getDimension()); } else { 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/JavaServerDeclareCommandsTranslator.java new file mode 100644 index 000000000..d57b89487 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaServerDeclareCommandsTranslator.java @@ -0,0 +1,311 @@ +/* + * 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; + +import com.github.steveice10.mc.protocol.data.game.command.CommandNode; +import com.github.steveice10.mc.protocol.data.game.command.CommandParser; +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerDeclareCommandsPacket; +import com.nukkitx.protocol.bedrock.data.CommandData; +import com.nukkitx.protocol.bedrock.data.CommandEnumData; +import com.nukkitx.protocol.bedrock.data.CommandParamData; +import com.nukkitx.protocol.bedrock.packet.AvailableCommandsPacket; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import lombok.Getter; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +import java.util.*; + +@Translator(packet = ServerDeclareCommandsPacket.class) +public class JavaServerDeclareCommandsTranslator extends PacketTranslator { + @Override + public void translate(ServerDeclareCommandsPacket packet, GeyserSession session) { + List commandData = new ArrayList<>(); + Int2ObjectMap commands = new Int2ObjectOpenHashMap<>(); + Int2ObjectMap> commandArgs = new Int2ObjectOpenHashMap<>(); + + // Get the first node, it should be a root node + CommandNode rootNode = packet.getNodes()[packet.getFirstNodeIndex()]; + + // Loop through the root nodes to get all commands + for (int nodeIndex : rootNode.getChildIndices()) { + CommandNode node = packet.getNodes()[nodeIndex]; + + // Make sure we don't have duplicated commands (happens if there is more than 1 root node) + if (commands.containsKey(nodeIndex)) { continue; } + + // Get and update the commandArgs list with the found arguments + if (node.getChildIndices().length >= 1) { + for (int childIndex : node.getChildIndices()) { + commandArgs.putIfAbsent(nodeIndex, new ArrayList<>()); + commandArgs.get(nodeIndex).add(packet.getNodes()[childIndex]); + } + } + + // Insert the command name into the list + commands.put(nodeIndex, node.getName()); + } + + // The command flags, not sure what these do apart from break things + List flags = new ArrayList<>(); + + // Loop through all the found commands + for (int commandID : commands.keySet()) { + String commandName = commands.get(commandID); + + // Create a basic alias + CommandEnumData aliases = new CommandEnumData( commandName + "Aliases", new String[] { commandName.toLowerCase() }, false); + + // Get and parse all params + CommandParamData[][] params = getParams(packet.getNodes()[commandID], packet.getNodes()); + + // Build the completed command and add it to the final list + CommandData data = new CommandData(commandName, session.getConnector().getCommandManager().getDescription(commandName), flags, (byte) 0, aliases, params); + commandData.add(data); + } + + // Add our commands to the AvailableCommandsPacket for the bedrock client + AvailableCommandsPacket availableCommandsPacket = new AvailableCommandsPacket(); + for (CommandData data : commandData) { + availableCommandsPacket.getCommands().add(data); + } + + GeyserConnector.getInstance().getLogger().debug("Sending command packet of " + commandData.size() + " commands"); + + // Finally, send the commands to the client + session.getUpstream().sendPacket(availableCommandsPacket); + } + + /** + * Build the command parameter array for the given command + * + * @param commandNode The command to build the parameters for + * @param allNodes Every command node + * + * @return An array of parameter option arrays + */ + private CommandParamData[][] getParams(CommandNode commandNode, CommandNode[] allNodes) { + // Check if the command is an alias and redirect it + if (commandNode.getRedirectIndex() != -1) { + GeyserConnector.getInstance().getLogger().debug("Redirecting command " + commandNode.getName() + " to " + allNodes[commandNode.getRedirectIndex()].getName()); + commandNode = allNodes[commandNode.getRedirectIndex()]; + } + + if (commandNode.getChildIndices().length >= 1) { + // Create the root param node and build all the children + ParamInfo rootParam = new ParamInfo(commandNode, null); + rootParam.buildChildren(allNodes); + + List treeData = rootParam.getTree(); + CommandParamData[][] params = new CommandParamData[treeData.size()][]; + + // Fill the nested params array + int i = 0; + for (CommandParamData[] tree : treeData) { + params[i] = tree; + i++; + } + + return params; + } + + return new CommandParamData[0][0]; + } + + /** + * Convert Java edition command types to Bedrock edition + * + * @param parser Command type to convert + * + * @return Bedrock parameter data type + */ + private CommandParamData.Type mapCommandType(CommandParser parser) { + if (parser == null) { return CommandParamData.Type.STRING; } + + switch (parser) { + case FLOAT: + return CommandParamData.Type.FLOAT; + + case INTEGER: + return CommandParamData.Type.INT; + + case ENTITY: + case GAME_PROFILE: + return CommandParamData.Type.TARGET; + + case BLOCK_POS: + return CommandParamData.Type.BLOCK_POSITION; + + case COLUMN_POS: + case VEC3: + return CommandParamData.Type.POSITION; + + case MESSAGE: + return CommandParamData.Type.MESSAGE; + + case NBT: + case NBT_COMPOUND_TAG: + case NBT_TAG: + case NBT_PATH: + return CommandParamData.Type.JSON; + + case RESOURCE_LOCATION: + return CommandParamData.Type.FILE_PATH; + + case INT_RANGE: + return CommandParamData.Type.INT_RANGE; + + case BOOL: + case DOUBLE: + case STRING: + case VEC2: + case BLOCK_STATE: + case BLOCK_PREDICATE: + case ITEM_STACK: + case ITEM_PREDICATE: + case COLOR: + case COMPONENT: + case OBJECTIVE: + case OBJECTIVE_CRITERIA: + case OPERATION: // Possibly OPERATOR + case PARTICLE: + case ROTATION: + case SCOREBOARD_SLOT: + case SCORE_HOLDER: + case SWIZZLE: + case TEAM: + case ITEM_SLOT: + case MOB_EFFECT: + case FUNCTION: + case ENTITY_ANCHOR: + case RANGE: + case FLOAT_RANGE: + case ITEM_ENCHANTMENT: + case ENTITY_SUMMON: + case DIMENSION: + case TIME: + default: + return CommandParamData.Type.STRING; + } + } + + @Getter + private class ParamInfo { + private CommandNode paramNode; + private CommandParamData paramData; + private List children; + + /** + * Create a new parameter info object + * + * @param paramNode CommandNode the parameter is for + * @param paramData The existing parameters for the command + */ + public ParamInfo(CommandNode paramNode, CommandParamData paramData) { + this.paramNode = paramNode; + this.paramData = paramData; + this.children = new ArrayList<>(); + } + + /** + * Build the array of all the child parameters (recursive) + * + * @param allNodes Every command node + */ + public void buildChildren(CommandNode[] allNodes) { + int enumIndex = -1; + + for (int paramID : paramNode.getChildIndices()) { + CommandNode paramNode = allNodes[paramID]; + + if (paramNode.getParser() == null) { + if (enumIndex == -1) { + enumIndex = children.size(); + + // Create the new enum command + CommandEnumData enumData = new CommandEnumData(paramNode.getName(), new String[] { paramNode.getName() }, false); + children.add(new ParamInfo(paramNode, new CommandParamData(paramNode.getName(), false, enumData, mapCommandType(paramNode.getParser()), null, Collections.emptyList()))); + } else { + // Get the existing enum + ParamInfo enumParamInfo = children.get(enumIndex); + + // Extend the current list of enum values + String[] enumOptions = Arrays.copyOf(enumParamInfo.getParamData().getEnumData().getValues(), enumParamInfo.getParamData().getEnumData().getValues().length + 1); + enumOptions[enumOptions.length - 1] = paramNode.getName(); + + // Re-create the command using the updated values + CommandEnumData enumData = new CommandEnumData(enumParamInfo.getParamData().getEnumData().getName(), enumOptions, false); + children.set(enumIndex, new ParamInfo(enumParamInfo.getParamNode(), new CommandParamData(enumParamInfo.getParamData().getName(), false, enumData, enumParamInfo.getParamData().getType(), null, Collections.emptyList()))); + } + }else{ + // Put the non-enum param into the list + children.add(new ParamInfo(paramNode, new CommandParamData(paramNode.getName(), false, null, mapCommandType(paramNode.getParser()), null, Collections.emptyList()))); + } + } + + // Recursively build all child options + for (ParamInfo child : children) { + child.buildChildren(allNodes); + } + } + + /** + * Get the tree of every parameter node (recursive) + * + * @return List of parameter options arrays for the command + */ + public List getTree() { + List treeParamData = new ArrayList<>(); + + for (ParamInfo child : children) { + // Get the tree from the child + List childTree = child.getTree(); + + // Un-pack the tree append the child node to it and push into the list + for (CommandParamData[] subchild : childTree) { + CommandParamData[] tmpTree = new ArrayList() { + { + add(child.getParamData()); + addAll(Arrays.asList(subchild)); + } + }.toArray(new CommandParamData[0]); + + treeParamData.add(tmpTree); + } + + // If we have no more child parameters just the child + if (childTree.size() == 0) { + treeParamData.add(new CommandParamData[] { child.getParamData() }); + } + } + + return treeParamData; + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEffectTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEffectTranslator.java index be54e967f..88e0969e3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEffectTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEffectTranslator.java @@ -26,6 +26,7 @@ package org.geysermc.connector.network.translators.java.entity; import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -42,6 +43,7 @@ public class JavaEntityEffectTranslator extends PacketTranslator { @Override public void translate(ServerPlayerActionAckPacket packet, GeyserSession session) { + LevelEventPacket levelEvent = new LevelEventPacket(); switch (packet.getAction()) { case FINISH_DIGGING: ChunkUtils.updateBlock(session, packet.getNewState(), packet.getPosition()); break; + + case START_DIGGING: + levelEvent.setType(LevelEventType.BLOCK_START_BREAK); + levelEvent.setPosition(Vector3f.from( + packet.getPosition().getX(), + packet.getPosition().getY(), + packet.getPosition().getZ() + )); + double blockHardness = BlockTranslator.JAVA_RUNTIME_ID_TO_HARDNESS.get(packet.getNewState().getId()); + PlayerInventory inventory = session.getInventory(); + ItemStack item = inventory.getItemInHand(); + ItemEntry itemEntry = null; + CompoundTag nbtData = new CompoundTag(""); + if (item != null) { + itemEntry = Translators.getItemTranslator().getItem(item); + nbtData = item.getNbt(); + } + double breakTime = Math.ceil(BlockUtils.getBreakTime(blockHardness, packet.getNewState().getId(), itemEntry, nbtData, session.getPlayerEntity()) * 20); + levelEvent.setData((int) (65535 / breakTime)); + session.getUpstream().sendPacket(levelEvent); + break; + + case CANCEL_DIGGING: + levelEvent.setType(LevelEventType.BLOCK_STOP_BREAK); + levelEvent.setPosition(Vector3f.from( + packet.getPosition().getX(), + packet.getPosition().getY(), + packet.getPosition().getZ() + )); + levelEvent.setData(0); + session.getUpstream().sendPacket(levelEvent); + break; } } -} +} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerChangeHeldItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerChangeHeldItemTranslator.java new file mode 100644 index 000000000..c4c1a3af4 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerChangeHeldItemTranslator.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.java.entity.player; + +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerChangeHeldItemPacket; +import com.nukkitx.protocol.bedrock.packet.PlayerHotbarPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +@Translator(packet = ServerPlayerChangeHeldItemPacket.class) +public class JavaPlayerChangeHeldItemTranslator extends PacketTranslator { + + @Override + public void translate(ServerPlayerChangeHeldItemPacket packet, GeyserSession session) { + PlayerHotbarPacket hotbarPacket = new PlayerHotbarPacket(); + hotbarPacket.setContainerId(0); + hotbarPacket.setSelectedHotbarSlot(packet.getSlot()); + hotbarPacket.setSelectHotbarSlot(true); + session.getUpstream().sendPacket(hotbarPacket); + + session.getInventory().setHeldItemSlot(packet.getSlot()); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/inventory/OpenWindowPacketTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/inventory/OpenWindowPacketTranslator.java deleted file mode 100644 index 2719a72a0..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/inventory/OpenWindowPacketTranslator.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.geysermc.connector.network.translators.java.inventory; - -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 com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerOpenWindowPacket; - -@Translator(packet = ServerOpenWindowPacket.class) -public class OpenWindowPacketTranslator extends PacketTranslator { - @Override - public void translate(ServerOpenWindowPacket packet, GeyserSession session) { - System.out.println("debug: " + packet.getType()); - InventoryTranslator translator = Translators.getInventoryTranslator(); - - translator.openInventory(session, new Inventory(packet.getName(), packet.getWindowId(), packet.getType(), 54)); - - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java index c9d1ccfe2..d3bc6b4e7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java @@ -33,6 +33,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.scoreboard.Scoreboard; +import org.geysermc.connector.scoreboard.Team; import org.geysermc.connector.scoreboard.UpdateType; import org.geysermc.connector.utils.MessageUtils; @@ -45,9 +46,10 @@ public class JavaTeamTranslator extends PacketTranslator { @Override public void translate(ServerTeamPacket packet, GeyserSession session) { - GeyserConnector.getInstance().getLogger().debug("Team packet " + packet.getTeamName() + " " + packet.getAction()+" "+ Arrays.toString(packet.getPlayers())); + GeyserConnector.getInstance().getLogger().debug("Team packet " + packet.getTeamName() + " " + packet.getAction() + " " + Arrays.toString(packet.getPlayers())); Scoreboard scoreboard = session.getScoreboardCache().getScoreboard(); + Team team = scoreboard.getTeam(packet.getTeamName()); switch (packet.getAction()) { case CREATE: scoreboard.registerNewTeam(packet.getTeamName(), toPlayerSet(packet.getPlayers())) @@ -57,22 +59,33 @@ public class JavaTeamTranslator extends PacketTranslator { .setSuffix(MessageUtils.getBedrockMessage(packet.getSuffix())); break; case UPDATE: - scoreboard.getTeam(packet.getTeamName()) - .setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) - .setColor(packet.getColor()) - .setPrefix(MessageUtils.getBedrockMessage(packet.getPrefix())) - .setSuffix(MessageUtils.getBedrockMessage(packet.getSuffix())) - .setUpdateType(UpdateType.UPDATE); + if (team != null) { + team.setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) + .setColor(packet.getColor()) + .setPrefix(MessageUtils.getBedrockMessage(packet.getPrefix())) + .setSuffix(MessageUtils.getBedrockMessage(packet.getSuffix())) + .setUpdateType(UpdateType.UPDATE); + } else { + GeyserConnector.getInstance().getLogger().error("Error while translating Team Packet " + packet.getAction() + "! Scoreboard Team " + packet.getTeamName() + " is not registered."); + } break; case ADD_PLAYER: - scoreboard.getTeam(packet.getTeamName()).addEntities(packet.getPlayers()); + if(team != null){ + team.addEntities(packet.getPlayers()); + } else { + GeyserConnector.getInstance().getLogger().error("Error while translating Team Packet " + packet.getAction() + "! Scoreboard Team " + packet.getTeamName() + " is not registered."); + } break; case REMOVE_PLAYER: - scoreboard.getTeam(packet.getTeamName()).removeEntities(packet.getPlayers()); + if(team != null){ + team.removeEntities(packet.getPlayers()); + } else { + GeyserConnector.getInstance().getLogger().error("Error while translating Team Packet " + packet.getAction() + "! Scoreboard Team " + packet.getTeamName() + " is not registered."); + } break; case REMOVE: - scoreboard.removeTeam(packet.getTeamName()); - break; + scoreboard.removeTeam(packet.getTeamName()); + break; } scoreboard.onUpdate(); } 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 new file mode 100644 index 000000000..8162b82a4 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.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.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; +import org.geysermc.connector.utils.InventoryUtils; + +@Translator(packet = ServerCloseWindowPacket.class) +public class JavaCloseWindowTranslator extends PacketTranslator { + + @Override + public void translate(ServerCloseWindowPacket packet, GeyserSession session) { + ContainerClosePacket closePacket = new ContainerClosePacket(); + closePacket.setWindowId((byte)packet.getWindowId()); + session.getUpstream().sendPacket(closePacket); + InventoryUtils.closeInventory(session, packet.getWindowId()); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java new file mode 100644 index 000000000..9ecf45598 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.java.window; + +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientConfirmTransactionPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerConfirmTransactionPacket; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +import org.geysermc.connector.utils.InventoryUtils; + +@Translator(packet = ServerConfirmTransactionPacket.class) +public class JavaConfirmTransactionTranslator extends PacketTranslator { + + @Override + public void translate(ServerConfirmTransactionPacket packet, GeyserSession session) { + if (!packet.isAccepted()) { + ClientConfirmTransactionPacket confirmPacket = new ClientConfirmTransactionPacket(packet.getWindowId(), packet.getActionId(), true); + session.getDownstream().getSession().send(confirmPacket); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java index 9495bed48..38276193f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java @@ -25,18 +25,68 @@ package org.geysermc.connector.network.translators.java.window; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCloseWindowPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerOpenWindowPacket; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.nukkitx.protocol.bedrock.packet.ContainerClosePacket; +import org.geysermc.connector.GeyserConnector; +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.utils.InventoryUtils; -import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerOpenWindowPacket; +import java.util.concurrent.TimeUnit; @Translator(packet = ServerOpenWindowPacket.class) public class JavaOpenWindowTranslator extends PacketTranslator { @Override public void translate(ServerOpenWindowPacket packet, GeyserSession session) { - InventoryUtils.openInventory(session, packet); + if (packet.getWindowId() == 0) { + return; + } + InventoryTranslator newTranslator = Translators.getInventoryTranslators().get(packet.getType()); + Inventory openInventory = session.getInventoryCache().getOpenInventory(); + if (newTranslator == null) { + if (openInventory != null) { + ContainerClosePacket closePacket = new ContainerClosePacket(); + closePacket.setWindowId((byte)openInventory.getId()); + session.getUpstream().sendPacket(closePacket); + Translators.getInventoryTranslators().get(openInventory.getWindowType()).closeInventory(session, openInventory); + } + ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(packet.getWindowId()); + session.getDownstream().getSession().send(closeWindowPacket); + return; + } + + String name = packet.getName(); + try { + JsonParser parser = new JsonParser(); + JsonObject jsonObject = parser.parse(packet.getName()).getAsJsonObject(); + if (jsonObject.has("text")) { + name = jsonObject.get("text").getAsString(); + } else if (jsonObject.has("translate")) { + name = jsonObject.get("translate").getAsString(); + } + } catch (Exception e) { + GeyserConnector.getInstance().getLogger().debug("JavaOpenWindowTranslator: " + e.toString()); + } + + Inventory newInventory = new Inventory(name, packet.getWindowId(), packet.getType(), newTranslator.size + 36); + session.getInventoryCache().cacheInventory(newInventory); + if (openInventory != null) { + InventoryTranslator openTranslator = Translators.getInventoryTranslators().get(openInventory.getWindowType()); + if (!openTranslator.getClass().equals(newTranslator.getClass())) { + InventoryUtils.closeInventory(session, openInventory.getId()); + GeyserConnector.getInstance().getGeneralThreadPool().schedule(() -> InventoryUtils.openInventory(session, newInventory), 500, TimeUnit.MILLISECONDS); + return; + } + } + + InventoryUtils.openInventory(session, newInventory); } } 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 93df65e82..6fafa2a47 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 @@ -25,47 +25,41 @@ package org.geysermc.connector.network.translators.java.window; +import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerSetSlotPacket; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.session.cache.InventoryCache; 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; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerSetSlotPacket; +import java.util.Objects; @Translator(packet = ServerSetSlotPacket.class) public class JavaSetSlotTranslator extends PacketTranslator { @Override public void translate(ServerSetSlotPacket packet, GeyserSession session) { - InventoryCache inventoryCache = session.getInventoryCache(); - if (!inventoryCache.getInventories().containsKey(packet.getWindowId())) { - inventoryCache.cachePacket(packet.getWindowId(), packet); + if (packet.getWindowId() == 255 && packet.getSlot() == -1) { //cursor + if (Objects.equals(session.getInventory().getCursor(), packet.getItem())) + return; + if (session.getCraftSlot() != 0) + return; + + session.getInventory().setCursor(packet.getItem()); + InventoryUtils.updateCursor(session); return; } - Inventory inventory = inventoryCache.getInventories().get(packet.getWindowId()); - if (packet.getWindowId() != 0 && inventory.getWindowType() == null) + Inventory inventory = session.getInventoryCache().getInventories().get(packet.getWindowId()); + if (inventory == null || (packet.getWindowId() != 0 && inventory.getWindowType() == null)) return; - // Player inventory - if (packet.getWindowId() == 0) { - if (packet.getSlot() >= inventory.getItems().length) - return; // Most likely not a player inventory - - ItemStack[] items = inventory.getItems(); - items[packet.getSlot()] = packet.getItem(); - inventory.setItems(items); - - InventoryUtils.refreshPlayerInventory(session, inventory); - - if (inventory.isOpen()) { - InventoryUtils.updateSlot(session, packet); - } else { - inventoryCache.cachePacket(packet.getWindowId(), packet); - } + InventoryTranslator translator = Translators.getInventoryTranslators().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 f2287f6dc..eab57a644 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 @@ -25,34 +25,34 @@ package org.geysermc.connector.network.translators.java.window; +import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerWindowItemsPacket; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.session.cache.InventoryCache; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.InventoryUtils; +import org.geysermc.connector.network.translators.Translators; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; -import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerWindowItemsPacket; +import java.util.Arrays; @Translator(packet = ServerWindowItemsPacket.class) public class JavaWindowItemsTranslator extends PacketTranslator { @Override public void translate(ServerWindowItemsPacket packet, GeyserSession session) { - InventoryCache inventoryCache = session.getInventoryCache(); - if (!inventoryCache.getInventories().containsKey(packet.getWindowId())) { - inventoryCache.cachePacket(packet.getWindowId(), packet); + Inventory inventory = session.getInventoryCache().getInventories().get(packet.getWindowId()); + if (inventory == null || (packet.getWindowId() != 0 && inventory.getWindowType() == null)) return; - } - Inventory inventory = inventoryCache.getInventories().get(packet.getWindowId()); - // Player inventory - if (packet.getWindowId() == 0) { + if (packet.getItems().length < inventory.getSize()) { + inventory.setItems(Arrays.copyOf(packet.getItems(), inventory.getSize())); + } else { inventory.setItems(packet.getItems()); - InventoryUtils.refreshPlayerInventory(session, inventory); - return; } - InventoryUtils.updateInventory(session, packet); + InventoryTranslator translator = Translators.getInventoryTranslators().get(inventory.getWindowType()); + if (translator != null) { + translator.updateInventory(session, inventory); + } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowPropertyTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowPropertyTranslator.java new file mode 100644 index 000000000..827da7b7c --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowPropertyTranslator.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.java.window; + +import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerWindowPropertyPacket; +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; + +@Translator(packet = ServerWindowPropertyPacket.class) +public class JavaWindowPropertyTranslator extends PacketTranslator { + + @Override + public void translate(ServerWindowPropertyPacket packet, GeyserSession session) { + Inventory inventory = session.getInventoryCache().getInventories().get(packet.getWindowId()); + if (inventory == null || (packet.getWindowId() != 0 && inventory.getWindowType() == null)) + return; + + InventoryTranslator translator = Translators.getInventoryTranslators().get(inventory.getWindowType()); + if (translator != null) { + translator.updateProperty(session, inventory, packet.getRawProperty(), packet.getValue()); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockValueTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockValueTranslator.java new file mode 100644 index 000000000..f4a4d9efa --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockValueTranslator.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.java.world; + +import com.github.steveice10.mc.protocol.data.game.world.block.value.ChestValue; +import com.github.steveice10.mc.protocol.data.game.world.block.value.EndGatewayValue; +import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerBlockValuePacket; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.packet.BlockEventPacket; + +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +@Translator(packet = ServerBlockValuePacket.class) +public class JavaBlockValueTranslator extends PacketTranslator { + + @Override + public void translate(ServerBlockValuePacket packet, GeyserSession session) { + BlockEventPacket blockEventPacket = new BlockEventPacket(); + blockEventPacket.setBlockPosition(Vector3i.from(packet.getPosition().getX(), + packet.getPosition().getY(), packet.getPosition().getZ())); + if (packet.getValue() instanceof ChestValue) { + ChestValue value = (ChestValue) packet.getValue() ; + blockEventPacket.setEventType(1); + blockEventPacket.setEventData(value.getViewers() > 0 ? 1 : 0); + session.getUpstream().sendPacket(blockEventPacket); + } + if (packet.getValue() instanceof EndGatewayValue) { + blockEventPacket.setEventType(1); + session.getUpstream().sendPacket(blockEventPacket); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java index bb73c5f02..e72038c51 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java @@ -25,6 +25,19 @@ package org.geysermc.connector.network.translators.java.world; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerChunkDataPacket; +import com.nukkitx.nbt.NbtUtils; +import com.nukkitx.nbt.stream.NBTOutputStream; +import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.network.VarInts; +import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufOutputStream; +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.objects.Object2IntMap; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.BiomeTranslator; @@ -33,14 +46,7 @@ import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.utils.ChunkUtils; import org.geysermc.connector.world.chunk.ChunkSection; -import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; -import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; -import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerChunkDataPacket; -import com.nukkitx.network.VarInts; -import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; +import java.util.Map; @Translator(packet = ServerChunkDataPacket.class) public class JavaChunkDataTranslator extends PacketTranslator { @@ -54,7 +60,6 @@ public class JavaChunkDataTranslator extends PacketTranslator { try { ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(packet.getColumn()); @@ -78,6 +83,14 @@ public class JavaChunkDataTranslator extends PacketTranslator blockEntityEntry : chunkData.getLoadBlockEntitiesLater().object2IntEntrySet()) { + int x = blockEntityEntry.getKey().getInt("x"); + int y = blockEntityEntry.getKey().getInt("y"); + int z = blockEntityEntry.getKey().getInt("z"); + ChunkUtils.updateBlock(session, new BlockState(blockEntityEntry.getIntValue()), new Position(x, y, z)); + } + chunkData.getLoadBlockEntitiesLater().clear(); + } catch (Exception ex) { ex.printStackTrace(); } 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 28022c16d..78681f8fb 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 @@ -51,13 +51,12 @@ public class JavaMapDataTranslator extends PacketTranslator mapItemDataPacket.setWidth(data.getColumns()); mapItemDataPacket.setHeight(data.getRows()); - // Every int entry is an ARGB color + // Every int entry is an ABGR color int[] colors = new int[data.getData().length]; int idx = 0; for (byte colorId : data.getData()) { - colors[idx] = MapColor.fromId(colorId).toARGB(); - idx++; + colors[idx++] = MapColor.fromId(colorId & 0xFF).toABGR(); } mapItemDataPacket.setColors(colors); 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 6c7eeaf92..3c11d87b6 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 @@ -25,14 +25,6 @@ package org.geysermc.connector.network.translators.java.world; -import java.util.Set; -import java.util.concurrent.ThreadLocalRandom; - -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - import com.github.steveice10.mc.protocol.data.game.ClientRequest; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.world.notify.EnterCreditsValue; @@ -43,13 +35,16 @@ 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.packet.AdventureSettingsPacket; -import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; -import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; -import com.nukkitx.protocol.bedrock.packet.SetPlayerGameTypePacket; -import com.nukkitx.protocol.bedrock.packet.ShowCreditsPacket; - +import com.nukkitx.protocol.bedrock.packet.*; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.inventory.PlayerInventoryTranslator; + +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; @Translator(packet = ServerNotifyClientPacket.class) public class JavaNotifyClientTranslator extends PacketTranslator { @@ -110,6 +105,10 @@ public class JavaNotifyClientTranslator extends PacketTranslator { + + // This should be modified if sign text is not showing up + private static final int DELAY = 500; + + @Override + public void translate(ServerUpdateTileEntityPacket packet, GeyserSession session) { + String id = BlockEntityUtils.getBedrockBlockEntityId(packet.getType().name()); + BlockEntityTranslator translator = BlockEntityUtils.getBlockEntityTranslator(id); + // If not null then the BlockState is used in BlockEntityTranslator.translateTag() + if (ChunkUtils.CACHED_BLOCK_ENTITIES.get(packet.getPosition()) != null) { + BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(id, packet.getNbt(), + ChunkUtils.CACHED_BLOCK_ENTITIES.get(packet.getPosition())), packet.getPosition()); + ChunkUtils.CACHED_BLOCK_ENTITIES.remove(packet.getPosition()); + } else if (translator.getClass().getAnnotation(BlockEntity.class).delay()) { + // Delay so chunks can finish sending + session.getConnector().getGeneralThreadPool().schedule(() -> + BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(id, packet.getNbt(), null), packet.getPosition()), + DELAY, TimeUnit.MILLISECONDS); + } else { + BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(id, packet.getNbt(), null), packet.getPosition()); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java index ccb69856c..365338443 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java @@ -25,6 +25,10 @@ package org.geysermc.connector.network.translators.java.world; +import com.nukkitx.protocol.bedrock.data.GameRuleData; +import com.nukkitx.protocol.bedrock.packet.GameRulesChangedPacket; +import it.unimi.dsi.fastutil.longs.Long2LongMap; +import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -35,11 +39,37 @@ import com.nukkitx.protocol.bedrock.packet.SetTimePacket; @Translator(packet = ServerUpdateTimePacket.class) public class JavaUpdateTimeTranslator extends PacketTranslator { + // If negative, the last time is stored so we know it's not some plugin behavior doing weird things. + // Per-player for multi-world support + private static final Long2LongMap LAST_RECORDED_TIMES = new Long2LongOpenHashMap(); + @Override public void translate(ServerUpdateTimePacket packet, GeyserSession session) { - // https://minecraft.gamepedia.com/Day-night_cycle#24-hour_Minecraft_day - SetTimePacket setTimePacket = new SetTimePacket(); - setTimePacket.setTime((int) Math.abs(packet.getTime()) % 24000); - session.getUpstream().sendPacket(setTimePacket); + + // Bedrock sends a GameRulesChangedPacket if there is no daylight cycle + // Java just sends a negative long if there is no daylight cycle + long lastTime = LAST_RECORDED_TIMES.getOrDefault(session.getPlayerEntity().getEntityId(), 0); + long time = packet.getTime(); + + if (lastTime != time) { + // https://minecraft.gamepedia.com/Day-night_cycle#24-hour_Minecraft_day + SetTimePacket setTimePacket = new SetTimePacket(); + setTimePacket.setTime((int) Math.abs(time) % 24000); + session.getUpstream().sendPacket(setTimePacket); + // TODO: Performance efficient to always do this? + LAST_RECORDED_TIMES.put(session.getPlayerEntity().getEntityId(), time); + } + if (lastTime < 0 && time >= 0) { + setDoDaylightCycleGamerule(session, true); + } else if (lastTime != time && time < 0) { + setDoDaylightCycleGamerule(session, false); + } } + + private void setDoDaylightCycleGamerule(GeyserSession session, boolean doCycle) { + GameRulesChangedPacket gameRulesChangedPacket = new GameRulesChangedPacket(); + gameRulesChangedPacket.getGameRules().add(new GameRuleData<>("dodaylightcycle", doCycle)); + session.getUpstream().sendPacket(gameRulesChangedPacket); + } + } diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java index d9d78dfd1..c3e6c863c 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java @@ -54,6 +54,8 @@ public class Objective { /** * /!\ This method is made for temporary objectives until the real objective is received + * @param scoreboard the scoreboard + * @param objectiveName the name of the objective */ public Objective(Scoreboard scoreboard, String objectiveName) { this(scoreboard); diff --git a/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java b/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java new file mode 100644 index 000000000..0dcd13ad9 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java @@ -0,0 +1,47 @@ +package org.geysermc.connector.utils; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; + +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.Translators; +import org.geysermc.connector.network.translators.block.entity.BlockEntityTranslator; + +public class BlockEntityUtils { + + private static final BlockEntityTranslator EMPTY_TRANSLATOR = Translators.getBlockEntityTranslators().get("Empty"); + + public static String getBedrockBlockEntityId(String id) { + // This is the only exception when it comes to block entity ids + if (id.contains("piston_head")) + return "PistonArm"; + + id = id.toLowerCase() + .replace("minecraft:", "") + .replace("_", " "); + String[] words = id.split(" "); + for (int i = 0; i < words.length; i++) { + words[i] = words[i].substring(0, 1).toUpperCase() + words[i].substring(1).toLowerCase(); + } + + id = String.join(" ", words); + return id.replace(" ", ""); + } + + public static BlockEntityTranslator getBlockEntityTranslator(String name) { + BlockEntityTranslator blockEntityTranslator = Translators.getBlockEntityTranslators().get(name); + if (blockEntityTranslator == null) { + return EMPTY_TRANSLATOR; + } + + return blockEntityTranslator; + } + + public static void updateBlockEntity(GeyserSession session, com.nukkitx.nbt.tag.CompoundTag blockEntity, Position position) { + BlockEntityDataPacket blockEntityPacket = new BlockEntityDataPacket(); + blockEntityPacket.setBlockPosition(Vector3i.from(position.getX(), position.getY(), position.getZ())); + blockEntityPacket.setData(blockEntity); + session.getUpstream().sendPacket(blockEntityPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java new file mode 100644 index 000000000..34287073e --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java @@ -0,0 +1,128 @@ +/* + * 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.entity.Effect; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import org.geysermc.connector.entity.PlayerEntity; +import org.geysermc.connector.network.translators.block.BlockTranslator; +import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.network.translators.item.ToolItemEntry; + +public class BlockUtils { + + private static boolean correctTool(String blockToolType, String itemToolType) { + return (blockToolType.equals("sword") && itemToolType.equals("sword")) || + (blockToolType.equals("shovel") && itemToolType.equals("shovel")) || + (blockToolType.equals("pickaxe") && itemToolType.equals("pickaxe")) || + (blockToolType.equals("axe") && itemToolType.equals("axe")) || + (blockToolType.equals("shears") && itemToolType.equals("shears")); + } + + private static double toolBreakTimeBonus(String toolType, String toolTier, boolean isWoolBlock) { + if (toolType.equals("shears")) return isWoolBlock ? 5.0 : 15.0; + if (toolType.equals("")) return 1.0; + switch (toolTier) { + case "wooden": + return 2.0; + case "stone": + return 4.0; + case "iron": + return 6.0; + case "diamond": + return 8.0; + case "golden": + return 12.0; + default: + return 1.0; + } + } + + //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*/) { + double baseTime = ((correctTool || canHarvestWithHand) ? 1.5 : 5.0) * blockHardness; + double speed = 1.0 / baseTime; + + if (correctTool) { + speed *= toolBreakTimeBonus(toolType, toolTier, isWoolBlock); + speed += toolEfficiencyLevel == 0 ? 0 : toolEfficiencyLevel * toolEfficiencyLevel + 1; + } else if (toolType.equals("sword")) { + speed*= (isCobweb ? 15.0 : 1.5); + } + speed *= 1.0 + (0.2 * hasteLevel); + + switch (miningFatigueLevel) { + case 0: + break; + case 1: + speed -= (speed * 0.7); + break; + case 2: + speed -= (speed * 0.91); + break; + case 3: + speed -= (speed * 0.9973); + break; + default: + speed -= (speed * 0.99919); + break; + } + + //if (insideOfWaterWithoutAquaAffinity) speed *= 0.2; + //if (outOfWaterButNotOnGround) speed *= 0.2; + // else if insideWaterAndNotOnGround speed *= 0.2; + return 1.0 / speed; + } + + public static double getBreakTime(double blockHardness, int blockId, ItemEntry item, CompoundTag nbtData, PlayerEntity player) { + 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, ""); + boolean canHarvestWithHand = BlockTranslator.JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND.get(blockId); + String toolType = ""; + String toolTier = ""; + boolean correctTool = false; + if (item instanceof ToolItemEntry) { + ToolItemEntry toolItem = (ToolItemEntry) item; + toolType = toolItem.getToolType(); + toolTier = toolItem.getToolTier(); + correctTool = correctTool(blockToolType, toolType); + } + int toolEfficiencyLevel = ItemUtils.getEnchantmentLevel(nbtData, "minecraft:efficiency"); + int hasteLevel = player.getEffectCache().getEffectLevel(Effect.FASTER_DIG); + int miningFatigueLevel = player.getEffectCache().getEffectLevel(Effect.SLOWER_DIG); + + // 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); + } + +} 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 a35b2cc58..d496215ac 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -29,27 +29,47 @@ import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; import com.github.steveice10.mc.protocol.data.game.chunk.Column; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector2i; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; import com.nukkitx.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; + +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; + +import lombok.Getter; +import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.block.entity.*; import org.geysermc.connector.network.translators.Translators; import org.geysermc.connector.network.translators.block.BlockTranslator; +import org.geysermc.connector.world.chunk.ChunkPosition; import org.geysermc.connector.world.chunk.ChunkSection; +import java.util.HashMap; +import java.util.Map; + import static org.geysermc.connector.network.translators.block.BlockTranslator.BEDROCK_WATER_ID; public class ChunkUtils { + + /** + * Temporarily stores positions of BlockState values that are needed for certain block entities actively + */ + public static final Map CACHED_BLOCK_ENTITIES = new HashMap<>(); + public static ChunkData translateToBedrock(Column column) { ChunkData chunkData = new ChunkData(); - Chunk[] chunks = column.getChunks(); - int chunkSectionCount = chunks.length; - chunkData.sections = new ChunkSection[chunkSectionCount]; + chunkData.sections = new ChunkSection[chunks.length]; - for (int chunkY = 0; chunkY < chunkSectionCount; chunkY++) { + CompoundTag[] blockEntities = column.getTileEntities(); + // Temporarily stores positions of BlockState values per chunk load + Map blockEntityPositions = new HashMap<>(); + + for (int chunkY = 0; chunkY < chunks.length; chunkY++) { chunkData.sections[chunkY] = new ChunkSection(); Chunk chunk = chunks[chunkY]; @@ -57,14 +77,28 @@ public class ChunkUtils { continue; ChunkSection section = chunkData.sections[chunkY]; - for (int x = 0; x < 16; x++) { for (int y = 0; y < 16; y++) { for (int z = 0; z < 16; z++) { BlockState blockState = chunk.get(x, y, z); int id = BlockTranslator.getBedrockBlockId(blockState); - section.getBlockStorageArray()[0].setFullBlock(ChunkSection.blockPosition(x, y, z), id); + // Check to see if the name is in BlockTranslator.getBlockEntityString, and therefore must be handled differently + if (BlockTranslator.getBlockEntityString(blockState) != null) { + // Get the block entity translator + BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(BlockTranslator.getBlockEntityString(blockState)); + Position pos = new ChunkPosition(column.getX(), column.getZ()).getBlock(x, (chunkY << 4) + y, z); + blockEntityPositions.put(pos, blockState); + // If there is a delay required for the block, allow it. + if (blockEntityTranslator.getClass().getAnnotation(BlockEntity.class).delay()) { + chunkData.loadBlockEntitiesLater.put(blockEntityTranslator.getDefaultBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(BlockTranslator.getBlockEntityString(blockState)), + pos.getX(), pos.getY(), pos.getZ()), blockState.getId()); + } else { + section.getBlockStorageArray()[0].setFullBlock(ChunkSection.blockPosition(x, y, z), id); + } + } else { + section.getBlockStorageArray()[0].setFullBlock(ChunkSection.blockPosition(x, y, z), id); + } if (BlockTranslator.isWaterlogged(blockState)) { section.getBlockStorageArray()[1].setFullBlock(ChunkSection.blockPosition(x, y, z), BEDROCK_WATER_ID); @@ -72,7 +106,27 @@ public class ChunkUtils { } } } + } + + com.nukkitx.nbt.tag.CompoundTag[] bedrockBlockEntities = new com.nukkitx.nbt.tag.CompoundTag[blockEntities.length]; + for (int i = 0; i < blockEntities.length; i++) { + CompoundTag tag = blockEntities[i]; + String tagName; + if (!tag.contains("id")) { + GeyserConnector.getInstance().getLogger().debug("Got tag with no id: " + tag.getValue()); + tagName = "Empty"; + } else { + tagName = (String) tag.get("id").getValue(); + } + + String id = BlockEntityUtils.getBedrockBlockEntityId(tagName); + BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(id); + BlockState blockState = blockEntityPositions.get(new Position((int) tag.get("x").getValue(), (int) tag.get("y").getValue(), (int) tag.get("z").getValue())); + bedrockBlockEntities[i] = blockEntityTranslator.getBlockEntityTag(tagName, tag, blockState); + } + + chunkData.blockEntities = bedrockBlockEntities; return chunkData; } @@ -114,6 +168,19 @@ public class ChunkUtils { waterPacket.setRuntimeId(0); } session.getUpstream().sendPacket(waterPacket); + + // Since Java stores bed colors/skull information as part of the namespaced ID and Bedrock stores it as a tag + // This is the only place I could find that interacts with the Java block state and block updates + // Iterates through all block entity translators and determines if the block state needs to be saved + for (Map.Entry entry : Translators.getBlockEntityTranslators().entrySet()) { + if (entry.getValue() instanceof RequiresBlockState) { + RequiresBlockState requiresBlockState = (RequiresBlockState) entry.getValue(); + if (requiresBlockState.isBlock(blockState)) { + CACHED_BLOCK_ENTITIES.put(new Position(position.getX(), position.getY(), position.getZ()), blockState); + break; //No block will be a part of two classes + } + } + } } public static void sendEmptyChunks(GeyserSession session, Vector3i position, int radius, boolean forceUpdate) { @@ -144,6 +211,9 @@ public class ChunkUtils { public static final class ChunkData { public ChunkSection[] sections; - public byte[] blockEntities = new byte[0]; + @Getter + private com.nukkitx.nbt.tag.CompoundTag[] blockEntities = new com.nukkitx.nbt.tag.CompoundTag[0]; + @Getter + private Object2IntMap loadBlockEntitiesLater = new Object2IntOpenHashMap<>(); } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java index 199c5a5c6..9d874f13d 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java @@ -61,6 +61,12 @@ public class DimensionUtils { session.getUpstream().sendPacket(stopSoundPacket); } + /** + * Map the Java edition dimension IDs to Bedrock edition + * + * @param javaDimension Dimension ID to convert + * @return Converted Bedrock edition dimension ID + */ public static int javaToBedrock(int javaDimension) { switch (javaDimension) { case -1: 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 ec1ff4512..79ce9e8b9 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/EntityUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/EntityUtils.java @@ -32,6 +32,12 @@ import org.geysermc.connector.entity.type.EntityType; public class EntityUtils { + /** + * Convert Java edition effect IDs to Bedrock edition + * + * @param effect Effect to convert + * @return The numeric ID for the Bedrock edition effect + */ public static int toBedrockEffectId(Effect effect) { switch (effect) { case GLOWING: @@ -51,6 +57,13 @@ public class EntityUtils { return effect.ordinal() + 1; } } + + /** + * Converts a MobType to a Bedrock edition EntityType, returns null if the EntityType is not found + * + * @param type The MobType to convert + * @return Converted EntityType + */ public static EntityType toBedrockEntity(MobType type) { try { return EntityType.valueOf(type.name()); @@ -59,6 +72,12 @@ public class EntityUtils { } } + /** + * 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) { try { return EntityType.valueOf(type.name()); 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 cf48f0055..5144628d5 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java @@ -30,14 +30,24 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; - import org.geysermc.connector.GeyserConnector; -import java.io.*; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.util.function.Function; public class FileUtils { + /** + * Load the given YAML file into the given class + * + * @param src File to load + * @param valueType Class to load file into + * @return The data as the given class + * @throws IOException if the config could not be loaded + */ public static T loadConfig(File src, Class valueType) throws IOException { ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()); return objectMapper.readValue(src, valueType); @@ -53,11 +63,28 @@ public class FileUtils { return objectMapper.readValue(src, valueType); } - public static File fileOrCopiedFromResource(String name, Function s) throws IOException { - return fileOrCopiedFromResource(new File(name), name, s); + /** + * Open the specified file or copy if from resources + * + * @param name File and resource name + * @param fallback Formatting callback + * @return File handle of the specified file + * @throws IOException if the file failed to copy from resource + */ + public static File fileOrCopiedFromResource(String name, Function fallback) throws IOException { + return fileOrCopiedFromResource(new File(name), name, fallback); } - public static File fileOrCopiedFromResource(File file, String name, Function s) throws IOException { + /** + * Open the specified file or copy if from resources + * + * @param file File to open + * @param name Name of the resource get if needed + * @param format Formatting callback + * @return File handle of the specified file + * @throws IOException if the file failed to copy from resource + */ + public static File fileOrCopiedFromResource(File file, String name, Function format) throws IOException { if (!file.exists()) { file.createNewFile(); FileOutputStream fos = new FileOutputStream(file); @@ -67,7 +94,7 @@ public class FileUtils { input.read(bytes); - for(char c : s.apply(new String(bytes)).toCharArray()) { + for(char c : format.apply(new String(bytes)).toCharArray()) { fos.write(c); } @@ -79,7 +106,14 @@ public class FileUtils { return file; } - private static void writeFile(File file, char[] data) throws IOException { + /** + * Writes the given data to the specified file on disk + * + * @param file File to write to + * @param data Data to write to the file + * @throws IOException if the file failed to write + */ + public static void writeFile(File file, char[] data) throws IOException { if (!file.exists()) { file.createNewFile(); } @@ -94,6 +128,13 @@ public class FileUtils { fos.close(); } + /** + * Writes the given data to the specified file on disk + * + * @param name File path to write to + * @param data Data to write to the file + * @throws IOException if the file failed to write + */ public static void writeFile(String name, char[] data) throws IOException { writeFile(new File(name), data); } 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 045b81d16..3717b432a 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java @@ -26,113 +26,76 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCloseWindowPacket; -import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerOpenWindowPacket; -import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerSetSlotPacket; -import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerWindowItemsPacket; -import com.github.steveice10.packetlib.packet.Packet; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.protocol.bedrock.data.ContainerId; import com.nukkitx.protocol.bedrock.data.ItemData; -import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; +import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; 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 java.util.List; +import java.util.Objects; import java.util.concurrent.TimeUnit; public class InventoryUtils { + public static final ItemStack REFRESH_ITEM = new ItemStack(1, 127, new CompoundTag("")); //TODO: stop using this - public static void refreshPlayerInventory(GeyserSession session, Inventory inventory) { - InventoryContentPacket inventoryContentPacket = new InventoryContentPacket(); - inventoryContentPacket.setContainerId(ContainerId.INVENTORY); - - ItemData[] contents = new ItemData[40]; - // Inventory - for (int i = 9; i < 36; i++) { - contents[i] = Translators.getItemTranslator().translateToBedrock(inventory.getItems()[i]); + public static void openInventory(GeyserSession session, Inventory inventory) { + InventoryTranslator translator = Translators.getInventoryTranslators().get(inventory.getWindowType()); + if (translator != null) { + session.getInventoryCache().setOpenInventory(inventory); + translator.prepareInventory(session, inventory); + //TODO: find better way to handle double chest delay + if (translator instanceof DoubleChestInventoryTranslator) { + GeyserConnector.getInstance().getGeneralThreadPool().schedule(() -> { + translator.openInventory(session, inventory); + translator.updateInventory(session, inventory); + }, 200, TimeUnit.MILLISECONDS); + } else { + translator.openInventory(session, inventory); + translator.updateInventory(session, inventory); + } } - - // Hotbar - for (int i = 36; i < 45; i++) { - contents[i - 36] = Translators.getItemTranslator().translateToBedrock(inventory.getItems()[i]); - } - - // Armor - for (int i = 5; i < 9; i++) { - contents[i + 31] = Translators.getItemTranslator().translateToBedrock(inventory.getItems()[i]); - } - - inventoryContentPacket.setContents(contents); - session.getUpstream().sendPacket(inventoryContentPacket); } - public static void openInventory(GeyserSession session, ServerOpenWindowPacket packet) { - Inventory inventory = new Inventory(packet.getWindowId(), packet.getType(), 45); // TODO: Find a way to set this value - session.getInventoryCache().getInventories().put(packet.getWindowId(), inventory); - session.getInventoryCache().setOpenInventory(inventory); - - InventoryTranslator translator = Translators.getInventoryTranslator(); - translator.prepareInventory(session, inventory); - GeyserConnector.getInstance().getGeneralThreadPool().schedule(() -> { - List packets = session.getInventoryCache().getCachedPackets().get(inventory.getId()); - packets.forEach(itemPacket -> { - if (itemPacket != null) { - if (ServerWindowItemsPacket.class.isAssignableFrom(itemPacket.getClass())) { - updateInventory(session, (ServerWindowItemsPacket) itemPacket); - } - } - }); - }, 200, TimeUnit.MILLISECONDS); + 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()); + translator.closeInventory(session, inventory); + session.getInventoryCache().uncacheInventory(windowId); + session.getInventoryCache().setOpenInventory(null); + } + } else { + Inventory inventory = session.getInventory(); + InventoryTranslator translator = Translators.getInventoryTranslators().get(inventory.getWindowType()); + translator.updateInventory(session, inventory); + } + session.setCraftSlot(0); + session.getInventory().setCursor(null); } - public static void updateInventory(GeyserSession session, ServerWindowItemsPacket packet) { - if (packet.getWindowId() == 0) - return; - - if (session.getInventoryCache().getOpenInventory() == null || !session.getInventoryCache().getInventories().containsKey(packet.getWindowId())) - return; - - Inventory openInventory = session.getInventoryCache().getOpenInventory(); - if (packet.getWindowId() != openInventory.getId()) - return; - - InventoryTranslator translator = Translators.getInventoryTranslator(); - if (translator == null) { - session.getDownstream().getSession().send(new ClientCloseWindowPacket(packet.getWindowId())); - return; - } - - openInventory.setItems(packet.getItems()); - translator.updateInventory(session, openInventory); + public static void updateCursor(GeyserSession session) { + InventorySlotPacket cursorPacket = new InventorySlotPacket(); + cursorPacket.setContainerId(ContainerId.CURSOR); + cursorPacket.setSlot(0); + cursorPacket.setItem(Translators.getItemTranslator().translateToBedrock(session, session.getInventory().getCursor())); + session.getUpstream().sendPacket(cursorPacket); } - public static void updateSlot(GeyserSession session, ServerSetSlotPacket packet) { - if (packet.getWindowId() == 0) - return; + public static boolean canStack(ItemStack item1, ItemStack item2) { + if (item1 == null || item2 == null) + return false; + return item1.getId() == item2.getId() && Objects.equals(item1.getNbt(), item2.getNbt()); + } - if (session.getInventoryCache().getOpenInventory() == null || !session.getInventoryCache().getInventories().containsKey(packet.getWindowId())) - return; - - Inventory openInventory = session.getInventoryCache().getOpenInventory(); - if (packet.getWindowId() != openInventory.getId()) - return; - - InventoryTranslator translator = Translators.getInventoryTranslator(); - if (translator == null) { - session.getDownstream().getSession().send(new ClientCloseWindowPacket(packet.getWindowId())); - return; - } - - if (packet.getSlot() >= openInventory.getSize()) { - session.getDownstream().getSession().send(new ClientCloseWindowPacket(packet.getWindowId())); - return; - } - - ItemStack[] items = openInventory.getItems(); - items[packet.getSlot()] = packet.getItem(); - translator.updateSlot(session, openInventory, packet.getSlot()); + public static boolean canStack(ItemData item1, ItemData item2) { + if (item1 == null || item2 == null) + return false; + return item1.equals(item2, false, true, true); } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/ItemUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ItemUtils.java new file mode 100644 index 000000000..bb3cf0ed0 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/ItemUtils.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.utils; + +import com.github.steveice10.opennbt.tag.builtin.*; + +public class ItemUtils { + + public static int getEnchantmentLevel(CompoundTag itemNBTData, String enchantmentId) { + ListTag enchantments = (itemNBTData == null ? null : itemNBTData.get("Enchantments")); + if (enchantments != null) { + int enchantmentLevel = 0; + for (Tag tag : enchantments) { + CompoundTag enchantment = (CompoundTag) tag; + StringTag enchantId = enchantment.get("id"); + if (enchantId.getValue().equals(enchantmentId)) { + enchantmentLevel = (int) ((ShortTag) enchantment.get("lvl")).getValue(); + } + } + return enchantmentLevel; + } + return 0; + } +} 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 e8555eb02..726ad1c19 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -57,9 +57,15 @@ public class LocaleUtils { downloadAndLoadLocale(DEFAULT_LOCALE); } + /** + * Fetch the latest versions asset cache from Mojang so we can grab the locale files later + */ 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); + + // Get the url for the latest version of the games manifest String latestInfoURL = ""; for (Version version : versionManifest.getVersions()) { if (version.getId().equals(versionManifest.getLatestVersion().getRelease())) { @@ -68,12 +74,15 @@ public class LocaleUtils { } } + // Make sure we definitely got a version if (latestInfoURL.isEmpty()) { throw new Exception("Unable to get latest Minecraft version"); } + // Get the individual version manifest VersionInfo versionInfo = Toolbox.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; for (VersionDownload download : versionInfo.getDownloads().values()) { if (download.getUrl().endsWith(".jar") && download.getSize() < currentSize) { @@ -82,8 +91,10 @@ public class LocaleUtils { } } + // Get the assets list JsonNode assets = Toolbox.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(); @@ -95,8 +106,15 @@ public class LocaleUtils { } } + /** + * Downloads a locale from Mojang if its not already loaded + * + * @param locale Locale to download and load + */ public static void downloadAndLoadLocale(String locale) { locale = locale.toLowerCase(); + + // Check the locale isn't already loaded if (!ASSET_MAP.containsKey("minecraft/lang/" + locale + ".json") && !locale.equals("en_us")) { GeyserConnector.getInstance().getLogger().warning("Invalid locale requested to download and load: " + locale); return; @@ -108,9 +126,15 @@ public class LocaleUtils { loadLocale(locale); } + /** + * Downloads the specified locale if its not already downloaded + * + * @param locale Locale to download + */ private static void downloadLocale(String locale) { File localeFile = new File("locales/" + locale + ".json"); + // Check if we have already downloaded the locale file if (localeFile.exists()) { GeyserConnector.getInstance().getLogger().debug("Locale already downloaded: " + locale); return; @@ -128,6 +152,11 @@ public class LocaleUtils { WebUtils.downloadFile("http://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, "locales/" + locale + ".json"); } + /** + * Loads a locale already downloaded, if the file doesn't exist it just logs a warning + * + * @param locale Locale to load + */ private static void loadLocale(String locale) { File localeFile = new File("locales/" + locale + ".json"); @@ -146,7 +175,7 @@ public class LocaleUtils { try { localeObj = Toolbox.JSON_MAPPER.readTree(localeStream); } catch (Exception e) { - throw new AssertionError("Unable to load Java lang map for " + locale, e); + throw new AssertionError("Unable to load Java edition lang map for " + locale, e); } // Parse all the locale fields @@ -164,6 +193,11 @@ public class LocaleUtils { } } + /** + * Download then en_us locale by downloading the server jar and extracting it from there. + * + * @param localeFile File to save the locale to + */ private static void downloadEN_US(File localeFile) { try { // Let the user know we are downloading the JAR @@ -199,6 +233,13 @@ public class LocaleUtils { } } + /** + * Translate the given language string into the given locale, or falls back to the default locale + * + * @param messageText Language string to translate + * @param locale Locale to translate to + * @return Translated string or the original message if it was not found in the given locale + */ public static String getLocaleString(String messageText, String locale) { Map localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(locale.toLowerCase()); if (localeStrings == null) 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 7f6c8eda1..300294a2f 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -40,9 +40,12 @@ import net.minidev.json.JSONObject; import org.geysermc.common.window.CustomFormBuilder; import org.geysermc.common.window.CustomFormWindow; import org.geysermc.common.window.FormWindow; +import org.geysermc.common.window.SimpleFormWindow; +import org.geysermc.common.window.button.FormButton; import org.geysermc.common.window.component.InputComponent; import org.geysermc.common.window.component.LabelComponent; import org.geysermc.common.window.response.CustomFormResponse; +import org.geysermc.common.window.response.SimpleFormResponse; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.auth.AuthData; @@ -152,43 +155,60 @@ public class LoginEncryptionUtils { session.getUpstream().sendPacketImmediately(packet); } - private static int AUTH_FORM_ID = 1337; + private static int AUTH_FORM_ID = 1336; + private static int AUTH_DETAILS_FORM_ID = 1337; public static void showLoginWindow(GeyserSession session) { - CustomFormWindow window = new CustomFormBuilder("Login") - .addComponent(new LabelComponent("Minecraft: Java Edition account authentication.")) + SimpleFormWindow window = new SimpleFormWindow("Login", "You need a Java Edition account to play on this server."); + window.getButtons().add(new FormButton("Login with Minecraft")); + window.getButtons().add(new FormButton("Disconnect")); + + session.sendForm(window, AUTH_FORM_ID); + } + + public static void showLoginDetailsWindow(GeyserSession session) { + CustomFormWindow window = new CustomFormBuilder("Login Details") .addComponent(new LabelComponent("Enter the credentials for your Minecraft: Java Edition account below.")) .addComponent(new InputComponent("Email/Username", "account@geysermc.org", "")) .addComponent(new InputComponent("Password", "123456", "")) .build(); - session.sendForm(window, AUTH_FORM_ID); + session.sendForm(window, AUTH_DETAILS_FORM_ID); } - public static boolean authenticateFromForm(GeyserSession session, GeyserConnector connector, String formData) { + public static boolean authenticateFromForm(GeyserSession session, GeyserConnector connector, int formId, String formData) { WindowCache windowCache = session.getWindowCache(); - if (!windowCache.getWindows().containsKey(AUTH_FORM_ID)) + if (!windowCache.getWindows().containsKey(formId)) return false; - FormWindow window = windowCache.getWindows().remove(AUTH_FORM_ID); - window.setResponse(formData.trim()); + if(formId == AUTH_FORM_ID || formId == AUTH_DETAILS_FORM_ID) { + FormWindow window = windowCache.getWindows().remove(formId); + window.setResponse(formData.trim()); - if (!session.isLoggedIn()) { - if (window instanceof CustomFormWindow) { - CustomFormWindow customFormWindow = (CustomFormWindow) window; - if (!customFormWindow.getTitle().equals("Login")) - return false; + if (!session.isLoggedIn()) { + if (formId == AUTH_DETAILS_FORM_ID && window instanceof CustomFormWindow) { + CustomFormWindow customFormWindow = (CustomFormWindow) window; - CustomFormResponse response = (CustomFormResponse) customFormWindow.getResponse(); - if (response != null) { - String email = response.getInputResponses().get(2); - String password = response.getInputResponses().get(3); + CustomFormResponse response = (CustomFormResponse) customFormWindow.getResponse(); + if (response != null) { + String email = response.getInputResponses().get(1); + String password = response.getInputResponses().get(2); - session.authenticate(email, password); + session.authenticate(email, password); + } + + // 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) { + showLoginDetailsWindow(session); + } else if(response.getClickedButtonId() == 1) { + session.disconnect("Login is required"); + } + } } - - // Clear windows so authentication data isn't accidentally cached - windowCache.getWindows().clear(); } } return true; 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 2c4a13b9f..b011edc71 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MapColor.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MapColor.java @@ -1,7 +1,5 @@ package org.geysermc.connector.utils; -import java.util.Arrays; - public enum MapColor { COLOR_0(-1, -1, -1), COLOR_1(-1, -1, -1), @@ -212,6 +210,8 @@ public enum MapColor { COLOR_206(37, 22, 16), COLOR_207(19, 11, 8); + private static final MapColor[] VALUES = values(); + private final int red; private final int green; private final int blue; @@ -222,23 +222,18 @@ public enum MapColor { this.blue = blue; } - int getId() { - return ordinal(); - } - public static MapColor fromId(int id) { - return Arrays.stream(values()).filter(color -> color.getId() == id).findFirst().get(); + return id >= 0 && id < VALUES.length ? VALUES[id] : COLOR_0; } - public int toARGB() { + public int toABGR() { int alpha = 255; if (red == -1 && green == -1 && blue == -1) alpha = 0; // transparent - long result = red & 0xff; - result |= (green & 0xff) << 8; - result |= (blue & 0xff) << 16; - result |= (alpha & 0xff) << 24; - return (int) (result & 0xFFFFFFFFL); + return ((alpha & 0xFF) << 24) | + ((blue & 0xFF) << 16) | + ((green & 0xFF) << 8) | + ((red & 0xFF) << 0); } } \ No newline at end of file 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 272dadd84..27a3fdd3f 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java @@ -27,6 +27,12 @@ package org.geysermc.connector.utils; public class MathUtils { + /** + * Round the given float to the next whole number + * + * @param floatNumber Float to round + * @return Rounded number + */ public static int ceil(float floatNumber) { int truncated = (int) floatNumber; return floatNumber > truncated ? truncated + 1 : truncated; 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 a28d6a7a1..ac111c71e 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java @@ -32,6 +32,10 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; +import net.kyori.text.Component; +import net.kyori.text.serializer.gson.GsonComponentSerializer; +import net.kyori.text.serializer.legacy.LegacyComponentSerializer; +import org.geysermc.connector.network.session.GeyserSession; import java.util.*; import java.util.regex.Matcher; @@ -58,10 +62,10 @@ public class MessageUtils { strings.add(" - no permission or invalid command!"); } - List furtherParams = getTranslationParams(translation.getTranslationParams()); + List furtherParams = getTranslationParams(translation.getTranslationParams(), locale); if (locale != null) { strings.add(insertParams(LocaleUtils.getLocaleString(translation.getTranslationKey(), locale), furtherParams)); - }else{ + } else { strings.addAll(furtherParams); } } else { @@ -96,7 +100,11 @@ public class MessageUtils { messageText = LocaleUtils.getLocaleString(messageText, locale); } - StringBuilder builder = new StringBuilder(messageText); + StringBuilder builder = new StringBuilder(); + builder.append(getFormat(message.getStyle().getFormats())); + builder.append(getColorOrParent(message.getStyle())); + builder.append(messageText); + for (Message msg : message.getExtra()) { builder.append(getFormat(msg.getStyle().getFormats())); builder.append(getColorOrParent(msg.getStyle())); @@ -113,9 +121,32 @@ public class MessageUtils { } public static String getBedrockMessage(Message message) { - return getTranslatedBedrockMessage(message, null, false); + Component component; + if (isMessage(message.getText())) { + component = GsonComponentSerializer.INSTANCE.deserialize(message.getText()); + } else { + component = GsonComponentSerializer.INSTANCE.deserialize(message.toJsonString()); + } + return LegacyComponentSerializer.legacy().serialize(component); } + public static String getBedrockMessage(String message) { + Component component = GsonComponentSerializer.INSTANCE.deserialize(message); + return LegacyComponentSerializer.legacy().serialize(component); + } + + public static String getJavaMessage(String message) { + Component component = LegacyComponentSerializer.legacy().deserialize(message); + return GsonComponentSerializer.INSTANCE.serialize(component); + } + + /** + * Inserts the given parameters into the given message both in sequence and as requested + * + * @param message Message containing possible parameter replacement strings + * @param params A list of parameter strings + * @return Parsed message with all params inserted as needed + */ public static String insertParams(String message, List params) { String newMessage = message; @@ -123,9 +154,9 @@ public class MessageUtils { Matcher m = p.matcher(message); while (m.find()) { try { - newMessage = newMessage.replaceFirst("%" + m.group(1) + "\\$s" , params.get(Integer.parseInt(m.group(1)) - 1)); + newMessage = newMessage.replaceFirst("%" + m.group(1) + "\\$s", params.get(Integer.parseInt(m.group(1)) - 1)); } catch (Exception e) { - // Couldnt find the param to replace + // Couldn't find the param to replace } } @@ -136,16 +167,28 @@ public class MessageUtils { return newMessage; } + /** + * Gets the colour for the message style or fetches it from the parent (recursive) + * + * @param style The style to get the colour from + * @return Colour string to be used + */ private static String getColorOrParent(MessageStyle style) { ChatColor chatColor = style.getColor(); - if (chatColor == ChatColor.NONE) { - return getColor(style.getParent().getColor()); + if (chatColor == ChatColor.NONE && style.getParent() != null) { + return getColorOrParent(style.getParent()); } return getColor(chatColor); } + /** + * Convert a ChatColor into a string for inserting into messages + * + * @param color ChatColor to convert + * @return The converted color string + */ private static String getColor(ChatColor color) { String base = "\u00a7"; switch (color) { @@ -208,6 +251,12 @@ public class MessageUtils { return base; } + /** + * Convert a list of ChatFormats into a string for inserting into messages + * + * @param formats ChatFormats to convert + * @return The converted chat formatting string + */ private static String getFormat(List formats) { StringBuilder str = new StringBuilder(); for (ChatFormat cf : formats) { @@ -238,6 +287,12 @@ public class MessageUtils { return str.toString(); } + /** + * Checks if the given text string is a json message + * + * @param text String to test + * @return True if its a valid message json string, false if not + */ public static boolean isMessage(String text) { JsonParser parser = new JsonParser(); try { @@ -290,4 +345,21 @@ public class MessageUtils { } return ""; } + + /** + * Checks if the given message is over 256 characters (Java edition server chat limit) and sends a message to the user if it is + * + * @param message Message to check + * @param session GeyserSession for the user + * @return True if the message is too long, false if not + */ + public static boolean isTooLong(String message, GeyserSession session) { + if (message.length() > 256) { + // TODO: Add Geyser localization and translate this based on language + session.sendMessage("Your message is bigger than 256 characters (" + message.length() + ") so it has not been sent."); + return true; + } + + return false; + } } 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 c6f53ae9b..ade03c54d 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java @@ -25,9 +25,6 @@ package org.geysermc.connector.utils; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - import lombok.AllArgsConstructor; import lombok.Getter; @@ -43,7 +40,6 @@ import java.util.UUID; import java.util.concurrent.*; public class SkinProvider { - public static final Gson GSON = new GsonBuilder().create(); public static final boolean ALLOW_THIRD_PARTY_CAPES = GeyserConnector.getInstance().getConfig().isAllowThirdPartyCapes(); private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(ALLOW_THIRD_PARTY_CAPES ? 21 : 14); 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 6e1444983..012bd73a4 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java @@ -25,8 +25,9 @@ package org.geysermc.connector.utils; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.github.steveice10.mc.auth.data.GameProfile; -import com.google.gson.JsonObject; import com.nukkitx.protocol.bedrock.data.ImageData; import com.nukkitx.protocol.bedrock.data.SerializedSkin; import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; @@ -104,22 +105,28 @@ public class SkinUtils { private String capeUrl; private boolean alex; + /** + * Generate the GameProfileData from the given GameProfile + * + * @param profile GameProfile to build the GameProfileData from + * @return The built GameProfileData + */ public static GameProfileData from(GameProfile profile) { try { GameProfile.Property skinProperty = profile.getProperty("textures"); - JsonObject skinObject = SkinProvider.GSON.fromJson(new String(Base64.getDecoder().decode(skinProperty.getValue()), StandardCharsets.UTF_8), JsonObject.class); - JsonObject textures = skinObject.getAsJsonObject("textures"); + JsonNode skinObject = new ObjectMapper().readTree(new String(Base64.getDecoder().decode(skinProperty.getValue()), StandardCharsets.UTF_8)); + JsonNode textures = skinObject.get("textures"); - JsonObject skinTexture = textures.getAsJsonObject("SKIN"); - String skinUrl = skinTexture.get("url").getAsString(); + JsonNode skinTexture = textures.get("SKIN"); + String skinUrl = skinTexture.get("url").asText(); boolean isAlex = skinTexture.has("metadata"); String capeUrl = null; if (textures.has("CAPE")) { - JsonObject capeTexture = textures.getAsJsonObject("CAPE"); - capeUrl = capeTexture.get("url").getAsString(); + JsonNode capeTexture = textures.get("CAPE"); + capeUrl = capeTexture.get("url").asText(); } return new GameProfileData(skinUrl, capeUrl, isAlex); @@ -184,10 +191,15 @@ public class SkinUtils { if (skinAndCapeConsumer != null) skinAndCapeConsumer.accept(skinAndCape); }); - }); } + /** + * 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/java/org/geysermc/connector/utils/Toolbox.java b/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java index 7815fd087..b17d45785 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java +++ b/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java @@ -32,6 +32,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.nukkitx.nbt.NbtUtils; import com.nukkitx.nbt.stream.NBTInputStream; import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.protocol.bedrock.data.ItemData; import com.nukkitx.protocol.bedrock.packet.StartGamePacket; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; @@ -39,6 +40,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.network.translators.item.ToolItemEntry; import java.io.*; import java.util.*; @@ -47,12 +49,15 @@ public class Toolbox { public static final ObjectMapper JSON_MAPPER = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES); public static final CompoundTag BIOMES; + public static final ItemData[] CREATIVE_ITEMS; public static final List ITEMS = new ArrayList<>(); public static final Int2ObjectMap ITEM_ENTRIES = new Int2ObjectOpenHashMap<>(); - public static final Map> LOCALE_MAPPINGS = new HashMap<>(); + public static CompoundTag ENTITY_IDENTIFIERS; + + public static int BARRIER_INDEX = 0; static { /* Load biomes */ @@ -102,15 +107,86 @@ public class Toolbox { Iterator> iterator = items.fields(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); - ITEM_ENTRIES.put(itemIndex, new ItemEntry(entry.getKey(), itemIndex, - entry.getValue().get("bedrock_id").intValue(), entry.getValue().get("bedrock_data").intValue())); + if (entry.getValue().has("tool_type")) { + if (entry.getValue().has("tool_tier")) { + ITEM_ENTRIES.put(itemIndex, new ToolItemEntry( + entry.getKey(), itemIndex, + entry.getValue().get("bedrock_id").intValue(), + entry.getValue().get("bedrock_data").intValue(), + entry.getValue().get("tool_type").textValue(), + entry.getValue().get("tool_tier").textValue())); + } else { + ITEM_ENTRIES.put(itemIndex, new ToolItemEntry( + entry.getKey(), itemIndex, + entry.getValue().get("bedrock_id").intValue(), + entry.getValue().get("bedrock_data").intValue(), + entry.getValue().get("tool_type").textValue(), + "")); + } + } else { + ITEM_ENTRIES.put(itemIndex, new ItemEntry( + entry.getKey(), itemIndex, + entry.getValue().get("bedrock_id").intValue(), + entry.getValue().get("bedrock_data").intValue())); + } + if (entry.getKey().equals("minecraft:barrier")) { + BARRIER_INDEX = itemIndex; + } + itemIndex++; } // Load the locale data LocaleUtils.init(); + + /* Load creative items */ + stream = getResource("bedrock/creative_items.json"); + + JsonNode creativeItemEntries; + try { + creativeItemEntries = JSON_MAPPER.readTree(stream).get("items"); + } catch (Exception e) { + throw new AssertionError("Unable to load creative items", e); + } + + List creativeItems = new ArrayList<>(); + for (JsonNode itemNode : creativeItemEntries) { + short damage = 0; + if (itemNode.has("damage")) { + damage = itemNode.get("damage").numberValue().shortValue(); + } + if (itemNode.has("nbt_b64")) { + byte[] bytes = Base64.getDecoder().decode(itemNode.get("nbt_b64").asText()); + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + try { + com.nukkitx.nbt.tag.CompoundTag tag = (com.nukkitx.nbt.tag.CompoundTag) NbtUtils.createReaderLE(bais).readTag(); + creativeItems.add(ItemData.of(itemNode.get("id").asInt(), damage, 1, tag)); + } catch (IOException e) { + e.printStackTrace(); + } + } else { + creativeItems.add(ItemData.of(itemNode.get("id").asInt(), damage, 1)); + } + } + CREATIVE_ITEMS = creativeItems.toArray(new ItemData[0]); + + + /* Load entity identifiers */ + stream = Toolbox.getResource("bedrock/entity_identifiers.dat"); + + try (NBTInputStream nbtInputStream = NbtUtils.createNetworkReader(stream)) { + ENTITY_IDENTIFIERS = (CompoundTag) nbtInputStream.readTag(); + } catch (Exception e) { + throw new AssertionError("Unable to get entities from entity identifiers", e); + } } + /** + * Get an InputStream for the given resource path, throws AssertionError if resource is not found + * + * @param resource Resource to get + * @return InputStream of the given resource + */ public static InputStream getResource(String resource) { InputStream stream = Toolbox.class.getClassLoader().getResourceAsStream(resource); if (stream == null) { diff --git a/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java b/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java index 065d683a0..50ab76d0b 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java @@ -36,6 +36,12 @@ import java.nio.file.StandardCopyOption; public class WebUtils { + /** + * Makes a web request to the given URL and returns the body as a string + * + * @param reqURL URL to fetch + * @return Body contents or error message if the request fails + */ public static String getBody(String reqURL) { URL url = null; try { @@ -61,6 +67,12 @@ public class WebUtils { } } + /** + * Downloads a file from the given URL and saves it to disk + * + * @param reqURL File to fetch + * @param fileLocation Location to save on disk + */ public static void downloadFile(String reqURL, String fileLocation) { try { InputStream in = new URL(reqURL).openStream(); diff --git a/connector/src/main/java/org/geysermc/connector/world/chunk/ChunkPosition.java b/connector/src/main/java/org/geysermc/connector/world/chunk/ChunkPosition.java index cafb7ab04..c45a7c942 100644 --- a/connector/src/main/java/org/geysermc/connector/world/chunk/ChunkPosition.java +++ b/connector/src/main/java/org/geysermc/connector/world/chunk/ChunkPosition.java @@ -48,7 +48,6 @@ public class ChunkPosition { int chunkX = x & 15; int chunkY = y & 15; int chunkZ = z & 15; - return new Position(chunkX, chunkY, chunkZ); } } diff --git a/connector/src/main/resources/bedrock/creative_items.json b/connector/src/main/resources/bedrock/creative_items.json index d55839bac..8045b219c 100644 --- a/connector/src/main/resources/bedrock/creative_items.json +++ b/connector/src/main/resources/bedrock/creative_items.json @@ -3008,21 +3008,6 @@ { "id" : -198 }, - { - "id" : 238, - "damage" : 8 - }, - { - "id" : 238 - }, - { - "id" : 238, - "damage" : 12 - }, - { - "id" : 238, - "damage" : 4 - }, { "id" : 379 }, @@ -3419,363 +3404,6 @@ { "id" : 386 }, - { - "id" : 36 - }, - { - "id" : -12 - }, - { - "id" : -13 - }, - { - "id" : -14 - }, - { - "id" : -15 - }, - { - "id" : -16 - }, - { - "id" : -17 - }, - { - "id" : -18 - }, - { - "id" : -19 - }, - { - "id" : -20 - }, - { - "id" : -21 - }, - { - "id" : -22 - }, - { - "id" : -23 - }, - { - "id" : -24 - }, - { - "id" : -25 - }, - { - "id" : -26 - }, - { - "id" : -27 - }, - { - "id" : -28 - }, - { - "id" : -29 - }, - { - "id" : -30 - }, - { - "id" : -31 - }, - { - "id" : -32 - }, - { - "id" : -33 - }, - { - "id" : -34 - }, - { - "id" : -35 - }, - { - "id" : -36 - }, - { - "id" : -37 - }, - { - "id" : -38 - }, - { - "id" : -39 - }, - { - "id" : -40 - }, - { - "id" : -41 - }, - { - "id" : -42 - }, - { - "id" : -43 - }, - { - "id" : -44 - }, - { - "id" : -45 - }, - { - "id" : -46 - }, - { - "id" : -47 - }, - { - "id" : -48 - }, - { - "id" : -49 - }, - { - "id" : -50 - }, - { - "id" : -51 - }, - { - "id" : -52 - }, - { - "id" : -53 - }, - { - "id" : -54 - }, - { - "id" : -55 - }, - { - "id" : -56 - }, - { - "id" : -57 - }, - { - "id" : -58 - }, - { - "id" : -59 - }, - { - "id" : -60 - }, - { - "id" : -61 - }, - { - "id" : -62 - }, - { - "id" : -63 - }, - { - "id" : -64 - }, - { - "id" : -65 - }, - { - "id" : -66 - }, - { - "id" : -67 - }, - { - "id" : -68 - }, - { - "id" : -69 - }, - { - "id" : -70 - }, - { - "id" : -71 - }, - { - "id" : -72 - }, - { - "id" : -73 - }, - { - "id" : -74 - }, - { - "id" : -75 - }, - { - "id" : -76 - }, - { - "id" : -77 - }, - { - "id" : -78 - }, - { - "id" : -79 - }, - { - "id" : -80 - }, - { - "id" : -81 - }, - { - "id" : -82 - }, - { - "id" : -83 - }, - { - "id" : -84 - }, - { - "id" : -85 - }, - { - "id" : -86 - }, - { - "id" : -87 - }, - { - "id" : -88 - }, - { - "id" : -89 - }, - { - "id" : -90 - }, - { - "id" : -91 - }, - { - "id" : -92 - }, - { - "id" : -93 - }, - { - "id" : -94 - }, - { - "id" : -95 - }, - { - "id" : -96 - }, - { - "id" : -97 - }, - { - "id" : -98 - }, - { - "id" : -99 - }, - { - "id" : -100 - }, - { - "id" : -101 - }, - { - "id" : -102 - }, - { - "id" : -103 - }, - { - "id" : -104 - }, - { - "id" : -105 - }, - { - "id" : -106 - }, - { - "id" : -107 - }, - { - "id" : -108 - }, - { - "id" : -109 - }, - { - "id" : -110 - }, - { - "id" : -111 - }, - { - "id" : -112 - }, - { - "id" : -113 - }, - { - "id" : -114 - }, - { - "id" : -115 - }, - { - "id" : -116 - }, - { - "id" : -117 - }, - { - "id" : -118 - }, - { - "id" : -119 - }, - { - "id" : -120 - }, - { - "id" : -121 - }, - { - "id" : -122 - }, - { - "id" : -123 - }, - { - "id" : -124 - }, - { - "id" : -125 - }, - { - "id" : -126 - }, - { - "id" : -127 - }, - { - "id" : -128 - }, - { - "id" : -129 - }, { "id" : 403, "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAAAAAA=" diff --git a/connector/src/main/resources/bedrock/entity_identifiers.dat b/connector/src/main/resources/bedrock/entity_identifiers.dat index c4f1524a7..cb8f0481b 100644 Binary files a/connector/src/main/resources/bedrock/entity_identifiers.dat and b/connector/src/main/resources/bedrock/entity_identifiers.dat differ