diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index af9314c60..8001187ca 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,30 +7,34 @@ assignees: '' --- + + + + **Describe the bug** -A clear and concise description of what the bug is. + **To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error + + + + + **Expected behavior** -A clear and concise description of what you expected to happen. + -**Screenshots** -If applicable, add screenshots to help explain your problem. +**Screenshots / Videos** + -**Server version** -run /version +**Server Version** + -**Geyser version** -Jenkins +**Geyser Version** + -**Bedrock version** -The version of your Minecraft pe +**Minecraft: Bedrock Edition Version** + -**Additional context** -Add any other context about the problem here. +**Additional Context** + diff --git a/.gitignore b/.gitignore index 9b233578c..6e0e8f49b 100644 --- a/.gitignore +++ b/.gitignore @@ -225,3 +225,4 @@ nbdist/ config.yml logs/ public-key.pem +locales/ diff --git a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitConfiguration.java b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitConfiguration.java index e38a982db..1ddda6528 100644 --- a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitConfiguration.java +++ b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitConfiguration.java @@ -101,6 +101,11 @@ public class GeyserBukkitConfiguration implements IGeyserConfiguration { return config.getBoolean("allow-third-party-capes", true); } + @Override + public String getDefaultLocale() { + return config.getString("default-locale", "en_us"); + } + @Override public Path getFloodgateKeyFile() { return Paths.get(dataFolder.toString(), config.getString("floodgate-key-file", "public-key.pem")); 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 94580e583..420f8347e 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 @@ -102,6 +102,11 @@ public class GeyserBungeeConfiguration implements IGeyserConfiguration { return config.getBoolean("allow-third-party-capes", true); } + @Override + public String getDefaultLocale() { + return config.getString("default-locale", "en_us"); + } + @Override public Path getFloodgateKeyFile() { return Paths.get(dataFolder.toString(), config.getString("floodgate-key-file", "public-key.pem")); 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 dbc83fbea..7b6a89f15 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 @@ -105,6 +105,11 @@ public class GeyserSpongeConfiguration implements IGeyserConfiguration { return node.getNode("allow-third-party-capes").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")); diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index 7c271339a..0a583fa7c 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -120,6 +120,9 @@ org.geysermc.platform.standalone.GeyserBootstrap + + true + @@ -130,4 +133,4 @@ - \ No newline at end of file + diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserConfiguration.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserConfiguration.java index 06cb711c1..a1362c7a7 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserConfiguration.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserConfiguration.java @@ -63,6 +63,9 @@ public class GeyserConfiguration implements IGeyserConfiguration { @JsonProperty("allow-third-party-capes") private boolean allowThirdPartyCapes; + @JsonProperty("default-locale") + private String defaultLocale; + private MetricsInfo metrics; @Override diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/console/GeyserLogger.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/console/GeyserLogger.java index 7df8a4efb..ac21215cb 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/console/GeyserLogger.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/console/GeyserLogger.java @@ -52,7 +52,7 @@ public class GeyserLogger extends SimpleTerminalConsole implements IGeyserLogger @Override protected void shutdown() { - GeyserConnector.getInstance().shutdown(); + GeyserConnector.getInstance().getBootstrap().onDisable(); } @Override diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java index 2fab448d9..920c65379 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 @@ -63,6 +63,9 @@ public class GeyserVelocityConfiguration implements IGeyserConfiguration { @JsonProperty("allow-third-party-capes") private boolean allowThirdPartyCapes; + @JsonProperty("default-locale") + private String defaultLocale; + private MetricsInfo metrics; @Override diff --git a/common/src/main/java/org/geysermc/common/IGeyserConfiguration.java b/common/src/main/java/org/geysermc/common/IGeyserConfiguration.java index db5d831b1..41534457e 100644 --- a/common/src/main/java/org/geysermc/common/IGeyserConfiguration.java +++ b/common/src/main/java/org/geysermc/common/IGeyserConfiguration.java @@ -46,6 +46,8 @@ public interface IGeyserConfiguration { boolean isAllowThirdPartyCapes(); + String getDefaultLocale(); + Path getFloodgateKeyFile(); IMetricsInfo getMetrics(); diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index fb93a27de..35f9085d0 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -49,6 +49,8 @@ import java.net.InetSocketAddress; import java.text.DecimalFormat; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.UUID; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -61,7 +63,7 @@ public class GeyserConnector { public static final String NAME = "Geyser"; public static final String VERSION = "1.0-SNAPSHOT"; - private final Map players = new HashMap<>(); + private final Map players = new HashMap<>(); private static GeyserConnector instance; @@ -141,6 +143,40 @@ public class GeyserConnector { bootstrap.getGeyserLogger().info("Shutting down Geyser."); shuttingDown = true; + if (players.size() >= 1) { + bootstrap.getGeyserLogger().info("Kicking " + players.size() + " player(s)"); + + for (GeyserSession playerSession : players.values()) { + playerSession.disconnect("Geyser Proxy shutting down."); + } + + CompletableFuture future = CompletableFuture.runAsync(new Runnable() { + @Override + public void run() { + // Simulate a long-running Job + try { + while (true) { + if (players.size() == 0) { + return; + } + + TimeUnit.MILLISECONDS.sleep(100); + } + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + } + }); + + // Block and wait for the future to complete + try { + future.get(); + bootstrap.getGeyserLogger().info("Kicked all players"); + } catch (Exception e) { + // Quietly fail + } + } + generalThreadPool.shutdown(); bedrockServer.close(); players.clear(); @@ -148,17 +184,15 @@ public class GeyserConnector { authType = null; commandMap.getCommands().clear(); commandMap = null; + + bootstrap.getGeyserLogger().info("Geyser shutdown successfully."); } public void addPlayer(GeyserSession player) { - players.put(player.getAuthData().getName(), player); - players.put(player.getAuthData().getUUID(), player); players.put(player.getSocketAddress(), player); } public void removePlayer(GeyserSession player) { - players.remove(player.getAuthData().getName()); - players.remove(player.getAuthData().getUUID()); players.remove(player.getSocketAddress()); } 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 4694d0fd0..2222cdef5 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 @@ -48,6 +48,11 @@ public class StopCommand extends GeyserCommand { if (!sender.isConsole() && connector.getPlatformType() == PlatformType.STANDALONE) { return; } + connector.shutdown(); + + if (connector.getPlatformType() == PlatformType.STANDALONE) { + System.exit(0); + } } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/FallingBlockEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FallingBlockEntity.java new file mode 100644 index 000000000..5a0cac8f4 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/FallingBlockEntity.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityData; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.translators.block.BlockTranslator; + +public class FallingBlockEntity extends Entity { + + public FallingBlockEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, int javaId) { + super(entityId, geyserId, entityType, position, motion, rotation); + + this.metadata.put(EntityData.VARIANT, BlockTranslator.getBedrockBlockId(javaId)); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java index db4858573..537a12511 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java @@ -42,6 +42,8 @@ public class BeeEntity extends AnimalEntity { if (entityMetadata.getId() == 16) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.ANGRY, (xd & 0x02) == 0x02); + // If the bee has nectar or not + metadata.getFlags().setFlag(EntityFlag.POWERED, (xd & 0x08) == 0x08); } super.updateBedrockMetadata(entityMetadata, session); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java index dbf759f58..26c13a5ce 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java @@ -25,12 +25,52 @@ package org.geysermc.connector.entity.living.animal.horse; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityData; +import com.nukkitx.protocol.bedrock.data.ItemData; +import com.nukkitx.protocol.bedrock.packet.MobArmorEquipmentPacket; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.block.BlockTranslator; public class LlamaEntity extends ChestedHorseEntity { public LlamaEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + // Strength + if (entityMetadata.getId() == 19) { + metadata.put(EntityData.STRENGTH, entityMetadata.getValue()); + } + // Color equipped on the llama + if (entityMetadata.getId() == 20) { + // Bedrock treats llama decoration as armor + MobArmorEquipmentPacket equipmentPacket = new MobArmorEquipmentPacket(); + equipmentPacket.setRuntimeEntityId(getGeyserId()); + // -1 means no armor + if ((int) entityMetadata.getValue() != -1) { + // The damage value is the dye color that Java sends us + // Always going to be a carpet so we can hardcode 171 in BlockTranslator + // The int then short conversion is required or we get a ClassCastException + equipmentPacket.setChestplate(ItemData.of(BlockTranslator.CARPET, (short)((int) entityMetadata.getValue()), 1)); + } else { + equipmentPacket.setChestplate(ItemData.AIR); + } + // Required to fill out the rest of the equipment or Bedrock ignores it, including above else statement if removing armor + equipmentPacket.setBoots(ItemData.AIR); + equipmentPacket.setHelmet(ItemData.AIR); + equipmentPacket.setLeggings(ItemData.AIR); + + session.getUpstream().sendPacket(equipmentPacket); + } + // Color of the llama + if (entityMetadata.getId() == 21) { + metadata.put(EntityData.VARIANT, entityMetadata.getValue()); + } + super.updateBedrockMetadata(entityMetadata, session); + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/TraderLlamaEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/TraderLlamaEntity.java new file mode 100644 index 000000000..5e591bc7c --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/TraderLlamaEntity.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living.animal.horse; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityData; +import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class TraderLlamaEntity extends LlamaEntity { + + public TraderLlamaEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void spawnEntity(GeyserSession session) { + // The trader llama is a separate entity from the llama in Java but a normal llama with extra metadata in Bedrock. + AddEntityPacket addEntityPacket = new AddEntityPacket(); + addEntityPacket.setIdentifier("minecraft:llama"); + addEntityPacket.setRuntimeEntityId(geyserId); + addEntityPacket.setUniqueEntityId(geyserId); + addEntityPacket.setPosition(position); + addEntityPacket.setMotion(motion); + addEntityPacket.setRotation(getBedrockRotation()); + addEntityPacket.setEntityType(entityType.getType()); + addEntityPacket.getMetadata().putAll(metadata); + // Here's the difference + addEntityPacket.getMetadata().put(EntityData.MARK_VARIANT, 1); + + valid = true; + session.getUpstream().sendPacket(addEntityPacket); + + session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); + } + +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java index 5a8a1dcf2..63a67a0a7 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java @@ -28,6 +28,7 @@ package org.geysermc.connector.entity.living.animal.tameable; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.EntityData; +import com.nukkitx.protocol.bedrock.data.EntityFlag; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -40,11 +41,31 @@ public class CatEntity extends TameableEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 18) { - metadata.put(EntityData.VARIANT, (int) entityMetadata.getValue()); + // Different colors in Java and Bedrock for some reason + int variantColor; + switch ((int) entityMetadata.getValue()) { + case 0: + variantColor = 8; + break; + case 8: + variantColor = 0; + break; + case 9: + variantColor = 10; + break; + case 10: + variantColor = 9; + break; + default: + variantColor = (int) entityMetadata.getValue(); + } + metadata.put(EntityData.VARIANT, variantColor); } if (entityMetadata.getId() == 21) { - // FIXME: Colors the whole animal instead of just collar - metadata.put(EntityData.COLOR, (byte) (int) entityMetadata.getValue()); + // Needed or else wild cats are a red color + if (metadata.getFlags().getFlag(EntityFlag.TAMED)) { + metadata.put(EntityData.COLOR, (byte) (int) entityMetadata.getValue()); + } } super.updateBedrockMetadata(entityMetadata, session); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/ParrotEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/ParrotEntity.java new file mode 100644 index 000000000..e02b3e7be --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/ParrotEntity.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living.animal.tameable; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityData; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class ParrotEntity extends TameableEntity { + + public ParrotEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + // Parrot color + if (entityMetadata.getId() == 18) { + metadata.put(EntityData.VARIANT, entityMetadata.getValue()); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java index 2f212595b..2d3e0b1d1 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java @@ -27,6 +27,7 @@ package org.geysermc.connector.entity.living.animal.tameable; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityData; import com.nukkitx.protocol.bedrock.data.EntityFlag; import org.geysermc.connector.entity.living.animal.AnimalEntity; import org.geysermc.connector.entity.type.EntityType; @@ -45,6 +46,11 @@ public class TameableEntity extends AnimalEntity { metadata.getFlags().setFlag(EntityFlag.SITTING, (xd & 0x01) == 0x01); metadata.getFlags().setFlag(EntityFlag.ANGRY, (xd & 0x02) == 0x02); metadata.getFlags().setFlag(EntityFlag.TAMED, (xd & 0x04) == 0x04); + // Must be set for wolf collar color to work + // Extending it to all entities to prevent future bugs + if (metadata.getFlags().getFlag(EntityFlag.TAMED)) { + metadata.put(EntityData.OWNER_EID, session.getPlayerEntity().getGeyserId()); + } // Can't de-tame an entity so no resetting the owner ID } super.updateBedrockMetadata(entityMetadata, session); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java index 6f6ab15f8..0ac49d55d 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java @@ -28,6 +28,7 @@ package org.geysermc.connector.entity.living.animal.tameable; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.EntityData; +import com.nukkitx.protocol.bedrock.data.EntityFlag; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -39,9 +40,14 @@ public class WolfEntity extends TameableEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + // "Begging" on wiki.vg, "Interested" in Nukkit - the tilt of the head + if (entityMetadata.getId() == 18) { + metadata.getFlags().setFlag(EntityFlag.INTERESTED, (boolean) entityMetadata.getValue()); + } + // Wolf collar color + // Relies on EntityData.OWNER_EID being set in TameableEntity.java if (entityMetadata.getId() == 19) { - // FIXME: Colors the whole animal instead of just collar - // metadata.put(EntityData.COLOR, (byte) (int) entityMetadata.getValue()); + metadata.put(EntityData.COLOR, (byte) (int) entityMetadata.getValue()); } super.updateBedrockMetadata(entityMetadata, session); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/AbstractMerchantEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/AbstractMerchantEntity.java similarity index 93% rename from connector/src/main/java/org/geysermc/connector/entity/living/AbstractMerchantEntity.java rename to connector/src/main/java/org/geysermc/connector/entity/living/merchant/AbstractMerchantEntity.java index da505fab5..ddeb31bd1 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/AbstractMerchantEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/AbstractMerchantEntity.java @@ -23,9 +23,10 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.entity.living; +package org.geysermc.connector.entity.living.merchant; import com.nukkitx.math.vector.Vector3f; +import org.geysermc.connector.entity.living.AgeableEntity; import org.geysermc.connector.entity.type.EntityType; public class AbstractMerchantEntity extends AgeableEntity { diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java new file mode 100644 index 000000000..9ed48b886 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living.merchant; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityData; +import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class VillagerEntity extends AbstractMerchantEntity { + + private static final Int2IntMap VILLAGER_VARIANTS = new Int2IntOpenHashMap(); + private static final Int2IntMap VILLAGER_REGIONS = new Int2IntOpenHashMap(); + + static { + // Java villager profession IDs -> Bedrock + VILLAGER_VARIANTS.put(0, 0); + VILLAGER_VARIANTS.put(1, 8); + VILLAGER_VARIANTS.put(2, 11); + VILLAGER_VARIANTS.put(3, 6); + VILLAGER_VARIANTS.put(4, 7); + VILLAGER_VARIANTS.put(5, 1); + VILLAGER_VARIANTS.put(6, 2); + VILLAGER_VARIANTS.put(7, 4); + VILLAGER_VARIANTS.put(8, 12); + VILLAGER_VARIANTS.put(9, 5); + VILLAGER_VARIANTS.put(10, 13); + VILLAGER_VARIANTS.put(11, 14); + VILLAGER_VARIANTS.put(12, 3); + VILLAGER_VARIANTS.put(13, 10); + VILLAGER_VARIANTS.put(14, 9); + + VILLAGER_REGIONS.put(0, 1); + VILLAGER_REGIONS.put(1, 2); + VILLAGER_REGIONS.put(2, 0); + VILLAGER_REGIONS.put(3, 3); + VILLAGER_REGIONS.put(4, 4); + VILLAGER_REGIONS.put(5, 5); + VILLAGER_REGIONS.put(6, 6); + } + + public VillagerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 17) { + VillagerData villagerData = (VillagerData) entityMetadata.getValue(); + // Profession + metadata.put(EntityData.VARIANT, VILLAGER_VARIANTS.get(villagerData.getProfession())); + //metadata.put(EntityData.SKIN_ID, villagerData.getType()); Looks like this is modified but for any reason? + // Region + metadata.put(EntityData.MARK_VARIANT, VILLAGER_REGIONS.get(villagerData.getType())); + // Trade tier - different indexing in Bedrock + metadata.put(EntityData.TRADE_TIER, villagerData.getLevel() - 1); + } + super.updateBedrockMetadata(entityMetadata, session); + } + + @Override + public void spawnEntity(GeyserSession session) { + AddEntityPacket addEntityPacket = new AddEntityPacket(); + // "v2" or else it's the legacy villager + addEntityPacket.setIdentifier("minecraft:villager_v2"); + addEntityPacket.setRuntimeEntityId(geyserId); + addEntityPacket.setUniqueEntityId(geyserId); + addEntityPacket.setPosition(position); + addEntityPacket.setMotion(motion); + addEntityPacket.setRotation(getBedrockRotation()); + addEntityPacket.setEntityType(entityType.getType()); + addEntityPacket.getMetadata().putAll(metadata); + + valid = true; + session.getUpstream().sendPacket(addEntityPacket); + + session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); + } + +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index f6af511cd..38f99abcf 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 @@ -29,13 +29,9 @@ import lombok.Getter; import org.geysermc.connector.entity.*; import org.geysermc.connector.entity.living.*; import org.geysermc.connector.entity.living.animal.*; -import org.geysermc.connector.entity.living.animal.tameable.CatEntity; -import org.geysermc.connector.entity.living.animal.tameable.TameableEntity; -import org.geysermc.connector.entity.living.animal.horse.AbstractHorseEntity; -import org.geysermc.connector.entity.living.animal.horse.ChestedHorseEntity; -import org.geysermc.connector.entity.living.animal.horse.HorseEntity; -import org.geysermc.connector.entity.living.animal.horse.LlamaEntity; -import org.geysermc.connector.entity.living.animal.tameable.WolfEntity; +import org.geysermc.connector.entity.living.animal.horse.*; +import org.geysermc.connector.entity.living.animal.tameable.*; +import org.geysermc.connector.entity.living.merchant.*; import org.geysermc.connector.entity.living.monster.*; import org.geysermc.connector.entity.living.monster.raid.AbstractIllagerEntity; import org.geysermc.connector.entity.living.monster.raid.RaidParticipantEntity; @@ -49,7 +45,7 @@ public enum EntityType { PIG(PigEntity.class, 12, 0.9f), SHEEP(SheepEntity.class, 13, 1.3f, 0.9f), WOLF(WolfEntity.class, 14, 0.85f, 0.6f), - VILLAGER(AbstractMerchantEntity.class, 15, 1.8f, 0.6f, 0.6f, 1.62f), + VILLAGER(VillagerEntity.class, 15, 1.8f, 0.6f, 0.6f, 1.62f), MOOSHROOM(AnimalEntity.class, 16, 1.4f, 0.9f), SQUID(WaterEntity.class, 17, 0.8f), RABBIT(RabbitEntity.class, 18, 0.5f, 0.4f), @@ -64,8 +60,8 @@ public enum EntityType { ZOMBIE_HORSE(AbstractHorseEntity.class, 27, 1.6f, 1.3965f), POLAR_BEAR(PolarBearEntity.class, 28, 1.4f, 1.3f), LLAMA(LlamaEntity.class, 29, 1.87f, 0.9f), - TRADER_LLAMA(LlamaEntity.class, 29, 1.187f, 0.9f), - PARROT(TameableEntity.class, 30, 0.9f, 0.5f), + TRADER_LLAMA(TraderLlamaEntity.class, 29, 1.187f, 0.9f), + PARROT(ParrotEntity.class, 30, 0.9f, 0.5f), DOLPHIN(WaterEntity.class, 31, 0.6f, 0.9f), ZOMBIE(ZombieEntity.class, 32, 1.8f, 0.6f, 0.6f, 1.62f), CREEPER(CreeperEntity.class, 33, 1.7f, 0.6f, 0.6f, 1.62f), @@ -103,7 +99,7 @@ public enum EntityType { PLAYER(PlayerEntity.class, 63, 1.8f, 0.6f, 0.6f, 1.62f), ITEM(ItemEntity.class, 64, 0.25f, 0.25f), TNT(Entity.class, 65, 0.98f, 0.98f), - FALLING_BLOCK(Entity.class, 66, 0.98f, 0.98f), + FALLING_BLOCK(FallingBlockEntity.class, 66, 0.98f, 0.98f), MOVING_BLOCK(Entity.class, 67, 0f), EXPERIENCE_BOTTLE(ThrowableEntity.class, 68, 0.25f, 0.25f), EXPERIENCE_ORB(ExpOrbEntity.class, 69, 0f), 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 6fffcda84..cf6c2ee2f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -27,6 +27,7 @@ package org.geysermc.connector.network; import com.nukkitx.protocol.bedrock.BedrockPacket; import com.nukkitx.protocol.bedrock.packet.*; +import org.geysermc.common.AuthType; import org.geysermc.common.IGeyserConfiguration; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; @@ -107,7 +108,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public boolean handle(MovePlayerPacket packet) { - if (!session.isLoggedIn() && !session.isLoggingIn()) { + if (!session.isLoggedIn() && !session.isLoggingIn() && session.getConnector().getAuthType() == AuthType.ONLINE) { // TODO it is safer to key authentication on something that won't change (UUID, not username) if (!couldLoginUserByName(session.getAuthData().getName())) { LoginEncryptionUtils.showLoginWindow(session); 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 d61d1159d..a7614af59 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 @@ -26,6 +26,7 @@ package org.geysermc.connector.network.session; import com.github.steveice10.mc.auth.data.GameProfile; +import com.github.steveice10.mc.auth.exception.request.InvalidCredentialsException; import com.github.steveice10.mc.auth.exception.request.RequestException; import com.github.steveice10.mc.protocol.MinecraftProtocol; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; @@ -70,6 +71,7 @@ import org.geysermc.connector.network.session.cache.*; import org.geysermc.connector.network.translators.Registry; import org.geysermc.connector.network.translators.block.BlockTranslator; import org.geysermc.connector.utils.ChunkUtils; +import org.geysermc.connector.utils.LocaleUtils; import org.geysermc.connector.utils.Toolbox; import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.EncryptionUtil; @@ -156,15 +158,6 @@ public class GeyserSession implements CommandSender { public void connect(RemoteServer remoteServer) { startGame(); this.remoteServer = remoteServer; - if (connector.getAuthType() != AuthType.ONLINE) { - connector.getLogger().info( - "Attempting to login using " + connector.getAuthType().name().toLowerCase() + " mode... " + - (connector.getAuthType() == AuthType.OFFLINE ? - "authentication is disabled." : "authentication will be encrypted" - ) - ); - authenticate(authData.getName()); - } ChunkUtils.sendEmptyChunks(this, playerEntity.getPosition().toInt(), 0, false); @@ -186,6 +179,18 @@ public class GeyserSession implements CommandSender { upstream.sendPacket(playStatusPacket); } + public void login() { + if (connector.getAuthType() != AuthType.ONLINE) { + connector.getLogger().info( + "Attempting to login using " + connector.getAuthType().name().toLowerCase() + " mode... " + + (connector.getAuthType() == AuthType.OFFLINE ? + "authentication is disabled." : "authentication will be encrypted" + ) + ); + authenticate(authData.getName()); + } + } + public void authenticate(String username) { authenticate(username, ""); } @@ -196,7 +201,7 @@ public class GeyserSession implements CommandSender { return; } - loggedIn = true; + loggingIn = true; // new thread so clients don't timeout new Thread(() -> { try { @@ -265,6 +270,17 @@ public class GeyserSession implements CommandSender { connector.getLogger().info(authData.getName() + " (logged in as: " + protocol.getProfile().getName() + ")" + " has connected to remote java server on address " + remoteServer.getAddress()); playerEntity.setUuid(protocol.getProfile().getId()); playerEntity.setUsername(protocol.getProfile().getName()); + + String locale = clientData.getLanguageCode(); + + // Let the user know there locale may take some time to download + // as it has to be extracted from a JAR + if (locale.toLowerCase().equals("en_us") && !LocaleUtils.LOCALE_MAPPINGS.containsKey("en_us")) { + sendMessage("Downloading your locale (en_us) this may take some time"); + } + + // Download and load the language for the player + LocaleUtils.downloadAndLoadLocale(locale); } @Override @@ -295,6 +311,9 @@ public class GeyserSession implements CommandSender { downstream.getSession().connect(); connector.addPlayer(this); + } catch (InvalidCredentialsException e) { + connector.getLogger().info("User '" + username + "' entered invalid login info, kicking."); + disconnect("Invalid/incorrect login info"); } catch (RequestException ex) { ex.printStackTrace(); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java index ebc45ff08..206f42d1f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java @@ -33,12 +33,10 @@ import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; -import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerState; import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPlaceBlockPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerStatePacket; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.packet.PlayStatusPacket; @@ -101,10 +99,7 @@ public class BedrockActionTranslator extends PacketTranslator JAVA_ID_BLOCK_MAP = new HashMap<>(); private static final IntSet WATERLOGGED = new IntOpenHashSet(); + // Bedrock carpet ID, used in LlamaEntity.java for decoration + public static final int CARPET = 171; + private static final Map JAVA_ID_TO_BLOCK_ENTITY_MAP = new HashMap<>(); private static final Object2ByteMap BED_COLORS = new Object2ByteOpenHashMap<>(); @@ -238,6 +241,10 @@ public class BlockTranslator { return JAVA_TO_BEDROCK_BLOCK_MAP.get(state.getId()); } + public static int getBedrockBlockId(int javaId) { + return JAVA_TO_BEDROCK_BLOCK_MAP.get(javaId); + } + public static BlockState getJavaBlockState(int bedrockId) { return BEDROCK_TO_JAVA_BLOCK_MAP.get(bedrockId); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaBossBarTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaBossBarTranslator.java index 3c1452a53..582f0b5aa 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaBossBarTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaBossBarTranslator.java @@ -51,7 +51,7 @@ public class JavaBossBarTranslator extends PacketTranslator bossEventPacket.setAction(BossEventPacket.Action.SHOW); bossEventPacket.setBossUniqueEntityId(entityId); - bossEventPacket.setTitle(MessageUtils.getBedrockMessage(packet.getTitle())); + bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(packet.getTitle(), session.getClientData().getLanguageCode())); bossEventPacket.setHealthPercentage(packet.getHealth()); bossEventPacket.setColor(0); //ignored by client bossEventPacket.setOverlay(1); @@ -59,7 +59,7 @@ public class JavaBossBarTranslator extends PacketTranslator break; case UPDATE_TITLE: bossEventPacket.setAction(BossEventPacket.Action.TITLE); - bossEventPacket.setTitle(MessageUtils.getBedrockMessage(packet.getTitle())); + bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(packet.getTitle(), session.getClientData().getLanguageCode())); break; case UPDATE_HEALTH: bossEventPacket.setAction(BossEventPacket.Action.HEALTH_PERCENTAGE); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java index 226bd9714..a527866c9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java @@ -34,6 +34,8 @@ import com.github.steveice10.mc.protocol.data.message.TranslationMessage; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerChatPacket; import com.nukkitx.protocol.bedrock.packet.TextPacket; +import java.util.List; + @Translator(packet = ServerChatPacket.class) public class JavaChatTranslator extends PacketTranslator { @@ -58,14 +60,20 @@ public class JavaChatTranslator extends PacketTranslator { break; } + String locale = session.getClientData().getLanguageCode(); + if (packet.getMessage() instanceof TranslationMessage) { textPacket.setType(TextPacket.Type.TRANSLATION); textPacket.setNeedsTranslation(true); - textPacket.setParameters(MessageUtils.getTranslationParams(((TranslationMessage) packet.getMessage()).getTranslationParams())); - textPacket.setMessage(MessageUtils.getBedrockMessage(packet.getMessage())); + + List paramsTranslated = MessageUtils.getTranslationParams(((TranslationMessage) packet.getMessage()).getTranslationParams(), locale); + textPacket.setParameters(paramsTranslated); + + textPacket.setMessage(MessageUtils.insertParams(MessageUtils.getTranslatedBedrockMessage(packet.getMessage(), locale, true), paramsTranslated)); } else { textPacket.setNeedsTranslation(false); - textPacket.setMessage(MessageUtils.getBedrockMessage(packet.getMessage())); + + textPacket.setMessage(MessageUtils.getTranslatedBedrockMessage(packet.getMessage(), locale, false)); } session.getUpstream().sendPacket(textPacket); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java index 0d6caaed0..34fe2271d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java @@ -29,7 +29,6 @@ import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.ChunkUtils; import org.geysermc.connector.utils.DimensionUtils; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerJoinGamePacket; @@ -69,7 +68,6 @@ public class JavaJoinGameTranslator extends PacketTranslator entityClass = type.getEntityClass(); try { - Constructor entityConstructor = entityClass.getConstructor(long.class, long.class, EntityType.class, - Vector3f.class, Vector3f.class, Vector3f.class); + Entity entity; + if (packet.getType() == ObjectType.FALLING_BLOCK) { + entity = new FallingBlockEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), + type, position, motion, rotation, ((FallingBlockData) packet.getData()).getId()); + } else { + Constructor entityConstructor = entityClass.getConstructor(long.class, long.class, EntityType.class, + Vector3f.class, Vector3f.class, Vector3f.class); - Entity entity = entityConstructor.newInstance(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), - type, position, motion, rotation - ); + entity = entityConstructor.newInstance(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), + type, position, motion, rotation + ); + } session.getEntityCache().spawnEntity(entity); } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException ex) { ex.printStackTrace(); 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 new file mode 100644 index 000000000..b4287b081 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaCollectItemTranslator.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.java.world; + +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityCollectItemPacket; +import com.nukkitx.protocol.bedrock.packet.TakeItemEntityPacket; +import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +@Translator(packet = ServerEntityCollectItemPacket.class) +public class JavaCollectItemTranslator extends PacketTranslator { + + @Override + public void translate(ServerEntityCollectItemPacket packet, GeyserSession session) { + // This is the definition of translating - both packets take the same values + TakeItemEntityPacket takeItemEntityPacket = new TakeItemEntityPacket(); + // Collected entity is the item + Entity collectedEntity = session.getEntityCache().getEntityByJavaId(packet.getCollectedEntityId()); + // Collector is the entity picking up the item + Entity collectorEntity; + if (packet.getCollectorEntityId() == session.getPlayerEntity().getEntityId()) { + collectorEntity = session.getPlayerEntity(); + } else { + collectorEntity = session.getEntityCache().getEntityByJavaId(packet.getCollectorEntityId()); + } + takeItemEntityPacket.setRuntimeEntityId(collectorEntity.getGeyserId()); + takeItemEntityPacket.setItemRuntimeEntityId(collectedEntity.getGeyserId()); + session.getUpstream().sendPacket(takeItemEntityPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java index ccf3e2090..28022c16d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java @@ -40,7 +40,7 @@ public class JavaMapDataTranslator extends PacketTranslator ClientboundMapItemDataPacket mapItemDataPacket = new ClientboundMapItemDataPacket(); mapItemDataPacket.setUniqueMapId(packet.getMapId()); - mapItemDataPacket.setDimensionId(session.getLastDimPacket().getDimension()); + mapItemDataPacket.setDimensionId(session.getPlayerEntity().getDimension()); mapItemDataPacket.setLocked(packet.isLocked()); mapItemDataPacket.setScale(packet.getScale()); 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 3070e743d..0938fa7c0 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java @@ -65,4 +65,23 @@ public class FileUtils { return file; } + + public static void writeFile(File file, char[] data) throws IOException { + if (!file.exists()) { + file.createNewFile(); + } + + FileOutputStream fos = new FileOutputStream(file); + + for (char c : data) { + fos.write(c); + } + + fos.flush(); + fos.close(); + } + + public static void writeFile(String name, char[] data) throws IOException { + writeFile(new File(name), data); + } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java new file mode 100644 index 000000000..e8555eb02 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT 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.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Getter; +import org.geysermc.connector.GeyserConnector; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.*; +import java.util.zip.ZipFile; + +public class LocaleUtils { + + public static final Map> LOCALE_MAPPINGS = new HashMap<>(); + + private static final Map ASSET_MAP = new HashMap<>(); + + private static final String DEFAULT_LOCALE = (GeyserConnector.getInstance().getConfig().getDefaultLocale() != null ? GeyserConnector.getInstance().getConfig().getDefaultLocale() : "en_us"); + + private static String smallestURL = ""; + + static { + // Create the locales folder + File localesFolder = new File("locales/"); + localesFolder.mkdir(); + + // Download the latest asset list and cache it + generateAssetCache(); + downloadAndLoadLocale(DEFAULT_LOCALE); + } + + private static void generateAssetCache() { + try { + VersionManifest versionManifest = Toolbox.JSON_MAPPER.readValue(WebUtils.getBody("https://launchermeta.mojang.com/mc/game/version_manifest.json"), VersionManifest.class); + String latestInfoURL = ""; + for (Version version : versionManifest.getVersions()) { + if (version.getId().equals(versionManifest.getLatestVersion().getRelease())) { + latestInfoURL = version.getUrl(); + break; + } + } + + if (latestInfoURL.isEmpty()) { + throw new Exception("Unable to get latest Minecraft version"); + } + + VersionInfo versionInfo = Toolbox.JSON_MAPPER.readValue(WebUtils.getBody(latestInfoURL), VersionInfo.class); + + int currentSize = Integer.MAX_VALUE; + for (VersionDownload download : versionInfo.getDownloads().values()) { + if (download.getUrl().endsWith(".jar") && download.getSize() < currentSize) { + smallestURL = download.getUrl(); + currentSize = download.getSize(); + } + } + + JsonNode assets = Toolbox.JSON_MAPPER.readTree(WebUtils.getBody(versionInfo.getAssetIndex().getUrl())).get("objects"); + + Iterator> assetIterator = assets.fields(); + while (assetIterator.hasNext()) { + Map.Entry entry = assetIterator.next(); + Asset asset = Toolbox.JSON_MAPPER.treeToValue(entry.getValue(), Asset.class); + ASSET_MAP.put(entry.getKey(), asset); + } + } catch (Exception e) { + GeyserConnector.getInstance().getLogger().info("Failed to load locale asset cache: " + (!e.getMessage().isEmpty() ? e.getMessage() : e.getStackTrace())); + } + } + + public static void downloadAndLoadLocale(String locale) { + locale = locale.toLowerCase(); + if (!ASSET_MAP.containsKey("minecraft/lang/" + locale + ".json") && !locale.equals("en_us")) { + GeyserConnector.getInstance().getLogger().warning("Invalid locale requested to download and load: " + locale); + return; + } + + GeyserConnector.getInstance().getLogger().debug("Downloading and loading locale: " + locale); + + downloadLocale(locale); + loadLocale(locale); + } + + private static void downloadLocale(String locale) { + File localeFile = new File("locales/" + locale + ".json"); + + if (localeFile.exists()) { + GeyserConnector.getInstance().getLogger().debug("Locale already downloaded: " + locale); + return; + } + + // Create the en_us locale + if (locale.equals("en_us")) { + downloadEN_US(localeFile); + + return; + } + + // 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, "locales/" + locale + ".json"); + } + + private static void loadLocale(String locale) { + File localeFile = new File("locales/" + locale + ".json"); + + // Load the locale + if (localeFile.exists()) { + // Read the localefile + InputStream localeStream; + try { + localeStream = new FileInputStream(localeFile); + } catch (FileNotFoundException e) { + throw new AssertionError("Unable to load locale: " + locale + " (" + e.getMessage() + ")"); + } + + // Parse the file as json + JsonNode localeObj; + try { + localeObj = Toolbox.JSON_MAPPER.readTree(localeStream); + } catch (Exception e) { + throw new AssertionError("Unable to load Java lang map for " + locale, e); + } + + // Parse all the locale fields + Iterator> localeIterator = localeObj.fields(); + Map langMap = new HashMap<>(); + while (localeIterator.hasNext()) { + Map.Entry entry = localeIterator.next(); + langMap.put(entry.getKey(), entry.getValue().asText()); + } + + // Insert the locale into the mappings + LOCALE_MAPPINGS.put(locale.toLowerCase(), langMap); + } else { + GeyserConnector.getInstance().getLogger().warning("Missing locale file: " + locale); + } + } + + private static void downloadEN_US(File localeFile) { + try { + // Let the user know we are downloading the JAR + GeyserConnector.getInstance().getLogger().info("Downloading Minecraft JAR to extract en_us locale, please wait... (this may take some time depending on the speed of your internet connection)"); + GeyserConnector.getInstance().getLogger().debug("Download URL: " + smallestURL); + + // Download the smallest JAR (client or server) + WebUtils.downloadFile(smallestURL, "tmp_locale.jar"); + + // Load in the JAR as a zip and extract the file + ZipFile localeJar = new ZipFile("tmp_locale.jar"); + InputStream inputStream = localeJar.getInputStream(localeJar.getEntry("assets/minecraft/lang/en_us.json")); + FileOutputStream outputStream = new FileOutputStream(localeFile); + + // Write the file to the locale dir + int data = inputStream.read(); + while(data != -1){ + outputStream.write(data); + data = inputStream.read(); + } + + // Flush all changes to disk and cleanup + outputStream.flush(); + outputStream.close(); + + inputStream.close(); + localeJar.close(); + + // Delete the nolonger needed client/server jar + Files.delete(Paths.get("tmp_locale.jar")); + } catch (Exception e) { + throw new AssertionError("Unable to download and extract en_us locale!", e); + } + } + + public static String getLocaleString(String messageText, String locale) { + Map localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(locale.toLowerCase()); + if (localeStrings == null) + localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(DEFAULT_LOCALE); + + return localeStrings.getOrDefault(messageText, messageText); + } + + public static void init() { + // no-op + } +} + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +class VersionManifest { + @JsonProperty("latest") + private LatestVersion latestVersion; + + @JsonProperty("versions") + private List versions; +} + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +class LatestVersion { + @JsonProperty("release") + private String release; + + @JsonProperty("snapshot") + private String snapshot; +} + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +class Version { + @JsonProperty("id") + private String id; + + @JsonProperty("type") + private String type; + + @JsonProperty("url") + private String url; + + @JsonProperty("time") + private String time; + + @JsonProperty("releaseTime") + private String releaseTime; +} + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +class VersionInfo { + @JsonProperty("id") + private String id; + + @JsonProperty("type") + private String type; + + @JsonProperty("time") + private String time; + + @JsonProperty("releaseTime") + private String releaseTime; + + @JsonProperty("assetIndex") + private AssetIndex assetIndex; + + @JsonProperty("downloads") + private Map downloads; +} + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +class VersionDownload { + @JsonProperty("sha1") + private String sha1; + + @JsonProperty("size") + private int size; + + @JsonProperty("url") + private String url; +} + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +class AssetIndex { + @JsonProperty("id") + private String id; + + @JsonProperty("sha1") + private String sha1; + + @JsonProperty("size") + private int size; + + @JsonProperty("totalSize") + private int totalSize; + + @JsonProperty("url") + private String url; +} + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +class Asset { + @JsonProperty("hash") + private String hash; + + @JsonProperty("size") + private int size; +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java index 7b4c84a7e..a28d6a7a1 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java @@ -26,30 +26,29 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; -import com.github.steveice10.mc.protocol.data.message.ChatColor; -import com.github.steveice10.mc.protocol.data.message.ChatFormat; -import com.github.steveice10.mc.protocol.data.message.Message; -import com.github.steveice10.mc.protocol.data.message.TranslationMessage; +import com.github.steveice10.mc.protocol.data.message.*; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class MessageUtils { - public static List getTranslationParams(Message[] messages) { + public static List getTranslationParams(Message[] messages, String locale) { List strings = new ArrayList<>(); for (Message message : messages) { if (message instanceof TranslationMessage) { TranslationMessage translation = (TranslationMessage) message; - String builder = "%" + translation.getTranslationKey(); - strings.add(builder); + if (locale == null) { + String builder = "%" + translation.getTranslationKey(); + strings.add(builder); + } if (translation.getTranslationKey().equals("commands.gamemode.success.other")) { strings.add(""); @@ -59,11 +58,16 @@ public class MessageUtils { strings.add(" - no permission or invalid command!"); } - strings.addAll(getTranslationParams(translation.getTranslationParams())); + List furtherParams = getTranslationParams(translation.getTranslationParams()); + if (locale != null) { + strings.add(insertParams(LocaleUtils.getLocaleString(translation.getTranslationKey(), locale), furtherParams)); + }else{ + strings.addAll(furtherParams); + } } else { String builder = getFormat(message.getStyle().getFormats()) + - getColor(message.getStyle().getColor()) + - getBedrockMessage(message); + getColorOrParent(message.getStyle()); + builder += getTranslatedBedrockMessage(message, locale, false); strings.add(builder); } } @@ -71,29 +75,77 @@ public class MessageUtils { return strings; } + public static List getTranslationParams(Message[] messages) { + return getTranslationParams(messages, null); + } + public static String getTranslationText(TranslationMessage message) { - return getFormat(message.getStyle().getFormats()) + getColor(message.getStyle().getColor()) + return getFormat(message.getStyle().getFormats()) + getColorOrParent(message.getStyle()) + "%" + message.getTranslationKey(); } - public static String getBedrockMessage(Message message) { + public static String getTranslatedBedrockMessage(Message message, String locale, boolean shouldTranslate) { JsonParser parser = new JsonParser(); if (isMessage(message.getText())) { JsonObject object = parser.parse(message.getText()).getAsJsonObject(); message = Message.fromJson(formatJson(object)); } - StringBuilder builder = new StringBuilder(message.getText()); + String messageText = message.getText(); + if (locale != null && shouldTranslate) { + messageText = LocaleUtils.getLocaleString(messageText, locale); + } + + StringBuilder builder = new StringBuilder(messageText); for (Message msg : message.getExtra()) { builder.append(getFormat(msg.getStyle().getFormats())); - builder.append(getColor(msg.getStyle().getColor())); + builder.append(getColorOrParent(msg.getStyle())); if (!(msg.getText() == null)) { - builder.append(getBedrockMessage(msg)); + boolean isTranslationMessage = (msg instanceof TranslationMessage); + builder.append(getTranslatedBedrockMessage(msg, locale, isTranslationMessage)); } } return builder.toString(); } + public static String getTranslatedBedrockMessage(Message message, String locale) { + return getTranslatedBedrockMessage(message, locale, true); + } + + public static String getBedrockMessage(Message message) { + return getTranslatedBedrockMessage(message, null, false); + } + + public static String insertParams(String message, List params) { + String newMessage = message; + + Pattern p = Pattern.compile("%([1-9])\\$s"); + Matcher m = p.matcher(message); + while (m.find()) { + try { + newMessage = newMessage.replaceFirst("%" + m.group(1) + "\\$s" , params.get(Integer.parseInt(m.group(1)) - 1)); + } catch (Exception e) { + // Couldnt find the param to replace + } + } + + for (String text : params) { + newMessage = newMessage.replaceFirst("%s", text); + } + + return newMessage; + } + + private static String getColorOrParent(MessageStyle style) { + ChatColor chatColor = style.getColor(); + + if (chatColor == ChatColor.NONE) { + return getColor(style.getParent().getColor()); + } + + return getColor(chatColor); + } + private static String getColor(ChatColor color) { String base = "\u00a7"; switch (color) { diff --git a/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java b/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java index 61509ca7b..439ba560a 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java +++ b/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java @@ -42,9 +42,7 @@ import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ToolItemEntry; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; import java.util.*; public class Toolbox { @@ -57,6 +55,8 @@ public class Toolbox { public static final Int2ObjectMap ITEM_ENTRIES = new Int2ObjectOpenHashMap<>(); + public static final Map> LOCALE_MAPPINGS = new HashMap<>(); + static { /* Load biomes */ InputStream biomestream = GeyserConnector.class.getClassLoader().getResourceAsStream("bedrock/biome_definitions.dat"); @@ -129,6 +129,9 @@ public class Toolbox { itemIndex++; } + // Load the locale data + LocaleUtils.init(); + stream = getResource("bedrock/creative_items.json"); JsonNode creativeItemEntries; diff --git a/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java b/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java new file mode 100644 index 000000000..065d683a0 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.utils; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +public class WebUtils { + + public static String getBody(String reqURL) { + URL url = null; + try { + url = new URL(reqURL); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("GET"); + + BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream())); + String inputLine; + StringBuffer content = new StringBuffer(); + + while ((inputLine = in.readLine()) != null) { + content.append(inputLine); + content.append("\n"); + } + + in.close(); + con.disconnect(); + + return content.toString(); + } catch (Exception e) { + return e.getMessage(); + } + } + + public static void downloadFile(String reqURL, String fileLocation) { + try { + InputStream in = new URL(reqURL).openStream(); + Files.copy(in, Paths.get(fileLocation), StandardCopyOption.REPLACE_EXISTING); + } catch (Exception e) { + throw new AssertionError("Unable to download and save file: " + fileLocation + " (" + reqURL + ")", e); + } + } +} diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index ba5700e27..ae0cbed8d 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -57,6 +57,9 @@ general-thread-pool: 32 # OptiFine capes, LabyMod capes, 5Zig capes and MinecraftCapes allow-third-party-capes: true +# The default locale if we dont have the one the client requested +default-locale: en_us + # 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/.