From d6290ccb6640317aeb932e7aca91f8dfaa72837e Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 19 Aug 2020 13:14:17 -0400 Subject: [PATCH 01/52] Auto-configure more if setting is enabled (#1168) * Auto-configure more if setting is enabled - Geyser dumps now show if the config was automatic - Floodgate is now automatically detected if the address is also automatically found - If the plugin versions' servers have the listening address set to something different, set our remote address to that * Fix Sponge config * Remove redundant Getter --- .../platform/bungeecord/GeyserBungeePlugin.java | 11 ++++++++++- .../platform/spigot/GeyserSpigotPlugin.java | 11 ++++++++++- .../sponge/GeyserSpongeConfiguration.java | 11 +++++++++-- .../platform/sponge/GeyserSpongePlugin.java | 5 +++-- .../standalone/GeyserStandaloneBootstrap.java | 3 ++- .../platform/velocity/GeyserVelocityPlugin.java | 15 +++++++++++---- .../configuration/GeyserJacksonConfiguration.java | 7 +++++++ 7 files changed, 52 insertions(+), 11 deletions(-) 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 059e1dfd830..e80207d3fb8 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 @@ -30,10 +30,10 @@ import net.md_5.bungee.config.Configuration; import net.md_5.bungee.config.ConfigurationProvider; import net.md_5.bungee.config.YamlConfiguration; -import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; +import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; @@ -83,6 +83,11 @@ public void onEnable() { // By default this should be localhost but may need to be changed in some circumstances if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { + this.geyserConfig.setAutoconfiguredRemote(true); + // Don't use localhost if not listening on all interfaces + if (!javaAddr.getHostString().equals("0.0.0.0") && !javaAddr.getHostString().equals("")) { + this.geyserConfig.getRemote().setAddress(javaAddr.getHostString()); + } this.geyserConfig.getRemote().setPort(javaAddr.getPort()); } @@ -97,6 +102,10 @@ public void onEnable() { if (geyserConfig.getRemote().getAuthType().equals("floodgate") && getProxy().getPluginManager().getPlugin("floodgate-bungee") == null) { geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); return; + } else if (geyserConfig.isAutoconfiguredRemote() && getProxy().getPluginManager().getPlugin("floodgate-bungee") != null) { + // Floodgate installed means that the user wants Floodgate authentication + geyserLogger.debug("Auto-setting to Floodgate authentication."); + geyserConfig.getRemote().setAuthType("floodgate"); } geyserConfig.loadFloodgate(this, configuration); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index 496681d3380..d51cf21a5bd 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -27,10 +27,10 @@ import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; -import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; +import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; import org.geysermc.connector.network.translators.world.WorldManager; @@ -83,6 +83,11 @@ public void onEnable() { // By default this should be localhost but may need to be changed in some circumstances if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { + geyserConfig.setAutoconfiguredRemote(true); + // Don't use localhost if not listening on all interfaces + if (!Bukkit.getIp().equals("0.0.0.0") && !Bukkit.getIp().equals("")) { + geyserConfig.getRemote().setAddress(Bukkit.getIp()); + } geyserConfig.getRemote().setPort(Bukkit.getPort()); } @@ -97,6 +102,10 @@ public void onEnable() { geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); this.getPluginLoader().disablePlugin(this); return; + } else if (geyserConfig.isAutoconfiguredRemote() && Bukkit.getPluginManager().getPlugin("floodgate-bukkit") != null) { + // Floodgate installed means that the user wants Floodgate authentication + geyserLogger.debug("Auto-setting to Floodgate authentication."); + geyserConfig.getRemote().setAuthType("floodgate"); } geyserConfig.loadFloodgate(this); 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 7f08a095099..35bbf97d5d9 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java @@ -26,9 +26,7 @@ package org.geysermc.platform.sponge; import lombok.AllArgsConstructor; - import ninja.leaping.configurate.ConfigurationNode; - import org.geysermc.connector.configuration.GeyserConfiguration; import java.io.File; @@ -41,6 +39,11 @@ public class GeyserSpongeConfiguration implements GeyserConfiguration { private File dataFolder; private ConfigurationNode node; + /** + * If the config was originally 'auto' before the values changed + */ + private boolean autoconfiguredRemote = false; + private SpongeBedrockConfiguration bedrockConfig; private SpongeRemoteConfiguration remoteConfig; private SpongeMetricsInfo metricsInfo; @@ -64,6 +67,10 @@ public GeyserSpongeConfiguration(File dataFolder, ConfigurationNode node) { } } + public void setAutoconfiguredRemote(boolean autoconfiguredRemote) { + this.autoconfiguredRemote = autoconfiguredRemote; + } + @Override public SpongeBedrockConfiguration getBedrock() { return bedrockConfig; 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 e57e383b237..0f1b7253368 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 @@ -29,11 +29,11 @@ import ninja.leaping.configurate.ConfigurationNode; import ninja.leaping.configurate.loader.ConfigurationLoader; import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; -import org.geysermc.connector.common.PlatformType; -import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; +import org.geysermc.connector.common.PlatformType; +import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; import org.geysermc.connector.ping.IGeyserPingPassthrough; @@ -105,6 +105,7 @@ public void onEnable() { // Don't change the ip if its listening on all interfaces // By default this should be 127.0.0.1 but may need to be changed in some circumstances if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { + this.geyserConfig.setAutoconfiguredRemote(true); serverPort.setValue(javaAddr.getPort()); } } diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java index e4b70133f80..35cc48610ed 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java @@ -33,8 +33,8 @@ import org.apache.logging.log4j.core.appender.ConsoleAppender; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; -import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.command.CommandManager; +import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; @@ -109,6 +109,7 @@ public void onEnable() { File configFile = FileUtils.fileOrCopiedFromResource("config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); geyserConfig = FileUtils.loadConfig(configFile, GeyserStandaloneConfiguration.class); if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { + geyserConfig.setAutoconfiguredRemote(true); // Doesn't really need to be set but /shrug geyserConfig.getRemote().setAddress("127.0.0.1"); } } catch (IOException ex) { diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java index 6d80323439c..291d7a0014e 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java @@ -26,19 +26,17 @@ package org.geysermc.platform.velocity; import com.google.inject.Inject; - import com.velocitypowered.api.command.CommandManager; import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.plugin.Plugin; - import com.velocitypowered.api.proxy.ProxyServer; import lombok.Getter; -import org.geysermc.connector.common.PlatformType; -import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; +import org.geysermc.connector.common.PlatformType; +import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; import org.geysermc.connector.ping.IGeyserPingPassthrough; @@ -94,6 +92,11 @@ public void onEnable() { // By default this should be localhost but may need to be changed in some circumstances if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { + this.geyserConfig.setAutoconfiguredRemote(true); + // Don't use localhost if not listening on all interfaces + if (!javaAddr.getHostString().equals("0.0.0.0") && !javaAddr.getHostString().equals("")) { + this.geyserConfig.getRemote().setAddress(javaAddr.getHostString()); + } geyserConfig.getRemote().setPort(javaAddr.getPort()); } @@ -107,6 +110,10 @@ public void onEnable() { if (geyserConfig.getRemote().getAuthType().equals("floodgate") && !proxyServer.getPluginManager().getPlugin("floodgate").isPresent()) { geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); return; + } else if (geyserConfig.isAutoconfiguredRemote() && proxyServer.getPluginManager().getPlugin("floodgate").isPresent()) { + // Floodgate installed means that the user wants Floodgate authentication + geyserLogger.debug("Auto-setting to Floodgate authentication."); + geyserConfig.getRemote().setAuthType("floodgate"); } geyserConfig.loadFloodgate(this, proxyServer, configFolder.toFile()); diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java index f322b4363a5..8390640f902 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java @@ -38,6 +38,12 @@ @JsonIgnoreProperties(ignoreUnknown = true) public abstract class GeyserJacksonConfiguration implements GeyserConfiguration { + /** + * If the config was originally 'auto' before the values changed + */ + @Setter + private boolean autoconfiguredRemote = false; + private BedrockConfiguration bedrock; private RemoteConfiguration remote; @@ -124,6 +130,7 @@ public static class RemoteConfiguration implements IRemoteConfiguration { @Setter private int port; + @Setter @JsonProperty("auth-type") private String authType; } From 7fcfa7d54d4fc3b0e6d4416047b0394eb48e962a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Thu, 20 Aug 2020 20:53:47 -0400 Subject: [PATCH 02/52] Implement an enchantment table GUI (#1177) Until 1.16, enchantment tables were impossible to implement properly in Geyser. When a user selects an enchantment in Bedrock, the client creates the book on its end and assumes the server is OK with it. Java requires a button to be pressed to select the enchantment. With 1.16, server authoritative inventories remove that on Bedrock. However, until our inventory rewrite is finished we are still stuck without enchantment table support. This commit serves as an alternative as we wait. Enchantment table GUI support is still impossible since we are using the pre-1.16 inventory system. To solve this, this commit replaces the enchantment table GUI with a hopper GUI. The first slot serves as the spot you place the weapon. The second slot acts as the lapis slot - Geyser prevents any item from going in there that is not lapis. The final three slots act as the buttons; an enchanted book acts as each button, with the ability to show the translated text of each enchantment. https://cdn.discordapp.com/attachments/613194828359925800/746164042359504927/unknown.png --- README.md | 2 +- .../network/session/GeyserSession.java | 10 +- .../EnchantmentInventoryTranslator.java | 231 +++++++++++++++++- .../inventory/InventoryTranslator.java | 2 +- 4 files changed, 238 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 64ff22522d9..92462e7eea3 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set ## What's Left to be Added/Fixed - The Following Inventories - - [ ] Enchantment Table + - [ ] Enchantment Table (as a proper GUI) - [ ] Beacon - [ ] Cartography Table - [ ] Stonecutter 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 cea17fdfcba..a385c21c841 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -70,6 +70,7 @@ import org.geysermc.connector.network.translators.BiomeTranslator; import org.geysermc.connector.network.translators.EntityIdentifierRegistry; import org.geysermc.connector.network.translators.PacketTranslatorRegistry; +import org.geysermc.connector.network.translators.inventory.EnchantmentInventoryTranslator; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.*; @@ -177,6 +178,11 @@ public class GeyserSession implements CommandSender { @Setter private long lastInteractedVillagerEid; + /** + * Stores the enchantment information the client has received if they are in an enchantment table GUI + */ + private final EnchantmentInventoryTranslator.EnchantmentSlotData[] enchantmentSlotData = new EnchantmentInventoryTranslator.EnchantmentSlotData[3]; + /** * The current attack speed of the player. Used for sending proper cooldown timings. */ @@ -189,8 +195,6 @@ public class GeyserSession implements CommandSender { @Setter private long lastHitTime; - private MinecraftProtocol protocol; - private boolean reducedDebugInfo = false; @Setter @@ -238,6 +242,8 @@ public class GeyserSession implements CommandSender { @Setter private boolean thunder = false; + private MinecraftProtocol protocol; + public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) { this.connector = connector; this.upstream = new UpstreamSession(bedrockServerSession); 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 index c8e9ed18690..cbcdce10bde 100644 --- 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 @@ -25,18 +25,243 @@ package org.geysermc.connector.network.translators.inventory; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; +import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater; +import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; +import org.geysermc.connector.network.translators.item.ItemTranslator; +import org.geysermc.connector.utils.InventoryUtils; +import org.geysermc.connector.utils.LocaleUtils; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * A temporary reconstruction of the enchantment table UI until our inventory rewrite is complete. + * The enchantment table on Bedrock without server authoritative inventories doesn't tell us which button is pressed + * when selecting an enchantment. + */ public class EnchantmentInventoryTranslator extends BlockInventoryTranslator { - public EnchantmentInventoryTranslator() { - super(2, "minecraft:enchanting_table", ContainerType.ENCHANTMENT, new ContainerInventoryUpdater()); + + private static final int DYE_ID = 351; + private static final short LAPIS_DAMAGE = 4; + private static final int ENCHANTED_BOOK_ID = 403; + + public EnchantmentInventoryTranslator(InventoryUpdater updater) { + super(2, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER, updater); + } + + @Override + public void translateActions(GeyserSession session, Inventory inventory, List actions) { + for (InventoryActionData action : actions) { + if (action.getSource().getContainerId() == inventory.getId()) { + // This is the hopper UI + switch (action.getSlot()) { + case 1: + // Don't allow the slot to be put through if the item isn't lapis + if ((action.getToItem().getId() != DYE_ID + && action.getToItem().getDamage() != LAPIS_DAMAGE) && action.getToItem() != ItemData.AIR) { + updateInventory(session, inventory); + InventoryUtils.updateCursor(session); + return; + } + break; + case 2: + case 3: + case 4: + // The books here act as buttons + ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), action.getSlot() - 2); + session.sendDownstreamPacket(packet); + updateInventory(session, inventory); + InventoryUtils.updateCursor(session); + return; + default: + break; + } + } + } + + super.translateActions(session, inventory, actions); + } + + @Override + public void updateInventory(GeyserSession session, Inventory inventory) { + super.updateInventory(session, inventory); + ItemData[] items = new ItemData[5]; + items[0] = ItemTranslator.translateToBedrock(session, inventory.getItem(0)); + items[1] = ItemTranslator.translateToBedrock(session, inventory.getItem(1)); + for (int i = 0; i < 3; i++) { + items[i + 2] = session.getEnchantmentSlotData()[i].getItem() != null ? session.getEnchantmentSlotData()[i].getItem() : createEnchantmentBook(); + } + + InventoryContentPacket contentPacket = new InventoryContentPacket(); + contentPacket.setContainerId(inventory.getId()); + contentPacket.setContents(items); + session.sendUpstreamPacket(contentPacket); } @Override public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { + int bookSlotToUpdate; + switch (key) { + case 0: + case 1: + case 2: + // Experience required + bookSlotToUpdate = key; + session.getEnchantmentSlotData()[bookSlotToUpdate].setExperienceRequired(value); + break; + case 4: + case 5: + case 6: + // Enchantment name + bookSlotToUpdate = key - 4; + if (value != -1) { + session.getEnchantmentSlotData()[bookSlotToUpdate].setEnchantmentType(EnchantmentTableEnchantments.values()[value - 1]); + } else { + // -1 means no enchantment specified + session.getEnchantmentSlotData()[bookSlotToUpdate].setEnchantmentType(null); + } + break; + case 7: + case 8: + case 9: + // Enchantment level + bookSlotToUpdate = key - 7; + session.getEnchantmentSlotData()[bookSlotToUpdate].setEnchantmentLevel(value); + break; + default: + return; + } + updateEnchantmentBook(session, inventory, bookSlotToUpdate); + } + + @Override + public void openInventory(GeyserSession session, Inventory inventory) { + super.openInventory(session, inventory); + for (int i = 0; i < session.getEnchantmentSlotData().length; i++) { + session.getEnchantmentSlotData()[i] = new EnchantmentSlotData(); + } + } + + @Override + public void closeInventory(GeyserSession session, Inventory inventory) { + super.closeInventory(session, inventory); + Arrays.fill(session.getEnchantmentSlotData(), null); + } + + private ItemData createEnchantmentBook() { + NbtMapBuilder root = NbtMap.builder(); + NbtMapBuilder display = NbtMap.builder(); + + display.putString("Name", ChatColor.RESET + "No Enchantment"); + + root.put("display", display.build()); + return ItemData.of(ENCHANTED_BOOK_ID, (short) 0, 1, root.build()); + } + + private void updateEnchantmentBook(GeyserSession session, Inventory inventory, int slot) { + NbtMapBuilder root = NbtMap.builder(); + NbtMapBuilder display = NbtMap.builder(); + EnchantmentSlotData data = session.getEnchantmentSlotData()[slot]; + if (data.getEnchantmentType() != null) { + display.putString("Name", ChatColor.ITALIC + data.getEnchantmentType().toString(session) + + (data.getEnchantmentLevel() != -1 ? " " + toRomanNumeral(session, data.getEnchantmentLevel()) : "") + "?"); + } else { + display.putString("Name", ChatColor.RESET + "No Enchantment"); + } + + display.putList("Lore", NbtType.STRING, Collections.singletonList(ChatColor.DARK_GRAY + data.getExperienceRequired() + "xp")); + root.put("display", display.build()); + ItemData book = ItemData.of(ENCHANTED_BOOK_ID, (short) 0, 1, root.build()); + + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(inventory.getId()); + slotPacket.setSlot(slot + 2); + slotPacket.setItem(book); + session.sendUpstreamPacket(slotPacket); + data.setItem(book); + } + + private String toRomanNumeral(GeyserSession session, int level) { + return LocaleUtils.getLocaleString("enchantment.level." + level, + session.getClientData().getLanguageCode()); + } + + /** + * Stores the data of each slot in an enchantment table + */ + @NoArgsConstructor + @Getter + @Setter + @ToString + public static class EnchantmentSlotData { + private EnchantmentTableEnchantments enchantmentType = null; + private int enchantmentLevel = 0; + private int experienceRequired = 0; + private ItemData item; + } + + /** + * Classifies enchantments by Java order + */ + public enum EnchantmentTableEnchantments { + PROTECTION, + FIRE_PROTECTION, + FEATHER_FALLING, + BLAST_PROTECTION, + PROJECTILE_PROTECTION, + RESPIRATION, + AQUA_AFFINITY, + THORNS, + DEPTH_STRIDER, + FROST_WALKER, + BINDING_CURSE, + SHARPNESS, + SMITE, + BANE_OF_ARTHROPODS, + KNOCKBACK, + FIRE_ASPECT, + LOOTING, + SWEEPING, + EFFICIENCY, + SILK_TOUCH, + UNBREAKING, + FORTUNE, + POWER, + PUNCH, + FLAME, + INFINITY, + LUCK_OF_THE_SEA, + LURE, + LOYALTY, + IMPALING, + RIPTIDE, + CHANNELING, + MENDING, + VANISHING_CURSE, // After this is not documented + MULTISHOT, + PIERCING, + QUICK_CHARGE, + SOUL_SPEED; + public String toString(GeyserSession session) { + return LocaleUtils.getLocaleString("enchantment.minecraft." + this.toString().toLowerCase(), + session.getClientData().getLanguageCode()); + } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index 7d06aed1404..e44e4bd0b05 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -56,7 +56,6 @@ public abstract class InventoryTranslator { put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator()); put(WindowType.MERCHANT, new MerchantInventoryTranslator()); put(WindowType.SMITHING, new SmithingInventoryTranslator()); - //put(WindowType.ENCHANTMENT, new EnchantmentInventoryTranslator()); //TODO InventoryTranslator furnace = new FurnaceInventoryTranslator(); put(WindowType.FURNACE, furnace); @@ -64,6 +63,7 @@ public abstract class InventoryTranslator { put(WindowType.SMOKER, furnace); InventoryUpdater containerUpdater = new ContainerInventoryUpdater(); + put(WindowType.ENCHANTMENT, new EnchantmentInventoryTranslator(containerUpdater)); //TODO put(WindowType.GENERIC_3X3, new BlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER, containerUpdater)); put(WindowType.HOPPER, new BlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER, containerUpdater)); put(WindowType.SHULKER_BOX, new BlockInventoryTranslator(27, "minecraft:shulker_box[facing=north]", ContainerType.CONTAINER, containerUpdater)); From ee42067d87fe3592d79bb3b2c682347dfa834ec4 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Fri, 21 Aug 2020 13:01:38 -0400 Subject: [PATCH 03/52] Don't play a sound when falling onto a block (#1175) --- .../translators/java/world/JavaSpawnParticleTranslator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java index 453e08445dd..52d3f6493a1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java @@ -50,7 +50,7 @@ public void translate(ServerSpawnParticlePacket packet, GeyserSession session) { LevelEventPacket particle = new LevelEventPacket(); switch (packet.getParticle().getType()) { case BLOCK: - particle.setType(LevelEventType.PARTICLE_DESTROY_BLOCK); + particle.setType(LevelEventType.PARTICLE_DESTROY_BLOCK_NO_SOUND); particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); particle.setData(BlockTranslator.getBedrockBlockId(((BlockParticleData) packet.getParticle().getData()).getBlockState())); session.sendUpstreamPacket(particle); From 94d6c872b1694be949f83943c9dbf1ec375a122a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Fri, 21 Aug 2020 13:01:50 -0400 Subject: [PATCH 04/52] Prevent a comma from appearing if Geyser fails to bind (#1174) --- .../src/main/java/org/geysermc/connector/GeyserConnector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 8203a1689a4..19b2fbac849 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -184,7 +184,7 @@ private GeyserConnector(PlatformType platformType, GeyserBootstrap bootstrap) { if (throwable == null) { logger.info(LanguageUtils.getLocaleStringLog("geyser.core.start", config.getBedrock().getAddress(), String.valueOf(config.getBedrock().getPort()))); } else { - logger.severe(LanguageUtils.getLocaleStringLog("geyser.core.fail", config.getBedrock().getAddress(), config.getBedrock().getPort())); + logger.severe(LanguageUtils.getLocaleStringLog("geyser.core.fail", config.getBedrock().getAddress(), String.valueOf(config.getBedrock().getPort()))); throwable.printStackTrace(); } }).join(); From 713085adf24b2b90875e1ae148666acfaa803100 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Fri, 21 Aug 2020 13:02:09 -0400 Subject: [PATCH 05/52] Fix NPE if block data string isn't in mappings (#1172) This happens when the server version is below the current version and the block state changed. A better solution would be to use ViaVersion to translate the block state strings but this would require getting the server version and figuring out mappings from there. --- .../spigot/world/GeyserSpigotBlockPlaceListener.java | 2 +- .../platform/spigot/world/GeyserSpigotWorldManager.java | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java index 63cb3b4abb8..cb59e202ba7 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java @@ -59,7 +59,7 @@ public void place(final BlockPlaceEvent event) { } else { javaBlockId = event.getBlockPlaced().getBlockData().getAsString(); } - placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaIdBlockMap().get(javaBlockId))); + placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaIdBlockMap().getOrDefault(javaBlockId, 0))); placeBlockSoundPacket.setIdentifier(":"); session.sendUpstreamPacket(placeBlockSoundPacket); session.setLastBlockPlacePosition(null); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java index c43d9eaba7d..4873a17506e 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java @@ -25,7 +25,6 @@ package org.geysermc.platform.spigot.world; -import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import lombok.AllArgsConstructor; import org.bukkit.Bukkit; import org.bukkit.block.Block; @@ -49,10 +48,14 @@ public int getBlockAt(GeyserSession session, int x, int y, int z) { if (session.getPlayerEntity() == null) { return BlockTranslator.AIR; } + if (Bukkit.getPlayer(session.getPlayerEntity().getUsername()) == null) { + return BlockTranslator.AIR; + } if (isLegacy) { return getLegacyBlock(session, x, y, z, isViaVersion); } - return BlockTranslator.getJavaIdBlockMap().get(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getBlockAt(x, y, z).getBlockData().getAsString()); + //TODO possibly: detect server version for all versions and use ViaVersion for block state mappings like below + return BlockTranslator.getJavaIdBlockMap().getOrDefault(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getBlockAt(x, y, z).getBlockData().getAsString(), 0); } @SuppressWarnings("deprecation") From 2d6264d7c1f44ea04add9a2f8b5f7881dbe3b93a Mon Sep 17 00:00:00 2001 From: Jordie <30464310+jordieh@users.noreply.github.com> Date: Sat, 22 Aug 2020 22:39:40 +0200 Subject: [PATCH 06/52] Add visual support for signs colored with dye (#1180) * Fix dyed signs in Bedrock Edition Add visual support (in Bedrock Edition) for signs colored with dye (in Java Edition) * Javadoc for getBedrockSignColor(string) * Simplified getBedrockSignColor(string) --- .../entity/SignBlockEntityTranslator.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java index a95c853e798..ab9ba30655b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java @@ -51,6 +51,11 @@ public Map translateTag(CompoundTag tag, int blockState) { signLine = signLine.substring(0, 14); } + // Java Edition 1.14 added the ability to change the text color of the whole sign using dye + if (tag.contains("Color")) { + signText.append(getBedrockSignColor(tag.get("Color").getValue().toString())); + } + signText.append(signLine); signText.append("\n"); } @@ -75,4 +80,66 @@ public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { .putString("Text", "") .build(); } + + /** + * Maps a color stored in a sign's Color tag to a Bedrock Edition formatting code. + *
+ * The color names correspond to dye names, because of this we can't use {@link MessageUtils#getColor(String)}. + * + * @param javaColor The dye color stored in the sign's Color tag. + * @return A Bedrock Edition formatting code for valid dye colors, otherwise an empty string. + */ + private static String getBedrockSignColor(String javaColor) { + String base = "\u00a7"; + switch (javaColor) { + case "white": + base += 'f'; + break; + case "orange": + base += '6'; + break; + case "magenta": + case "purple": + base += '5'; + break; + case "light_blue": + base += 'b'; + break; + case "yellow": + base += 'e'; + break; + case "lime": + base += 'a'; + break; + case "pink": + base += 'd'; + break; + case "gray": + base += '8'; + break; + case "light_gray": + base += '7'; + break; + case "cyan": + base += '3'; + break; + case "blue": + base += '9'; + break; + case "brown": // Brown does not have a bedrock counterpart. + case "red": // In Java Edition light red (&c) can only be applied using commands. Red dye gives &4. + base += '4'; + break; + case "green": + base += '2'; + break; + case "black": + base += '0'; + break; + default: + return ""; + } + return base; + } + } From 73e09def06d06edd7cf7dd4893e5c10787e3974e Mon Sep 17 00:00:00 2001 From: EasyClifton <63668444+EasyClifton@users.noreply.github.com> Date: Sun, 23 Aug 2020 18:24:31 +0300 Subject: [PATCH 07/52] Add Loom to fix list (#1185) Added loom to the What's Left to be Added/Fixed list. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 92462e7eea3..a34edb4fda0 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set - [ ] Command Block - [ ] Structure Block - [ ] Horse Inventory + - [ ] Loom - Some Entity Flags ## Compiling From a306e9d35b0aa8b43efe01bba6749c3d7b9544af Mon Sep 17 00:00:00 2001 From: Comstepr <32700815+Comstepr@users.noreply.github.com> Date: Sun, 23 Aug 2020 23:29:02 +0800 Subject: [PATCH 08/52] Add smithing table to what's left to be added/fixed (#1187) Co-authored-by: Camotoy <20743703+DoctorMacc@users.noreply.github.com> --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a34edb4fda0..ead5b3b70f1 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set - [ ] Structure Block - [ ] Horse Inventory - [ ] Loom + - [ ] Smithing Table - Some Entity Flags ## Compiling From 1d5b453595422ffa06bcef5bab6a5b06d9c62a70 Mon Sep 17 00:00:00 2001 From: E404NNF <37207067+E404NNF@users.noreply.github.com> Date: Sun, 23 Aug 2020 17:34:09 +0200 Subject: [PATCH 09/52] Add a warning at start about movement translation (#1069) Co-authored-by: Redned Co-authored-by: Camotoy <20743703+DoctorMacc@users.noreply.github.com> --- .../src/main/java/org/geysermc/connector/GeyserConnector.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 19b2fbac849..fb0840d3f78 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -216,6 +216,10 @@ private GeyserConnector(PlatformType platformType, GeyserBootstrap bootstrap) { message += LanguageUtils.getLocaleStringLog("geyser.core.finish.console"); } logger.info(message); + + if (platformType == PlatformType.STANDALONE) { + logger.warning(LanguageUtils.getLocaleStringLog("geyser.core.movement_warn")); + } } public void shutdown() { From 8b7165a5645630b735db8713b220cc2560c5ae79 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 24 Aug 2020 09:31:21 -0400 Subject: [PATCH 10/52] Implement (hopefully) temporary dimension switching fix (#1188) This fixes rare (?) instances where dimension switching doesn't finish loading on the client. Ideally a proper fix would send the finishing packets in the correct order but I didn't get far in this regard. Fixes #1154 and #1072. Other miscellaeous chunk-related fixes have also been included here. --- .../connector/network/session/GeyserSession.java | 2 -- .../java/world/JavaChunkDataTranslator.java | 13 +------------ .../java/world/JavaSpawnPositionTranslator.java | 2 ++ .../org/geysermc/connector/utils/ChunkUtils.java | 2 -- .../geysermc/connector/utils/DimensionUtils.java | 4 ++++ 5 files changed, 7 insertions(+), 16 deletions(-) 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 a385c21c841..72dc0dac2bb 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 @@ -159,8 +159,6 @@ public class GeyserSession implements CommandSender { @Setter private Vector3i lastInteractionPosition; - @Setter - private boolean switchingDimension = false; private boolean manyDimPackets = false; private ServerRespawnPacket lastDimPacket = null; 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 556d0eab331..5170929a9dc 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,25 +25,22 @@ package org.geysermc.connector.network.translators.java.world; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerChunkDataPacket; import com.nukkitx.nbt.NBTOutputStream; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtUtils; import com.nukkitx.network.VarInts; import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; - import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufOutputStream; import io.netty.buffer.Unpooled; -import it.unimi.dsi.fastutil.objects.Object2IntMap; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.BiomeTranslator; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.ChunkUtils; import org.geysermc.connector.network.translators.world.chunk.ChunkSection; +import org.geysermc.connector.utils.ChunkUtils; @Translator(packet = ServerChunkDataPacket.class) public class JavaChunkDataTranslator extends PacketTranslator { @@ -99,14 +96,6 @@ public void translate(ServerChunkDataPacket packet, GeyserSession session) { levelChunkPacket.setData(payload); session.sendUpstreamPacket(levelChunkPacket); - // Some block entities need to be loaded in later or else text doesn't show (signs) or they crash the game (end gateway blocks) - for (Object2IntMap.Entry blockEntityEntry : chunkData.getLoadBlockEntitiesLater().object2IntEntrySet()) { - int x = blockEntityEntry.getKey().getInt("x"); - int y = blockEntityEntry.getKey().getInt("y"); - int z = blockEntityEntry.getKey().getInt("z"); - ChunkUtils.updateBlock(session, blockEntityEntry.getIntValue(), new Position(x, y, z)); - } - chunkData.getLoadBlockEntitiesLater().clear(); session.getChunkCache().addToCache(packet.getColumn()); } catch (Exception ex) { ex.printStackTrace(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnPositionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnPositionTranslator.java index a59c71ea36c..6120c737c2a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnPositionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnPositionTranslator.java @@ -32,6 +32,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerSpawnPositionPacket; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.packet.SetSpawnPositionPacket; +import org.geysermc.connector.utils.DimensionUtils; @Translator(packet = ServerSpawnPositionPacket.class) public class JavaSpawnPositionTranslator extends PacketTranslator { @@ -41,6 +42,7 @@ public void translate(ServerSpawnPositionPacket packet, GeyserSession session) { SetSpawnPositionPacket spawnPositionPacket = new SetSpawnPositionPacket(); spawnPositionPacket.setBlockPosition(Vector3i.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ())); spawnPositionPacket.setSpawnForced(true); + spawnPositionPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getPlayerEntity().getDimension())); spawnPositionPacket.setSpawnType(SetSpawnPositionPacket.Type.WORLD_SPAWN); session.sendUpstreamPacket(spawnPositionPacket); } 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 06b400908b4..79fc5a7820a 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -271,7 +271,5 @@ public static final class ChunkData { @Getter private NbtMap[] blockEntities = new NbtMap[0]; - @Getter - private Object2IntMap loadBlockEntitiesLater = new Object2IntOpenHashMap<>(); } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java index de09ed8c659..7b283e9cb3f 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java @@ -85,6 +85,10 @@ public static void switchDimension(GeyserSession session, String javaDimension) stopSoundPacket.setStoppingAllSound(true); stopSoundPacket.setSoundName(""); session.sendUpstreamPacket(stopSoundPacket); + + // TODO - fix this hack of a fix by sending the final dimension switching logic after chunks have been sent. + // The client wants chunks sent to it before it can successfully respawn. + ChunkUtils.sendEmptyChunks(session, player.getPosition().toInt(), 3, true); } /** From aaa3d7238defb95b7d1a11f3d181dc4c1e62ccc0 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 24 Aug 2020 10:26:37 -0400 Subject: [PATCH 11/52] BedrockEmoteTranslator: ensure sending player is valid for all other sessions (#1194) --- .../bedrock/entity/player/BedrockEmoteTranslator.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockEmoteTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockEmoteTranslator.java index e76fece0b59..813c5594c96 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockEmoteTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockEmoteTranslator.java @@ -26,7 +26,7 @@ package org.geysermc.connector.network.translators.bedrock.entity.player; import com.nukkitx.protocol.bedrock.packet.EmotePacket; -import org.geysermc.connector.GeyserConnector; +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; @@ -37,9 +37,12 @@ public class BedrockEmoteTranslator extends PacketTranslator { @Override public void translate(EmotePacket packet, GeyserSession session) { long javaId = session.getPlayerEntity().getEntityId(); - for (GeyserSession otherSession : GeyserConnector.getInstance().getPlayers()) { + for (GeyserSession otherSession : session.getConnector().getPlayers()) { if (otherSession != session) { - packet.setRuntimeEntityId(otherSession.getEntityCache().getEntityByJavaId(javaId).getGeyserId()); + if (otherSession.isClosed()) continue; + Entity otherEntity = otherSession.getEntityCache().getEntityByJavaId(javaId); + if (otherEntity == null) continue; + packet.setRuntimeEntityId(otherEntity.getGeyserId()); otherSession.sendUpstreamPacket(packet); } } From 65c45386b9d162ae6e93374514d7696ea67364ef Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 24 Aug 2020 22:04:25 -0400 Subject: [PATCH 12/52] Update mappings (#1196) --- connector/src/main/resources/languages | 2 +- connector/src/main/resources/mappings | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index 2641db5aa91..93b2caed3c4 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 2641db5aa9100cdbe21b4493489e9be19092a600 +Subproject commit 93b2caed3c4ecd94b3c77a87f1b2304a7bf4f062 diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index 88678e69bf3..ec8b68297c4 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 88678e69bf358cd562bd410a2459384aeb7ba482 +Subproject commit ec8b68297c4d62a9e4d640a8c7e77977062628ee From 6e8106eeec7b4d68f5eb7a75df098e1fee90026e Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 24 Aug 2020 22:05:39 -0400 Subject: [PATCH 13/52] Add shulker box item tooltip translating (#1189) * Add shulker box item tooltip translating This commit adds support for previewing the items inside of a shulker box. This does not do a full translation, and only does enough to translate the item information to the client, so as to prevent any accidental item modifying/removing on creative mode. * Swap values --- .../translators/item/ItemTranslator.java | 69 +++++++------- .../item/NbtItemStackTranslator.java | 18 +++- .../translators/nbt/BasicItemTranslator.java | 3 +- .../translators/nbt/BookPagesTranslator.java | 3 +- .../translators/nbt/CrossbowTranslator.java | 3 +- .../nbt/EnchantedBookTranslator.java | 3 +- .../nbt/EnchantmentTranslator.java | 3 +- .../translators/nbt/FireworkTranslator.java | 3 +- .../nbt/LeatherArmorTranslator.java | 3 +- .../translators/nbt/MapItemTranslator.java | 3 +- .../nbt/ShulkerBoxItemTranslator.java | 90 +++++++++++++++++++ 11 files changed, 158 insertions(+), 43 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java 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 37dd066edee..dce0f4b4d5d 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 @@ -138,11 +138,13 @@ public static ItemData translateToBedrock(GeyserSession session, ItemStack stack if (nbt != null) { for (NbtItemStackTranslator translator : NBT_TRANSLATORS) { if (translator.acceptItem(bedrockItem)) { - translator.translateToBedrock(nbt, bedrockItem); + translator.translateToBedrock(session, nbt, bedrockItem); } } } + translateDisplayProperties(session, nbt); + ItemData itemData; ItemTranslator itemStackTranslator = ITEM_STACK_TRANSLATORS.get(bedrockItem.getJavaId()); if (itemStackTranslator != null) { @@ -151,39 +153,6 @@ public static ItemData translateToBedrock(GeyserSession session, ItemStack stack itemData = DEFAULT_TRANSLATOR.translateToBedrock(itemStack, bedrockItem); } - - // Get the display name of the item - NbtMap tag = itemData.getTag(); - if (tag != null) { - NbtMap display = tag.getCompound("display"); - if (display != null && !display.isEmpty() && display.containsKey("Name")) { - String name = display.getString("Name"); - - // If its not a message convert it - if (!MessageUtils.isMessage(name)) { - TextComponent component = LegacyComponentSerializer.legacySection().deserialize(name); - name = GsonComponentSerializer.gson().serialize(component); - } - - // Check if its a message to translate - if (MessageUtils.isMessage(name)) { - // Get the translated name - name = MessageUtils.getTranslatedBedrockMessage(MessageSerializer.fromString(name), session.getClientData().getLanguageCode()); - - // Build the new display tag - NbtMapBuilder displayBuilder = display.toBuilder(); - displayBuilder.putString("Name", name); - - // Build the new root tag - NbtMapBuilder builder = tag.toBuilder(); - builder.put("display", displayBuilder.build()); - - // Create a new item with the original data + updated name - itemData = ItemData.of(itemData.getId(), itemData.getDamage(), itemData.getCount(), builder.build()); - } - } - } - return itemData; } @@ -375,6 +344,38 @@ private com.github.steveice10.opennbt.tag.builtin.Tag translateToJavaNBT(String return null; } + /** + * Translates the display name of the item + * @param session the Bedrock client's session + * @param tag the tag to translate + */ + public static void translateDisplayProperties(GeyserSession session, CompoundTag tag) { + if (tag != null) { + CompoundTag display = tag.get("display"); + if (display != null && !display.isEmpty() && display.contains("Name")) { + String name = ((StringTag) display.get("Name")).getValue(); + + // If its not a message convert it + if (!MessageUtils.isMessage(name)) { + TextComponent component = LegacyComponentSerializer.legacySection().deserialize(name); + name = GsonComponentSerializer.gson().serialize(component); + } + + // Check if its a message to translate + if (MessageUtils.isMessage(name)) { + // Get the translated name + name = MessageUtils.getTranslatedBedrockMessage(MessageSerializer.fromString(name), session.getClientData().getLanguageCode()); + + // Add the new name tag + display.put(new StringTag("Name", name)); + + // Add to the new root tag + tag.put(display); + } + } + } + } + /** * Checks if an {@link ItemStack} is equal to another item stack * diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/NbtItemStackTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/NbtItemStackTranslator.java index 3c0b75b75dc..89d41e98d70 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/NbtItemStackTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/NbtItemStackTranslator.java @@ -26,17 +26,33 @@ package org.geysermc.connector.network.translators.item; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import org.geysermc.connector.network.session.GeyserSession; public class NbtItemStackTranslator { - public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) { + /** + * Translate the item NBT to Bedrock + * @param session the client's current session + * @param itemTag the item's CompoundTag + * @param itemEntry Geyser's item entry + */ + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { } + /** + * Translate the item NBT to Java. + * @param itemTag the item's CompoundTag + * @param itemEntry Geyser's item entry + */ public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { } + /** + * @param itemEntry Geyser's item entry + * @return if the item should be processed under this class + */ public boolean acceptItem(ItemEntry itemEntry) { return true; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java index b30031ebc66..e45566264ba 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java @@ -33,6 +33,7 @@ import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; @@ -45,7 +46,7 @@ public class BasicItemTranslator extends NbtItemStackTranslator { @Override - public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) { + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { if (!itemTag.contains("display")) { return; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java index e802f017447..41ee4fbca20 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java @@ -29,6 +29,7 @@ import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.github.steveice10.opennbt.tag.builtin.Tag; +import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; import org.geysermc.connector.network.translators.item.ItemEntry; @@ -41,7 +42,7 @@ public class BookPagesTranslator extends NbtItemStackTranslator { @Override - public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) { + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { if (!itemTag.contains("pages")) { return; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java index 3c5271ebf13..979c5a20569 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java @@ -29,6 +29,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; +import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; import org.geysermc.connector.network.translators.item.ItemEntry; @@ -37,7 +38,7 @@ public class CrossbowTranslator extends NbtItemStackTranslator { @Override - public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) { + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { if (itemTag.get("ChargedProjectiles") != null) { ListTag chargedProjectiles = itemTag.get("ChargedProjectiles"); if (!chargedProjectiles.getValue().isEmpty()) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantedBookTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantedBookTranslator.java index 63ac6c5101f..990d5a7ad23 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantedBookTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantedBookTranslator.java @@ -28,6 +28,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.github.steveice10.opennbt.tag.builtin.Tag; +import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; import org.geysermc.connector.network.translators.item.ItemEntry; @@ -36,7 +37,7 @@ public class EnchantedBookTranslator extends NbtItemStackTranslator { @Override - public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) { + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { if (!itemTag.contains("StoredEnchantments")) { return; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantmentTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantmentTranslator.java index 6bd9cc77ed3..6884c00bac9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantmentTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/EnchantmentTranslator.java @@ -27,6 +27,7 @@ import com.github.steveice10.opennbt.tag.builtin.*; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; import org.geysermc.connector.network.translators.item.Enchantment; @@ -40,7 +41,7 @@ public class EnchantmentTranslator extends NbtItemStackTranslator { @Override - public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) { + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { List newTags = new ArrayList<>(); if (itemTag.contains("Enchantments")) { ListTag enchantmentTag = itemTag.get("Enchantments"); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java index 0e23169f8f0..6023d77deeb 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java @@ -26,6 +26,7 @@ package org.geysermc.connector.network.translators.item.translators.nbt; import com.github.steveice10.opennbt.tag.builtin.*; +import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; @@ -36,7 +37,7 @@ public class FireworkTranslator extends NbtItemStackTranslator { @Override - public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) { + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { if (!itemTag.contains("Fireworks")) { return; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/LeatherArmorTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/LeatherArmorTranslator.java index 9f864ccf42c..93af3e709ec 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/LeatherArmorTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/LeatherArmorTranslator.java @@ -27,6 +27,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.IntTag; +import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; import org.geysermc.connector.network.translators.item.ItemEntry; @@ -37,7 +38,7 @@ public class LeatherArmorTranslator extends NbtItemStackTranslator { private static final String[] ITEMS = new String[]{"minecraft:leather_helmet", "minecraft:leather_chestplate", "minecraft:leather_leggings", "minecraft:leather_boots"}; @Override - public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) { + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { if (!itemTag.contains("display")) { return; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java index 4a36880c900..c9b49efd475 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java @@ -29,6 +29,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.IntTag; import com.github.steveice10.opennbt.tag.builtin.LongTag; +import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; @@ -37,7 +38,7 @@ public class MapItemTranslator extends NbtItemStackTranslator { @Override - public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) { + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { IntTag mapId = itemTag.get("map"); if (mapId != null) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java new file mode 100644 index 00000000000..1780537c5d5 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT 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 com.nukkitx.protocol.bedrock.packet.StartGamePacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.item.ItemTranslator; +import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; + +@ItemRemapper +public class ShulkerBoxItemTranslator extends NbtItemStackTranslator { + + @Override + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { + if (!itemTag.contains("BlockEntityTag")) return; // Empty shulker box + + CompoundTag blockEntityTag = itemTag.get("BlockEntityTag"); + ListTag itemsList = new ListTag("Items"); + for (Tag item : (ListTag) blockEntityTag.get("Items")) { + CompoundTag itemData = (CompoundTag) item; // Information about the item + CompoundTag boxItemTag = new CompoundTag(""); // Final item tag to add to the list + boxItemTag.put(new ByteTag("Slot", ((ByteTag) itemData.get("Slot")).getValue())); + boxItemTag.put(new ByteTag("WasPickedUp", (byte) 0)); // ??? + + ItemEntry boxItemEntry = ItemRegistry.getItemEntry(((StringTag) itemData.get("id")).getValue()); + String blockName = ""; + for (StartGamePacket.ItemEntry startGamePacketItemEntry : ItemRegistry.ITEMS) { + if (startGamePacketItemEntry.getId() == (short) boxItemEntry.getBedrockId()) { + blockName = startGamePacketItemEntry.getIdentifier(); // Find the Bedrock string name + break; + } + } + + boxItemTag.put(new StringTag("Name", blockName)); + boxItemTag.put(new ShortTag("Damage", (short) boxItemEntry.getBedrockData())); + boxItemTag.put(new ByteTag("Count", ((ByteTag) itemData.get("Count")).getValue())); + if (itemData.contains("tag")) { + // Only the display name is what we have interest in, so just translate that if relevant + CompoundTag displayTag = itemData.get("tag"); + ItemTranslator.translateDisplayProperties(session, displayTag); + boxItemTag.put(displayTag); + } + + itemsList.add(boxItemTag); + } + itemTag.put(itemsList); + // Don't actually bother with removing the block entity tag. Too risky to translate + // if the user is on creative and messing with a shulker box + //itemTag.remove("BlockEntityTag"); + } + + @Override + public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { + if (itemTag.contains("Items")) { // Remove any extraneous Bedrock tag and don't touch the Java one + itemTag.remove("Items"); + } + } + + @Override + public boolean acceptItem(ItemEntry itemEntry) { + return itemEntry.getJavaIdentifier().contains("shulker_box"); + } +} From c1a70c77545cac5ddc5b82dfc4422759cb7c3277 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 24 Aug 2020 22:14:44 -0400 Subject: [PATCH 14/52] Translate client-computed recipes (#1181) * Translate client-computed recipes A handful of recipes are complex enough on Java Edition that the client simply calculates them after getting an assurance that they are valid recipes. This PR stores those recipes in a Bedrock-compatible format in mappings, then generates the CraftingData information on startup to send to the Bedrock client when called. This fixes firework rocket and star crafting, and fixes leather armor and shulker box dyeing. The recipe information for everything except leather armor was taken right from the Bedrock server. The leather armor had to be created separately (see https://github.com/DoctorMacc/LeatherDyeingCreation). There will be a slight visual difference in the crafting result preview if the armor is not perfectly dyed to one of the sixteen colors, but this is a visual issue that will persist unless we calculate every single possbile combination. * Revert other changes * Register shulker box recipes properly * Add break * Update mappings --- .../geysermc/connector/GeyserConnector.java | 2 + .../translators/item/ItemRegistry.java | 44 ++++--- .../translators/item/RecipeRegistry.java | 117 ++++++++++++++++++ .../translators/nbt/FireworkTranslator.java | 3 + .../java/JavaDeclareRecipesTranslator.java | 23 +++- 5 files changed, 170 insertions(+), 19 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index fb0840d3f78..ec332ed66f9 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -47,6 +47,7 @@ import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.network.translators.item.PotionMixRegistry; +import org.geysermc.connector.network.translators.item.RecipeRegistry; import org.geysermc.connector.network.translators.sound.SoundHandlerRegistry; import org.geysermc.connector.network.translators.sound.SoundRegistry; import org.geysermc.connector.network.translators.world.WorldManager; @@ -131,6 +132,7 @@ private GeyserConnector(PlatformType platformType, GeyserBootstrap bootstrap) { ItemTranslator.init(); LocaleUtils.init(); PotionMixRegistry.init(); + RecipeRegistry.init(); SoundRegistry.init(); SoundHandlerRegistry.init(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index 4828fbf27bc..56ed2d6e01d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -173,21 +173,8 @@ public static void init() { int netId = 1; List creativeItems = new ArrayList<>(); for (JsonNode itemNode : creativeItemEntries) { - try { - short damage = 0; - NbtMap tag = null; - if (itemNode.has("damage")) { - damage = itemNode.get("damage").numberValue().shortValue(); - } - if (itemNode.has("nbt_b64")) { - byte[] bytes = Base64.getDecoder().decode(itemNode.get("nbt_b64").asText()); - ByteArrayInputStream bais = new ByteArrayInputStream(bytes); - tag = (NbtMap) NbtUtils.createReaderLE(bais).readTag(); - } - creativeItems.add(ItemData.fromNet(netId++, itemNode.get("id").asInt(), damage, 1, tag)); - } catch (IOException e) { - e.printStackTrace(); - } + ItemData item = getBedrockItemFromJson(itemNode); + creativeItems.add(ItemData.fromNet(netId++, item.getId(), item.getDamage(), item.getCount(), item.getTag())); } CREATIVE_ITEMS = creativeItems.toArray(new ItemData[0]); } @@ -233,4 +220,31 @@ public static ItemEntry getItemEntry(String javaIdentifier) { return JAVA_IDENTIFIER_MAP.computeIfAbsent(javaIdentifier, key -> ITEM_ENTRIES.values() .stream().filter(itemEntry -> itemEntry.getJavaIdentifier().equals(key)).findFirst().orElse(null)); } + + /** + * Gets a Bedrock {@link ItemData} from a {@link JsonNode} + * @param itemNode the JSON node that contains ProxyPass-compatible Bedrock item data + * @return + */ + public static ItemData getBedrockItemFromJson(JsonNode itemNode) { + int count = 1; + short damage = 0; + NbtMap tag = null; + if (itemNode.has("damage")) { + damage = itemNode.get("damage").numberValue().shortValue(); + } + if (itemNode.has("count")) { + count = itemNode.get("count").asInt(); + } + if (itemNode.has("nbt_b64")) { + byte[] bytes = Base64.getDecoder().decode(itemNode.get("nbt_b64").asText()); + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + try { + tag = (NbtMap) NbtUtils.createReaderLE(bais).readTag(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return ItemData.of(itemNode.get("id").asInt(), damage, count, tag); + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java new file mode 100644 index 00000000000..191b285c6a8 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.item; + +import com.fasterxml.jackson.databind.JsonNode; +import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.utils.FileUtils; +import org.geysermc.connector.utils.LanguageUtils; + +import java.io.InputStream; +import java.util.List; +import java.util.UUID; + +/** + * Manages any recipe-related storing + */ +public class RecipeRegistry { + + /** + * A list of all possible leather armor dyeing recipes. + * Created manually. + */ + public static List LEATHER_DYEING_RECIPES = new ObjectArrayList<>(); + /** + * A list of all possible firework rocket recipes, including the base rocket. + * Obtained from a ProxyPass dump of protocol v407 + */ + public static List FIREWORK_ROCKET_RECIPES = new ObjectArrayList<>(21); + /** + * A list of all possible firework star recipes. + * Obtained from a ProxyPass dump of protocol v407 + */ + public static List FIREWORK_STAR_RECIPES = new ObjectArrayList<>(40); + /** + * A list of all possible shulker box dyeing options. + * Obtained from a ProxyPass dump of protocol v407 + */ + public static List SHULKER_BOX_DYEING_RECIPES = new ObjectArrayList<>(); + + static { + // Get all recipes that are not directly sent from a Java server + InputStream stream = FileUtils.getResource("mappings/recipes.json"); + + JsonNode items; + try { + items = GeyserConnector.JSON_MAPPER.readTree(stream); + } catch (Exception e) { + throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e); + } + + for (JsonNode entry: items.get("leather_armor")) { + // This won't be perfect, as we can't possibly send every leather input for every kind of color + // But it does display the correct output from a base leather armor, and besides visuals everything works fine + LEATHER_DYEING_RECIPES.add(getCraftingDataFromJsonNode(entry)); + } + for (JsonNode entry : items.get("firework_rockets")) { + FIREWORK_ROCKET_RECIPES.add(getCraftingDataFromJsonNode(entry)); + } + for (JsonNode entry : items.get("firework_stars")) { + FIREWORK_STAR_RECIPES.add(getCraftingDataFromJsonNode(entry)); + } + for (JsonNode entry : items.get("shulker_boxes")) { + SHULKER_BOX_DYEING_RECIPES.add(getCraftingDataFromJsonNode(entry)); + } + } + + /** + * Computes a Bedrock crafting recipe from the given JSON data. + * @param node the JSON data to compute + * @return the {@link CraftingData} to send to the Bedrock client. + */ + private static CraftingData getCraftingDataFromJsonNode(JsonNode node) { + ItemData output = ItemRegistry.getBedrockItemFromJson(node.get("output").get(0)); + List inputs = new ObjectArrayList<>(); + for (JsonNode entry : node.get("input")) { + inputs.add(ItemRegistry.getBedrockItemFromJson(entry)); + } + UUID uuid = UUID.randomUUID(); + if (node.get("type").asInt() == 5) { + // Shulker box + return CraftingData.fromShulkerBox(uuid.toString(), + inputs.toArray(new ItemData[0]), new ItemData[]{output}, uuid, "crafting_table", 0); + } + return CraftingData.fromShapeless(uuid.toString(), + inputs.toArray(new ItemData[0]), new ItemData[]{output}, uuid, "crafting_table", 0); + } + + public static void init() { + // no-op + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java index 6023d77deeb..3b453ea1827 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java @@ -107,6 +107,9 @@ public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { fireworks.put(new ByteTag("Flight", MathUtils.convertByte(fireworks.get("Flight").getValue()))); } + if (!itemTag.contains("Explosions")) { + return; + } ListTag explosions = fireworks.get("Explosions"); for (Tag effect : explosions.getValue()) { CompoundTag effectData = (CompoundTag) effect; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java index 75ccc0a5a0a..9ffb4f0d9ae 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java @@ -41,10 +41,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.geysermc.connector.network.translators.item.PotionMixRegistry; +import org.geysermc.connector.network.translators.item.*; import java.util.*; import java.util.stream.Collectors; @@ -83,6 +80,24 @@ public void translate(ServerDeclareRecipesPacket packet, GeyserSession session) } break; } + case CRAFTING_SPECIAL_FIREWORK_ROCKET: { + // Java doesn't actually tell us the recipes so we need to calculate this ahead of time. + craftingDataPacket.getCraftingData().addAll(RecipeRegistry.FIREWORK_ROCKET_RECIPES); + break; + } + case CRAFTING_SPECIAL_FIREWORK_STAR: { + craftingDataPacket.getCraftingData().addAll(RecipeRegistry.FIREWORK_STAR_RECIPES); + break; + } + case CRAFTING_SPECIAL_SHULKERBOXCOLORING: { + craftingDataPacket.getCraftingData().addAll(RecipeRegistry.SHULKER_BOX_DYEING_RECIPES); + break; + } + case CRAFTING_SPECIAL_ARMORDYE: { + // This one's even worse since it's not actually on Bedrock, but it still works! + craftingDataPacket.getCraftingData().addAll(RecipeRegistry.LEATHER_DYEING_RECIPES); + break; + } } } craftingDataPacket.getPotionMixData().addAll(PotionMixRegistry.POTION_MIXES); From 81f58ee9bf42a698e9f017b1ad26127c2bcfe993 Mon Sep 17 00:00:00 2001 From: James Cahill Date: Tue, 25 Aug 2020 14:29:55 +0100 Subject: [PATCH 15/52] Add Server Name config option (#1170) * Add bedrock.server-name config option * Fix spelling mistake oops! * Remove trailing whitespace --- .../geysermc/platform/sponge/GeyserSpongeConfiguration.java | 5 +++++ .../connector/configuration/GeyserConfiguration.java | 2 ++ .../connector/configuration/GeyserJacksonConfiguration.java | 4 ++++ .../geysermc/connector/network/session/GeyserSession.java | 6 ++++-- connector/src/main/resources/config.yml | 2 ++ 5 files changed, 17 insertions(+), 2 deletions(-) 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 35bbf97d5d9..ba4161105d1 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 @@ -205,6 +205,11 @@ public String getMotd1() { public String getMotd2() { return node.getNode("motd2").getString("GeyserMC"); } + + @Override + public String getServerName() { + return node.getNode("server-name").getString("Geyser"); + } } @AllArgsConstructor diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java index 2cf9a181e60..5803ff13b33 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java @@ -95,6 +95,8 @@ interface IBedrockConfiguration { String getMotd1(); String getMotd2(); + + String getServerName(); } interface IRemoteConfiguration { diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java index 8390640f902..378eba681a2 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java @@ -29,6 +29,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.Setter; +import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.common.serializer.AsteriskSerializer; import java.nio.file.Path; @@ -118,6 +119,9 @@ public static class BedrockConfiguration implements IBedrockConfiguration { private String motd1; private String motd2; + + @JsonProperty("server-name") + private String serverName = GeyserConnector.NAME; } @Getter 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 72dc0dac2bb..b208aaf3cb9 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 @@ -590,8 +590,10 @@ private void startGame() { startGamePacket.setFromWorldTemplate(false); startGamePacket.setWorldTemplateOptionLocked(false); - startGamePacket.setLevelId("world"); - startGamePacket.setLevelName("world"); + String serverName = connector.getConfig().getBedrock().getServerName(); + startGamePacket.setLevelId(serverName); + startGamePacket.setLevelName(serverName); + startGamePacket.setPremiumWorldTemplateId("00000000-0000-0000-0000-000000000000"); // startGamePacket.setCurrentTick(0); startGamePacket.setEnchantmentSeed(0); diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index f59939b6a05..af5881858a7 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -20,6 +20,8 @@ bedrock: # The MOTD that will be broadcasted to Minecraft: Bedrock Edition clients. Irrelevant if "passthrough-motd" is set to true motd1: "GeyserMC" motd2: "Another GeyserMC forced host." + # The Server Name that will be sent to Minecraft: Bedrock Edition clients. Visible in both the pause menu and the settings menu. + server-name: "Geyser" remote: # The IP address of the remote (Java Edition) server # If it is "auto", for standalone version the remote address will be set to 127.0.0.1, From 5458a85ed79f2980d79904416ef765852819e9dc Mon Sep 17 00:00:00 2001 From: abeshi-softwire <67909514+abeshi-softwire@users.noreply.github.com> Date: Tue, 25 Aug 2020 22:39:51 +0100 Subject: [PATCH 16/52] Adding option to use different config file for standalone (#1102) * Added config option to standalone Geyser * Cleanup * Added --gui, --nogui options * Made new options read default config.yml from correct place internally * Changed to locale strings rather than hardcoded English * Using separate options texts * Changed '-c' to be string parameter so it isn't translated --- .../standalone/GeyserStandaloneBootstrap.java | 58 ++++++++++++++++--- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java index 35cc48610ed..123a9a600a5 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java @@ -49,6 +49,7 @@ import java.lang.reflect.Method; import java.nio.file.Path; import java.nio.file.Paths; +import java.text.MessageFormat; import java.util.UUID; public class GeyserStandaloneBootstrap implements GeyserBootstrap { @@ -62,22 +63,61 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { @Getter private boolean useGui = System.console() == null && !isHeadless(); + private String configFilename = "config.yml"; private GeyserConnector connector; + public static void main(String[] args) { - for (String arg : args) { + GeyserStandaloneBootstrap bootstrap = new GeyserStandaloneBootstrap(); + // Set defaults + boolean useGuiOpts = bootstrap.useGui; + String configFilenameOpt = bootstrap.configFilename; + + for (int i = 0; i < args.length; i++) { // By default, standalone Geyser will check if it should open the GUI based on if the GUI is null // Optionally, you can force the use of a GUI or no GUI by specifying args - if (arg.equals("gui")) { - new GeyserStandaloneBootstrap().onEnable(true); - return; - } else if (arg.equals("nogui")) { - new GeyserStandaloneBootstrap().onEnable(false); - return; + // Allows gui and nogui without options, for backwards compatibility + String arg = args[i]; + switch (arg) { + case "--gui": + case "gui": + useGuiOpts = true; + break; + case "--nogui": + case "nogui": + useGuiOpts = false; + break; + case "--config": + case "-c": + if (i >= args.length - 1) { + System.err.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.confignotspecified"), "-c")); + return; + } + configFilenameOpt = args[i+1]; i++; + System.out.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.configspecified"), configFilenameOpt)); + break; + case "--help": + case "-h": + System.out.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.usage"), "[java -jar] Geyser.jar [opts]")); + System.out.println(" " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.options")); + System.out.println(" -c, --config [file] " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.config")); + System.out.println(" -h, --help " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.help")); + System.out.println(" --gui, --nogui " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.gui")); + return; + default: + String badArgMsg = LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.unrecognised"); + System.err.println(MessageFormat.format(badArgMsg, arg)); + return; } } - new GeyserStandaloneBootstrap().onEnable(); + bootstrap.onEnable(useGuiOpts, configFilenameOpt); + } + + public void onEnable(boolean useGui, String configFilename) { + this.configFilename = configFilename; + this.useGui = useGui; + this.onEnable(); } public void onEnable(boolean useGui) { @@ -106,7 +146,7 @@ public void onEnable() { LoopbackUtil.checkLoopback(geyserLogger); try { - File configFile = FileUtils.fileOrCopiedFromResource("config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); + File configFile = FileUtils.fileOrCopiedFromResource(new File(configFilename), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); geyserConfig = FileUtils.loadConfig(configFile, GeyserStandaloneConfiguration.class); if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { geyserConfig.setAutoconfiguredRemote(true); // Doesn't really need to be set but /shrug From 7cbfdcf52192479be2eda88cf6c3aae20b72fc05 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Fri, 28 Aug 2020 16:29:48 +0100 Subject: [PATCH 17/52] Fix reflections relocation (#1199) --- bootstrap/bungeecord/pom.xml | 2 +- bootstrap/spigot/pom.xml | 2 +- bootstrap/sponge/pom.xml | 2 +- bootstrap/velocity/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 8497b9684ee..682690a215f 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -66,7 +66,7 @@ org.geysermc.platform.bungeecord.shaded.netty - org.reflections.reflections + org.reflections org.geysermc.platform.bungeecord.shaded.reflections diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 422b2769b44..8b3ad742847 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -76,7 +76,7 @@ org.geysermc.platform.spigot.shaded.jackson - org.reflections.reflections + org.reflections org.geysermc.platform.spigot.shaded.reflections diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index f3c898084e9..c21a3a539af 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -70,7 +70,7 @@ org.geysermc.platform.sponge.shaded.fastutil - org.reflections.reflections + org.reflections org.geysermc.platform.sponge.shaded.reflections diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index b08e5cbc55a..0ebb70cab90 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -62,7 +62,7 @@ org.geysermc.platform.velocity.shaded.fastutil - org.reflections.reflections + org.reflections org.geysermc.platform.velocity.shaded.reflections From 1c849938539ed919f184d4243fd674728ca65127 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Fri, 28 Aug 2020 17:47:52 +0200 Subject: [PATCH 18/52] Scoreboard improvements (#1166) * Added a way to check if debug logging is enabled * Improved scoreboard performance * Include Teams in pps and return pending pps instead when higher then pps Some servers have a huge amount of score packets when the player logs in, but before this commit, only after the first high pps (packets per second) the ScoreboardUpdater will be used (after pending packets per second have been moved to packets per second). But this commit fixes that the ScoreboardUpdater can be used on the second that the pps is getting high. * Fixed team pre + suffix "null" issue and added threshold config option Fixed team pre + suffix "null" issue. When the prefix and/or suffix of a Team is null, "null" will be returned instead of null (Due to the way that MCProtocolLib is made and designed). This is fixed by simply checking if the prefix and/or suffix equal "null" and if that is the case, replace it with "". Added threshold option. Gave the person who is running Geyser an option to specify the first Scoreboard packets per second threshold to further improve performance by lowering the setting or decrease performance by relaxing the setting a bit. The value can't be higher then 250 (the second threshold), because it'll always choose the lowest threshold. * Forgot to bump config version * Small changes * Reverted version bump, changed Sponge config, changed FloodgateKeyLoader Reverted version bump Camotoy said that you only need to bump the config version if the change is breaking, the config version bump has been reverted. Changed Sponge config The Sponge config has been modified to look like the other platform configurations. Changed FloodgateKeyLoader * Changed default-locale and (remote) address as requested by Camotoy * Reduce bandwidth and a few final tweaks * Made the scoreboard-packet-threshold a bit higher due to improvements --- .../bungeecord/GeyserBungeeConfiguration.java | 17 +- .../bungeecord/GeyserBungeeLogger.java | 20 +- .../bungeecord/GeyserBungeePlugin.java | 7 +- .../spigot/GeyserSpigotConfiguration.java | 18 +- .../platform/spigot/GeyserSpigotLogger.java | 17 +- .../sponge/GeyserSpongeConfiguration.java | 263 +----------------- .../platform/sponge/GeyserSpongeLogger.java | 17 +- .../platform/sponge/GeyserSpongePlugin.java | 16 +- .../GeyserStandaloneConfiguration.java | 11 +- .../standalone/GeyserStandaloneLogger.java | 12 +- .../velocity/GeyserVelocityConfiguration.java | 19 +- .../velocity/GeyserVelocityLogger.java | 17 +- .../connector/FloodgateKeyLoader.java | 8 +- .../org/geysermc/connector/GeyserLogger.java | 5 + .../configuration/GeyserConfiguration.java | 5 +- .../GeyserJacksonConfiguration.java | 60 ++-- .../connector/entity/PlayerEntity.java | 17 +- .../network/session/GeyserSession.java | 6 +- .../network/session/UpstreamSession.java | 14 +- .../network/session/cache/WorldCache.java | 23 +- .../JavaScoreboardObjectiveTranslator.java | 10 +- .../java/scoreboard/JavaTeamTranslator.java | 60 ++-- .../scoreboard/JavaUpdateScoreTranslator.java | 60 ++-- .../connector/scoreboard/Objective.java | 81 +++--- .../geysermc/connector/scoreboard/Score.java | 39 ++- .../connector/scoreboard/Scoreboard.java | 178 ++++++------ .../scoreboard/ScoreboardUpdater.java | 121 ++++++++ .../geysermc/connector/scoreboard/Team.java | 38 ++- connector/src/main/resources/config.yml | 6 + 29 files changed, 549 insertions(+), 616 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java 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 d9b86a2e812..00b091d1f36 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java @@ -29,27 +29,22 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Getter; import net.md_5.bungee.api.plugin.Plugin; -import net.md_5.bungee.config.Configuration; import org.geysermc.connector.FloodgateKeyLoader; import org.geysermc.connector.configuration.GeyserJacksonConfiguration; import java.nio.file.Path; -import java.nio.file.Paths; @Getter @JsonIgnoreProperties(ignoreUnknown = true) -public class GeyserBungeeConfiguration extends GeyserJacksonConfiguration { - +public final class GeyserBungeeConfiguration extends GeyserJacksonConfiguration { @JsonIgnore - private Path floodgateKey; + private Path floodgateKeyPath; - public void loadFloodgate(GeyserBungeePlugin plugin, Configuration configuration) { + public void loadFloodgate(GeyserBungeePlugin plugin) { Plugin floodgate = plugin.getProxy().getPluginManager().getPlugin("floodgate-bungee"); - floodgateKey = FloodgateKeyLoader.getKey(plugin.getGeyserLogger(), this, Paths.get(plugin.getDataFolder().toString(), configuration.getString("floodgate-key-file"), "public-key.pem"), floodgate, floodgate != null ? floodgate.getDataFolder().toPath() : null); - } + Path geyserDataFolder = plugin.getDataFolder().toPath(); + Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null; - @Override - public Path getFloodgateKeyFile() { - return floodgateKey; + floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgate, floodgateDataFolder, geyserDataFolder, plugin.getGeyserLogger()); } } diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeLogger.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeLogger.java index cd07b333d15..e40f404c191 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeLogger.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeLogger.java @@ -25,19 +25,21 @@ package org.geysermc.platform.bungeecord; +import lombok.Getter; +import lombok.Setter; import org.geysermc.connector.GeyserLogger; import java.util.logging.Level; import java.util.logging.Logger; public class GeyserBungeeLogger implements GeyserLogger { + private final Logger logger; + @Getter @Setter + private boolean debug; - private Logger logger; - private boolean debugMode; - - public GeyserBungeeLogger(Logger logger, boolean debugMode) { + public GeyserBungeeLogger(Logger logger, boolean debug) { this.logger = logger; - this.debugMode = debugMode; + this.debug = debug; } @Override @@ -72,12 +74,8 @@ public void info(String message) { @Override public void debug(String message) { - if (debugMode) + if (debug) { info(message); - } - - @Override - public void setDebug(boolean debug) { - debugMode = debug; + } } } diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java index e80207d3fb8..abb9789e19b 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java @@ -27,9 +27,6 @@ import net.md_5.bungee.api.config.ListenerInfo; import net.md_5.bungee.api.plugin.Plugin; -import net.md_5.bungee.config.Configuration; -import net.md_5.bungee.config.ConfigurationProvider; -import net.md_5.bungee.config.YamlConfiguration; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; @@ -64,13 +61,11 @@ public void onEnable() { if (!getDataFolder().exists()) getDataFolder().mkdir(); - Configuration configuration = null; try { if (!getDataFolder().exists()) getDataFolder().mkdir(); File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); this.geyserConfig = FileUtils.loadConfig(configFile, GeyserBungeeConfiguration.class); - configuration = ConfigurationProvider.getProvider(YamlConfiguration.class).load(new File(getDataFolder(), "config.yml")); } catch (IOException ex) { getLogger().log(Level.WARNING, LanguageUtils.getLocaleStringLog("geyser.config.failed"), ex); ex.printStackTrace(); @@ -108,7 +103,7 @@ public void onEnable() { geyserConfig.getRemote().setAuthType("floodgate"); } - geyserConfig.loadFloodgate(this, configuration); + geyserConfig.loadFloodgate(this); this.connector = GeyserConnector.start(PlatformType.BUNGEECORD, this); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java index 380f7037680..5c48efe887f 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java @@ -27,7 +27,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; @@ -35,26 +34,19 @@ import org.geysermc.connector.configuration.GeyserJacksonConfiguration; import java.nio.file.Path; -import java.nio.file.Paths; @Getter @JsonIgnoreProperties(ignoreUnknown = true) -public class GeyserSpigotConfiguration extends GeyserJacksonConfiguration { - - @JsonProperty("floodgate-key-file") - private String floodgateKeyFile; - +public final class GeyserSpigotConfiguration extends GeyserJacksonConfiguration { @JsonIgnore - private Path floodgateKey; + private Path floodgateKeyPath; public void loadFloodgate(GeyserSpigotPlugin plugin) { Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate-bukkit"); - floodgateKey = FloodgateKeyLoader.getKey(plugin.getGeyserLogger(), this, Paths.get(plugin.getDataFolder().toString(), plugin.getConfig().getString("floodgate-key-file", "public-key.pem")), floodgate, floodgate != null ? floodgate.getDataFolder().toPath() : null); - } + Path geyserDataFolder = plugin.getDataFolder().toPath(); + Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null; - @Override - public Path getFloodgateKeyFile() { - return floodgateKey; + floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgate, floodgateDataFolder, geyserDataFolder, plugin.getGeyserLogger()); } @Override diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotLogger.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotLogger.java index 252d6bbed33..b462f1f1cc1 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotLogger.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotLogger.java @@ -26,7 +26,8 @@ package org.geysermc.platform.spigot; import lombok.AllArgsConstructor; - +import lombok.Getter; +import lombok.Setter; import org.geysermc.connector.GeyserLogger; import java.util.logging.Level; @@ -34,9 +35,9 @@ @AllArgsConstructor public class GeyserSpigotLogger implements GeyserLogger { - - private Logger logger; - private boolean debugMode; + private final Logger logger; + @Getter @Setter + private boolean debug; @Override public void severe(String message) { @@ -70,12 +71,8 @@ public void info(String message) { @Override public void debug(String message) { - if (debugMode) + if (debug) { info(message); - } - - @Override - public void setDebug(boolean debug) { - debugMode = debug; + } } } 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 ba4161105d1..2d5eefebd0b 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java @@ -25,268 +25,13 @@ package org.geysermc.platform.sponge; -import lombok.AllArgsConstructor; -import ninja.leaping.configurate.ConfigurationNode; -import org.geysermc.connector.configuration.GeyserConfiguration; +import org.geysermc.connector.configuration.GeyserJacksonConfiguration; -import java.io.File; import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.*; - -public class GeyserSpongeConfiguration implements GeyserConfiguration { - - private File dataFolder; - private ConfigurationNode node; - - /** - * If the config was originally 'auto' before the values changed - */ - private boolean autoconfiguredRemote = false; - - private SpongeBedrockConfiguration bedrockConfig; - private SpongeRemoteConfiguration remoteConfig; - private SpongeMetricsInfo metricsInfo; - - private Map userAuthInfo = new HashMap<>(); - - public GeyserSpongeConfiguration(File dataFolder, ConfigurationNode node) { - this.dataFolder = dataFolder; - this.node = node; - - this.bedrockConfig = new SpongeBedrockConfiguration(node.getNode("bedrock")); - this.remoteConfig = new SpongeRemoteConfiguration(node.getNode("remote")); - this.metricsInfo = new SpongeMetricsInfo(); - - if (node.getNode("userAuths").getValue() == null) - return; - - List userAuths = new ArrayList(((LinkedHashMap)node.getNode("userAuths").getValue()).keySet()); - for (String key : userAuths) { - userAuthInfo.put(key, new SpongeUserAuthenticationInfo(key)); - } - } - - public void setAutoconfiguredRemote(boolean autoconfiguredRemote) { - this.autoconfiguredRemote = autoconfiguredRemote; - } - - @Override - public SpongeBedrockConfiguration getBedrock() { - return bedrockConfig; - } - - @Override - public SpongeRemoteConfiguration getRemote() { - return remoteConfig; - } - - @Override - public Map getUserAuths() { - return userAuthInfo; - } - - @Override - public boolean isCommandSuggestions() { - return node.getNode("command-suggestions").getBoolean(true); - } - - @Override - public boolean isPassthroughMotd() { - return node.getNode("passthrough-motd").getBoolean(false); - } - - @Override - public boolean isPassthroughProtocolName() { - return node.getNode("passthrough-protocol-name").getBoolean(false); - } - - @Override - public boolean isPassthroughPlayerCounts() { - return node.getNode("passthrough-player-counts").getBoolean(false); - } - - @Override - public boolean isLegacyPingPassthrough() { - return node.getNode("legacy-ping-passthrough").getBoolean(false); - } - - @Override - public int getPingPassthroughInterval() { - return node.getNode("ping-passthrough-interval").getInt(3); - } - - @Override - public int getMaxPlayers() { - return node.getNode("max-players").getInt(100); - } - - @Override - public boolean isDebugMode() { - return node.getNode("debug-mode").getBoolean(false); - } - - @Override - public int getGeneralThreadPool() { - return node.getNode("genereal-thread-pool").getInt(32); - } - - @Override - public boolean isAllowThirdPartyCapes() { - return node.getNode("allow-third-party-capes").getBoolean(true); - } - - @Override - public boolean isAllowThirdPartyEars() { - return node.getNode("allow-third-party-ears").getBoolean(false); - } - - @Override - public boolean isShowCooldown() { - return node.getNode("show-cooldown").getBoolean(true); - } - - @Override - public String getDefaultLocale() { - return node.getNode("default-locale").getString("en_us"); - } - - @Override - public Path getFloodgateKeyFile() { - return Paths.get(dataFolder.toString(), node.getNode("floodgate-key-file").getString("public-key.pem")); - } - - @Override - public boolean isCacheChunks() { - return node.getNode("cache-chunks").getBoolean(false); - } - - @Override - public int getCacheImages() { - return node.getNode("cache-skins").getInt(0); - } - - @Override - public boolean isAboveBedrockNetherBuilding() { - return node.getNode("above-bedrock-nether-building").getBoolean(false); - } - - @Override - public SpongeMetricsInfo getMetrics() { - return metricsInfo; - } - - @AllArgsConstructor - public class SpongeBedrockConfiguration implements IBedrockConfiguration { - - private ConfigurationNode node; - - @Override - public String getAddress() { - return node.getNode("address").getString("0.0.0.0"); - } - - @Override - public int getPort() { - return node.getNode("port").getInt(19132); - } - - @Override - public boolean isCloneRemotePort() { - return node.getNode("clone-remote-port").getBoolean(false); - } - - @Override - public String getMotd1() { - return node.getNode("motd1").getString("GeyserMC"); - } - - @Override - public String getMotd2() { - return node.getNode("motd2").getString("GeyserMC"); - } - - @Override - public String getServerName() { - return node.getNode("server-name").getString("Geyser"); - } - } - - @AllArgsConstructor - public class SpongeRemoteConfiguration implements IRemoteConfiguration { - - private ConfigurationNode node; - - @Override - public String getAddress() { - return node.getNode("address").getString("127.0.0.1"); - } - - @Override - public void setAddress(String address) { - node.getNode("address").setValue(address); - } - - @Override - public int getPort() { - return node.getNode("port").getInt(25565); - } - - @Override - public void setPort(int port) { - node.getNode("port").setValue(port); - } - - @Override - public String getAuthType() { - return node.getNode("auth-type").getString("online"); - } - } - - public class SpongeUserAuthenticationInfo implements IUserAuthenticationInfo { - - private String key; - - public SpongeUserAuthenticationInfo(String key) { - this.key = key; - } - - @Override - public String getEmail() { - return node.getNode("userAuths").getNode(key).getNode("email").getString(); - } - - @Override - public String getPassword() { - return node.getNode("userAuths").getNode(key).getNode("password").getString(); - } - } - - public class SpongeMetricsInfo implements IMetricsInfo { - - @Override - public boolean isEnabled() { - return node.getNode("metrics").getNode("enabled").getBoolean(true); - } - - @Override - public String getUniqueId() { - return node.getNode("metrics").getNode("uuid").getString("generateduuid"); - } - } - - @Override - public boolean isEnableProxyConnections() { - return node.getNode("enable-proxy-connections").getBoolean(false); - } - - @Override - public int getMtu() { - return node.getNode("mtu").getInt(1400); - } +public final class GeyserSpongeConfiguration extends GeyserJacksonConfiguration { @Override - public int getConfigVersion() { - return node.getNode("config-version").getInt(0); + public Path getFloodgateKeyPath() { + return null; //floodgate isn't available for Sponge } } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeLogger.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeLogger.java index fb7cb54bb69..bdbd253116f 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeLogger.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeLogger.java @@ -26,15 +26,16 @@ package org.geysermc.platform.sponge; import lombok.AllArgsConstructor; - +import lombok.Getter; +import lombok.Setter; import org.geysermc.connector.GeyserLogger; import org.slf4j.Logger; @AllArgsConstructor public class GeyserSpongeLogger implements GeyserLogger { - - private Logger logger; - private boolean debugMode; + private final Logger logger; + @Getter @Setter + private boolean debug; @Override public void severe(String message) { @@ -68,12 +69,8 @@ public void info(String message) { @Override public void debug(String message) { - if (debugMode) + if (debug) { info(message); - } - - @Override - public void setDebug(boolean debugMode) { - this.debugMode = debugMode; + } } } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java index 0f1b7253368..106d2b15524 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java @@ -26,9 +26,6 @@ package org.geysermc.platform.sponge; import com.google.inject.Inject; -import ninja.leaping.configurate.ConfigurationNode; -import ninja.leaping.configurate.loader.ConfigurationLoader; -import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; @@ -85,20 +82,14 @@ public void onEnable() { ex.printStackTrace(); } - ConfigurationLoader loader = YAMLConfigurationLoader.builder().setPath(configFile.toPath()).build(); - ConfigurationNode config; try { - config = loader.load(); - this.geyserConfig = new GeyserSpongeConfiguration(configDir, config); + this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpongeConfiguration.class); } catch (IOException ex) { logger.warn(LanguageUtils.getLocaleStringLog("geyser.config.failed")); ex.printStackTrace(); return; } - ConfigurationNode serverIP = config.getNode("remote").getNode("address"); - ConfigurationNode serverPort = config.getNode("remote").getNode("port"); - if (Sponge.getServer().getBoundAddress().isPresent()) { InetSocketAddress javaAddr = Sponge.getServer().getBoundAddress().get(); @@ -106,13 +97,12 @@ public void onEnable() { // By default this should be 127.0.0.1 but may need to be changed in some circumstances if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { this.geyserConfig.setAutoconfiguredRemote(true); - serverPort.setValue(javaAddr.getPort()); + geyserConfig.getRemote().setPort(javaAddr.getPort()); } } - ConfigurationNode bedrockPort = config.getNode("bedrock").getNode("port"); if (geyserConfig.getBedrock().isCloneRemotePort()){ - bedrockPort.setValue(serverPort.getValue()); + geyserConfig.getBedrock().setPort(geyserConfig.getRemote().getPort()); } this.geyserLogger = new GeyserSpongeLogger(logger, geyserConfig.isDebugMode()); diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneConfiguration.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneConfiguration.java index 29e18d08f50..5707863c23c 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneConfiguration.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneConfiguration.java @@ -26,7 +26,6 @@ package org.geysermc.platform.standalone; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import org.geysermc.connector.configuration.GeyserJacksonConfiguration; @@ -35,13 +34,9 @@ @Getter @JsonIgnoreProperties(ignoreUnknown = true) -public class GeyserStandaloneConfiguration extends GeyserJacksonConfiguration { - - @JsonProperty("floodgate-key-file") - private String floodgateKeyFile; - +public final class GeyserStandaloneConfiguration extends GeyserJacksonConfiguration { @Override - public Path getFloodgateKeyFile() { - return Paths.get(floodgateKeyFile); + public Path getFloodgateKeyPath() { + return Paths.get(getFloodgateKeyFile()); } } diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java index 0d9ced951ec..f0f7156f926 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java @@ -26,18 +26,16 @@ package org.geysermc.platform.standalone; import lombok.extern.log4j.Log4j2; - import net.minecrell.terminalconsole.SimpleTerminalConsole; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.config.Configurator; -import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.GeyserLogger; import org.geysermc.connector.command.CommandSender; +import org.geysermc.connector.common.ChatColor; @Log4j2 -public class GeyserStandaloneLogger extends SimpleTerminalConsole implements org.geysermc.connector.GeyserLogger, CommandSender { - +public class GeyserStandaloneLogger extends SimpleTerminalConsole implements GeyserLogger, CommandSender { private boolean colored = true; @Override @@ -99,10 +97,6 @@ public void setDebug(boolean debug) { Configurator.setLevel(log.getName(), debug ? Level.DEBUG : Level.INFO); } - /** - * Used for setting debug mode in GUI mode - * @return if debug is enabled - */ public boolean isDebug() { return log.isDebugEnabled(); } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java index 0758044377a..7dc6746b063 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java @@ -27,7 +27,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.proxy.ProxyServer; import lombok.Getter; @@ -37,25 +36,15 @@ import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Optional; @Getter @JsonIgnoreProperties(ignoreUnknown = true) -public class GeyserVelocityConfiguration extends GeyserJacksonConfiguration { - - @JsonProperty("floodgate-key-file") - private String floodgateKeyFile; - +public final class GeyserVelocityConfiguration extends GeyserJacksonConfiguration { @JsonIgnore - private Path floodgateKey; - - @Override - public Path getFloodgateKeyFile() { - return floodgateKey; - } + private Path floodgateKeyPath; public void loadFloodgate(GeyserVelocityPlugin plugin, ProxyServer proxyServer, File dataFolder) { - Optional floodgate = proxyServer.getPluginManager().getPlugin("floodgate"); - floodgate.ifPresent(it -> floodgateKey = FloodgateKeyLoader.getKey(plugin.getGeyserLogger(), this, Paths.get(dataFolder.toString(), floodgateKeyFile.isEmpty() ? floodgateKeyFile : "public-key.pem"), it, Paths.get("plugins/floodgate/"))); + PluginContainer floodgate = proxyServer.getPluginManager().getPlugin("floodgate").orElse(null); + floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgate, Paths.get("plugins/floodgate/"), dataFolder.toPath(), plugin.getGeyserLogger()); } } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityLogger.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityLogger.java index a935d786c2d..cb98411c80b 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityLogger.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityLogger.java @@ -26,15 +26,16 @@ package org.geysermc.platform.velocity; import lombok.AllArgsConstructor; - +import lombok.Getter; +import lombok.Setter; import org.geysermc.connector.GeyserLogger; import org.slf4j.Logger; @AllArgsConstructor public class GeyserVelocityLogger implements GeyserLogger { - - private Logger logger; - private boolean debugMode; + private final Logger logger; + @Getter @Setter + private boolean debug; @Override public void severe(String message) { @@ -68,12 +69,8 @@ public void info(String message) { @Override public void debug(String message) { - if (debugMode) + if (debug) { info(message); - } - - @Override - public void setDebug(boolean debugMode) { - this.debugMode = debugMode; + } } } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java b/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java index a30a5f4d4a3..ec5dd349a97 100644 --- a/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java +++ b/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java @@ -25,17 +25,19 @@ package org.geysermc.connector; +import org.geysermc.connector.configuration.GeyserJacksonConfiguration; import org.geysermc.connector.utils.LanguageUtils; -import org.geysermc.connector.configuration.GeyserConfiguration; import java.nio.file.Files; import java.nio.file.Path; public class FloodgateKeyLoader { - public static Path getKey(GeyserLogger logger, GeyserConfiguration config, Path floodgateKey, Object floodgate, Path floodgateFolder) { + public static Path getKeyPath(GeyserJacksonConfiguration config, Object floodgate, Path floodgateDataFolder, Path geyserDataFolder, GeyserLogger logger) { + Path floodgateKey = geyserDataFolder.resolve(config.getFloodgateKeyFile()); + if (!Files.exists(floodgateKey) && config.getRemote().getAuthType().equals("floodgate")) { if (floodgate != null) { - Path autoKey = floodgateFolder.resolve("public-key.pem"); + Path autoKey = floodgateDataFolder.resolve("public-key.pem"); if (Files.exists(autoKey)) { logger.info(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.auto_loaded")); floodgateKey = autoKey; diff --git a/connector/src/main/java/org/geysermc/connector/GeyserLogger.java b/connector/src/main/java/org/geysermc/connector/GeyserLogger.java index 0ba2a689f94..138285eb1c8 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserLogger.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserLogger.java @@ -84,4 +84,9 @@ public interface GeyserLogger { * @param debug if the logger should print debug messages */ void setDebug(boolean debug); + + /** + * If debug is enabled for this logger + */ + boolean isDebug(); } diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java index 5803ff13b33..2d76e574068 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java @@ -27,7 +27,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import org.geysermc.connector.GeyserLogger; - import org.geysermc.connector.utils.LanguageUtils; import java.nio.file.Path; @@ -74,7 +73,7 @@ public interface GeyserConfiguration { String getDefaultLocale(); - Path getFloodgateKeyFile(); + Path getFloodgateKeyPath(); boolean isAboveBedrockNetherBuilding(); @@ -125,6 +124,8 @@ interface IMetricsInfo { String getUniqueId(); } + int getScoreboardPacketThreshold(); + // if u have offline mode enabled pls be safe boolean isEnableProxyConnections(); diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java index 378eba681a2..6748ef7e7c3 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java @@ -49,76 +49,75 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration private RemoteConfiguration remote; @JsonProperty("floodgate-key-file") - private String floodgateKeyFile; + private String floodgateKeyFile = "public-key.pem"; - public abstract Path getFloodgateKeyFile(); + public abstract Path getFloodgateKeyPath(); private Map userAuths; @JsonProperty("command-suggestions") - private boolean commandSuggestions; + private boolean commandSuggestions = true; @JsonProperty("passthrough-motd") - private boolean isPassthroughMotd; + private boolean isPassthroughMotd = false; @JsonProperty("passthrough-player-counts") - private boolean isPassthroughPlayerCounts; + private boolean isPassthroughPlayerCounts = false; @JsonProperty("passthrough-protocol-name") - private boolean isPassthroughProtocolName; + private boolean isPassthroughProtocolName = false; @JsonProperty("legacy-ping-passthrough") - private boolean isLegacyPingPassthrough; + private boolean isLegacyPingPassthrough = false; @JsonProperty("ping-passthrough-interval") - private int pingPassthroughInterval; + private int pingPassthroughInterval = 3; @JsonProperty("max-players") - private int maxPlayers; + private int maxPlayers = 100; @JsonProperty("debug-mode") - private boolean debugMode; + private boolean debugMode = false; @JsonProperty("general-thread-pool") - private int generalThreadPool; + private int generalThreadPool = 32; @JsonProperty("allow-third-party-capes") - private boolean allowThirdPartyCapes; + private boolean allowThirdPartyCapes = true; @JsonProperty("show-cooldown") private boolean showCooldown = true; @JsonProperty("allow-third-party-ears") - private boolean allowThirdPartyEars; + private boolean allowThirdPartyEars = false; @JsonProperty("default-locale") - private String defaultLocale; + private String defaultLocale = null; // is null by default so system language takes priority @JsonProperty("cache-chunks") - private boolean cacheChunks; + private boolean cacheChunks = false; @JsonProperty("cache-images") private int cacheImages = 0; @JsonProperty("above-bedrock-nether-building") - private boolean aboveBedrockNetherBuilding; + private boolean aboveBedrockNetherBuilding = false; private MetricsInfo metrics; @Getter public static class BedrockConfiguration implements IBedrockConfiguration { - @AsteriskSerializer.Asterisk(sensitive = true) - private String address; + private String address = "0.0.0.0"; @Setter - private int port; + private int port = 19132; @JsonProperty("clone-remote-port") - private boolean cloneRemotePort; + private boolean cloneRemotePort = false; - private String motd1; - private String motd2; + private String motd1 = "GeyserMC"; + private String motd2 = "Geyser"; @JsonProperty("server-name") private String serverName = GeyserConnector.NAME; @@ -126,17 +125,16 @@ public static class BedrockConfiguration implements IBedrockConfiguration { @Getter public static class RemoteConfiguration implements IRemoteConfiguration { - @Setter @AsteriskSerializer.Asterisk(sensitive = true) - private String address; + private String address = "auto"; @Setter - private int port; + private int port = 25565; @Setter @JsonProperty("auth-type") - private String authType; + private String authType = "online"; } @Getter @@ -150,13 +148,15 @@ public static class UserAuthenticationInfo implements IUserAuthenticationInfo { @Getter public static class MetricsInfo implements IMetricsInfo { - - private boolean enabled; + private boolean enabled = true; @JsonProperty("uuid") - private String uniqueId; + private String uniqueId = "generateuuid"; } + @JsonProperty("scoreboard-packet-threshold") + private int scoreboardPacketThreshold = 10; + @JsonProperty("enable-proxy-connections") private boolean enableProxyConnections = false; @@ -164,5 +164,5 @@ public static class MetricsInfo implements IMetricsInfo { private int mtu = 1400; @JsonProperty("config-version") - private int configVersion; + private int configVersion = 0; } 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 52b2735137b..da75812fb62 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -30,13 +30,15 @@ import com.github.steveice10.mc.protocol.data.message.TextMessage; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.AdventureSetting; import com.nukkitx.protocol.bedrock.data.AttributeData; import com.nukkitx.protocol.bedrock.data.PlayerPermission; import com.nukkitx.protocol.bedrock.data.command.CommandPermission; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData; -import com.nukkitx.protocol.bedrock.packet.*; +import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket; +import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; +import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket; +import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; import lombok.Getter; import lombok.Setter; import org.geysermc.connector.entity.attribute.Attribute; @@ -48,7 +50,10 @@ import org.geysermc.connector.utils.AttributeUtils; import org.geysermc.connector.utils.MessageUtils; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; import java.util.concurrent.TimeUnit; @Getter @Setter @@ -209,11 +214,6 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s super.updateBedrockMetadata(entityMetadata, session); if (entityMetadata.getId() == 2) { - // System.out.println(session.getScoreboardCache().getScoreboard().getObjectives().keySet()); - for (Team team : session.getWorldCache().getScoreboard().getTeams().values()) { - // session.getConnector().getLogger().info("team name " + team.getName()); - // session.getConnector().getLogger().info("team entities " + team.getEntities()); - } String username = this.username; TextMessage name = (TextMessage) entityMetadata.getValue(); if (name != null) { @@ -221,7 +221,6 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s } Team team = session.getWorldCache().getScoreboard().getTeamFor(username); if (team != null) { - // session.getConnector().getLogger().info("team name es " + team.getName() + " with prefix " + team.getPrefix() + " and suffix " + team.getSuffix()); metadata.put(EntityData.NAMETAG, team.getPrefix() + MessageUtils.toChatColor(team.getColor()) + username + team.getSuffix()); } } 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 b208aaf3cb9..3b27fc6aeff 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 @@ -346,7 +346,7 @@ public void authenticate(String username, String password) { PublicKey key = null; try { key = EncryptionUtil.getKeyFromFile( - connector.getConfig().getFloodgateKeyFile(), + connector.getConfig().getFloodgateKeyPath(), PublicKey.class ); } catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) { @@ -626,7 +626,7 @@ public boolean confirmTeleport(Vector3d position) { * @param packet the bedrock packet from the NukkitX protocol lib */ public void sendUpstreamPacket(BedrockPacket packet) { - if (upstream != null && !upstream.isClosed()) { + if (upstream != null) { upstream.sendPacket(packet); } else { connector.getLogger().debug("Tried to send upstream packet " + packet.getClass().getSimpleName() + " but the session was null"); @@ -639,7 +639,7 @@ public void sendUpstreamPacket(BedrockPacket packet) { * @param packet the bedrock packet from the NukkitX protocol lib */ public void sendUpstreamPacketImmediately(BedrockPacket packet) { - if (upstream != null && !upstream.isClosed()) { + if (upstream != null) { upstream.sendPacketImmediately(packet); } else { connector.getLogger().debug("Tried to send upstream packet " + packet.getClass().getSimpleName() + " immediately but the session was null"); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java b/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java index 09870eef000..393ebfa82fa 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java @@ -41,17 +41,15 @@ public class UpstreamSession { private boolean initialized = false; public void sendPacket(@NonNull BedrockPacket packet) { - if (isClosed()) - return; - - session.sendPacket(packet); + if (!isClosed()) { + session.sendPacket(packet); + } } public void sendPacketImmediately(@NonNull BedrockPacket packet) { - if (isClosed()) - return; - - session.sendPacketImmediately(packet); + if (!isClosed()) { + session.sendPacketImmediately(packet); + } } public void disconnect(String reason) { diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java index 310e5f9d7f7..ce49d2a0986 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java @@ -31,37 +31,40 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.scoreboard.Objective; import org.geysermc.connector.scoreboard.Scoreboard; - -import java.util.Collection; +import org.geysermc.connector.scoreboard.ScoreboardUpdater; @Getter public class WorldCache { - - private GeyserSession session; - + private final GeyserSession session; @Setter private Difficulty difficulty = Difficulty.EASY; - private boolean showCoordinates = true; private Scoreboard scoreboard; + private final ScoreboardUpdater scoreboardUpdater; public WorldCache(GeyserSession session) { this.session = session; this.scoreboard = new Scoreboard(session); + scoreboardUpdater = new ScoreboardUpdater(this); + scoreboardUpdater.start(); } public void removeScoreboard() { if (scoreboard != null) { - Collection objectives = scoreboard.getObjectives().values(); - scoreboard = new Scoreboard(session); - - for (Objective objective : objectives) { + for (Objective objective : scoreboard.getObjectives().values()) { scoreboard.despawnObjective(objective); } + scoreboard = new Scoreboard(session); } } + public int increaseAndGetScoreboardPacketsPerSecond() { + int pendingPps = scoreboardUpdater.incrementAndGetPacketsPerSecond(); + int pps = scoreboardUpdater.getPacketsPerSecond(); + return Math.max(pps, pendingPps); + } + /** * Tell the client to hide or show the coordinates * diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java index ce1ba398765..141ff03f1ee 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java @@ -41,13 +41,11 @@ public class JavaScoreboardObjectiveTranslator extends PacketTranslator { + private static final GeyserLogger LOGGER = GeyserConnector.getInstance().getLogger(); @Override public void translate(ServerTeamPacket packet, GeyserSession session) { - GeyserConnector.getInstance().getLogger().debug("Team packet " + packet.getTeamName() + " " + packet.getAction() + " " + Arrays.toString(packet.getPlayers())); + if (LOGGER.isDebug()) { + LOGGER.debug("Team packet " + packet.getTeamName() + " " + packet.getAction() + " " + Arrays.toString(packet.getPlayers())); + } + + int pps = session.getWorldCache().increaseAndGetScoreboardPacketsPerSecond(); Scoreboard scoreboard = session.getWorldCache().getScoreboard(); Team team = scoreboard.getTeam(packet.getTeamName()); @@ -58,38 +65,53 @@ public void translate(ServerTeamPacket packet, GeyserSession session) { .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode())); break; case UPDATE: - if (team != null) { - team.setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) - .setColor(packet.getColor()) - .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getClientData().getLanguageCode())) - .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode())) - .setUpdateType(UpdateType.UPDATE); - } else { - GeyserConnector.getInstance().getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_not_registered", packet.getAction(), packet.getTeamName())); + if (team == null) { + LOGGER.debug(LanguageUtils.getLocaleStringLog( + "geyser.network.translator.team.failed_not_registered", + packet.getAction(), packet.getTeamName() + )); + return; } + + team.setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) + .setColor(packet.getColor()) + .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getClientData().getLanguageCode())) + .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode())) + .setUpdateType(UpdateType.UPDATE); break; case ADD_PLAYER: - if (team != null) { - team.addEntities(packet.getPlayers()); - } else { - GeyserConnector.getInstance().getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_not_registered", packet.getAction(), packet.getTeamName())); + if (team == null) { + LOGGER.debug(LanguageUtils.getLocaleStringLog( + "geyser.network.translator.team.failed_not_registered", + packet.getAction(), packet.getTeamName() + )); + return; } + team.addEntities(packet.getPlayers()); break; case REMOVE_PLAYER: - if (team != null) { - team.removeEntities(packet.getPlayers()); - } else { - GeyserConnector.getInstance().getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_not_registered", packet.getAction(), packet.getTeamName())); + if (team == null) { + LOGGER.debug(LanguageUtils.getLocaleStringLog( + "geyser.network.translator.team.failed_not_registered", + packet.getAction(), packet.getTeamName() + )); + return; } + team.removeEntities(packet.getPlayers()); break; case REMOVE: scoreboard.removeTeam(packet.getTeamName()); break; } - scoreboard.onUpdate(); + + // ScoreboardUpdater will handle it for us if the packets per second + // (for score and team packets) is higher then the first threshold + if (pps < ScoreboardUpdater.FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) { + scoreboard.onUpdate(); + } } private Set toPlayerSet(String[] players) { - return new ObjectOpenHashSet<>(Arrays.asList(players)); + return new ObjectOpenHashSet<>(players); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java index 8d7d59a892c..35033ca52a7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java @@ -25,48 +25,58 @@ package org.geysermc.connector.network.translators.java.scoreboard; +import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardAction; +import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerUpdateScorePacket; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.GeyserLogger; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.session.cache.WorldCache; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.scoreboard.Objective; import org.geysermc.connector.scoreboard.Scoreboard; - -import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardAction; -import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerUpdateScorePacket; +import org.geysermc.connector.scoreboard.ScoreboardUpdater; import org.geysermc.connector.utils.LanguageUtils; @Translator(packet = ServerUpdateScorePacket.class) public class JavaUpdateScoreTranslator extends PacketTranslator { + private final GeyserLogger logger; + + public JavaUpdateScoreTranslator() { + logger = GeyserConnector.getInstance().getLogger(); + } @Override public void translate(ServerUpdateScorePacket packet, GeyserSession session) { - try { - Scoreboard scoreboard = session.getWorldCache().getScoreboard(); + WorldCache worldCache = session.getWorldCache(); + Scoreboard scoreboard = worldCache.getScoreboard(); + int pps = worldCache.increaseAndGetScoreboardPacketsPerSecond(); - Objective objective = scoreboard.getObjective(packet.getObjective()); - if (objective == null && packet.getAction() != ScoreboardAction.REMOVE) { - GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.translator.score.failed_objective", packet.getObjective())); - return; - } + Objective objective = scoreboard.getObjective(packet.getObjective()); + if (objective == null && packet.getAction() != ScoreboardAction.REMOVE) { + logger.info(LanguageUtils.getLocaleStringLog("geyser.network.translator.score.failed_objective", packet.getObjective())); + return; + } - switch (packet.getAction()) { - case ADD_OR_UPDATE: - objective.setScore(packet.getEntry(), packet.getValue()); - break; - case REMOVE: - if (objective != null) { - objective.resetScore(packet.getEntry()); - } else { - for (Objective objective1 : scoreboard.getObjectives().values()) { - objective1.resetScore(packet.getEntry()); - } + switch (packet.getAction()) { + case ADD_OR_UPDATE: + objective.setScore(packet.getEntry(), packet.getValue()); + break; + case REMOVE: + if (objective != null) { + objective.removeScore(packet.getEntry()); + } else { + for (Objective objective1 : scoreboard.getObjectives().values()) { + objective1.removeScore(packet.getEntry()); } - break; - } + } + break; + } + + // ScoreboardUpdater will handle it for us if the packets per second + // (for score and team packets) is higher then the first threshold + if (pps < ScoreboardUpdater.FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) { scoreboard.onUpdate(); - } catch (Exception ex) { - ex.printStackTrace(); } } } diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java index c3e6c863c2a..92a1add3428 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java @@ -29,23 +29,23 @@ import lombok.Getter; import lombok.Setter; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; @Getter public class Objective { - private Scoreboard scoreboard; - private long id; - private boolean temp; + private final Scoreboard scoreboard; + private final long id; + private boolean active = true; @Setter private UpdateType updateType = UpdateType.ADD; private String objectiveName; - private String displaySlot; + private String displaySlotName; private String displayName = "unknown"; private int type = 0; // 0 = integer, 1 = heart - private Map scores = new HashMap<>(); + private Map scores = new ConcurrentHashMap<>(); private Objective(Scoreboard scoreboard) { this.id = scoreboard.getNextId().getAndIncrement(); @@ -54,23 +54,20 @@ private Objective(Scoreboard scoreboard) { /** * /!\ This method is made for temporary objectives until the real objective is received - * @param scoreboard the scoreboard + * + * @param scoreboard the scoreboard * @param objectiveName the name of the objective */ public Objective(Scoreboard scoreboard, String objectiveName) { this(scoreboard); this.objectiveName = objectiveName; - this.temp = true; + this.active = false; } public Objective(Scoreboard scoreboard, String objectiveName, ScoreboardPosition displaySlot, String displayName, int type) { - this(scoreboard, objectiveName, displaySlot.name().toLowerCase(), displayName, type); - } - - public Objective(Scoreboard scoreboard, String objectiveName, String displaySlot, String displayName, int type) { this(scoreboard); this.objectiveName = objectiveName; - this.displaySlot = displaySlot; + this.displaySlotName = translateDisplaySlot(displaySlot); this.displayName = displayName; this.type = type; } @@ -87,21 +84,9 @@ public void registerScore(String id, int score) { public void setScore(String id, int score) { if (scores.containsKey(id)) { scores.get(id).setScore(score).setUpdateType(UpdateType.ADD); - } else { - registerScore(id, score); + return; } - } - - public void setScoreText(String oldText, String newText) { - if (!scores.containsKey(oldText) || oldText.equals(newText)) return; - Score oldScore = scores.get(oldText); - - Score newScore = new Score(this, newText) - .setScore(oldScore.getScore()) - .setTeam(scoreboard.getTeamFor(newText)); - - scores.put(newText, newScore); - oldScore.setUpdateType(UpdateType.REMOVE); + registerScore(id, score); } public int getScore(String id) { @@ -113,37 +98,61 @@ public int getScore(String id) { public Score getScore(int line) { for (Score score : scores.values()) { - if (score.getScore() == line) return score; + if (score.getScore() == line) { + return score; + } } return null; } - public void resetScore(String id) { + public void removeScore(String id) { if (scores.containsKey(id)) { scores.get(id).setUpdateType(UpdateType.REMOVE); } } - public void removeScore(String id) { + /** + * Used internally to remove a score from the score map + */ + public void removeScore0(String id) { scores.remove(id); } public Objective setDisplayName(String displayName) { this.displayName = displayName; - if (updateType == UpdateType.NOTHING) updateType = UpdateType.UPDATE; + if (updateType == UpdateType.NOTHING) { + updateType = UpdateType.UPDATE; + } return this; } public Objective setType(int type) { this.type = type; - if (updateType == UpdateType.NOTHING) updateType = UpdateType.UPDATE; + if (updateType == UpdateType.NOTHING) { + updateType = UpdateType.UPDATE; + } return this; } - public void removeTemp(ScoreboardPosition displaySlot) { - if (temp) { - temp = false; - this.displaySlot = displaySlot.name().toLowerCase(); + public void setActive(ScoreboardPosition displaySlot) { + if (!active) { + active = true; + displaySlotName = translateDisplaySlot(displaySlot); + } + } + + public void removed() { + scores = null; + } + + private static String translateDisplaySlot(ScoreboardPosition displaySlot) { + switch (displaySlot) { + case BELOW_NAME: + return "belowname"; + case PLAYER_LIST: + return "list"; + default: + return "sidebar"; } } } diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java index d5a65e8c0a8..635bafa3d0c 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java @@ -25,40 +25,63 @@ package org.geysermc.connector.scoreboard; +import com.nukkitx.protocol.bedrock.data.ScoreInfo; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; -@Getter @Setter +@Getter @Accessors(chain = true) public class Score { - private Objective objective; - private long id; + private final Objective objective; + private ScoreInfo cachedInfo; + private final long id; + @Setter private UpdateType updateType = UpdateType.ADD; - private String name; + private final String name; private Team team; private int score; + @Setter private int oldScore = Integer.MIN_VALUE; public Score(Objective objective, String name) { this.id = objective.getScoreboard().getNextId().getAndIncrement(); this.objective = objective; this.name = name; + update(); } public String getDisplayName() { - if (team != null && team.getUpdateType() != UpdateType.REMOVE) { + if (team != null) { return team.getPrefix() + name + team.getSuffix(); } return name; } public Score setScore(int score) { - if (oldScore == Integer.MIN_VALUE) { - this.oldScore = score; - } this.score = score; + updateType = UpdateType.UPDATE; + return this; + } + + public Score setTeam(Team team) { + if (this.team != null && team != null) { + if (!this.team.equals(team)) { + this.team = team; + updateType = UpdateType.UPDATE; + } + return this; + } + // simplified from (this.team != null && team == null) || (this.team == null && team != null) + if (this.team != null || team != null) { + this.team = team; + updateType = UpdateType.UPDATE; + } return this; } + + public void update() { + cachedInfo = new ScoreInfo(id, objective.getObjectiveName(), score, getDisplayName()); + } } diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java index 5fdda617ff1..9f89d9d2bc3 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java @@ -26,73 +26,72 @@ package org.geysermc.connector.scoreboard; import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition; +import com.nukkitx.protocol.bedrock.BedrockPacket; import com.nukkitx.protocol.bedrock.data.ScoreInfo; import com.nukkitx.protocol.bedrock.packet.RemoveObjectivePacket; import com.nukkitx.protocol.bedrock.packet.SetDisplayObjectivePacket; import com.nukkitx.protocol.bedrock.packet.SetScorePacket; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import lombok.Getter; - +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.GeyserLogger; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.LanguageUtils; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import static org.geysermc.connector.scoreboard.UpdateType.*; @Getter public class Scoreboard { - private GeyserSession session; - private AtomicLong nextId = new AtomicLong(0); + private final GeyserSession session; + private final GeyserLogger logger; + private final AtomicLong nextId = new AtomicLong(0); + + private final Map objectives = new ConcurrentHashMap<>(); + private final Map teams = new HashMap<>(); - private Map objectives = new HashMap<>(); - private Map teams = new HashMap<>(); + private int lastScoreCount = 0; public Scoreboard(GeyserSession session) { this.session = session; + this.logger = GeyserConnector.getInstance().getLogger(); } - public Objective registerNewObjective(String objectiveId, boolean temp) { - if (!temp || objectives.containsKey(objectiveId)) return objectives.get(objectiveId); + public Objective registerNewObjective(String objectiveId, boolean active) { + if (active || objectives.containsKey(objectiveId)) { + return objectives.get(objectiveId); + } Objective objective = new Objective(this, objectiveId); objectives.put(objectiveId, objective); return objective; } public Objective registerNewObjective(String objectiveId, ScoreboardPosition displaySlot) { - Objective objective = null; - if (objectives.containsKey(objectiveId)) { - objective = objectives.get(objectiveId); - if (objective.isTemp()) objective.removeTemp(displaySlot); - else { - despawnObjective(objective); - objective = null; + Objective objective = objectives.get(objectiveId); + if (objective != null) { + if (!objective.isActive()) { + objective.setActive(displaySlot); + return objective; } + despawnObjective(objective); } - if (objective == null) { - objective = new Objective(this, objectiveId, displaySlot, "unknown", 0); - objectives.put(objectiveId, objective); - } + + objective = new Objective(this, objectiveId, displaySlot, "unknown", 0); + objectives.put(objectiveId, objective); return objective; } public Team registerNewTeam(String teamName, Set players) { - if (teams.containsKey(teamName)) { - session.getConnector().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_overrides", teamName)); - return getTeam(teamName); + Team team = teams.get(teamName); + if (team != null) { + logger.info(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_overrides", teamName)); + return team; } - Team team = new Team(this, teamName).setEntities(players); + team = new Team(this, teamName).setEntities(players); teams.put(teamName, team); - - for (Objective objective : objectives.values()) { - for (Score score : objective.getScores().values()) { - if (players.contains(score.getName())) { - score.setTeam(team); - } - } - } return team; } @@ -106,102 +105,119 @@ public Team getTeam(String teamName) { public void unregisterObjective(String objectiveName) { Objective objective = getObjective(objectiveName); - if (objective != null) objective.setUpdateType(REMOVE); + if (objective != null) { + objective.setUpdateType(REMOVE); + } } public void removeTeam(String teamName) { Team remove = teams.remove(teamName); - if (remove != null) remove.setUpdateType(REMOVE); + if (remove != null) { + remove.setUpdateType(REMOVE); + } } public void onUpdate() { - Set changedObjectives = new ObjectOpenHashSet<>(); - List addScores = new ArrayList<>(); - List removeScores = new ArrayList<>(); - - for (String objectiveId : new ArrayList<>(objectives.keySet())) { - Objective objective = objectives.get(objectiveId); - if (objective.isTemp()) { - session.getConnector().getLogger().debug("Ignoring temp Scoreboard Objective '"+ objectiveId +'\''); + List addScores = new ArrayList<>(getLastScoreCount()); + List removeScores = new ArrayList<>(getLastScoreCount()); + + for (Objective objective : objectives.values()) { + if (!objective.isActive()) { + logger.debug("Ignoring non-active Scoreboard Objective '"+ objective.getObjectiveName() +'\''); continue; } - if (objective.getUpdateType() != NOTHING) changedObjectives.add(objective); + // hearts can't hold teams, so we treat them differently + if (objective.getType() == 1) { + for (Score score : objective.getScores().values()) { + if (score.getUpdateType() == NOTHING) { + continue; + } - boolean globalUpdate = objective.getUpdateType() == UPDATE; - boolean globalAdd = objective.getUpdateType() == ADD || globalUpdate; - boolean globalRemove = objective.getUpdateType() == REMOVE || globalUpdate; + boolean update = score.getUpdateType() == UPDATE; + if (update) { + score.update(); + } - boolean hasUpdate = globalUpdate; + if (score.getUpdateType() == ADD || update) { + addScores.add(score.getCachedInfo()); + } + if (score.getUpdateType() == REMOVE || update) { + removeScores.add(score.getCachedInfo()); + } + } + continue; + } - List handledScores = new ArrayList<>(); - for (String identifier : new ObjectOpenHashSet<>(objective.getScores().keySet())) { - Score score = objective.getScores().get(identifier); - Team team = score.getTeam(); + boolean globalUpdate = objective.getUpdateType() == UPDATE; + boolean globalAdd = objective.getUpdateType() == ADD; + boolean globalRemove = objective.getUpdateType() == REMOVE; - boolean inTeam = team != null && team.getEntities().contains(score.getName()); + for (Score score : objective.getScores().values()) { + Team team = score.getTeam(); - boolean teamAdd = team != null && (team.getUpdateType() == ADD || team.getUpdateType() == UPDATE); - boolean teamRemove = team != null && (team.getUpdateType() == REMOVE || team.getUpdateType() == UPDATE); + boolean add = globalAdd || globalUpdate; + boolean remove = globalRemove; + boolean teamChanged = false; + if (team != null) { + if (team.getUpdateType() == REMOVE || !team.hasEntity(score.getName())) { + score.setTeam(null); + teamChanged = true; + } - if (team != null && (team.getUpdateType() == REMOVE || !inTeam)) score.setTeam(null); + teamChanged |= team.getUpdateType() == UPDATE; - boolean add = (hasUpdate || globalAdd || teamAdd || teamRemove || score.getUpdateType() == ADD || score.getUpdateType() == UPDATE) && (score.getUpdateType() != REMOVE); - boolean remove = hasUpdate || globalRemove || teamAdd || teamRemove || score.getUpdateType() == REMOVE || score.getUpdateType() == UPDATE; + add |= team.getUpdateType() == ADD || team.getUpdateType() == UPDATE; + remove |= team.getUpdateType() == REMOVE; + } - boolean updated = false; - if (!hasUpdate) { - updated = hasUpdate = add; + add |= score.getUpdateType() == ADD || score.getUpdateType() == UPDATE; + remove |= score.getUpdateType() == REMOVE; + if (score.getUpdateType() == REMOVE) { + add = false; } - if (updated) { - for (Score score1 : handledScores) { - ScoreInfo scoreInfo = new ScoreInfo(score1.getId(), score1.getObjective().getObjectiveName(), score1.getScore(), score1.getDisplayName()); - addScores.add(scoreInfo); - removeScores.add(scoreInfo); - } + if (score.getUpdateType() == UPDATE || teamChanged) { + score.update(); } if (add) { - addScores.add(new ScoreInfo(score.getId(), score.getObjective().getObjectiveName(), score.getScore(), score.getDisplayName())); + addScores.add(score.getCachedInfo()); } if (remove) { - removeScores.add(new ScoreInfo(score.getId(), score.getObjective().getObjectiveName(), score.getOldScore(), score.getDisplayName())); + removeScores.add(score.getCachedInfo()); } + // score is pending to be updated, so we use the current score as the old score score.setOldScore(score.getScore()); + // score is pending to be removed, so we can remove it from the objective if (score.getUpdateType() == REMOVE) { - objective.removeScore(score.getName()); + objective.removeScore0(score.getName()); } - if (add || remove) { - changedObjectives.add(objective); - } else { // stays the same like before - handledScores.add(score); - } score.setUpdateType(NOTHING); } - } - for (Objective objective : changedObjectives) { - boolean update = objective.getUpdateType() == NOTHING || objective.getUpdateType() == UPDATE; - if (objective.getUpdateType() == REMOVE || update) { + if (globalRemove || globalUpdate) { RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); session.sendUpstreamPacket(removeObjectivePacket); if (objective.getUpdateType() == REMOVE) { objectives.remove(objective.getObjectiveName()); // now we can deregister + objective.removed(); } } - if (objective.getUpdateType() == ADD || update) { + + if (globalAdd || globalUpdate) { SetDisplayObjectivePacket displayObjectivePacket = new SetDisplayObjectivePacket(); displayObjectivePacket.setObjectiveId(objective.getObjectiveName()); displayObjectivePacket.setDisplayName(objective.getDisplayName()); displayObjectivePacket.setCriteria("dummy"); - displayObjectivePacket.setDisplaySlot(objective.getDisplaySlot()); + displayObjectivePacket.setDisplaySlot(objective.getDisplaySlotName()); displayObjectivePacket.setSortOrder(1); // ?? session.sendUpstreamPacket(displayObjectivePacket); } + objective.setUpdateType(NOTHING); } @@ -218,6 +234,8 @@ public void onUpdate() { setScorePacket.setInfos(addScores); session.sendUpstreamPacket(setScorePacket); } + + lastScoreCount = addScores.size(); } public void despawnObjective(Objective objective) { @@ -234,6 +252,8 @@ public void despawnObjective(Objective objective) { 0, "" )); } + + objective.removed(); if (!toRemove.isEmpty()) { SetScorePacket setScorePacket = new SetScorePacket(); diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java b/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java new file mode 100644 index 00000000000..ee38cecd988 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.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.scoreboard; + +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.configuration.GeyserConfiguration; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.session.cache.WorldCache; +import org.geysermc.connector.utils.LanguageUtils; + +import java.util.concurrent.atomic.AtomicInteger; + +public class ScoreboardUpdater extends Thread { + public static final int FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD; + public static final int SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD = 250; + + private static final int FIRST_MILLIS_BETWEEN_UPDATES = 250; // 4 updates per second + private static final int SECOND_MILLIS_BETWEEN_UPDATES = 1000 * 3; // 1 update per 3 seconds + + private static final boolean DEBUG_ENABLED; + + private final WorldCache worldCache; + private final GeyserSession session; + + private int millisBetweenUpdates = FIRST_MILLIS_BETWEEN_UPDATES; + private long lastUpdate = System.currentTimeMillis(); + private long lastLog = -1; + + private long lastPacketsPerSecondUpdate = System.currentTimeMillis(); + private final AtomicInteger packetsPerSecond = new AtomicInteger(0); + private final AtomicInteger pendingPacketsPerSecond = new AtomicInteger(0); + + public ScoreboardUpdater(WorldCache worldCache) { + super("Scoreboard Updater"); + this.worldCache = worldCache; + session = worldCache.getSession(); + } + + @Override + public void run() { + while (!session.isClosed()) { + long currentTime = System.currentTimeMillis(); + + // reset score-packets per second every second + if (currentTime - lastPacketsPerSecondUpdate > 1000) { + lastPacketsPerSecondUpdate = currentTime; + packetsPerSecond.set(pendingPacketsPerSecond.get()); + pendingPacketsPerSecond.set(0); + } + + if (currentTime - lastUpdate > millisBetweenUpdates) { + lastUpdate = currentTime; + + int pps = packetsPerSecond.get(); + if (pps >= FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) { + boolean reachedSecondThreshold = pps >= SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD; + if (reachedSecondThreshold) { + millisBetweenUpdates = SECOND_MILLIS_BETWEEN_UPDATES; + } else { + millisBetweenUpdates = FIRST_MILLIS_BETWEEN_UPDATES; + } + + worldCache.getScoreboard().onUpdate(); + + if (DEBUG_ENABLED && (currentTime - lastLog > 60000)) { // one minute + int threshold = reachedSecondThreshold ? + SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD : + FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD; + + GeyserConnector.getInstance().getLogger().info( + LanguageUtils.getLocaleStringLog("geyser.scoreboard.updater.threshold_reached.log", session.getName(), threshold, pps) + + LanguageUtils.getLocaleStringLog("geyser.scoreboard.updater.threshold_reached", (millisBetweenUpdates / 1000.0)) + ); + + lastLog = currentTime; + } + } + } + } + } + + public int getPacketsPerSecond() { + return packetsPerSecond.get(); + } + + /** + * Increase the Scoreboard Packets Per Second and return the updated value + */ + public int incrementAndGetPacketsPerSecond() { + return pendingPacketsPerSecond.incrementAndGet(); + } + + static { + GeyserConfiguration config = GeyserConnector.getInstance().getConfig(); + FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD = Math.min(config.getScoreboardPacketThreshold(), SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD); + DEBUG_ENABLED = config.isDebugMode(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java index c2fcc02c876..087ea2dde8e 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java @@ -35,8 +35,7 @@ import java.util.List; import java.util.Set; -@Getter -@Setter +@Getter @Setter @Accessors(chain = true) public class Team { private final Scoreboard scoreboard; @@ -44,6 +43,7 @@ public class Team { private UpdateType updateType = UpdateType.ADD; private String name; + private String prefix; private TeamColor color; private String suffix; @@ -57,8 +57,7 @@ public Team(Scoreboard scoreboard, String id) { public void addEntities(String... names) { List added = new ArrayList<>(); for (String name : names) { - if (!entities.contains(name)) { - entities.add(name); + if (entities.add(name)) { added.add(name); } } @@ -78,4 +77,35 @@ public void removeEntities(String... names) { } setUpdateType(UpdateType.UPDATE); } + + public boolean hasEntity(String name) { + return entities.contains(name); + } + + public Team setPrefix(String prefix) { + // replace "null" to an empty string, + // we do this here to improve the performance of Score#getDisplayName + if (prefix.length() == 4 && "null".equals(prefix)) { + this.prefix = ""; + return this; + } + this.prefix = prefix; + return this; + } + + public Team setSuffix(String suffix) { + // replace "null" to an empty string, + // we do this here to improve the performance of Score#getDisplayName + if (suffix.length() == 4 && "null".equals(suffix)) { + this.suffix = ""; + return this; + } + this.suffix = suffix; + return this; + } + + @Override + public int hashCode() { + return id.hashCode(); + } } diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index af5881858a7..bde95d24b1f 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -124,6 +124,12 @@ metrics: # ADVANCED OPTIONS - DO NOT TOUCH UNLESS YOU KNOW WHAT YOU ARE DOING! +# Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle +# a lot of scoreboard packets per second can cause serious lag. +# This option allows you to specify after how many Scoreboard packets per seconds +# the Scoreboard updates will be limited to four updates per second. +scoreboard-packet-threshold: 20 + # Allow connections from ProxyPass and Waterdog. # See https://www.spigotmc.org/wiki/firewall-guide/ for assistance - use UDP instead of TCP. enable-proxy-connections: false From 79bf56a75cdc04f58748a4c819555937a2908499 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Fri, 28 Aug 2020 19:36:24 +0100 Subject: [PATCH 19/52] Tweaks to support Android (#1206) * Downgrade reflections to 0.9.11 * Add comment explaining downgrade * Move to pre-build reflections * Update skins to use https and relative cache dir * Move to https OptiFine cape url * Add javadoc to isProduction * Add ANDROID as a platform type * Re-ordered PlatformType * Change stop command to call onDisable --- connector/pom.xml | 53 ++++++++++++++++++- .../geysermc/connector/GeyserConnector.java | 14 ++++- .../command/defaults/StopCommand.java | 6 +-- .../connector/common/PlatformType.java | 1 + .../translators/PacketTranslatorRegistry.java | 3 +- .../translators/item/ItemTranslator.java | 3 +- .../sound/SoundHandlerRegistry.java | 4 +- .../world/block/BlockTranslator.java | 2 +- .../block/entity/BlockEntityTranslator.java | 3 +- .../geysermc/connector/utils/FileUtils.java | 21 ++++++++ .../geysermc/connector/utils/LocaleUtils.java | 2 +- .../connector/utils/SkinProvider.java | 6 +-- .../geysermc/connector/utils/SkinUtils.java | 4 +- 13 files changed, 104 insertions(+), 18 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index a3748044afd..e2c01e647f6 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -129,7 +129,12 @@ org.reflections reflections - 0.9.12 + 0.9.11 + + + org.dom4j + dom4j + 2.1.3 net.kyori @@ -232,6 +237,52 @@ + + org.codehaus.gmavenplus + gmavenplus-plugin + 1.9.1 + + + process-classes + + execute + + + + + + + + + + + org.reflections + reflections + 0.9.11 + + + org.dom4j + dom4j + 2.1.3 + + + org.codehaus.groovy + groovy-all + 3.0.5 + runtime + pom + + + diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index ec332ed66f9..2593c516ab5 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -163,7 +163,7 @@ private GeyserConnector(PlatformType platformType, GeyserBootstrap bootstrap) { config.getRemote().setPort(remotePort = Integer.parseInt(record[2])); logger.debug("Found SRV record \"" + remoteAddress + ":" + remotePort + "\""); } - } catch (Exception ex) { + } catch (Exception | NoClassDefFoundError ex) { // Check for a NoClassDefFoundError to prevent Android crashes logger.debug("Exception while trying to find an SRV record for the remote host."); if (config.isDebugMode()) ex.printStackTrace(); // Otherwise we can get a stack trace for any domain that doesn't have an SRV record @@ -307,6 +307,18 @@ public WorldManager getWorldManager() { return bootstrap.getWorldManager(); } + /** + * Get the production status of the current runtime. + * Will return true if the version number is not 'DEV'. + * Should only happen in compiled jars. + * + * @return If we are in a production build/environment + */ + public boolean isProduction() { + //noinspection ConstantConditions + return !"DEV".equals(GeyserConnector.VERSION); + } + public static GeyserConnector getInstance() { return instance; } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java index 636058a02f1..c69a8705f69 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java @@ -49,10 +49,6 @@ public void execute(CommandSender sender, String[] args) { return; } - connector.shutdown(); - - if (connector.getPlatformType() == PlatformType.STANDALONE) { - System.exit(0); - } + connector.getBootstrap().onDisable(); } } diff --git a/connector/src/main/java/org/geysermc/connector/common/PlatformType.java b/connector/src/main/java/org/geysermc/connector/common/PlatformType.java index 7c245c9bfea..4daa5d37d4c 100644 --- a/connector/src/main/java/org/geysermc/connector/common/PlatformType.java +++ b/connector/src/main/java/org/geysermc/connector/common/PlatformType.java @@ -32,6 +32,7 @@ @AllArgsConstructor public enum PlatformType { + ANDROID("Android"), BUNGEECORD("BungeeCord"), SPIGOT("Spigot"), SPONGE("Sponge"), diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java index 92d2e910294..c438648165e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java @@ -33,6 +33,7 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; import org.reflections.Reflections; @@ -48,7 +49,7 @@ public class PacketTranslatorRegistry { private static final ObjectArrayList> IGNORED_PACKETS = new ObjectArrayList<>(); static { - Reflections ref = new Reflections("org.geysermc.connector.network.translators"); + Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators") : new Reflections("org.geysermc.connector.network.translators"); for (Class clazz : ref.getTypesAnnotatedWith(Translator.class)) { Class packet = clazz.getAnnotation(Translator.class).packet(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java index dce0f4b4d5d..c08531205fd 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 @@ -41,6 +41,7 @@ import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.MessageUtils; import org.reflections.Reflections; @@ -62,7 +63,7 @@ public static void init() { static { /* Load item translators */ - Reflections ref = new Reflections("org.geysermc.connector.network.translators.item"); + Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.item") : new Reflections("org.geysermc.connector.network.translators.item"); Map loadedNbtItemTranslators = new HashMap<>(); for (Class clazz : ref.getTypesAnnotatedWith(ItemRemapper.class)) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandlerRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandlerRegistry.java index 893975e5e6f..163c451cb9b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandlerRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandlerRegistry.java @@ -25,6 +25,8 @@ package org.geysermc.connector.network.translators.sound; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.utils.FileUtils; import org.reflections.Reflections; import java.util.HashMap; @@ -38,7 +40,7 @@ public class SoundHandlerRegistry { static final Map> INTERACTION_HANDLERS = new HashMap<>(); static { - Reflections ref = new Reflections("org.geysermc.connector.network.translators.sound"); + Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.sound") : new Reflections("org.geysermc.connector.network.translators.sound"); for (Class clazz : ref.getTypesAnnotatedWith(SoundHandler.class)) { try { SoundInteractionHandler interactionHandler = (SoundInteractionHandler) clazz.newInstance(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java index e627b84547e..8da09f6f3a5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java @@ -106,7 +106,7 @@ public class BlockTranslator { addedStatesMap.defaultReturnValue(-1); List paletteList = new ArrayList<>(); - Reflections ref = new Reflections("org.geysermc.connector.network.translators.world.block.entity"); + Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") : new Reflections("org.geysermc.connector.network.translators.world.block.entity"); ref.getTypesAnnotatedWith(BlockEntity.class); int waterRuntimeId = -1; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java index c4401c4c854..a538c2dcc14 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java @@ -34,6 +34,7 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.utils.BlockEntityUtils; +import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; import org.reflections.Reflections; @@ -66,7 +67,7 @@ public static void init() { } static { - Reflections ref = new Reflections("org.geysermc.connector.network.translators.world.block.entity"); + Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") : new Reflections("org.geysermc.connector.network.translators.world.block.entity"); for (Class clazz : ref.getTypesAnnotatedWith(BlockEntity.class)) { GeyserConnector.getInstance().getLogger().debug("Found annotated block entity: " + clazz.getCanonicalName()); 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 38369d6c823..300946b34d2 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java @@ -28,11 +28,15 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import org.geysermc.connector.GeyserConnector; +import org.reflections.Reflections; +import org.reflections.serializers.XmlSerializer; +import org.reflections.util.ConfigurationBuilder; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URL; import java.util.function.Function; public class FileUtils { @@ -140,4 +144,21 @@ public static InputStream getResource(String resource) { } return stream; } + + /** + * Get the stored reflection data for a given path + * + * @param path The path to get the reflection data for + * @return The created Reflections object + */ + public static Reflections getReflections(String path) { + Reflections reflections = new Reflections(new ConfigurationBuilder()); + XmlSerializer serializer = new XmlSerializer(); + URL resource = FileUtils.class.getClassLoader().getResource("META-INF/reflections/" + path + "-reflections.xml"); + try (InputStream inputStream = resource.openConnection().getInputStream()) { + reflections.merge(serializer.read(inputStream)); + } catch (IOException e) { } + + return reflections; + } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java index 285846a9733..c09af2b7d74 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -150,7 +150,7 @@ private static void downloadLocale(String locale) { // Get the hash and download the locale String hash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash(); - WebUtils.downloadFile("http://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, localeFile.toString()); + WebUtils.downloadFile("https://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, localeFile.toString()); } /** 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 5551230b92f..7c30e48aba9 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java @@ -119,7 +119,7 @@ public class SkinProvider { // Schedule Daily Image Expiry if we are caching them if (GeyserConnector.getInstance().getConfig().getCacheImages() > 0) { GeyserConnector.getInstance().getGeneralThreadPool().scheduleAtFixedRate(() -> { - File cacheFolder = Paths.get("cache", "images").toFile(); + File cacheFolder = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("images").toFile(); if (!cacheFolder.exists()) { return; } @@ -395,7 +395,7 @@ private static byte[] requestImage(String imageUrl, CapeProvider provider) throw BufferedImage image = null; // First see if we have a cached file. We also update the modification stamp so we know when the file was last used - File imageFile = Paths.get("cache", "images", UUID.nameUUIDFromBytes(imageUrl.getBytes()).toString() + ".png").toFile(); + File imageFile = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("images").resolve(UUID.nameUUIDFromBytes(imageUrl.getBytes()).toString() + ".png").toFile(); if (imageFile.exists()) { try { GeyserConnector.getInstance().getLogger().debug("Reading cached image from file " + imageFile.getPath() + " for " + imageUrl); @@ -600,7 +600,7 @@ public static SkinGeometry getEars(boolean isSlim) { @Getter public enum CapeProvider { MINECRAFT, - OPTIFINE("http://s.optifine.net/capes/%s.png", CapeUrlType.USERNAME), + OPTIFINE("https://optifine.net/capes/%s.png", CapeUrlType.USERNAME), LABYMOD("https://www.labymod.net/page/php/getCapeTexture.php?uuid=%s", CapeUrlType.UUID_DASHED), FIVEZIG("https://textures.5zigreborn.eu/profile/%s", CapeUrlType.UUID_DASHED), MINECRAFTCAPES("https://minecraftcapes.net/profile/%s/cape", CapeUrlType.UUID); diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java index f0b88c435b4..e3488d6e7e5 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java @@ -150,14 +150,14 @@ public static GameProfileData from(GameProfile profile) { JsonNode textures = skinObject.get("textures"); JsonNode skinTexture = textures.get("SKIN"); - String skinUrl = skinTexture.get("url").asText(); + String skinUrl = skinTexture.get("url").asText().replace("http://", "https://"); isAlex = skinTexture.has("metadata"); String capeUrl = null; if (textures.has("CAPE")) { JsonNode capeTexture = textures.get("CAPE"); - capeUrl = capeTexture.get("url").asText(); + capeUrl = capeTexture.get("url").asText().replace("http://", "https://"); } return new GameProfileData(skinUrl, capeUrl, isAlex); From 3f0080349986ac687e152f5fbb12f4f62b0d24cf Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Fri, 28 Aug 2020 15:16:35 -0400 Subject: [PATCH 20/52] connector/pom.xml: Move back to main MCProtocolLib repository (#1211) Fixes a regression where ClientPlayerAbilitiesPacket was sending the incorrect flag. --- connector/pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index e2c01e647f6..76a83c23f38 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -109,9 +109,9 @@ compile - com.github.GeyserMC - MCProtocolLib - f37c98dc70 + com.github.steveice10 + mcprotocollib + b3cf3acbb3 compile From caa5098d2c9ad44c0121fec076c3899fe3e6c2be Mon Sep 17 00:00:00 2001 From: jackson-57 <49173011+jackson-57@users.noreply.github.com> Date: Fri, 28 Aug 2020 13:58:37 -0700 Subject: [PATCH 21/52] Fix minor spelling error in issue template (#1214) --- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 75a76cd05ac..46653e624ae 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -11,4 +11,4 @@ assignees: '' Add a description **Alternatives?** -Any alternatives you have tryed +List any alternatives you might have tried From 37c4192c1270c1e9bbe13e7bcc76c052a456f308 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Fri, 28 Aug 2020 19:11:32 -0500 Subject: [PATCH 22/52] Show the supported Bedrock versions in the version command rather than just the default codec --- .../connector/command/defaults/VersionCommand.java | 12 +++++++++++- .../geysermc/connector/network/BedrockProtocol.java | 11 ++++++----- connector/src/main/resources/mappings | 2 +- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java index 681474a982b..f7f62e597c8 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java @@ -26,6 +26,7 @@ package org.geysermc.connector.command.defaults; import com.github.steveice10.mc.protocol.MinecraftConstants; +import com.nukkitx.protocol.bedrock.BedrockPacketCodec; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.GeyserCommand; @@ -38,6 +39,7 @@ import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.util.List; import java.util.Properties; public class VersionCommand extends GeyserCommand { @@ -51,7 +53,15 @@ public VersionCommand(GeyserConnector connector, String name, String description @Override public void execute(CommandSender sender, String[] args) { - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.version", GeyserConnector.NAME, GeyserConnector.VERSION, MinecraftConstants.GAME_VERSION, BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion())); + String bedrockVersions; + List supportedCodecs = BedrockProtocol.SUPPORTED_BEDROCK_CODECS; + if (supportedCodecs.size() > 1) { + bedrockVersions = supportedCodecs.get(0).getMinecraftVersion() + " - " + supportedCodecs.get(supportedCodecs.size() - 1).getMinecraftVersion(); + } else { + bedrockVersions = BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion(); + } + + sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.version", GeyserConnector.NAME, GeyserConnector.VERSION, MinecraftConstants.GAME_VERSION, bedrockVersions)); // Disable update checking in dev mode //noinspection ConstantConditions - changes in production diff --git a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java index 904f70a0a04..f040fc1fec9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java +++ b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java @@ -30,25 +30,26 @@ import com.nukkitx.protocol.bedrock.v408.Bedrock_v408; import com.nukkitx.protocol.bedrock.v409.Bedrock_v409; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.ArrayList; +import java.util.List; /** * Contains information about the supported Bedrock protocols in Geyser. */ public class BedrockProtocol { /** - * Default Bedrock codec that should act as a fallback and as the version shown in /geyser version + * Default Bedrock codec that should act as a fallback. Should represent the latest available + * release of the game that Geyser supports. */ public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v408.V408_CODEC; /** * A list of all supported Bedrock versions that can join Geyser */ - public static final Set SUPPORTED_BEDROCK_CODECS = ConcurrentHashMap.newKeySet(); + public static final List SUPPORTED_BEDROCK_CODECS = new ArrayList<>(); static { - SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); SUPPORTED_BEDROCK_CODECS.add(Bedrock_v407.V407_CODEC); + SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); SUPPORTED_BEDROCK_CODECS.add(Bedrock_v409.V409_CODEC); } diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index ec8b68297c4..15958f16ca3 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit ec8b68297c4d62a9e4d640a8c7e77977062628ee +Subproject commit 15958f16ca36c9536490f8af67f556eba5665dcb From 2f9ff0c6220376f276ca46f1e038411a834da356 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Sun, 30 Aug 2020 00:18:23 -0400 Subject: [PATCH 23/52] ShulkerBoxItemTranslator: Ensure Items ListTag is present (#1221) * ShulkerBoxItemTranslator: Ensure Items ListTag is present * Compile --- .../connector/network/translators/item/ItemTranslator.java | 4 ++-- .../item/translators/nbt/ShulkerBoxItemTranslator.java | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) 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 c08531205fd..13725522b8c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java @@ -35,7 +35,7 @@ import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.geysermc.connector.GeyserConnector; @@ -358,7 +358,7 @@ public static void translateDisplayProperties(GeyserSession session, CompoundTag // If its not a message convert it if (!MessageUtils.isMessage(name)) { - TextComponent component = LegacyComponentSerializer.legacySection().deserialize(name); + Component component = LegacyComponentSerializer.legacySection().deserialize(name); name = GsonComponentSerializer.gson().serialize(component); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java index 1780537c5d5..0645c9807f2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java @@ -42,6 +42,7 @@ public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemE if (!itemTag.contains("BlockEntityTag")) return; // Empty shulker box CompoundTag blockEntityTag = itemTag.get("BlockEntityTag"); + if (blockEntityTag.get("Items") == null) return; ListTag itemsList = new ListTag("Items"); for (Tag item : (ListTag) blockEntityTag.get("Items")) { CompoundTag itemData = (CompoundTag) item; // Information about the item From b5f6ada4aea1d5235ec89af508336e092f5ce002 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 1 Sep 2020 13:30:40 -0400 Subject: [PATCH 24/52] ScoreboardUpdater: Quick fix to lessen CPU usage (#1238) This prevents one/multiple CPU cores from taking up 100% usage. A better, permanent fix will replace this in the coming days. --- .../org/geysermc/connector/scoreboard/ScoreboardUpdater.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java b/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java index ee38cecd988..3812fb141e9 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java @@ -31,6 +31,7 @@ import org.geysermc.connector.network.session.cache.WorldCache; import org.geysermc.connector.utils.LanguageUtils; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; public class ScoreboardUpdater extends Thread { @@ -61,7 +62,7 @@ public ScoreboardUpdater(WorldCache worldCache) { @Override public void run() { - while (!session.isClosed()) { + if (!session.isClosed()) { long currentTime = System.currentTimeMillis(); // reset score-packets per second every second @@ -99,6 +100,8 @@ public void run() { } } } + + session.getConnector().getGeneralThreadPool().schedule(this, 50, TimeUnit.MILLISECONDS); } } From 7c4868cada6d133ad3e25ca2191bea0efd6ba49d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 1 Sep 2020 18:29:53 -0400 Subject: [PATCH 25/52] LocaleUtils: don't NPE if no default locale mapping exists (#1239) --- .../main/java/org/geysermc/connector/utils/LocaleUtils.java | 5 +++++ 1 file changed, 5 insertions(+) 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 c09af2b7d74..07c33eb1a35 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -246,6 +246,11 @@ public static String getLocaleString(String messageText, String locale) { Map localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(locale.toLowerCase()); if (localeStrings == null) localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(LanguageUtils.getDefaultLocale()); + if (localeStrings == null) { + // Don't cause a NPE if the locale is STILL missing + GeyserConnector.getInstance().getLogger().debug("MISSING DEFAULT LOCALE: " + LanguageUtils.getDefaultLocale()); + return messageText; + } return localeStrings.getOrDefault(messageText, messageText); } From d717085c6b1689ad5b305c27d79ab3d9ac7290d3 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 2 Sep 2020 00:36:53 -0400 Subject: [PATCH 26/52] JaveNotifyClientTranslator: Translate invalid bed message (#1212) This isn't sent as its own message but as a specific event. --- .../translators/java/world/JavaNotifyClientTranslator.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 33e602eb3ad..a7bc7b61f2c 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 @@ -43,6 +43,7 @@ import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.inventory.PlayerInventoryTranslator; +import org.geysermc.connector.utils.LocaleUtils; @Translator(packet = ServerNotifyClientPacket.class) public class JavaNotifyClientTranslator extends PacketTranslator { @@ -141,6 +142,11 @@ public void translate(ServerNotifyClientPacket packet, GeyserSession session) { packet.getValue() == RespawnScreenValue.IMMEDIATE_RESPAWN)); session.sendUpstreamPacket(gamerulePacket); break; + case INVALID_BED: + // Not sent as a proper message? Odd. + session.sendMessage(LocaleUtils.getLocaleString("block.minecraft.spawn.not_valid", + session.getClientData().getLanguageCode())); + break; default: break; } From 6f161a380fad6d3a6a686dc7606c69b08028356e Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 2 Sep 2020 00:37:24 -0400 Subject: [PATCH 27/52] GeyserSession: Always set Keep Inventory to true (#1213) * GeyserSession: Always set Keep Inventory to true This prevents the client from removing items on death in creative mode if Keep Inventory is true, but doesn't break existing behavior. Essentially, this assures full server-side behavior of the inventory during death. * Small comment update * OK, it was fine before the last commit, but make it better --- .../geysermc/connector/network/session/GeyserSession.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 3b27fc6aeff..865ef0f4d74 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 @@ -301,10 +301,13 @@ public void connect(RemoteServer remoteServer) { attributesPacket.setAttributes(attributes); upstream.sendPacket(attributesPacket); + GameRulesChangedPacket gamerulePacket = new GameRulesChangedPacket(); // Only allow the server to send health information // Setting this to false allows natural regeneration to work false but doesn't break it being true - GameRulesChangedPacket gamerulePacket = new GameRulesChangedPacket(); gamerulePacket.getGameRules().add(new GameRuleData<>("naturalregeneration", false)); + // Don't let the client modify the inventory on death + // Setting this to true allows keep inventory to work if enabled but doesn't break functionality being false + gamerulePacket.getGameRules().add(new GameRuleData<>("keepinventory", true)); upstream.sendPacket(gamerulePacket); } From b97bf56d99fdeae9e59332f759353aed9645b47c Mon Sep 17 00:00:00 2001 From: Comstepr <32700815+Comstepr@users.noreply.github.com> Date: Wed, 2 Sep 2020 12:37:49 +0800 Subject: [PATCH 28/52] Minor changes to config.yml (#1224) --- connector/src/main/resources/config.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index bde95d24b1f..7b0bd851394 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -13,14 +13,14 @@ bedrock: address: 0.0.0.0 # The port that will listen for connections port: 19132 - # Some hosting services change your Java port everytime you open the server, and require the same port to be used for Bedrock. + # Some hosting services change your Java port everytime you start the server and require the same port to be used for Bedrock. # This option makes the Bedrock port the same as the Java port every time you start the server. # This option is for the plugin version only. clone-remote-port: false - # The MOTD that will be broadcasted to Minecraft: Bedrock Edition clients. Irrelevant if "passthrough-motd" is set to true + # The MOTD that will be broadcasted to Minecraft: Bedrock Edition clients. This is irrelevant if "passthrough-motd" is set to true motd1: "GeyserMC" motd2: "Another GeyserMC forced host." - # The Server Name that will be sent to Minecraft: Bedrock Edition clients. Visible in both the pause menu and the settings menu. + # The Server Name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. server-name: "Geyser" remote: # The IP address of the remote (Java Edition) server @@ -55,7 +55,7 @@ floodgate-key-file: public-key.pem # Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. command-suggestions: true -# The following three options enable "ping passthrough" -the MOTD, player count and/or protocol name gets retrieved from the Java server. +# The following three options enable "ping passthrough" - the MOTD, player count and/or protocol name gets retrieved from the Java server. # Relay the MOTD from the remote server to Bedrock players. passthrough-motd: false # Relay the protocol name (e.g. BungeeCord [X.X], Paper 1.X) - only really useful when using a custom protocol name! From 81a48bf96d0f336c4e2e96e67f77b3162e64ea77 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 2 Sep 2020 00:38:10 -0400 Subject: [PATCH 29/52] Relocate the rest of our dependencies (#1227) - Relocate all of our dependencies. This does not include MCProtocolLib and Nukkit dependencies at this time as there are no other known plugins that use these dependencies. - Switch to a static commit for Adventure dependencies. Tested working on all versions. --- bootstrap/bungeecord/pom.xml | 16 ++++++++++++++++ bootstrap/spigot/pom.xml | 12 ++++++++++++ bootstrap/sponge/pom.xml | 12 ++++++++++++ bootstrap/velocity/pom.xml | 16 ++++++++++++++++ connector/pom.xml | 12 ++++++------ 5 files changed, 62 insertions(+), 6 deletions(-) diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 682690a215f..3fb7090f83f 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -61,6 +61,10 @@ net.md_5.bungee.jni org.geysermc.platform.bungeecord.shaded.jni + + com.fasterxml.jackson + org.geysermc.platform.bungeecord.shaded.jackson + io.netty org.geysermc.platform.bungeecord.shaded.netty @@ -69,6 +73,18 @@ org.reflections org.geysermc.platform.bungeecord.shaded.reflections + + com.google.guava + org.geysermc.platform.bungeecord.shaded.guava + + + org.dom4j + org.geysermc.platform.bungeecord.shaded.dom4j + + + net.kyori.adventure + org.geysermc.platform.bungeecord.shaded.adventure + diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 8b3ad742847..79bdfea01f9 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -79,6 +79,18 @@ org.reflections org.geysermc.platform.spigot.shaded.reflections + + com.google.guava + org.geysermc.platform.spigot.shaded.guava + + + org.dom4j + org.geysermc.platform.spigot.shaded.dom4j + + + net.kyori.adventure + org.geysermc.platform.spigot.shaded.adventure + diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index c21a3a539af..3c20239f123 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -73,6 +73,18 @@ org.reflections org.geysermc.platform.sponge.shaded.reflections + + com.google.guava + org.geysermc.platform.sponge.shaded.guava + + + org.dom4j + org.geysermc.platform.sponge.shaded.dom4j + + + net.kyori.adventure + org.geysermc.platform.sponge.shaded.adventure + diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index 0ebb70cab90..efe6c80a366 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -57,6 +57,10 @@ + + com.fasterxml.jackson + org.geysermc.platform.velocity.shaded.jackson + it.unimi.dsi.fastutil org.geysermc.platform.velocity.shaded.fastutil @@ -65,6 +69,18 @@ org.reflections org.geysermc.platform.velocity.shaded.reflections + + com.google.guava + org.geysermc.platform.velocity.shaded.guava + + + org.dom4j + org.geysermc.platform.velocity.shaded.dom4j + + + net.kyori.adventure + org.geysermc.platform.velocity.shaded.adventure + diff --git a/connector/pom.xml b/connector/pom.xml index 76a83c23f38..fe35b67822b 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -137,21 +137,21 @@ 2.1.3 - net.kyori + com.github.kyoripowered.adventure adventure-api - 4.0.0-SNAPSHOT + 557865caef compile - net.kyori + com.github.kyoripowered.adventure adventure-text-serializer-gson - 4.0.0-SNAPSHOT + 557865caef compile - net.kyori + com.github.kyoripowered.adventure adventure-text-serializer-legacy - 4.0.0-SNAPSHOT + 557865caef compile From dcf1731d8adc2f0f6ea176239a71cd7520da3038 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 2 Sep 2020 00:38:36 -0400 Subject: [PATCH 30/52] Implement correct sign wrapping (#1228) * Implement correct sign wrapping This commit ensures that the auto-wrapping nature of Bedrock with signs is corrected. If a Bedrock player sends a sign that is auto-wrapped, it will now be interpreted by Geyser to fit on multiple lines. Additionally, Geyser will crop incoming sign text to prevent auto-wrapping. * Don't wrap if it's the last line --- .../network/session/GeyserSession.java | 9 ++ .../BedrockBlockEntityDataTranslator.java | 59 +++++++---- .../entity/SignBlockEntityTranslator.java | 17 +++- .../geysermc/connector/utils/SignUtils.java | 99 +++++++++++++++++++ 4 files changed, 163 insertions(+), 21 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/utils/SignUtils.java 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 865ef0f4d74..27423056442 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 @@ -240,6 +240,15 @@ public class GeyserSession implements CommandSender { @Setter private boolean thunder = false; + /** + * Stores the last text inputted into a sign. + * + * Bedrock sends packets every time you update the sign, Java only wants the final packet. + * Until we determine that the user has finished editing, we save the sign's current status. + */ + @Setter + private String lastSignMessage; + private MinecraftProtocol protocol; public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java index 38b9403998a..147b8a3b15e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java @@ -32,17 +32,11 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; - -import java.util.HashMap; -import java.util.Map; +import org.geysermc.connector.utils.SignUtils; @Translator(packet = BlockEntityDataPacket.class) public class BedrockBlockEntityDataTranslator extends PacketTranslator { - // In case two people are editing signs at the same time this array holds the temporary messages to be sent - // Position -> Message being held - protected static Map lastMessages = new HashMap<>(); - @Override public void translate(BlockEntityDataPacket packet, GeyserSession session) { NbtMap tag = packet.getData(); @@ -50,9 +44,8 @@ public void translate(BlockEntityDataPacket packet, GeyserSession session) { // 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")); + if (!tag.getString("Text").equals(session.getLastSignMessage())) { + session.setLastSignMessage(tag.getString("Text")); return; } // Otherwise the two messages are identical and we can get to work deconstructing @@ -61,29 +54,61 @@ public void translate(BlockEntityDataPacket packet, GeyserSession session) { // (Initialized all with empty strings because it complains about null) String[] lines = new String[] {"", "", "", ""}; int iterator = 0; + // Keep track of the width of each character + // If it goes over the maximum, we need to start a new line to match Java + int widthCount = 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') { + widthCount += SignUtils.getCharacterWidth(character); + // If we get a return in Bedrock, or go over the character width max, that signals to use the next line. + if (character == '\n' || widthCount > SignUtils.JAVA_CHARACTER_WIDTH_MAX) { + // We need to apply some more logic if we went over the character width max + boolean wentOverMax = widthCount > SignUtils.JAVA_CHARACTER_WIDTH_MAX && character != '\n'; + widthCount = 0; + // Saves if we're moving a word to the next line + String word = null; + if (wentOverMax && iterator < lines.length - 1) { + // If we went over the max, we want to try to wrap properly like Bedrock does. + // So we look for a space in the Bedrock user's text to imply a word. + int index = newMessage.lastIndexOf(" "); + if (index != -1) { + // There is indeed a space in this line; let's get it + word = newMessage.substring(index + 1); + // 'Delete' that word from the string builder + newMessage.delete(index, newMessage.length()); + } + } lines[iterator] = newMessage.toString(); iterator++; - // Bedrock, for whatever reason, can hold a message out of bounds + // Bedrock, for whatever reason, can hold a message out of the bounds of the four lines // We don't care about that so we discard that if (iterator > lines.length - 1) { break; } newMessage = new StringBuilder(); + if (wentOverMax) { + // Apply the wrapped word to the new line + if (word != null) { + newMessage.append(word); + // And apply the width count + for (char wordCharacter : word.toCharArray()) { + widthCount += SignUtils.getCharacterWidth(wordCharacter); + } + } + // If we went over the max, we want to append the character to the new line. + newMessage.append(character); + widthCount += SignUtils.getCharacterWidth(character); + } } else newMessage.append(character); } // Put the final line on since it isn't done in the for loop if (iterator < lines.length) lines[iterator] = newMessage.toString(); + Position pos = new Position(tag.getInt("x"), tag.getInt("y"), tag.getInt("z")); ClientUpdateSignPacket clientUpdateSignPacket = new ClientUpdateSignPacket(pos, lines); session.sendDownstreamPacket(clientUpdateSignPacket); - //TODO (potentially): originally I was going to update the sign blocks so Bedrock and Java users would match visually - // However Java can still store a lot per-line and visuals are still messed up so that doesn't work - // We remove the sign position from map to indicate there is no work-in-progress sign - lastMessages.remove(pos); + // We set the sign text cached in the session to null to indicate there is no work-in-progress sign + session.setLastSignMessage(null); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java index ab9ba30655b..acec16c5cea 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java @@ -29,6 +29,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.nbt.NbtMap; import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.utils.SignUtils; import java.util.HashMap; import java.util.Map; @@ -46,9 +47,17 @@ public Map translateTag(CompoundTag tag, int blockState) { String signLine = getOrDefault(tag.getValue().get("Text" + currentLine), ""); signLine = MessageUtils.getBedrockMessage(MessageSerializer.fromString(signLine)); - //Java allows up to 16+ characters on certain symbols. - if(signLine.length() >= 15 && (signLine.contains("-") || signLine.contains("="))) { - signLine = signLine.substring(0, 14); + // Check the character width on the sign to ensure there is no overflow that is usually hidden + // to Java Edition clients but will appear to Bedrock clients + int signWidth = 0; + StringBuilder finalSignLine = new StringBuilder(); + for (char c : signLine.toCharArray()) { + signWidth += SignUtils.getCharacterWidth(c); + if (signWidth <= SignUtils.BEDROCK_CHARACTER_WIDTH_MAX) { + finalSignLine.append(c); + } else { + break; + } } // Java Edition 1.14 added the ability to change the text color of the whole sign using dye @@ -56,7 +65,7 @@ public Map translateTag(CompoundTag tag, int blockState) { signText.append(getBedrockSignColor(tag.get("Color").getValue().toString())); } - signText.append(signLine); + signText.append(finalSignLine.toString()); signText.append("\n"); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/SignUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SignUtils.java new file mode 100644 index 00000000000..06406b55ea8 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/SignUtils.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.utils; + +/** + * Provides utilities for interacting with signs. Mainly, it deals with the widths of each character. + * Since Bedrock auto-wraps signs and Java does not, we have to take this into account when translating signs. + */ +public class SignUtils { + + // TODO: If we send the Java font via resource pack, does width change? + /** + * The maximum character width that a sign can hold in Bedrock + */ + public static final int BEDROCK_CHARACTER_WIDTH_MAX = 88; + + /** + * The maximum character width that a sign can hold in Java + */ + public static final int JAVA_CHARACTER_WIDTH_MAX = 90; + + /** + * Gets the Minecraft width of a character + * @param c character to determine + * @return width of the character + */ + public static int getCharacterWidth(char c) { + switch (c) { + case '!': + case ',': + case '.': + case ':': + case ';': + case 'i': + case '|': + case '¡': + return 2; + + case '\'': + case 'l': + case 'ì': + case 'í': + return 3; + + case ' ': + case 'I': + case '[': + case ']': + case 't': + case '×': + case 'ï': + return 4; + + case '"': + case '(': + case ')': + case '*': + case '<': + case '>': + case 'f': + case 'k': + case '{': + case '}': + return 5; + + case '@': + case '~': + case '®': + return 7; + + default: + return 6; + } + } + +} From b21f4773669a304694bc64ed5ed199263f5ab473 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 2 Sep 2020 00:39:06 -0400 Subject: [PATCH 31/52] Parrot mounting fixes (#1236) * Parrot mounting fixes - Fix duplicate parrots when a parrot leaves the player - Fix rotation of parrots * Remove critical debug information --- .../connector/entity/PlayerEntity.java | 31 ++++++++++++++++--- .../player/BedrockMovePlayerTranslator.java | 7 +++++ 2 files changed, 34 insertions(+), 4 deletions(-) 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 da75812fb62..58b32e62203 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -43,6 +43,7 @@ import lombok.Setter; import org.geysermc.connector.entity.attribute.Attribute; import org.geysermc.connector.entity.attribute.AttributeType; +import org.geysermc.connector.entity.living.animal.tameable.ParrotEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.cache.EntityEffectCache; @@ -65,8 +66,14 @@ public class PlayerEntity extends LivingEntity { private boolean playerList = true; // Player is in the player list private final EntityEffectCache effectCache; - private Entity leftParrot; - private Entity rightParrot; + /** + * Saves the parrot currently on the player's left shoulder; otherwise null + */ + private ParrotEntity leftParrot; + /** + * Saves the parrot currently on the player's right shoulder; otherwise null + */ + private ParrotEntity rightParrot; public PlayerEntity(GameProfile gameProfile, long entityId, long geyserId, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, EntityType.PLAYER, position, motion, rotation); @@ -189,6 +196,12 @@ public void updateHeadLookRotation(GeyserSession session, float headYaw) { @Override public void updatePositionAndRotation(GeyserSession session, double moveX, double moveY, double moveZ, float yaw, float pitch, boolean isOnGround) { moveRelative(session, moveX, moveY, moveZ, yaw, pitch, isOnGround); + if (leftParrot != null) { + leftParrot.moveRelative(session, moveX, moveY, moveZ, yaw, pitch, isOnGround); + } + if (rightParrot != null) { + rightParrot.moveRelative(session, moveX, moveY, moveZ, yaw, pitch, isOnGround); + } } @Override @@ -202,6 +215,12 @@ public void updateRotation(GeyserSession session, float yaw, float pitch, boolea movePlayerPacket.setOnGround(isOnGround); movePlayerPacket.setMode(MovePlayerPacket.Mode.HEAD_ROTATION); session.sendUpstreamPacket(movePlayerPacket); + if (leftParrot != null) { + leftParrot.updateRotation(session, yaw, pitch, isOnGround); + } + if (rightParrot != null) { + rightParrot.updateRotation(session, yaw, pitch, isOnGround); + } } @Override @@ -237,11 +256,15 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s } // Parrot occupying shoulder - if ((entityMetadata.getId() == 18 && leftParrot == null) || (entityMetadata.getId() == 19 && rightParrot == null)) { // null check since this code just creates the parrot + if (entityMetadata.getId() == 18 || entityMetadata.getId() == 19) { CompoundTag tag = (CompoundTag) entityMetadata.getValue(); if (tag != null && !tag.isEmpty()) { + if ((entityMetadata.getId() == 18 && leftParrot != null) || (entityMetadata.getId() == 19 && rightParrot != null)) { + // No need to update a parrot's data when it already exists + return; + } // The parrot is a separate entity in Bedrock, but part of the player entity in Java - Entity parrot = new Entity(0, session.getEntityCache().getNextEntityId().incrementAndGet(), + ParrotEntity parrot = new ParrotEntity(0, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.PARROT, position, motion, rotation); parrot.spawnEntity(session); parrot.getMetadata().put(EntityData.VARIANT, tag.get("Variant").getValue()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java index be918ba74c4..8809941b982 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java @@ -86,6 +86,13 @@ public void translate(MovePlayerPacket packet, GeyserSession session) { entity.setPosition(packet.getPosition().sub(0, EntityType.PLAYER.getOffset(), 0)); entity.setRotation(rotation); entity.setOnGround(packet.isOnGround()); + // Move parrots to match if applicable + if (entity.getLeftParrot() != null) { + entity.getLeftParrot().moveAbsolute(session, entity.getPosition(), entity.getRotation(), true, false); + } + if (entity.getRightParrot() != null) { + entity.getRightParrot().moveAbsolute(session, entity.getPosition(), entity.getRotation(), true, false); + } /* boolean colliding = false; From 4f761c5bde26d76aac772d2c6dad30bfd810d582 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 2 Sep 2020 00:39:14 -0400 Subject: [PATCH 32/52] Translate scoreboard nametag visibility (#1240) This commit adds support for name tag visibility in teams. If a player is set to hide their nametag, it will be hidden from the Bedrock client. Notably, this fixes most NPC nametag hiding, including Citizens. This does not fix some NPC nametag hiding - there are several NPCs in Hypixel that still have a nametag show up, and they are not a part of any team. --- .../geysermc/connector/entity/PlayerEntity.java | 14 +++++++++++++- .../java/scoreboard/JavaTeamTranslator.java | 2 ++ .../org/geysermc/connector/scoreboard/Team.java | 2 ++ 3 files changed, 17 insertions(+), 1 deletion(-) 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 58b32e62203..0c269b10d41 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -27,6 +27,7 @@ import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.scoreboard.NameTagVisibility; import com.github.steveice10.mc.protocol.data.message.TextMessage; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3f; @@ -240,7 +241,18 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s } Team team = session.getWorldCache().getScoreboard().getTeamFor(username); if (team != null) { - metadata.put(EntityData.NAMETAG, team.getPrefix() + MessageUtils.toChatColor(team.getColor()) + username + team.getSuffix()); + // Cover different visibility settings + if (team.getNameTagVisibility() == NameTagVisibility.NEVER) { + metadata.put(EntityData.NAMETAG, ""); + } else if (team.getNameTagVisibility() == NameTagVisibility.HIDE_FOR_OTHER_TEAMS && + !team.getEntities().contains(session.getPlayerEntity().getUsername())) { + metadata.put(EntityData.NAMETAG, ""); + } else if (team.getNameTagVisibility() == NameTagVisibility.HIDE_FOR_OWN_TEAM && + team.getEntities().contains(session.getPlayerEntity().getUsername())) { + metadata.put(EntityData.NAMETAG, ""); + } else { + metadata.put(EntityData.NAMETAG, team.getPrefix() + MessageUtils.toChatColor(team.getColor()) + username + team.getSuffix()); + } } } 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 684245d47b1..998e5effe9c 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 @@ -61,6 +61,7 @@ public void translate(ServerTeamPacket packet, GeyserSession session) { scoreboard.registerNewTeam(packet.getTeamName(), toPlayerSet(packet.getPlayers())) .setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) .setColor(packet.getColor()) + .setNameTagVisibility(packet.getNameTagVisibility()) .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getClientData().getLanguageCode())) .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode())); break; @@ -75,6 +76,7 @@ public void translate(ServerTeamPacket packet, GeyserSession session) { team.setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) .setColor(packet.getColor()) + .setNameTagVisibility(packet.getNameTagVisibility()) .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getClientData().getLanguageCode())) .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode())) .setUpdateType(UpdateType.UPDATE); diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java index 087ea2dde8e..a073e2e99c7 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java @@ -25,6 +25,7 @@ package org.geysermc.connector.scoreboard; +import com.github.steveice10.mc.protocol.data.game.scoreboard.NameTagVisibility; import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import lombok.Getter; @@ -44,6 +45,7 @@ public class Team { private UpdateType updateType = UpdateType.ADD; private String name; + private NameTagVisibility nameTagVisibility; private String prefix; private TeamColor color; private String suffix; From 4c58568eb411cc4a83bc772148f7f5956de68d9b Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 2 Sep 2020 23:42:53 -0400 Subject: [PATCH 33/52] Relocate Google Common (#1242) Fixes NoSuchMethodErrors from occuring on certain Spigot servers. --- bootstrap/bungeecord/pom.xml | 6 +++++- bootstrap/spigot/pom.xml | 6 +++++- bootstrap/sponge/pom.xml | 6 +++++- bootstrap/velocity/pom.xml | 6 +++++- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 3fb7090f83f..44b28e931ce 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -73,9 +73,13 @@ org.reflections org.geysermc.platform.bungeecord.shaded.reflections + + com.google.common + org.geysermc.platform.bungeecord.shaded.google.common + com.google.guava - org.geysermc.platform.bungeecord.shaded.guava + org.geysermc.platform.bungeecord.shaded.google.guava org.dom4j diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 79bdfea01f9..194ec6e5a27 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -79,9 +79,13 @@ org.reflections org.geysermc.platform.spigot.shaded.reflections + + com.google.common + org.geysermc.platform.spigot.shaded.google.common + com.google.guava - org.geysermc.platform.spigot.shaded.guava + org.geysermc.platform.spigot.shaded.google.guava org.dom4j diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index 3c20239f123..132f381736a 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -73,9 +73,13 @@ org.reflections org.geysermc.platform.sponge.shaded.reflections + + com.google.common + org.geysermc.platform.sponge.shaded.google.common + com.google.guava - org.geysermc.platform.sponge.shaded.guava + org.geysermc.platform.sponge.shaded.google.guava org.dom4j diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index efe6c80a366..ee445b6e73d 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -69,9 +69,13 @@ org.reflections org.geysermc.platform.velocity.shaded.reflections + + com.google.common + org.geysermc.platform.velocity.shaded.google.common + com.google.guava - org.geysermc.platform.velocity.shaded.guava + org.geysermc.platform.velocity.shaded.google.guava org.dom4j From 5b76a858954ebc2e41058f1ebcf4c4faa28b8589 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Thu, 3 Sep 2020 19:00:36 -0400 Subject: [PATCH 34/52] Non-full-chunk support (#574) This commit adds non-full chunk support if chunk caching is enabled. --- .gitignore | 2 +- bootstrap/spigot/pom.xml | 2 +- .../platform/spigot/GeyserSpigotPlugin.java | 12 +- .../world/GeyserSpigotWorldManager.java | 93 ++++++++++- .../spigot/src/main/resources/biomes.json | 155 ++++++++++++++++++ .../network/session/cache/ChunkCache.java | 11 +- .../java/world/JavaChunkDataTranslator.java | 26 ++- .../translators/world/GeyserWorldManager.java | 8 + .../translators/world/WorldManager.java | 10 ++ .../geysermc/connector/utils/ChunkUtils.java | 18 +- 10 files changed, 320 insertions(+), 17 deletions(-) create mode 100644 bootstrap/spigot/src/main/resources/biomes.json diff --git a/.gitignore b/.gitignore index c4c878af454..69c07e500ed 100644 --- a/.gitignore +++ b/.gitignore @@ -241,4 +241,4 @@ config.yml logs/ public-key.pem locales/ -cache/ \ No newline at end of file +/cache/ \ No newline at end of file diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 194ec6e5a27..714c0852f57 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -20,7 +20,7 @@ org.spigotmc spigot-api - 1.14-R0.1-SNAPSHOT + 1.15.2-R0.1-SNAPSHOT provided diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index d51cf21a5bd..9cc0bc06489 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -56,7 +56,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { private GeyserSpigotConfiguration geyserConfig; private GeyserSpigotLogger geyserLogger; private IGeyserPingPassthrough geyserSpigotPingPassthrough; - private GeyserSpigotBlockPlaceListener blockPlaceListener; private GeyserSpigotWorldManager geyserWorldManager; private GeyserConnector connector; @@ -124,10 +123,15 @@ public void onEnable() { // Used to determine if Block.getBlockData() is present. boolean isLegacy = !isCompatible(Bukkit.getServer().getVersion(), "1.13.0"); if (isLegacy) - geyserLogger.debug("Legacy version of Minecraft (1.12.2 or older) detected."); + geyserLogger.debug("Legacy version of Minecraft (1.12.2 or older) detected; falling back to ViaVersion for block state retrieval."); - this.geyserWorldManager = new GeyserSpigotWorldManager(isLegacy, isViaVersion); - this.blockPlaceListener = new GeyserSpigotBlockPlaceListener(connector, isLegacy, isViaVersion); + boolean use3dBiomes = isCompatible(Bukkit.getServer().getVersion(), "1.16.0"); + if (!use3dBiomes) { + geyserLogger.debug("Legacy version of Minecraft (1.15.2 or older) detected; not using 3D biomes."); + } + + this.geyserWorldManager = new GeyserSpigotWorldManager(isLegacy, use3dBiomes, isViaVersion); + GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(connector, isLegacy, isViaVersion); Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java index 4873a17506e..c6443bd056c 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java @@ -25,23 +25,71 @@ package org.geysermc.platform.spigot.world; -import lombok.AllArgsConstructor; +import com.fasterxml.jackson.databind.JsonNode; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.block.Biome; import org.bukkit.block.Block; +import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.GeyserWorldManager; import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.GameRule; +import org.geysermc.connector.utils.LanguageUtils; import us.myles.ViaVersion.protocols.protocol1_13_1to1_13.Protocol1_13_1To1_13; import us.myles.ViaVersion.protocols.protocol1_16_2to1_16_1.data.MappingData; -@AllArgsConstructor +import java.io.InputStream; + public class GeyserSpigotWorldManager extends GeyserWorldManager { private final boolean isLegacy; - // You need ViaVersion to connect to an older server with Geyser. - // However, we still check for ViaVersion in case there's some other way that gets Geyser on a pre-1.13 Bukkit server + private final boolean use3dBiomes; + /** + * You need ViaVersion to connect to an older server with Geyser. + * However, we still check for ViaVersion in case there's some other way that gets Geyser on a pre-1.13 Bukkit server + */ private final boolean isViaVersion; + /** + * Stores a list of {@link Biome} ordinal numbers to Minecraft biome numeric IDs. + * + * Working with the Biome enum in Spigot poses two problems: + * 1: The Biome enum values change in both order and names over the years. + * 2: There is no way to get the Minecraft biome ID from the name itself with Spigot. + * To solve both of these problems, we store a JSON file of every Biome enum that has existed, + * along with its 1.16 biome number. + * + * The key is the Spigot Biome ordinal; the value is the Minecraft Java biome numerical ID + */ + private final Int2IntMap biomeToIdMap = new Int2IntOpenHashMap(Biome.values().length); + + public GeyserSpigotWorldManager(boolean isLegacy, boolean use3dBiomes, boolean isViaVersion) { + this.isLegacy = isLegacy; + this.use3dBiomes = use3dBiomes; + this.isViaVersion = isViaVersion; + + // Load the values into the biome-to-ID map + InputStream biomeStream = FileUtils.getResource("biomes.json"); + JsonNode biomes; + try { + biomes = GeyserConnector.JSON_MAPPER.readTree(biomeStream); + } catch (Exception e) { + throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e); + } + // Only load in the biomes that are present in this version of Minecraft + for (Biome enumBiome : Biome.values()) { + if (biomes.has(enumBiome.toString())) { + biomeToIdMap.put(enumBiome.ordinal(), biomes.get(enumBiome.toString()).intValue()); + } else { + GeyserConnector.getInstance().getLogger().debug("No biome mapping found for " + enumBiome.toString() + + ", defaulting to 0"); + biomeToIdMap.put(enumBiome.ordinal(), 0); + } + } + } @Override public int getBlockAt(GeyserSession session, int x, int y, int z) { @@ -77,6 +125,43 @@ public static int getLegacyBlock(GeyserSession session, int x, int y, int z, boo } @Override + @SuppressWarnings("deprecation") + public int[] getBiomeDataAt(GeyserSession session, int x, int z) { + if (session.getPlayerEntity() == null) { + return new int[1024]; + } + int[] biomeData = new int[1024]; + World world = Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld(); + int chunkX = x << 4; + int chunkZ = z << 4; + int chunkXmax = chunkX + 16; + int chunkZmax = chunkZ + 16; + // 3D biomes didn't exist until 1.15 + if (use3dBiomes) { + for (int localX = chunkX; localX < chunkXmax; localX += 4) { + for (int localY = 0; localY < 255; localY += + 4) { + for (int localZ = chunkZ; localZ < chunkZmax; localZ += 4) { + // Index is based on wiki.vg's index requirements + final int i = ((localY >> 2) & 63) << 4 | ((localZ >> 2) & 3) << 2 | ((localX >> 2) & 3); + biomeData[i] = biomeToIdMap.getOrDefault(world.getBiome(localX, localY, localZ).ordinal(), 0); + } + } + } + } else { + // Looks like the same code, but we're not checking the Y coordinate here + for (int localX = chunkX; localX < chunkXmax; localX += 4) { + for (int localY = 0; localY < 255; localY += + 4) { + for (int localZ = chunkZ; localZ < chunkZmax; localZ += 4) { + // Index is based on wiki.vg's index requirements + final int i = ((localY >> 2) & 63) << 4 | ((localZ >> 2) & 3) << 2 | ((localX >> 2) & 3); + biomeData[i] = biomeToIdMap.getOrDefault(world.getBiome(localX, localZ).ordinal(), 0); + } + } + } + } + return biomeData; + } + public Boolean getGameRuleBool(GeyserSession session, GameRule gameRule) { return Boolean.parseBoolean(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getGameRuleValue(gameRule.getJavaID())); } diff --git a/bootstrap/spigot/src/main/resources/biomes.json b/bootstrap/spigot/src/main/resources/biomes.json new file mode 100644 index 00000000000..56520e9145b --- /dev/null +++ b/bootstrap/spigot/src/main/resources/biomes.json @@ -0,0 +1,155 @@ +{ + "MUTATED_ICE_FLATS" : 140, + "MUTATED_TAIGA" : 133, + "SAVANNA_PLATEAU_MOUNTAINS" : 164, + "DEEP_WARM_OCEAN" : 47, + "REDWOOD_TAIGA_HILLS" : 33, + "THE_VOID" : 127, + "COLD_TAIGA_MOUNTAINS" : 158, + "BAMBOO_JUNGLE_HILLS" : 169, + "MOUNTAINS" : 3, + "MESA_PLATEAU" : 39, + "SNOWY_TAIGA_HILLS" : 31, + "DEEP_FROZEN_OCEAN" : 50, + "EXTREME_HILLS" : 3, + "BIRCH_FOREST_MOUNTAINS" : 155, + "FOREST" : 4, + "BIRCH_FOREST" : 27, + "SNOWY_TUNDRA" : 12, + "ICE_SPIKES" : 140, + "FROZEN_OCEAN" : 10, + "WARPED_FOREST" : 172, + "WOODED_BADLANDS_PLATEAU" : 38, + "BADLANDS_PLATEAU" : 39, + "ICE_PLAINS_SPIKES" : 140, + "MEGA_TAIGA" : 32, + "MUTATED_SAVANNA_ROCK" : 164, + "SAVANNA_PLATEAU" : 36, + "DARK_FOREST_HILLS" : 157, + "END_MIDLANDS" : 41, + "SHATTERED_SAVANNA_PLATEAU" : 164, + "SAVANNA" : 35, + "MUSHROOM_ISLAND_SHORE" : 15, + "SWAMP" : 6, + "ICE_MOUNTAINS" : 13, + "BEACH" : 16, + "MUTATED_MESA_CLEAR_ROCK" : 167, + "END_HIGHLANDS" : 42, + "COLD_BEACH" : 26, + "JUNGLE" : 21, + "MUTATED_TAIGA_COLD" : 158, + "TALL_BIRCH_HILLS" : 156, + "DARK_FOREST" : 29, + "WOODED_HILLS" : 18, + "HELL" : 8, + "MUTATED_REDWOOD_TAIGA" : 160, + "MESA_PLATEAU_FOREST" : 38, + "MUSHROOM_ISLAND" : 14, + "BADLANDS" : 37, + "END_BARRENS" : 43, + "MUTATED_EXTREME_HILLS_WITH_TREES" : 162, + "MUTATED_JUNGLE_EDGE" : 151, + "MODIFIED_BADLANDS_PLATEAU" : 167, + "ROOFED_FOREST_MOUNTAINS" : 157, + "SOUL_SAND_VALLEY" : 170, + "DESERT" : 2, + "MUTATED_PLAINS" : 129, + "MUTATED_BIRCH_FOREST" : 155, + "WOODED_MOUNTAINS" : 34, + "TAIGA_HILLS" : 19, + "BAMBOO_JUNGLE" : 168, + "SWAMPLAND_MOUNTAINS" : 134, + "DESERT_MOUNTAINS" : 130, + "REDWOOD_TAIGA" : 32, + "MUSHROOM_FIELDS" : 14, + "GIANT_TREE_TAIGA_HILLS" : 33, + "PLAINS" : 1, + "JUNGLE_EDGE" : 23, + "SAVANNA_MOUNTAINS" : 163, + "DEEP_COLD_OCEAN" : 49, + "DESERT_LAKES" : 130, + "MOUNTAIN_EDGE" : 20, + "SNOWY_MOUNTAINS" : 13, + "MESA_PLATEAU_MOUNTAINS" : 167, + "JUNGLE_MOUNTAINS" : 149, + "SMALLER_EXTREME_HILLS" : 20, + "MESA_PLATEAU_FOREST_MOUNTAINS" : 166, + "NETHER_WASTES" : 8, + "BIRCH_FOREST_HILLS_MOUNTAINS" : 156, + "MUTATED_JUNGLE" : 149, + "WARM_OCEAN" : 44, + "DEEP_OCEAN" : 24, + "STONE_BEACH" : 25, + "MODIFIED_JUNGLE" : 149, + "MUTATED_SAVANNA" : 163, + "TAIGA_COLD_HILLS" : 31, + "OCEAN" : 0, + "SMALL_END_ISLANDS" : 40, + "MUSHROOM_FIELD_SHORE" : 15, + "GRAVELLY_MOUNTAINS" : 131, + "FROZEN_RIVER" : 11, + "TAIGA_COLD" : 30, + "BASALT_DELTAS" : 173, + "EXTREME_HILLS_WITH_TREES" : 34, + "MEGA_TAIGA_HILLS" : 33, + "MUTATED_FOREST" : 132, + "MUTATED_BIRCH_FOREST_HILLS" : 156, + "SKY" : 9, + "LUKEWARM_OCEAN" : 45, + "EXTREME_HILLS_MOUNTAINS" : 131, + "COLD_TAIGA_HILLS" : 31, + "THE_END" : 9, + "SUNFLOWER_PLAINS" : 129, + "SAVANNA_ROCK" : 36, + "ERODED_BADLANDS" : 165, + "STONE_SHORE" : 25, + "EXTREME_HILLS_PLUS_MOUNTAINS" : 162, + "CRIMSON_FOREST" : 171, + "VOID" : 127, + "SNOWY_TAIGA" : 30, + "SNOWY_TAIGA_MOUNTAINS" : 158, + "FLOWER_FOREST" : 132, + "COLD_OCEAN" : 46, + "BEACHES" : 16, + "MESA" : 37, + "MUSHROOM_SHORE" : 15, + "MESA_CLEAR_ROCK" : 39, + "NETHER" : 8, + "ICE_PLAINS" : 12, + "SHATTERED_SAVANNA" : 163, + "ROOFED_FOREST" : 29, + "GIANT_SPRUCE_TAIGA_HILLS" : 161, + "SNOWY_BEACH" : 26, + "MESA_BRYCE" : 165, + "JUNGLE_EDGE_MOUNTAINS" : 151, + "MUTATED_DESERT" : 130, + "MODIFIED_GRAVELLY_MOUNTAINS" : 158, + "MEGA_SPRUCE_TAIGA" : 160, + "TAIGA_MOUNTAINS" : 133, + "SMALL_MOUNTAINS" : 20, + "EXTREME_HILLS_PLUS" : 34, + "GIANT_SPRUCE_TAIGA" : 160, + "FOREST_HILLS" : 18, + "DESERT_HILLS" : 17, + "MUTATED_REDWOOD_TAIGA_HILLS" : 161, + "MEGA_SPRUCE_TAIGA_HILLS" : 161, + "RIVER" : 7, + "GIANT_TREE_TAIGA" : 32, + "SWAMPLAND" : 6, + "JUNGLE_HILLS" : 22, + "TALL_BIRCH_FOREST" : 155, + "DEEP_LUKEWARM_OCEAN" : 48, + "MESA_ROCK" : 38, + "SWAMP_HILLS" : 134, + "MODIFIED_WOODED_BADLANDS_PLATEAU" : 166, + "MODIFIED_JUNGLE_EDGE" : 151, + "BIRCH_FOREST_HILLS" : 28, + "COLD_TAIGA" : 30, + "TAIGA" : 5, + "MUTATED_MESA_ROCK" : 166, + "MUTATED_SWAMPLAND" : 134, + "ICE_FLATS" : 12, + "MUTATED_ROOFED_FOREST" : 157, + "MUTATED_MESA" : 165, + "MUTATED_EXTREME_HILLS" : 131 +} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java index 9601a2981c5..2cc9ea134ee 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java @@ -42,7 +42,7 @@ public class ChunkCache { private final boolean cache; @Getter - private Map chunks = new HashMap<>(); + private final Map chunks = new HashMap<>(); public ChunkCache(GeyserSession session) { if (session.getConnector().getWorldManager().getClass() == GeyserBootstrap.DEFAULT_CHUNK_MANAGER.getClass()) { @@ -57,6 +57,15 @@ public void addToCache(Column chunk) { return; } ChunkPosition position = new ChunkPosition(chunk.getX(), chunk.getZ()); + if (chunk.getBiomeData() == null && chunks.containsKey(position)) { + Column newColumn = chunk; + chunk = chunks.get(position); + for (int i = 0; i < newColumn.getChunks().length; i++) { + if (newColumn.getChunks()[i] != null) { + chunk.getChunks()[i] = newColumn.getChunks()[i]; + } + } + } chunks.put(position, chunk); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java index 5170929a9dc..6dae9b4d68c 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 @@ -45,18 +45,33 @@ @Translator(packet = ServerChunkDataPacket.class) public class JavaChunkDataTranslator extends PacketTranslator { + /** + * Determines if we should process non-full chunks + */ + private final boolean isCacheChunks; + + public JavaChunkDataTranslator() { + isCacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks(); + } + @Override public void translate(ServerChunkDataPacket packet, GeyserSession session) { if (session.isSpawned()) { ChunkUtils.updateChunkPosition(session, session.getPlayerEntity().getPosition().toInt()); } - if (packet.getColumn().getBiomeData() == null) //Non-full chunk + if (packet.getColumn().getBiomeData() == null && !isCacheChunks) { + // Non-full chunk without chunk caching + session.getConnector().getLogger().debug("Not sending non-full chunk because chunk caching is off."); return; + } + + // Non-full chunks don't have all the chunk data, and Bedrock won't accept that + final boolean isNonFullChunk = (packet.getColumn().getBiomeData() == null); GeyserConnector.getInstance().getGeneralThreadPool().execute(() -> { try { - ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(packet.getColumn()); + ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(session, packet.getColumn(), isNonFullChunk); ByteBuf byteBuf = Unpooled.buffer(32); ChunkSection[] sections = chunkData.sections; @@ -71,7 +86,12 @@ public void translate(ServerChunkDataPacket packet, GeyserSession session) { section.writeToNetwork(byteBuf); } - byte[] bedrockBiome = BiomeTranslator.toBedrockBiome(packet.getColumn().getBiomeData()); + byte[] bedrockBiome; + if (packet.getColumn().getBiomeData() == null) { + bedrockBiome = BiomeTranslator.toBedrockBiome(session.getConnector().getWorldManager().getBiomeDataAt(session, packet.getColumn().getX(), packet.getColumn().getZ())); + } else { + bedrockBiome = BiomeTranslator.toBedrockBiome(packet.getColumn().getBiomeData()); + } byteBuf.writeBytes(bedrockBiome); // Biomes - 256 bytes byteBuf.writeByte(0); // Border blocks - Edu edition only diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java index 83f1a7783f8..46aa6f7f6c8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java @@ -32,6 +32,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.chunk.ChunkPosition; import org.geysermc.connector.utils.GameRule; public class GeyserWorldManager extends WorldManager { @@ -43,6 +44,13 @@ public int getBlockAt(GeyserSession session, int x, int y, int z) { return session.getChunkCache().getBlockAt(new Position(x, y, z)); } + @Override + public int[] getBiomeDataAt(GeyserSession session, int x, int z) { + if (!session.getConnector().getConfig().isCacheChunks()) + return new int[1024]; + return session.getChunkCache().getChunks().get(new ChunkPosition(x, z)).getBiomeData(); + } + @Override public void setGameRule(GeyserSession session, String name, Object value) { session.sendDownstreamPacket(new ClientChatPacket("/gamerule " + name + " " + value)); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java index 3260122778b..ba78b8f6f49 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java @@ -74,6 +74,16 @@ public int getBlockAt(GeyserSession session, Vector3i vector) { */ public abstract int getBlockAt(GeyserSession session, int x, int y, int z); + /** + * Gets the biome data for the specified chunk. + * + * @param session the session of the player + * @param x the chunk's X coordinate + * @param z the chunk's Z coordinate + * @return the biome data for the specified region with a length of 1024. + */ + public abstract int[] getBiomeDataAt(GeyserSession session, int x, int z); + /** * Updates a gamerule value on the Java server * diff --git a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java index 79fc5a7820a..9413bbba519 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -81,7 +81,7 @@ public class ChunkUtils { } } - public static ChunkData translateToBedrock(Column column) { + public static ChunkData translateToBedrock(GeyserSession session, Column column, boolean isNonFullChunk) { ChunkData chunkData = new ChunkData(); Chunk[] chunks = column.getChunks(); chunkData.sections = new ChunkSection[chunks.length]; @@ -97,14 +97,26 @@ public static ChunkData translateToBedrock(Column column) { chunkData.sections[chunkY] = new ChunkSection(); Chunk chunk = chunks[chunkY]; - if (chunk == null || chunk.isEmpty()) + // Chunk is null and caching chunks is off or this isn't a non-full chunk + if (chunk == null && (!session.getConnector().getConfig().isCacheChunks() || !isNonFullChunk)) + continue; + + // If chunk is empty then no need to process + if (chunk != null && chunk.isEmpty()) continue; ChunkSection section = chunkData.sections[chunkY]; for (int x = 0; x < 16; x++) { for (int y = 0; y < 16; y++) { for (int z = 0; z < 16; z++) { - int blockState = chunk.get(x, y, z); + int blockState; + // If a non-full chunk, then grab the block that should be here to create a 'full' chunk + if (chunk == null) { + Position pos = new ChunkPosition(column.getX(), column.getZ()).getBlock(x, (chunkY << 4) + y, z); + blockState = session.getConnector().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ()); + } else { + blockState = chunk.get(x, y, z); + } int id = BlockTranslator.getBedrockBlockId(blockState); // Check to see if the name is in BlockTranslator.getBlockEntityString, and therefore must be handled differently From 854849f63c29b5f3b3ccef3913e53cef88799bf3 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Fri, 4 Sep 2020 14:08:04 -0400 Subject: [PATCH 35/52] Update mappings to fix 1.16 wood slabs (#1249) --- connector/src/main/resources/mappings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index 15958f16ca3..28a22d2baad 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 15958f16ca36c9536490f8af67f556eba5665dcb +Subproject commit 28a22d2baad680f511bffc36d90d06bf626f0527 From d47360d6fb7b1625b89968deb6e9d829099d099a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Sat, 5 Sep 2020 16:22:31 -0400 Subject: [PATCH 36/52] GeyserSession: send command permission level OPERATOR if qualified (#1254) Mobile clients have a GUI for commands that shows if CommandPermission.OPERATOR or higher is sent. The commands present all require OP permission 2 or higher; therefore we set that command permission if the server tells us we have a OP permission level of 2 or higher. --- .../geysermc/connector/network/session/GeyserSession.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 27423056442..7a61eb220bf 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 @@ -710,7 +710,10 @@ public Boolean hasPermission(String permission) { public void sendAdventureSettings() { AdventureSettingsPacket adventureSettingsPacket = new AdventureSettingsPacket(); adventureSettingsPacket.setUniqueEntityId(playerEntity.getGeyserId()); - adventureSettingsPacket.setCommandPermission(CommandPermission.NORMAL); + // Set command permission if OP permission level is high enough + // This allows mobile players access to a GUI for doing commands. The commands there do not change above OPERATOR + // and all commands there are accessible with OP permission level 2 + adventureSettingsPacket.setCommandPermission(opPermissionLevel >= 2 ? CommandPermission.OPERATOR : CommandPermission.NORMAL); adventureSettingsPacket.setPlayerPermission(PlayerPermission.MEMBER); Set flags = new HashSet<>(); From 8c8630814d5f2e238844ade6d3d65c053b47b0c7 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Thu, 10 Sep 2020 11:30:56 -0400 Subject: [PATCH 37/52] Update to 1.16.3 (#1272) * Update for 1.16.3-rc1 * Update to 1.16.3 * Update README * Update MCProtocolLib --- README.md | 2 +- bootstrap/spigot/pom.xml | 2 +- connector/pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ead5b3b70f1..156745df370 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have now joined us here! -### Currently supporting Minecraft Bedrock v1.16.x and Minecraft Java v1.16.2. +### Currently supporting Minecraft Bedrock v1.16.x and Minecraft Java v1.16.3. ## Setting Up Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser. diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 714c0852f57..d4dc332600d 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -26,7 +26,7 @@ us.myles viaversion - 3.1.0 + 3.1.1 provided diff --git a/connector/pom.xml b/connector/pom.xml index fe35b67822b..5a19a5015a7 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -109,9 +109,9 @@ compile - com.github.steveice10 + com.github.GeyserMC mcprotocollib - b3cf3acbb3 + e4a3aa636a compile From 46c34842d8de76a51cf5ef4fdf1b53369d07a2eb Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Sat, 12 Sep 2020 09:47:43 -0400 Subject: [PATCH 38/52] BedrockInventoryTransactionTranslator: check to make sure bucket usage is on purpose (#1280) Otherwise buckets can be activated when opening block inventories. --- .../bedrock/BedrockInventoryTransactionTranslator.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 f8e4eb6d07d..cc1da85e8c3 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 @@ -98,7 +98,9 @@ public void translate(InventoryTransactionPacket packet, GeyserSession session) session.sendDownstreamPacket(blockPacket); // Otherwise boats will not be able to be placed in survival and buckets wont work on mobile - if (packet.getItemInHand() != null && (packet.getItemInHand().getId() == ItemRegistry.BOAT.getBedrockId() || packet.getItemInHand().getId() == ItemRegistry.BUCKET.getBedrockId())) { + // Check actions, otherwise buckets may be activated when block inventories are accessed + if (packet.getItemInHand() != null && (packet.getItemInHand().getId() == ItemRegistry.BOAT.getBedrockId() || + packet.getItemInHand().getId() == ItemRegistry.BUCKET.getBedrockId()) && !packet.getActions().isEmpty()) { ClientPlayerUseItemPacket itemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND); session.sendDownstreamPacket(itemPacket); } From 9643b208f38e39730220136954c9c8b2683b94e5 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 14 Sep 2020 20:40:41 -0400 Subject: [PATCH 39/52] Check if Fireworks tag is null (#1255) Thank you Mineplex, very cool. --- .../java/org/geysermc/connector/entity/FireworkEntity.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java index b940b87b91d..c100c6f37ad 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java @@ -72,6 +72,10 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s } CompoundTag fireworks = tag.get("Fireworks"); + if (fireworks == null) { + // Thank you Mineplex very cool + return; + } NbtMapBuilder fireworksBuilder = NbtMap.builder(); if (fireworks.get("Flight") != null) { From 26802e6dabb796121cb21a95a3e17175b68e959a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 14 Sep 2020 20:50:07 -0400 Subject: [PATCH 40/52] Translate CanPlaceOn/CanDestroy NBT (#1253) * Translate CanPlaceOn/CanDestroy NBT This commit adds support for the translation of the CanPlaceOn/CanDestroy NBT for Bedrock clients. * Remove debug line --- .../connector/entity/ItemFrameEntity.java | 9 +---- .../translators/item/ItemRegistry.java | 17 +++++++++ .../translators/item/ItemTranslator.java | 35 +++++++++++++++++++ .../nbt/ShulkerBoxItemTranslator.java | 9 +---- .../world/block/BlockTranslator.java | 34 +++++++++++++----- 5 files changed, 79 insertions(+), 25 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java index 392cec24c27..f9d2ace4635 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java @@ -34,7 +34,6 @@ import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; -import com.nukkitx.protocol.bedrock.packet.StartGamePacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -102,13 +101,7 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s ItemEntry itemEntry = ItemRegistry.getItem((ItemStack) entityMetadata.getValue()); NbtMapBuilder builder = NbtMap.builder(); - String blockName = ""; - for (StartGamePacket.ItemEntry startGamePacketItemEntry : ItemRegistry.ITEMS) { - if (startGamePacketItemEntry.getId() == (short) itemEntry.getBedrockId()) { - blockName = startGamePacketItemEntry.getIdentifier(); - break; - } - } + String blockName = ItemRegistry.getBedrockIdentifer(itemEntry); builder.putByte("Count", (byte) itemData.getCount()); if (itemData.getTag() != null) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index 56ed2d6e01d..1f3af019fa3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -221,6 +221,23 @@ public static ItemEntry getItemEntry(String javaIdentifier) { .stream().filter(itemEntry -> itemEntry.getJavaIdentifier().equals(key)).findFirst().orElse(null)); } + /** + * Finds the Bedrock string identifier of an ItemEntry + * + * @param entry the ItemEntry to search for + * @return the Bedrock identifier + */ + public static String getBedrockIdentifer(ItemEntry entry) { + String blockName = ""; + for (StartGamePacket.ItemEntry startGamePacketItemEntry : ItemRegistry.ITEMS) { + if (startGamePacketItemEntry.getId() == (short) entry.getBedrockId()) { + blockName = startGamePacketItemEntry.getIdentifier(); // Find the Bedrock string name + break; + } + } + return blockName; + } + /** * Gets a Bedrock {@link ItemData} from a {@link JsonNode} * @param itemNode the JSON node that contains ProxyPass-compatible Bedrock item data 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 13725522b8c..864e9fe03d5 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 @@ -41,6 +41,7 @@ import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.MessageUtils; @@ -154,9 +155,43 @@ public static ItemData translateToBedrock(GeyserSession session, ItemStack stack itemData = DEFAULT_TRANSLATOR.translateToBedrock(itemStack, bedrockItem); } + if (nbt != null) { + // Translate the canDestroy and canPlaceOn Java NBT + ListTag canDestroy = nbt.get("CanDestroy"); + String[] canBreak = new String[0]; + ListTag canPlaceOn = nbt.get("CanPlaceOn"); + String[] canPlace = new String[0]; + canBreak = getCanModify(canDestroy, canBreak); + canPlace = getCanModify(canPlaceOn, canPlace); + itemData = ItemData.of(itemData.getId(), itemData.getDamage(), itemData.getCount(), itemData.getTag(), canPlace, canBreak); + } + return itemData; } + /** + * Translates the Java NBT of canDestroy and canPlaceOn to its Bedrock counterparts. + * In Java, this is treated as normal NBT, but in Bedrock, these arguments are extra parts of the item data itself. + * @param canModifyJava the list of items in Java + * @param canModifyBedrock the empty list of items in Bedrock + * @return the new list of items in Bedrock + */ + private static String[] getCanModify(ListTag canModifyJava, String[] canModifyBedrock) { + if (canModifyJava != null && canModifyJava.size() > 0) { + canModifyBedrock = new String[canModifyJava.size()]; + for (int i = 0; i < canModifyBedrock.length; i++) { + // Get the Java identifier of the block that can be placed + String block = ((StringTag) canModifyJava.get(i)).getValue(); + // Sometimes this is done but it's still valid + if (!block.startsWith("minecraft:")) block = "minecraft:" + block; + // Get the Bedrock identifier of the item and replace it. + // This will unfortunately be limited - for example, beds and banners will be translated weirdly + canModifyBedrock[i] = BlockTranslator.getBedrockBlockIdentifier(block).replace("minecraft:", ""); + } + } + return canModifyBedrock; + } + private static final ItemTranslator DEFAULT_TRANSLATOR = new ItemTranslator() { @Override public List getAppliedItems() { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java index 0645c9807f2..a9930f698d0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java @@ -26,7 +26,6 @@ package org.geysermc.connector.network.translators.item.translators.nbt; import com.github.steveice10.opennbt.tag.builtin.*; -import com.nukkitx.protocol.bedrock.packet.StartGamePacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.item.ItemEntry; @@ -51,13 +50,7 @@ public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemE boxItemTag.put(new ByteTag("WasPickedUp", (byte) 0)); // ??? ItemEntry boxItemEntry = ItemRegistry.getItemEntry(((StringTag) itemData.get("id")).getValue()); - String blockName = ""; - for (StartGamePacket.ItemEntry startGamePacketItemEntry : ItemRegistry.ITEMS) { - if (startGamePacketItemEntry.getId() == (short) boxItemEntry.getBedrockId()) { - blockName = startGamePacketItemEntry.getIdentifier(); // Find the Bedrock string name - break; - } - } + String blockName = ItemRegistry.getBedrockIdentifer(boxItemEntry); boxItemTag.put(new StringTag("Name", blockName)); boxItemTag.put(new ShortTag("Damage", (short) boxItemEntry.getBedrockData())); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java index 8da09f6f3a5..81c86c307d7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java @@ -28,15 +28,12 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; -import com.nukkitx.nbt.NBTInputStream; -import com.nukkitx.nbt.NbtList; -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.nbt.NbtMapBuilder; -import com.nukkitx.nbt.NbtType; -import com.nukkitx.nbt.NbtUtils; +import com.nukkitx.nbt.*; import it.unimi.dsi.fastutil.ints.*; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.translators.world.block.entity.BlockEntity; import org.geysermc.connector.utils.FileUtils; @@ -52,6 +49,11 @@ public class BlockTranslator { private static final Int2IntMap JAVA_TO_BEDROCK_BLOCK_MAP = new Int2IntOpenHashMap(); private static final Int2IntMap BEDROCK_TO_JAVA_BLOCK_MAP = new Int2IntOpenHashMap(); + /** + * Stores a list of differences in block identifiers. + * Items will not be added to this list if the key and value is the same. + */ + private static final Object2ObjectMap JAVA_TO_BEDROCK_IDENTIFIERS = new Object2ObjectOpenHashMap<>(); private static final BiMap JAVA_ID_BLOCK_MAP = HashBiMap.create(); private static final IntSet WATERLOGGED = new IntOpenHashSet(); private static final Object2IntMap ITEM_FRAMES = new Object2IntOpenHashMap<>(); @@ -152,11 +154,11 @@ public class BlockTranslator { // Used for adding all "special" Java block states to block state map String identifier; - String bedrock_identifer = entry.getValue().get("bedrock_identifier").asText(); + String bedrockIdentifier = entry.getValue().get("bedrock_identifier").asText(); for (Class clazz : ref.getTypesAnnotatedWith(BlockEntity.class)) { identifier = clazz.getAnnotation(BlockEntity.class).regex(); // Endswith, or else the block bedrock gets picked up for bed - if (bedrock_identifer.endsWith(identifier) && !identifier.equals("")) { + if (bedrockIdentifier.endsWith(identifier) && !identifier.equals("")) { JAVA_ID_TO_BLOCK_ENTITY_MAP.put(javaRuntimeId, clazz.getAnnotation(BlockEntity.class).name()); break; } @@ -164,9 +166,15 @@ public class BlockTranslator { BlockStateValues.storeBlockStateValues(entry, javaRuntimeId); + String cleanJavaIdentifier = entry.getKey().split("\\[")[0]; + + if (!cleanJavaIdentifier.equals(bedrockIdentifier)) { + JAVA_TO_BEDROCK_IDENTIFIERS.put(cleanJavaIdentifier, bedrockIdentifier); + } + // Get the tag needed for non-empty flower pots if (entry.getValue().get("pottable") != null) { - BlockStateValues.getFlowerPotBlocks().put(entry.getKey().split("\\[")[0], buildBedrockState(entry.getValue())); + BlockStateValues.getFlowerPotBlocks().put(cleanJavaIdentifier, buildBedrockState(entry.getValue())); } if ("minecraft:water[level=0]".equals(javaId)) { @@ -297,6 +305,14 @@ public static int getJavaBlockState(int bedrockId) { return BEDROCK_TO_JAVA_BLOCK_MAP.get(bedrockId); } + /** + * @param javaIdentifier the Java identifier of the block to search for + * @return the Bedrock identifier if different, or else the Java identifier + */ + public static String getBedrockBlockIdentifier(String javaIdentifier) { + return JAVA_TO_BEDROCK_IDENTIFIERS.getOrDefault(javaIdentifier, javaIdentifier); + } + public static int getItemFrame(NbtMap tag) { return ITEM_FRAMES.getOrDefault(tag, -1); } From b13f5e900f448f7a40f5df27373a5d2d96e22794 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 14 Sep 2020 20:50:21 -0400 Subject: [PATCH 41/52] PlayerEntity: despawn even if still on the player list (#1263) Fixes LibsDisguises not working, as it uses the same entity ID for the disguised entity and player. The player still appears on the player list. --- .../java/org/geysermc/connector/entity/PlayerEntity.java | 6 ------ 1 file changed, 6 deletions(-) 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 0c269b10d41..cc00403a0e3 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -86,12 +86,6 @@ public PlayerEntity(GameProfile gameProfile, long entityId, long geyserId, Vecto if (geyserId == 1) valid = true; } - @Override - public boolean despawnEntity(GeyserSession session) { - super.despawnEntity(session); - return !playerList; // don't remove from cache when still on playerlist - } - @Override public void spawnEntity(GeyserSession session) { if (geyserId == 1) return; From 1e1402a23ffd26205a78739210c5d0f122674249 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 14 Sep 2020 20:51:07 -0400 Subject: [PATCH 42/52] DumpInfo: Mark internal IP as sensitive (#1264) Sometimes the internal IP is the external IP of the server. --- .../org/geysermc/connector/dump/DumpInfo.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java b/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java index 9d91cde6b56..8193953a7e4 100644 --- a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java +++ b/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java @@ -31,6 +31,7 @@ import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import lombok.Getter; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.common.serializer.AsteriskSerializer; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.network.BedrockProtocol; import org.geysermc.connector.network.session.GeyserSession; @@ -112,16 +113,21 @@ public static class NetworkInfo { private final boolean dockerCheck; NetworkInfo() { - try { - // This is the most reliable for getting the main local IP - Socket socket = new Socket(); - socket.connect(new InetSocketAddress("geysermc.org", 80)); - this.internalIP = socket.getLocalAddress().getHostAddress(); - } catch (IOException e1) { + if (AsteriskSerializer.showSensitive) { try { - // Fallback to the normal way of getting the local IP - this.internalIP = InetAddress.getLocalHost().getHostAddress(); - } catch (UnknownHostException ignored) { } + // This is the most reliable for getting the main local IP + Socket socket = new Socket(); + socket.connect(new InetSocketAddress("geysermc.org", 80)); + this.internalIP = socket.getLocalAddress().getHostAddress(); + } catch (IOException e1) { + try { + // Fallback to the normal way of getting the local IP + this.internalIP = InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException ignored) { } + } + } else { + // Sometimes the internal IP is the external IP... + this.internalIP = "***"; } this.dockerCheck = DockerCheck.checkBasic(); From 3b274ef9d15d925cb8f2511e06ceb670b89a6484 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 14 Sep 2020 20:52:16 -0400 Subject: [PATCH 43/52] Pick block improvements (#1265) * Pick block improvements - Creative block picking is now implemented. If the survival-styled block picking fails, then the item is created, following Java-style mechanics. - Entity 'picking' is also implemented. The item is crafted using the same mechanics, and the same rules apply as normal block-picking (except it only works in creative mode, following Java. * Switch some logic around --- .../connector/inventory/PlayerInventory.java | 4 + ...drockBlockPickRequestPacketTranslator.java | 99 --------------- .../BedrockBlockPickRequestTranslator.java | 52 ++++++++ .../BedrockEntityPickRequestTranslator.java | 115 ++++++++++++++++++ .../connector/utils/InventoryUtils.java | 96 ++++++++++++++- 5 files changed, 266 insertions(+), 100 deletions(-) delete mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestPacketTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEntityPickRequestTranslator.java 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 432ca8270d9..225335a97c6 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java @@ -31,6 +31,10 @@ public class PlayerInventory extends Inventory { + /** + * Stores the held item slot, starting at index 0. + * Add 36 in order to get the network item slot. + */ @Getter @Setter private int heldItemSlot; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestPacketTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestPacketTranslator.java deleted file mode 100644 index 39cfceb1031..00000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestPacketTranslator.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.bedrock; - -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientMoveItemToHotbarPacket; -import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.protocol.bedrock.packet.BlockPickRequestPacket; -import com.nukkitx.protocol.bedrock.packet.PlayerHotbarPacket; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; - -@Translator(packet = BlockPickRequestPacket.class) -public class BedrockBlockPickRequestPacketTranslator extends PacketTranslator { - - @Override - public void translate(BlockPickRequestPacket packet, GeyserSession session) { - Vector3i vector = packet.getBlockPosition(); - int blockToPick = session.getConnector().getWorldManager().getBlockAt(session, vector.getX(), vector.getY(), vector.getZ()); - - // Block is air - chunk caching is probably off - if (blockToPick == 0) { - return; - } - - // Get the inventory to choose a slot to pick - Inventory inventory = session.getInventoryCache().getOpenInventory(); - if (inventory == null) { - inventory = session.getInventory(); - } - - String targetIdentifier = BlockTranslator.getJavaIdBlockMap().inverse().get(blockToPick).split("\\[")[0]; - - // Check hotbar for item - for (int i = 36; i < 45; i++) { - if (inventory.getItem(i) == null) { - continue; - } - ItemEntry item = ItemRegistry.getItem(inventory.getItem(i)); - // If this isn't the item we're looking for - if (!item.getJavaIdentifier().equals(targetIdentifier)) { - continue; - } - - PlayerHotbarPacket hotbarPacket = new PlayerHotbarPacket(); - hotbarPacket.setContainerId(0); - // Java inventory slot to hotbar slot ID - hotbarPacket.setSelectedHotbarSlot(i - 36); - hotbarPacket.setSelectHotbarSlot(true); - session.sendUpstreamPacket(hotbarPacket); - session.getInventory().setHeldItemSlot(i - 36); - // Don't check inventory if item was in hotbar - return; - } - - // Check inventory for item - for (int i = 9; i < 36; i++) { - if (inventory.getItem(i) == null) { - continue; - } - ItemEntry item = ItemRegistry.getItem(inventory.getItem(i)); - // If this isn't the item we're looking for - if (!item.getJavaIdentifier().equals(targetIdentifier)) { - continue; - } - - ClientMoveItemToHotbarPacket packetToSend = new ClientMoveItemToHotbarPacket(i); // https://wiki.vg/Protocol#Pick_Item - session.sendDownstreamPacket(packetToSend); - return; - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java new file mode 100644 index 00000000000..023a83afe61 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.bedrock; + +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.packet.BlockPickRequestPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.connector.utils.InventoryUtils; + +@Translator(packet = BlockPickRequestPacket.class) +public class BedrockBlockPickRequestTranslator extends PacketTranslator { + + @Override + public void translate(BlockPickRequestPacket packet, GeyserSession session) { + Vector3i vector = packet.getBlockPosition(); + int blockToPick = session.getConnector().getWorldManager().getBlockAt(session, vector.getX(), vector.getY(), vector.getZ()); + + // Block is air - chunk caching is probably off + if (blockToPick == 0) { + return; + } + + String targetIdentifier = BlockTranslator.getJavaIdBlockMap().inverse().get(blockToPick).split("\\[")[0]; + InventoryUtils.findOrCreatePickedBlock(session, targetIdentifier); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEntityPickRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEntityPickRequestTranslator.java new file mode 100644 index 00000000000..4aa044ee452 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEntityPickRequestTranslator.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.bedrock; + +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.packet.EntityPickRequestPacket; +import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.utils.InventoryUtils; + +/** + * Called when the Bedrock user uses the pick block button on an entity + */ +@Translator(packet = EntityPickRequestPacket.class) +public class BedrockEntityPickRequestTranslator extends PacketTranslator { + + @Override + public void translate(EntityPickRequestPacket packet, GeyserSession session) { + if (session.getGameMode() != GameMode.CREATIVE) return; // Apparently Java behavior + Entity entity = session.getEntityCache().getEntityByGeyserId(packet.getRuntimeEntityId()); + if (entity == null) return; + + // Get the corresponding item + String itemName; + switch (entity.getEntityType()) { + case BOAT: + // Include type of boat in the name + int variant = entity.getMetadata().getInt(EntityData.VARIANT); + String typeOfBoat; + switch (variant) { + case 1: + typeOfBoat = "spruce"; + break; + case 2: + typeOfBoat = "birch"; + break; + case 3: + typeOfBoat = "jungle"; + break; + case 4: + typeOfBoat = "acacia"; + break; + case 5: + typeOfBoat = "dark_oak"; + break; + default: + typeOfBoat = "oak"; + break; + } + itemName = typeOfBoat + "_boat"; + break; + case LEASH_KNOT: + itemName = "lead"; + break; + case MINECART_CHEST: + case MINECART_COMMAND_BLOCK: + case MINECART_FURNACE: + case MINECART_HOPPER: + case MINECART_TNT: + // Move MINECART to the end of the name + itemName = entity.getEntityType().toString().toLowerCase().replace("minecart_", "") + "_minecart"; + break; + case MINECART_SPAWNER: + // Turns into a normal minecart + itemName = "minecart"; + break; + case ARMOR_STAND: + case END_CRYSTAL: + case ITEM_FRAME: + case MINECART: + case PAINTING: + // No spawn egg, just an item + itemName = entity.getEntityType().toString().toLowerCase(); + break; + default: + itemName = entity.getEntityType().toString().toLowerCase() + "_spawn_egg"; + break; + } + + String fullItemName = "minecraft:" + itemName; + ItemEntry entry = ItemRegistry.getItemEntry(fullItemName); + // Verify it is, indeed, an item + if (entry == null) return; + + InventoryUtils.findOrCreatePickedBlock(session, fullItemName); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java index cb51e2f3b6d..b3cc14b9743 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java @@ -26,6 +26,10 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerChangeHeldItemPacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCreativeInventoryActionPacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientMoveItemToHotbarPacket; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; @@ -33,12 +37,14 @@ import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; -import org.geysermc.connector.common.ChatColor; +import com.nukkitx.protocol.bedrock.packet.PlayerHotbarPacket; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.DoubleChestInventoryTranslator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.item.ItemTranslator; @@ -151,4 +157,92 @@ public static ItemData createUnusableSpaceBlock(String description) { root.put("display", display.build()); return ItemData.of(ItemRegistry.ITEM_ENTRIES.get(ItemRegistry.BARRIER_INDEX).getBedrockId(), (short) 0, 1, root.build()); } + + /** + * Attempt to find the specified item name in the session's inventory. + * If it is found and in the hotbar, set the user's held item to that slot. + * If it is found in another part of the inventory, move it. + * If it is not found and the user is in creative mode, create the item, + * overriding the current item slot if no other hotbar slots are empty, or otherwise selecting the empty slot. + * + * This attempts to mimic Java Edition behavior as best as it can. + * @param session the Bedrock client's session + * @param itemName the Java identifier of the item to search/select + */ + public static void findOrCreatePickedBlock(GeyserSession session, String itemName) { + // Get the inventory to choose a slot to pick + Inventory inventory = session.getInventoryCache().getOpenInventory(); + if (inventory == null) { + inventory = session.getInventory(); + } + + // Check hotbar for item + for (int i = 36; i < 45; i++) { + if (inventory.getItem(i) == null) { + continue; + } + ItemEntry item = ItemRegistry.getItem(inventory.getItem(i)); + // If this isn't the item we're looking for + if (!item.getJavaIdentifier().equals(itemName)) { + continue; + } + + setHotbarItem(session, i); + // Don't check inventory if item was in hotbar + return; + } + + // Check inventory for item + for (int i = 9; i < 36; i++) { + if (inventory.getItem(i) == null) { + continue; + } + ItemEntry item = ItemRegistry.getItem(inventory.getItem(i)); + // If this isn't the item we're looking for + if (!item.getJavaIdentifier().equals(itemName)) { + continue; + } + + ClientMoveItemToHotbarPacket packetToSend = new ClientMoveItemToHotbarPacket(i); // https://wiki.vg/Protocol#Pick_Item + session.sendDownstreamPacket(packetToSend); + return; + } + + // If we still have not found the item, and we're in creative, ask for the item from the server. + if (session.getGameMode() == GameMode.CREATIVE) { + int slot = session.getInventory().getHeldItemSlot() + 36; + if (session.getInventory().getItemInHand() != null) { // Otherwise we should just use the current slot + for (int i = 36; i < 45; i++) { + if (inventory.getItem(i) == null) { + slot = i; + break; + } + } + } + + ClientCreativeInventoryActionPacket actionPacket = new ClientCreativeInventoryActionPacket(slot, + new ItemStack(ItemRegistry.getItemEntry(itemName).getJavaId())); + if ((slot - 36) != session.getInventory().getHeldItemSlot()) { + setHotbarItem(session, slot); + } + session.sendDownstreamPacket(actionPacket); + } + } + + /** + * Changes the held item slot to the specified slot + * @param session GeyserSession + * @param slot inventory slot to be selected + */ + private static void setHotbarItem(GeyserSession session, int slot) { + PlayerHotbarPacket hotbarPacket = new PlayerHotbarPacket(); + hotbarPacket.setContainerId(0); + // Java inventory slot to hotbar slot ID + hotbarPacket.setSelectedHotbarSlot(slot - 36); + hotbarPacket.setSelectHotbarSlot(true); + session.sendUpstreamPacket(hotbarPacket); + ClientPlayerChangeHeldItemPacket heldItemPacket = new ClientPlayerChangeHeldItemPacket(slot); + session.sendDownstreamPacket(heldItemPacket); + session.getInventory().setHeldItemSlot(slot - 36); + } } From 9c8eb00cd59d9272314d2b127bab3c6b5ed02bc8 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 14 Sep 2020 20:52:50 -0400 Subject: [PATCH 44/52] JavaCollectItemTranslator: check null for entities (#1267) --- .../translators/java/world/JavaCollectItemTranslator.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaCollectItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaCollectItemTranslator.java index 31379bd2d48..a90c7016b9a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaCollectItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaCollectItemTranslator.java @@ -41,6 +41,7 @@ public void translate(ServerEntityCollectItemPacket packet, GeyserSession sessio TakeItemEntityPacket takeItemEntityPacket = new TakeItemEntityPacket(); // Collected entity is the item Entity collectedEntity = session.getEntityCache().getEntityByJavaId(packet.getCollectedEntityId()); + if (collectedEntity == null) return; // Collector is the entity picking up the item Entity collectorEntity; if (packet.getCollectorEntityId() == session.getPlayerEntity().getEntityId()) { @@ -48,6 +49,7 @@ public void translate(ServerEntityCollectItemPacket packet, GeyserSession sessio } else { collectorEntity = session.getEntityCache().getEntityByJavaId(packet.getCollectorEntityId()); } + if (collectorEntity == null) return; takeItemEntityPacket.setRuntimeEntityId(collectorEntity.getGeyserId()); takeItemEntityPacket.setItemRuntimeEntityId(collectedEntity.getGeyserId()); session.sendUpstreamPacket(takeItemEntityPacket); From f5a9254faec7db958868aa34e85dcf4f8ae7a6c7 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 14 Sep 2020 20:53:47 -0400 Subject: [PATCH 45/52] Disconnect player if Java sends disconnection (#1274) --- .../java/JavaDisconnectPacket.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java new file mode 100644 index 00000000000..43211111930 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.java; + +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerDisconnectPacket; +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.MessageUtils; + +@Translator(packet = ServerDisconnectPacket.class) +public class JavaDisconnectPacket extends PacketTranslator { + + @Override + public void translate(ServerDisconnectPacket packet, GeyserSession session) { + session.disconnect(MessageUtils.getTranslatedBedrockMessage(packet.getReason(), session.getClientData().getLanguageCode(), true)); + } +} From 6638c53029eca3dc742ec6db08f92d011771cb8a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 14 Sep 2020 20:54:19 -0400 Subject: [PATCH 46/52] Implement command block and jigsaw support (#1291) * Implement command block and jigsaw support - Command block UI is now fully implemented to match Java Edition. - Command block minecarts are now supported. - Command blocks now show the correct type of command block. - Jigsaw blocks are translated. Structure blocks can be implemented, but these will be trickier as there are significant GUI differences between Java and Bedrock. * Add more detail about command block minecart color * Set PlayerPermission.OPERATOR to allow command blocks to be destroyed --- README.md | 1 - .../entity/CommandBlockMinecartEntity.java | 67 +++++++++++++++++ .../connector/entity/type/EntityType.java | 2 +- .../network/session/GeyserSession.java | 3 +- .../BedrockBlockEntityDataTranslator.java | 13 ++++ .../BedrockCommandBlockUpdateTranslator.java | 71 +++++++++++++++++++ ...BedrockInventoryTransactionTranslator.java | 33 +++++++++ .../world/JavaUpdateTileEntityTranslator.java | 16 ++++- .../world/block/BlockStateValues.java | 16 +++++ .../world/block/BlockTranslator.java | 33 ++++++--- .../entity/BannerBlockEntityTranslator.java | 16 ----- .../entity/BedBlockEntityTranslator.java | 12 ---- .../block/entity/BlockEntityTranslator.java | 9 +-- .../entity/CampfireBlockEntityTranslator.java | 12 ---- .../CommandBlockBlockEntityTranslator.java | 67 +++++++++++++++++ .../DoubleChestBlockEntityTranslator.java | 10 --- .../entity/EmptyBlockEntityTranslator.java | 10 --- .../EndGatewayBlockEntityTranslator.java | 20 +----- .../JigsawBlockBlockEntityTranslator.java | 47 ++++++++++++ .../ShulkerBoxBlockEntityTranslator.java | 12 ---- .../entity/SignBlockEntityTranslator.java | 18 ----- .../entity/SkullBlockEntityTranslator.java | 13 ---- .../entity/SpawnerBlockEntityTranslator.java | 13 ---- 23 files changed, 359 insertions(+), 155 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandBlockUpdateTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java diff --git a/README.md b/README.md index 156745df370..ba3e6fc2d3c 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,6 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set - [ ] Beacon - [ ] Cartography Table - [ ] Stonecutter - - [ ] Command Block - [ ] Structure Block - [ ] Horse Inventory - [ ] Loom diff --git a/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java new file mode 100644 index 00000000000..8cabba64557 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.message.Message; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.connector.utils.MessageUtils; + +public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity { + + public CommandBlockMinecartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + // Required, or else the GUI will not open + metadata.put(EntityData.CONTAINER_TYPE, (byte) 16); + metadata.put(EntityData.CONTAINER_BASE_SIZE, 1); + // Required, or else the client does not bother to send a packet back with the new information + metadata.put(EntityData.COMMAND_BLOCK_ENABLED, (byte) 1); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 13) { + metadata.put(EntityData.COMMAND_BLOCK_COMMAND, entityMetadata.getValue()); + } + if (entityMetadata.getId() == 14) { + metadata.put(EntityData.COMMAND_BLOCK_LAST_OUTPUT, MessageUtils.getBedrockMessage((Message) entityMetadata.getValue())); + } + super.updateBedrockMetadata(entityMetadata, session); + } + + /** + * By default, the command block shown is purple on Bedrock, which does not match Java Edition's orange. + */ + @Override + public void updateDefaultBlockMetadata() { + metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.BEDROCK_RUNTIME_COMMAND_BLOCK_ID); + metadata.put(EntityData.DISPLAY_OFFSET, 6); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index 87f4c8b506d..500e135ed79 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java @@ -135,7 +135,7 @@ public enum EntityType { MINECART_CHEST(MinecartEntity.class, 98, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:chest_minecart"), MINECART_FURNACE(FurnaceMinecartEntity.class, 98, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:minecart"), MINECART_SPAWNER(SpawnerMinecartEntity.class, 98, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:minecart"), - MINECART_COMMAND_BLOCK(MinecartEntity.class, 100, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:command_block_minecart"), + MINECART_COMMAND_BLOCK(CommandBlockMinecartEntity.class, 100, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:command_block_minecart"), LINGERING_POTION(ThrowableEntity.class, 101, 0f), LLAMA_SPIT(Entity.class, 102, 0.25f), EVOKER_FANGS(Entity.class, 103, 0.8f, 0.5f, 0.5f, 0f, "minecraft:evocation_fang"), diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 7a61eb220bf..efdb1aa6155 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 @@ -714,7 +714,8 @@ public void sendAdventureSettings() { // This allows mobile players access to a GUI for doing commands. The commands there do not change above OPERATOR // and all commands there are accessible with OP permission level 2 adventureSettingsPacket.setCommandPermission(opPermissionLevel >= 2 ? CommandPermission.OPERATOR : CommandPermission.NORMAL); - adventureSettingsPacket.setPlayerPermission(PlayerPermission.MEMBER); + // Required to make command blocks destroyable + adventureSettingsPacket.setPlayerPermission(opPermissionLevel >= 2 ? PlayerPermission.OPERATOR : PlayerPermission.MEMBER); Set flags = new HashSet<>(); if (canFly) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java index 147b8a3b15e..3522b4d56ad 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java @@ -26,6 +26,7 @@ package org.geysermc.connector.network.translators.bedrock; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientUpdateJigsawBlockPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientUpdateSignPacket; import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; @@ -109,6 +110,18 @@ public void translate(BlockEntityDataPacket packet, GeyserSession session) { // We set the sign text cached in the session to null to indicate there is no work-in-progress sign session.setLastSignMessage(null); + + } else if (tag.getString("id").equals("JigsawBlock")) { + // Client has just sent a jigsaw block update + Position pos = new Position(tag.getInt("x"), tag.getInt("y"), tag.getInt("z")); + String name = tag.getString("name"); + String target = tag.getString("target"); + String pool = tag.getString("target_pool"); + String finalState = tag.getString("final_state"); + String joint = tag.getString("joint"); + ClientUpdateJigsawBlockPacket jigsawPacket = new ClientUpdateJigsawBlockPacket(pos, name, target, pool, + finalState, joint); + session.sendDownstreamPacket(jigsawPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandBlockUpdateTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandBlockUpdateTranslator.java new file mode 100644 index 00000000000..b1e732ffebf --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandBlockUpdateTranslator.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.bedrock; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.data.game.world.block.CommandBlockMode; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientUpdateCommandBlockMinecartPacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientUpdateCommandBlockPacket; +import com.nukkitx.protocol.bedrock.packet.CommandBlockUpdatePacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +@Translator(packet = CommandBlockUpdatePacket.class) +public class BedrockCommandBlockUpdateTranslator extends PacketTranslator { + + @Override + public void translate(CommandBlockUpdatePacket packet, GeyserSession session) { + String command = packet.getCommand(); + boolean outputTracked = packet.isOutputTracked(); + if (packet.isBlock()) { + CommandBlockMode mode; + switch (packet.getMode()) { + case CHAIN: // The green one + mode = CommandBlockMode.SEQUENCE; + break; + case REPEATING: // The purple one + mode = CommandBlockMode.AUTO; + break; + default: // NORMAL, the orange one + mode = CommandBlockMode.REDSTONE; + break; + } + boolean isConditional = packet.isConditional(); + boolean automatic = !packet.isRedstoneMode(); // Automatic = Always Active option in Java + ClientUpdateCommandBlockPacket commandBlockPacket = new ClientUpdateCommandBlockPacket( + new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()), + command, mode, outputTracked, isConditional, automatic); + session.sendDownstreamPacket(commandBlockPacket); + } else { + ClientUpdateCommandBlockMinecartPacket commandMinecartPacket = new ClientUpdateCommandBlockMinecartPacket( + (int) session.getEntityCache().getEntityByGeyserId(packet.getMinecartRuntimeEntityId()).getEntityId(), + command, outputTracked + ); + session.sendDownstreamPacket(commandMinecartPacket); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index cc1da85e8c3..95e66957287 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -39,8 +39,11 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import com.nukkitx.protocol.bedrock.packet.InventoryTransactionPacket; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; +import org.geysermc.connector.entity.CommandBlockMinecartEntity; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.ItemFrameEntity; import org.geysermc.connector.entity.living.merchant.AbstractMerchantEntity; @@ -105,6 +108,24 @@ public void translate(InventoryTransactionPacket packet, GeyserSession session) session.sendDownstreamPacket(itemPacket); } + if (packet.getActions().isEmpty()) { + if (session.getOpPermissionLevel() >= 2 && session.getGameMode() == GameMode.CREATIVE) { + // Otherwise insufficient permissions + int blockState = BlockTranslator.getJavaBlockState(packet.getBlockRuntimeId()); + String blockName = BlockTranslator.getJavaIdBlockMap().inverse().getOrDefault(blockState, ""); + // In the future this can be used for structure blocks too, however not all elements + // are available in each GUI + if (blockName.contains("jigsaw")) { + ContainerOpenPacket openPacket = new ContainerOpenPacket(); + openPacket.setBlockPosition(packet.getBlockPosition()); + openPacket.setId((byte) 1); + openPacket.setType(ContainerType.JIGSAW_EDITOR); + openPacket.setUniqueEntityId(-1); + session.sendUpstreamPacket(openPacket); + } + } + } + Vector3i blockPos = packet.getBlockPosition(); // TODO: Find a better way to do this? switch (packet.getBlockFace()) { @@ -197,6 +218,18 @@ public void translate(InventoryTransactionPacket packet, GeyserSession session) //https://wiki.vg/Protocol#Interact_Entity switch (packet.getActionType()) { case 0: //Interact + if (entity instanceof CommandBlockMinecartEntity) { + // The UI is handled client-side on Java Edition + // Ensure OP permission level and gamemode is appropriate + if (session.getOpPermissionLevel() < 2 || session.getGameMode() != GameMode.CREATIVE) return; + ContainerOpenPacket openPacket = new ContainerOpenPacket(); + openPacket.setBlockPosition(Vector3i.ZERO); + openPacket.setId((byte) 1); + openPacket.setType(ContainerType.COMMAND_BLOCK); + openPacket.setUniqueEntityId(entity.getGeyserId()); + session.sendUpstreamPacket(openPacket); + break; + } Vector3f vector = packet.getClickPosition(); ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(), InteractAction.INTERACT, Hand.MAIN_HAND, session.isSneaking()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTileEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTileEntityTranslator.java index ae4ed779296..bfe4d8108ba 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTileEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTileEntityTranslator.java @@ -25,8 +25,12 @@ package org.geysermc.connector.network.translators.java.world; +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.data.game.world.block.UpdatedTileType; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdateTileEntityPacket; - +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -54,5 +58,15 @@ public void translate(ServerUpdateTileEntityPacket packet, GeyserSession session } else { BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(id, packet.getNbt(), 0), packet.getPosition()); } + // If block entity is command block, OP permission level is appropriate, player is in creative mode and the NBT is not empty + if (packet.getType() == UpdatedTileType.COMMAND_BLOCK && session.getOpPermissionLevel() >= 2 && + session.getGameMode() == GameMode.CREATIVE && packet.getNbt().size() > 5) { + ContainerOpenPacket openPacket = new ContainerOpenPacket(); + openPacket.setBlockPosition(Vector3i.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ())); + openPacket.setId((byte) 1); + openPacket.setType(ContainerType.COMMAND_BLOCK); + openPacket.setUniqueEntityId(-1); + session.sendUpstreamPacket(openPacket); + } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java index 53607317a66..305118e6fd0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java @@ -39,6 +39,7 @@ public class BlockStateValues { private static final Int2IntMap BANNER_COLORS = new Int2IntOpenHashMap(); private static final Int2ByteMap BED_COLORS = new Int2ByteOpenHashMap(); + private static final Int2ByteMap COMMAND_BLOCK_VALUES = new Int2ByteOpenHashMap(); private static final Int2ObjectMap DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>(); private static final Map FLOWER_POT_BLOCKS = new HashMap<>(); @@ -67,6 +68,11 @@ public static void storeBlockStateValues(Map.Entry entry, int return; } + if (entry.getKey().contains("command_block")) { + COMMAND_BLOCK_VALUES.put(javaBlockState, entry.getKey().contains("conditional=true") ? (byte) 1 : (byte) 0); + return; + } + if (entry.getValue().get("double_chest_position") != null) { boolean isX = (entry.getValue().get("x") != null); boolean isDirectionPositive = ((entry.getValue().get("x") != null && entry.getValue().get("x").asBoolean()) || @@ -138,6 +144,16 @@ public static byte getBedColor(int state) { return -1; } + /** + * The block state in Java and Bedrock both contain the conditional bit, however command block block entity tags + * in Bedrock need the conditional information. + * + * @return the list of all command blocks and if they are conditional (1 or 0) + */ + public static Int2ByteMap getCommandBlockValues() { + return COMMAND_BLOCK_VALUES; + } + /** * All double chest values are part of the block state in Java and part of the block entity tag in Bedrock. * This gives the DoubleChestValue that can be calculated into the final tag. diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java index 81c86c307d7..fc7c852cb66 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java @@ -67,6 +67,11 @@ public class BlockTranslator { public static final Int2BooleanMap JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND = new Int2BooleanOpenHashMap(); public static final Int2ObjectMap JAVA_RUNTIME_ID_TO_TOOL_TYPE = new Int2ObjectOpenHashMap<>(); + /** + * Runtime command block ID, used for fixing command block minecart appearances + */ + public static final int BEDROCK_RUNTIME_COMMAND_BLOCK_ID; + // For block breaking animation math public static final IntSet JAVA_RUNTIME_WOOL_IDS = new IntOpenHashSet(); public static final int JAVA_RUNTIME_COBWEB_ID; @@ -115,6 +120,7 @@ public class BlockTranslator { int javaRuntimeId = -1; int bedrockRuntimeId = 0; int cobwebRuntimeId = -1; + int commandBlockRuntimeId = -1; int furnaceRuntimeId = -1; int furnaceLitRuntimeId = -1; int spawnerRuntimeId = -1; @@ -142,14 +148,6 @@ public class BlockTranslator { JAVA_RUNTIME_ID_TO_TOOL_TYPE.put(javaRuntimeId, toolTypeNode.textValue()); } - if (javaId.contains("wool")) { - JAVA_RUNTIME_WOOL_IDS.add(javaRuntimeId); - } - - if (javaId.contains("cobweb")) { - cobwebRuntimeId = javaRuntimeId; - } - JAVA_ID_BLOCK_MAP.put(javaId, javaRuntimeId); // Used for adding all "special" Java block states to block state map @@ -205,15 +203,23 @@ public class BlockTranslator { } JAVA_TO_BEDROCK_BLOCK_MAP.put(javaRuntimeId, bedrockRuntimeId); - if (javaId.startsWith("minecraft:furnace[facing=north")) { + if (javaId.contains("wool")) { + JAVA_RUNTIME_WOOL_IDS.add(javaRuntimeId); + + } else if (javaId.contains("cobweb")) { + cobwebRuntimeId = javaRuntimeId; + + } else if (javaId.equals("minecraft:command_block[conditional=false,facing=north]")) { + commandBlockRuntimeId = bedrockRuntimeId; + + } else if (javaId.startsWith("minecraft:furnace[facing=north")) { if (javaId.contains("lit=true")) { furnaceLitRuntimeId = javaRuntimeId; } else { furnaceRuntimeId = javaRuntimeId; } - } - if (javaId.startsWith("minecraft:spawner")) { + } else if (javaId.startsWith("minecraft:spawner")) { spawnerRuntimeId = javaRuntimeId; } @@ -225,6 +231,11 @@ public class BlockTranslator { } JAVA_RUNTIME_COBWEB_ID = cobwebRuntimeId; + if (commandBlockRuntimeId == -1) { + throw new AssertionError("Unable to find command block in palette"); + } + BEDROCK_RUNTIME_COMMAND_BLOCK_ID = commandBlockRuntimeId; + if (furnaceRuntimeId == -1) { throw new AssertionError("Unable to find furnace in palette"); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java index 9e86cb4cfb6..57393a6c594 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java @@ -27,12 +27,9 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.nbt.NbtType; import org.geysermc.connector.network.translators.item.translators.BannerTranslator; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -65,17 +62,4 @@ public Map translateTag(CompoundTag tag, int blockState) { return tags; } - @Override - public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { - CompoundTag tag = getConstantJavaTag(javaId, x, y, z); - tag.put(new ListTag("Patterns")); - return tag; - } - - @Override - public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - return getConstantBedrockTag(bedrockId, x, y, z).toBuilder() - .putList("Patterns", NbtType.COMPOUND, new ArrayList<>()) - .build(); - } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java index b84aad9846e..080bdc3b2e1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java @@ -26,7 +26,6 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.nukkitx.nbt.NbtMap; import org.geysermc.connector.network.translators.world.block.BlockStateValues; import java.util.HashMap; @@ -50,15 +49,4 @@ public Map translateTag(CompoundTag tag, int blockState) { return tags; } - @Override - public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { - return null; - } - - @Override - public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - return getConstantBedrockTag(bedrockId, x, y, z).toBuilder() - .putByte("color", (byte) 0) - .build(); - } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java index a538c2dcc14..54f593a61a2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java @@ -28,9 +28,9 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.IntTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; - import it.unimi.dsi.fastutil.objects.ObjectArrayList; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.utils.BlockEntityUtils; @@ -53,6 +53,7 @@ public abstract class BlockEntityTranslator { { // Bedrock/Java differences put("minecraft:enchanting_table", "EnchantTable"); + put("minecraft:jigsaw", "JigsawBlock"); put("minecraft:piston_head", "PistonArm"); put("minecraft:trapped_chest", "Chest"); // There are some legacy IDs sent but as far as I can tell they are not needed for things to work properly @@ -90,10 +91,6 @@ public static void init() { public abstract Map translateTag(CompoundTag tag, int blockState); - public abstract CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z); - - public abstract NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z); - public NbtMap getBlockEntityTag(String id, CompoundTag tag, int blockState) { int x = Integer.parseInt(String.valueOf(tag.getValue().get("x").getValue())); int y = Integer.parseInt(String.valueOf(tag.getValue().get("y").getValue())); @@ -124,7 +121,7 @@ protected NbtMap getConstantBedrockTag(String bedrockId, int x, int y, int z) { } @SuppressWarnings("unchecked") - protected T getOrDefault(com.github.steveice10.opennbt.tag.builtin.Tag tag, T defaultValue) { + protected T getOrDefault(Tag tag, T defaultValue) { return (tag != null && tag.getValue() != null) ? (T) tag.getValue() : defaultValue; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java index e3d2c9f5e81..d6ac0281b62 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java @@ -50,18 +50,6 @@ public Map translateTag(CompoundTag tag, int blockState) { return tags; } - @Override - public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { - CompoundTag tag = getConstantJavaTag(javaId, x, y, z); - tag.put(new ListTag("Items")); - return tag; - } - - @Override - public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - return getConstantBedrockTag(bedrockId, x, y, z); - } - protected NbtMap getItem(CompoundTag tag) { ItemEntry entry = ItemRegistry.getItemEntry((String) tag.get("id").getValue()); NbtMapBuilder tagBuilder = NbtMap.builder() diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java new file mode 100644 index 00000000000..6bc940adb78 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.world.block.entity; + +import com.github.steveice10.opennbt.tag.builtin.*; +import org.geysermc.connector.network.translators.world.block.BlockStateValues; +import org.geysermc.connector.utils.MessageUtils; + +import java.util.HashMap; +import java.util.Map; + +@BlockEntity(name = "CommandBlock", regex = "command_block") +public class CommandBlockBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { + + @Override + public Map translateTag(CompoundTag tag, int blockState) { + Map map = new HashMap<>(); + if (tag.size() < 5) { + return map; // These values aren't here + } + // Java infers from the block state, but Bedrock needs it in the tag + map.put("conditionalMode", BlockStateValues.getCommandBlockValues().getOrDefault(blockState, (byte) 0)); + // Java and Bedrock values + map.put("conditionMet", ((ByteTag) tag.get("conditionMet")).getValue()); + map.put("auto", ((ByteTag) tag.get("auto")).getValue()); + map.put("CustomName", MessageUtils.getBedrockMessage(((StringTag) tag.get("CustomName")).getValue())); + map.put("powered", ((ByteTag) tag.get("powered")).getValue()); + map.put("Command", ((StringTag) tag.get("Command")).getValue()); + map.put("SuccessCount", ((IntTag) tag.get("SuccessCount")).getValue()); + map.put("TrackOutput", ((ByteTag) tag.get("TrackOutput")).getValue()); + map.put("UpdateLastExecution", ((ByteTag) tag.get("UpdateLastExecution")).getValue()); + if (tag.get("LastExecution") != null) { + map.put("LastExecution", ((LongTag) tag.get("LastExecution")).getValue()); + } else { + map.put("LastExecution", (long) 0); + } + return map; + } + + @Override + public boolean isBlock(int blockState) { + return BlockStateValues.getCommandBlockValues().containsKey(blockState); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java index fa8bab3b09a..5b59420e0dc 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java @@ -27,7 +27,6 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockStateValues; @@ -92,13 +91,4 @@ public Map translateTag(CompoundTag tag, int blockState) { return tags; } - @Override - public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { - return null; - } - - @Override - public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - return null; - } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java index 6de136119d7..e9715bd3299 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java @@ -26,7 +26,6 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.nukkitx.nbt.NbtMap; import java.util.HashMap; import java.util.Map; @@ -39,13 +38,4 @@ public Map translateTag(CompoundTag tag, int blockState) { return new HashMap<>(); } - @Override - public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { - return getConstantJavaTag(javaId, x, y, z); - } - - @Override - public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - return getConstantBedrockTag(bedrockId, x, y, z); - } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java index 784afed5bc2..af94c560dcb 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java @@ -26,14 +26,12 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.github.steveice10.opennbt.tag.builtin.LongTag; +import com.github.steveice10.opennbt.tag.builtin.IntTag; import com.nukkitx.nbt.NbtList; -import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtType; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; -import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; @@ -56,25 +54,11 @@ public Map translateTag(CompoundTag tag, int blockState) { return tags; } - @Override - public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { - CompoundTag tag = getConstantJavaTag(javaId, x, y, z); - tag.put(new LongTag("Age")); - return tag; - } - - @Override - public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - return getConstantBedrockTag(bedrockId, x, y, z).toBuilder() - .putList("ExitPortal", NbtType.INT, Arrays.asList(0, 0, 0)) - .build(); - } - private int getExitPortalCoordinate(CompoundTag tag, String axis) { // Return 0 if it doesn't exist, otherwise give proper value if (tag.get("ExitPortal") != null) { LinkedHashMap compoundTag = (LinkedHashMap) tag.get("ExitPortal").getValue(); - com.github.steveice10.opennbt.tag.builtin.IntTag intTag = (com.github.steveice10.opennbt.tag.builtin.IntTag) compoundTag.get(axis); + IntTag intTag = (IntTag) compoundTag.get(axis); return intTag.getValue(); } return 0; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java new file mode 100644 index 00000000000..43ac1a96f14 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.world.block.entity; + +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; + +import java.util.HashMap; +import java.util.Map; + +@BlockEntity(name = "JigsawBlock", regex = "jigsaw") +public class JigsawBlockBlockEntityTranslator extends BlockEntityTranslator { + + @Override + public Map translateTag(CompoundTag tag, int blockState) { + Map map = new HashMap<>(); + map.put("joint", ((StringTag) tag.get("joint")).getValue()); + map.put("name", ((StringTag) tag.get("name")).getValue()); + map.put("target_pool", ((StringTag) tag.get("pool")).getValue()); + map.put("final_state", ((StringTag) tag.get("final_state")).getValue()); + map.put("target", ((StringTag) tag.get("target")).getValue()); + return map; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java index 08a7ae1879b..08e3abaab83 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java @@ -26,7 +26,6 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.nukkitx.nbt.NbtMap; import org.geysermc.connector.network.translators.world.block.BlockStateValues; import java.util.HashMap; @@ -46,15 +45,4 @@ public Map translateTag(CompoundTag tag, int blockState) { return tags; } - @Override - public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { - return null; - } - - @Override - public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - return getConstantBedrockTag(bedrockId, x, y, z).toBuilder() - .putByte("facing", (byte) 1) - .build(); - } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java index acec16c5cea..b40ed42c995 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java @@ -27,7 +27,6 @@ import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.nukkitx.nbt.NbtMap; import org.geysermc.connector.utils.MessageUtils; import org.geysermc.connector.utils.SignUtils; @@ -73,23 +72,6 @@ public Map translateTag(CompoundTag tag, int blockState) { 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 NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - return getConstantBedrockTag(bedrockId, x, y, z).toBuilder() - .putString("Text", "") - .build(); - } - /** * Maps a color stored in a sign's Color tag to a Bedrock Edition formatting code. *
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java index 9547ba2ff9e..6d350c0ccff 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java @@ -25,7 +25,6 @@ package org.geysermc.connector.network.translators.world.block.entity; -import com.nukkitx.nbt.NbtMap; import org.geysermc.connector.network.translators.world.block.BlockStateValues; import java.util.HashMap; @@ -51,16 +50,4 @@ public Map translateTag(com.github.steveice10.opennbt.tag.builti return tags; } - @Override - public com.github.steveice10.opennbt.tag.builtin.CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { - return null; - } - - @Override - public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - return getConstantBedrockTag(bedrockId, x, y, z).toBuilder() - .putFloat("Rotation", 0f) - .putByte("SkullType", (byte) 0) - .build(); - } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java index 3c443eeebb8..2601e3de9b9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java @@ -26,7 +26,6 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.nukkitx.nbt.NbtMap; import org.geysermc.connector.entity.type.EntityType; import java.util.HashMap; @@ -86,16 +85,4 @@ public Map translateTag(CompoundTag tag, int blockState) { return tags; } - @Override - public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { - return null; - } - - @Override - public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) { - return getConstantBedrockTag(bedrockId, x, y, z).toBuilder() - .putByte("isMovable", (byte) 1) - .putString("id", "MobSpawner") - .build(); - } } From f9c1d3f21837f9426db9bf9108faca38e0d6a333 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 16 Sep 2020 00:11:56 -0400 Subject: [PATCH 47/52] Remove Protocol v409 support (#1300) * Remove Protocol v409 support Protocol has dropped support for this version. * Fix movement * Use a static commit for Protocol --- connector/pom.xml | 6 +++--- .../org/geysermc/connector/network/BedrockProtocol.java | 2 -- .../geysermc/connector/network/session/GeyserSession.java | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index 5a19a5015a7..f52713cea00 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -31,9 +31,9 @@ compile
- com.nukkitx.protocol - bedrock-v409 - 2.6.0-SNAPSHOT + com.github.CloudburstMC.Protocol + bedrock-v408 + 250beb2a94 compile diff --git a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java index f040fc1fec9..5d4462b456a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java +++ b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java @@ -28,7 +28,6 @@ import com.nukkitx.protocol.bedrock.BedrockPacketCodec; import com.nukkitx.protocol.bedrock.v407.Bedrock_v407; import com.nukkitx.protocol.bedrock.v408.Bedrock_v408; -import com.nukkitx.protocol.bedrock.v409.Bedrock_v409; import java.util.ArrayList; import java.util.List; @@ -50,7 +49,6 @@ public class BedrockProtocol { static { SUPPORTED_BEDROCK_CODECS.add(Bedrock_v407.V407_CODEC); SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); - SUPPORTED_BEDROCK_CODECS.add(Bedrock_v409.V409_CODEC); } /** 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 efdb1aa6155..81b9228266d 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 @@ -613,7 +613,7 @@ private void startGame() { startGamePacket.setBlockPalette(BlockTranslator.BLOCKS); startGamePacket.setItemEntries(ItemRegistry.ITEMS); startGamePacket.setVanillaVersion("*"); - // startGamePacket.setMovementServerAuthoritative(true); + startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT); upstream.sendPacket(startGamePacket); } From 3c1d4aae937f445aab50ea62f8e5f02150bba526 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 16 Sep 2020 00:18:18 -0400 Subject: [PATCH 48/52] Fix inconsistencies with players and the player list (#1298) * Fix inconsistencies with players and the player list This commit makes the player list entry packet control the player cache, fixing inconsistencies that appeared when removing the override on despawning the player. * Update comments --- .../geysermc/connector/entity/PlayerEntity.java | 2 +- .../network/session/cache/EntityCache.java | 3 --- .../player/JavaPlayerListEntryTranslator.java | 15 +++++---------- 3 files changed, 6 insertions(+), 14 deletions(-) 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 cc00403a0e3..390110d1849 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -118,7 +118,7 @@ public void spawnEntity(GeyserSession session) { } public void sendPlayer(GeyserSession session) { - if(session.getEntityCache().getPlayerEntity(uuid) == null) + if (session.getEntityCache().getPlayerEntity(uuid) == null) return; if (session.getUpstream().isInitialized() && session.getEntityCache().getEntityByGeyserId(geyserId) == null) { 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 0bc51ac7ae3..4b54c943458 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java @@ -76,9 +76,6 @@ public boolean removeEntity(Entity entity, boolean force) { if (entity != null && entity.isValid() && (force || entity.despawnEntity(session))) { long geyserId = entityIdTranslations.remove(entity.getEntityId()); entities.remove(geyserId); - if (entity.is(PlayerEntity.class)) { - playerEntities.remove(entity.as(PlayerEntity.class).getUuid()); - } return true; } return false; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerListEntryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerListEntryTranslator.java index 10b2ba9add3..6a9ef4dc6fc 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerListEntryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerListEntryTranslator.java @@ -88,18 +88,13 @@ public void translate(ServerPlayerListEntryPacket packet, GeyserSession session) break; case REMOVE_PLAYER: PlayerEntity entity = session.getEntityCache().getPlayerEntity(entry.getProfile().getId()); - if (entity != null && entity.isValid()) { - // remove from tablist but player entity is still there + if (entity != null) { + // Just remove the entity's player list status + // Don't despawn the entity - the Java server will also take care of that. entity.setPlayerList(false); - } else { - if (entity == null) { - // just remove it from caching - session.getEntityCache().removePlayerEntity(entry.getProfile().getId()); - } else { - entity.setPlayerList(false); - session.getEntityCache().removeEntity(entity, false); - } } + // As the player entity is no longer present, we can remove the entry + session.getEntityCache().removePlayerEntity(entry.getProfile().getId()); if (entity == session.getPlayerEntity()) { // If removing ourself we use our AuthData UUID translate.getEntries().add(new PlayerListPacket.Entry(session.getAuthData().getUUID())); From 1a49e882d3f3c5c34c426e13a4e2fe850ee5da59 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Wed, 16 Sep 2020 16:33:59 +0100 Subject: [PATCH 49/52] [Android] Remove usage of MCProtocolLib Base64 in SkinUtils + more (#1237) * Remove usage of MCProtocolLib Base64 in SkinUtils * Fix path resolution for downloading locales --- .../src/main/java/org/geysermc/connector/utils/LocaleUtils.java | 2 +- .../src/main/java/org/geysermc/connector/utils/SkinUtils.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 07c33eb1a35..d1d59490fc8 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -133,7 +133,7 @@ public static void downloadAndLoadLocale(String locale) { * @param locale Locale to download */ private static void downloadLocale(String locale) { - File localeFile = Paths.get(GeyserConnector.getInstance().getBootstrap().getConfigFolder().toString(),"locales",locale + ".json").toFile(); + File localeFile = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("locales/" + locale + ".json").toFile(); // Check if we have already downloaded the locale file if (localeFile.exists()) { 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 e3488d6e7e5..fe2a8aa96ae 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java @@ -270,7 +270,7 @@ public static void handleBedrockSkin(PlayerEntity playerEntity, BedrockClientDat GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.skin.bedrock.register", playerEntity.getUsername(), playerEntity.getUuid())); try { - byte[] skinBytes = com.github.steveice10.mc.auth.util.Base64.decode(clientData.getSkinData().getBytes("UTF-8")); + byte[] skinBytes = Base64.getDecoder().decode(clientData.getSkinData().getBytes("UTF-8")); byte[] capeBytes = clientData.getCapeData(); byte[] geometryNameBytes = Base64.getDecoder().decode(clientData.getGeometryName().getBytes("UTF-8")); From 99e72f35b308542cf0dbfb5b58816503c3d6a129 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Thu, 17 Sep 2020 01:08:26 +0100 Subject: [PATCH 50/52] Add support for manually supplying Bedrock resource packs (#1076) * send resource packs A lot of this code is nukkit-credits in the classes * send resource packs A lot of this code is nukkit-credits in the classes * Remove unnecessary code/debugs * use separately generated hashes * Updated mappings and added .mcpack support * "packs" directory auto-create (#484) * "packs" directory auto-create * cleaned indentation in ResourcePack.java * Cleaned ResourcePack.java * Another cleanup I hate editor on github. * Yet another * Another indentation cleanup * Fix resource pack loading (cherry picked from commit f93b07491ea0d4cd96254af47a16f10ce9b92128) * Move back to internal sha256 hashing (cherry picked from commit 812a3d82b2a4d34e67bbccc193fbd87ac9fb174b) * Add resource pack loading back after merge * Add comments, config option and removed unused files * Fix packs folder location and cleanup code * Move to better options for the client * Fix typos in comments * Fix pack loading * Try to make it compile * Final touches? * Add Javadoc for MathUtils#constrain Co-authored-by: EOT3000 <43685885+EOT3000@users.noreply.github.com> Co-authored-by: Vesek <61123478+Vesek@users.noreply.github.com> Co-authored-by: Heath123 Co-authored-by: Camotoy <20743703+DoctorMacc@users.noreply.github.com> --- .gitignore | 3 +- .../geysermc/connector/GeyserConnector.java | 6 +- .../configuration/GeyserConfiguration.java | 2 + .../GeyserJacksonConfiguration.java | 3 + .../network/UpstreamPacketHandler.java | 81 ++++++++++-- .../geysermc/connector/utils/FileUtils.java | 30 +++++ .../geysermc/connector/utils/MathUtils.java | 20 +++ .../connector/utils/ResourcePack.java | 114 +++++++++++++++++ .../connector/utils/ResourcePackManifest.java | 117 ++++++++++++++++++ connector/src/main/resources/config.yml | 5 + 10 files changed, 372 insertions(+), 9 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java create mode 100644 connector/src/main/java/org/geysermc/connector/utils/ResourcePackManifest.java diff --git a/.gitignore b/.gitignore index 69c07e500ed..e3c3b0a5699 100644 --- a/.gitignore +++ b/.gitignore @@ -241,4 +241,5 @@ config.yml logs/ public-key.pem locales/ -/cache/ \ No newline at end of file +/cache/ +/packs/ \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 2593c516ab5..3c814393daf 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -25,6 +25,7 @@ package org.geysermc.connector; +import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.nukkitx.network.raknet.RakNetConstants; @@ -56,6 +57,7 @@ import org.geysermc.connector.utils.DimensionUtils; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.LocaleUtils; +import org.geysermc.connector.utils.ResourcePack; import javax.naming.directory.Attribute; import javax.naming.directory.InitialDirContext; @@ -73,7 +75,7 @@ @Getter public class GeyserConnector { - public static final ObjectMapper JSON_MAPPER = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES); + public static final ObjectMapper JSON_MAPPER = new ObjectMapper().enable(JsonParser.Feature.IGNORE_UNDEFINED).enable(JsonParser.Feature.ALLOW_COMMENTS).disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); public static final String NAME = "Geyser"; public static final String VERSION = "DEV"; // A fallback for running in IDEs @@ -136,6 +138,8 @@ private GeyserConnector(PlatformType platformType, GeyserBootstrap bootstrap) { SoundRegistry.init(); SoundHandlerRegistry.init(); + ResourcePack.loadPacks(); + if (platformType != PlatformType.STANDALONE && config.getRemote().getAddress().equals("auto")) { // Set the remote address to localhost since that is where we are always connecting try { diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java index 2d76e574068..c1cc4d03640 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java @@ -79,6 +79,8 @@ public interface GeyserConfiguration { boolean isCacheChunks(); + boolean isForceResourcePacks(); + int getCacheImages(); IMetricsInfo getMetrics(); diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java index 6748ef7e7c3..99a3a7a5a1d 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java @@ -103,6 +103,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("above-bedrock-nether-building") private boolean aboveBedrockNetherBuilding = false; + @JsonProperty("force-resource-packs") + private boolean forceResourcePacks = true; + private MetricsInfo metrics; @Getter 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 f65b3ef1abb..7e97d429828 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -26,17 +26,24 @@ package org.geysermc.connector.network; import com.nukkitx.protocol.bedrock.BedrockPacket; +import com.nukkitx.protocol.bedrock.data.ResourcePackType; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; import com.nukkitx.protocol.bedrock.packet.*; +import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.configuration.GeyserConfiguration; -import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslatorRegistry; -import org.geysermc.connector.utils.LoginEncryptionUtils; import org.geysermc.connector.utils.LanguageUtils; +import org.geysermc.connector.utils.LoginEncryptionUtils; +import org.geysermc.connector.utils.MathUtils; +import org.geysermc.connector.utils.ResourcePack; +import org.geysermc.connector.utils.ResourcePackManifest; import org.geysermc.connector.utils.SettingsUtils; +import java.io.FileInputStream; +import java.io.InputStream; + public class UpstreamPacketHandler extends LoggingPacketHandler { public UpstreamPacketHandler(GeyserConnector connector, GeyserSession session) { @@ -70,6 +77,11 @@ public boolean handle(LoginPacket loginPacket) { session.sendUpstreamPacket(playStatus); ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket(); + for(ResourcePack resourcePack : ResourcePack.PACKS.values()) { + ResourcePackManifest.Header header = resourcePack.getManifest().getHeader(); + resourcePacksInfo.getResourcePackInfos().add(new ResourcePacksInfoPacket.Entry(header.getUuid().toString(), header.getVersionString(), resourcePack.getFile().length(), "", "", "", false)); + } + resourcePacksInfo.setForcedToAccept(GeyserConnector.getInstance().getConfig().isForceResourcePacks()); session.sendUpstreamPacket(resourcePacksInfo); return true; } @@ -81,13 +93,42 @@ public boolean handle(ResourcePackClientResponsePacket packet) { session.connect(connector.getRemoteServer()); connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.connect", session.getAuthData().getName())); break; + + case SEND_PACKS: + for(String id : packet.getPackIds()) { + ResourcePackDataInfoPacket data = new ResourcePackDataInfoPacket(); + String[] packID = id.split("_"); + ResourcePack pack = ResourcePack.PACKS.get(packID[0]); + ResourcePackManifest.Header header = pack.getManifest().getHeader(); + + data.setPackId(header.getUuid()); + int chunkCount = (int) Math.ceil((int) pack.getFile().length() / (double) ResourcePack.CHUNK_SIZE); + data.setChunkCount(chunkCount); + data.setCompressedPackSize(pack.getFile().length()); + data.setMaxChunkSize(ResourcePack.CHUNK_SIZE); + data.setHash(pack.getSha256()); + data.setPackVersion(packID[1]); + data.setPremium(false); + data.setType(ResourcePackType.RESOURCE); + + session.sendUpstreamPacket(data); + } + break; + case HAVE_ALL_PACKS: - ResourcePackStackPacket stack = new ResourcePackStackPacket(); - stack.setExperimental(false); - stack.setForcedToAccept(false); - stack.setGameVersion("*"); - session.sendUpstreamPacket(stack); + ResourcePackStackPacket stackPacket = new ResourcePackStackPacket(); + stackPacket.setExperimental(false); + stackPacket.setForcedToAccept(false); // Leaving this as false allows the player to choose to download or not + stackPacket.setGameVersion(session.getClientData().getGameVersion()); + + for (ResourcePack pack : ResourcePack.PACKS.values()) { + ResourcePackManifest.Header header = pack.getManifest().getHeader(); + stackPacket.getResourcePacks().add(new ResourcePackStackPacket.Entry(header.getUuid().toString(), header.getVersionString(), "")); + } + + session.sendUpstreamPacket(stackPacket); break; + default: session.disconnect("disconnectionScreen.resourcePack"); break; @@ -149,4 +190,30 @@ public boolean handle(MovePlayerPacket packet) { boolean defaultHandler(BedrockPacket packet) { return translateAndDefault(packet); } + + @Override + public boolean handle(ResourcePackChunkRequestPacket packet) { + ResourcePackChunkDataPacket data = new ResourcePackChunkDataPacket(); + ResourcePack pack = ResourcePack.PACKS.get(packet.getPackId().toString()); + + data.setChunkIndex(packet.getChunkIndex()); + data.setProgress(packet.getChunkIndex() * ResourcePack.CHUNK_SIZE); + data.setPackVersion(packet.getPackVersion()); + data.setPackId(packet.getPackId()); + + int offset = packet.getChunkIndex() * ResourcePack.CHUNK_SIZE; + byte[] packData = new byte[(int) MathUtils.constrain(pack.getFile().length() - offset, 0, ResourcePack.CHUNK_SIZE)]; + + try (InputStream inputStream = new FileInputStream(pack.getFile())) { + inputStream.skip(offset); + inputStream.read(packData, 0, packData.length); + } catch (Exception e) { + e.printStackTrace(); + } + + data.setData(packData); + + session.sendUpstreamPacket(data); + return true; + } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java index 300946b34d2..63255cfa020 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java @@ -25,6 +25,8 @@ package org.geysermc.connector.utils; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import org.geysermc.connector.GeyserConnector; @@ -37,6 +39,8 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.nio.file.Files; +import java.security.MessageDigest; import java.util.function.Function; public class FileUtils { @@ -55,6 +59,15 @@ public static T loadConfig(File src, Class valueType) throws IOException return objectMapper.readValue(src, valueType); } + public static T loadYaml(InputStream src, Class valueType) throws IOException { + ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()).enable(JsonParser.Feature.IGNORE_UNDEFINED).disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + return objectMapper.readValue(src, valueType); + } + + public static T loadJson(InputStream src, Class valueType) throws IOException { + return GeyserConnector.JSON_MAPPER.readValue(src, valueType); + } + /** * Open the specified file or copy if from resources * @@ -145,6 +158,23 @@ public static InputStream getResource(String resource) { return stream; } + /** + * Calculate the SHA256 hash of the resource pack file + * @param file File to calculate the hash for + * @return A byte[] representation of the hash + */ + public static byte[] calculateSHA256(File file) { + byte[] sha256; + + try { + sha256 = MessageDigest.getInstance("SHA-256").digest(Files.readAllBytes(file.toPath())); + } catch (Exception e) { + throw new RuntimeException("Could not calculate pack hash", e); + } + + return sha256; + } + /** * Get the stored reflection data for a given path * 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 48702492252..29dd2cc23d2 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java @@ -40,6 +40,26 @@ public static int ceil(float floatNumber) { return floatNumber > truncated ? truncated + 1 : truncated; } + /** + * If number is greater than the max, set it to max, and if number is lower than low, set it to low. + * @param num number to calculate + * @param min the lowest value the number can be + * @param max the greatest value the number can be + * @return - min if num is lower than min
+ * - max if num is greater than max
+ * - num otherwise + */ + public static double constrain(double num, double min, double max) { + if (num > max) { + num = max; + } + if (num < min) { + num = min; + } + + return num; + } + /** * Converts the given object from an int or byte to byte. * This is used for NBT data that might be either an int diff --git a/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java b/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java new file mode 100644 index 00000000000..3e9848dbe48 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.utils; + +import org.geysermc.connector.GeyserConnector; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.ZipFile; + +/** + * This represents a resource pack and all the data relevant to it + */ +public class ResourcePack { + /** + * The list of loaded resource packs + */ + public static final Map PACKS = new HashMap<>(); + + /** + * The size of each chunk to use when sending the resource packs to clients in bytes + */ + public static final int CHUNK_SIZE = 102400; + + private byte[] sha256; + private File file; + private ResourcePackManifest manifest; + private ResourcePackManifest.Version version; + + /** + * Loop through the packs directory and locate valid resource pack files + */ + public static void loadPacks() { + File directory = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("packs").toFile(); + + if (!directory.exists()) { + directory.mkdir(); + + // As we just created the directory it will be empty + return; + } + + for (File file : directory.listFiles()) { + if (file.getName().endsWith(".zip") || file.getName().endsWith(".mcpack")) { + ResourcePack pack = new ResourcePack(); + + pack.sha256 = FileUtils.calculateSHA256(file); + + try { + ZipFile zip = new ZipFile(file); + + zip.stream().forEach((x) -> { + if (x.getName().contains("manifest.json")) { + try { + ResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), ResourcePackManifest.class); + + pack.file = file; + pack.manifest = manifest; + pack.version = ResourcePackManifest.Version.fromArray(manifest.getHeader().getVersion()); + + PACKS.put(pack.getManifest().getHeader().getUuid().toString(), pack); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } catch (Exception e) { + GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.resource_pack.broken", file.getName())); + e.printStackTrace(); + } + } + } + } + + public byte[] getSha256() { + return sha256; + } + + public File getFile() { + return file; + } + + public ResourcePackManifest getManifest() { + return manifest; + } + + public ResourcePackManifest.Version getVersion() { + return version; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/ResourcePackManifest.java b/connector/src/main/java/org/geysermc/connector/utils/ResourcePackManifest.java new file mode 100644 index 00000000000..6a08c4dbc68 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/ResourcePackManifest.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.utils; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; +import lombok.Value; + +import java.util.Collection; +import java.util.Collections; +import java.util.UUID; + +/** + * author: NukkitX + * Nukkit Project + */ +@Getter +@EqualsAndHashCode +public class ResourcePackManifest { + @JsonProperty("format_version") + private Integer formatVersion; + private Header header; + private Collection modules; + protected Collection dependencies; + + public Collection getModules() { + return Collections.unmodifiableCollection(modules); + } + + @Getter + @ToString + public static class Header { + private String description; + private String name; + private UUID uuid; + private int[] version; + @JsonProperty("min_engine_version") + private int[] minimumSupportedMinecraftVersion; + + public String getVersionString() { + return version[0] + "." + version[1] + "." + version[2]; + } + } + + @Getter + @ToString + public static class Module { + private String description; + private String name; + private UUID uuid; + private int[] version; + } + + @Getter + @ToString + public static class Dependency { + private UUID uuid; + private int[] version; + } + + @Value + public static class Version { + private final int major; + private final int minor; + private final int patch; + + public static Version fromString(String ver) { + String[] split = ver.replace(']', ' ') + .replace('[', ' ') + .replaceAll(" ", "").split(","); + + return new Version(Integer.parseInt(split[0]), Integer.parseInt(split[1]), Integer.parseInt(split[2])); + } + + public static Version fromArray(int[] ver) { + return new Version(ver[0], ver[1], ver[2]); + } + + private Version(int major, int minor, int patch) { + this.major = major; + this.minor = minor; + this.patch = patch; + } + + + @Override + public String toString() { + return major + "." + minor + "." + patch; + } + } +} + diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index 7b0bd851394..0602bb54684 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -112,6 +112,11 @@ cache-images: 0 # the end sky in the nether, but ultimately it's the only way for this feature to work. above-bedrock-nether-building: false +# Force clients to load all resource packs if there are any. +# If set to false it allows the user to disconnect from the server if they don't +# want to download the resource packs +force-resource-packs: true + # bStats is a stat tracker that is entirely anonymous and tracks only basic information # about Geyser, such as how many people are online, how many servers are using Geyser, # what OS is being used, etc. You can learn more about bStats here: https://bstats.org/. From 2f2164f38775442fd909f32267774dcc96d5fc6c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Thu, 17 Sep 2020 23:07:20 -0400 Subject: [PATCH 51/52] InventoryUtils: Don't send Java packet on hotbar item selection (#1301) The Bedrock client sends a confirmation packet we translate regardless. --- .../java/org/geysermc/connector/utils/InventoryUtils.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) 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 b3cc14b9743..c1224e6e255 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java @@ -27,7 +27,6 @@ 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.player.ClientPlayerChangeHeldItemPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCreativeInventoryActionPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientMoveItemToHotbarPacket; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; @@ -241,8 +240,6 @@ private static void setHotbarItem(GeyserSession session, int slot) { hotbarPacket.setSelectedHotbarSlot(slot - 36); hotbarPacket.setSelectHotbarSlot(true); session.sendUpstreamPacket(hotbarPacket); - ClientPlayerChangeHeldItemPacket heldItemPacket = new ClientPlayerChangeHeldItemPacket(slot); - session.sendDownstreamPacket(heldItemPacket); - session.getInventory().setHeldItemSlot(slot - 36); + // No need to send a Java packet as Bedrock sends a confirmation packet back that we translate } } From 5fafa0759ef847382785e686c6e7c4a92214845e Mon Sep 17 00:00:00 2001 From: Comstepr <32700815+Comstepr@users.noreply.github.com> Date: Sat, 19 Sep 2020 09:21:44 +0800 Subject: [PATCH 52/52] Update pullrequest.yml (#1305) --- .github/workflows/pullrequest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 9cb0726ca08..78a3ce2994c 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -22,7 +22,7 @@ jobs: - name: submodules-init uses: snickerbockers/submodules-init@v4 - name: Build with Maven - run: mvn -B package + run: mvn -B package -T 2C - name: Archive artifacts (Geyser Standalone) uses: actions/upload-artifact@v2 if: success()