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/pom.xml b/bootstrap/bukkit/pom.xml index 0e5dd3df7..fd2ecbf0d 100644 --- a/bootstrap/bukkit/pom.xml +++ b/bootstrap/bukkit/pom.xml @@ -54,7 +54,6 @@ org.geysermc.platform.bukkit.shaded.fastutil - true 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/pom.xml b/bootstrap/bungeecord/pom.xml index 317e80cb5..0f6de3faa 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -54,7 +54,6 @@ org.geysermc.platform.bungeecord.shaded.netty - true 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/pom.xml b/bootstrap/sponge/pom.xml index 83c070aed..696721d20 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -58,7 +58,6 @@ org.geysermc.platform.sponge.shaded.fastutil - true 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/pom.xml b/bootstrap/velocity/pom.xml index 22fe92115..075aedc32 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -44,7 +44,12 @@ shade - true + + + it.unimi.dsi.fastutil + org.geysermc.platform.velocity.shaded.fastutil + + 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 f88639c75..a620acd88 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -50,6 +50,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; @@ -64,7 +66,7 @@ public class GeyserConnector { //Change this on every game version public static final String GAME_VERSION = "1.14.0"; - private final Map players = new HashMap<>(); + private final Map players = new HashMap<>(); private static GeyserConnector instance; @@ -145,6 +147,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(); @@ -152,17 +188,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/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java index 56d59ec3d..fb48871b3 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -26,8 +26,11 @@ package org.geysermc.connector.entity; 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.message.TextMessage; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.CommandPermission; +import com.nukkitx.protocol.bedrock.data.EntityData; import com.nukkitx.protocol.bedrock.data.PlayerPermission; import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket; import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; @@ -39,6 +42,8 @@ import lombok.Setter; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.scoreboard.Team; +import org.geysermc.connector.utils.MessageUtils; import org.geysermc.connector.utils.SkinUtils; import java.util.UUID; @@ -152,4 +157,27 @@ public class PlayerEntity extends LivingEntity { public void setPosition(Vector3f position) { this.position = position.add(0, entityType.getOffset(), 0); } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + super.updateBedrockMetadata(entityMetadata, session); + + if (entityMetadata.getId() == 2) { + // System.out.println(session.getScoreboardCache().getScoreboard().getObjectives().keySet()); + for (Team team : session.getScoreboardCache().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) { + username = MessageUtils.getBedrockMessage(name); + } + Team team = session.getScoreboardCache().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/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 873c85345..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), @@ -123,7 +119,7 @@ public enum EntityType { PAINTING(PaintingEntity.class, 83, 0f), MINECART(MinecartEntity.class, 84, 0f), FIREBALL(ItemedFireballEntity.class, 85, 0f), - SPLASH_POTION(ThrowableEntity.class, 86, 0f), + POTION(ThrowableEntity.class, 86, 0f), ENDER_PEARL(ThrowableEntity.class, 87, 0f), LEASH_KNOT(Entity.class, 88, 0f), WITHER_SKULL(Entity.class, 89, 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 8e3eef081..d02112bea 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; @@ -142,7 +143,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 ceab8555e..02427042b 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; @@ -38,6 +39,7 @@ import com.github.steveice10.packetlib.tcp.TcpSessionFactory; import com.nukkitx.math.GenericMath; import com.nukkitx.math.TrigMath; import com.nukkitx.math.vector.Vector2f; +import com.nukkitx.math.vector.Vector2i; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.tag.CompoundTag; @@ -63,6 +65,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; @@ -96,6 +99,8 @@ public class GeyserSession implements CommandSender { private DataCache javaPacketCache; + @Setter + private Vector2i lastChunkPosition = null; private int renderDistance; private boolean loggedIn; @@ -144,15 +149,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); @@ -169,6 +165,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, ""); } @@ -179,7 +187,7 @@ public class GeyserSession implements CommandSender { return; } - loggedIn = true; + loggingIn = true; // new thread so clients don't timeout new Thread(() -> { try { @@ -248,6 +256,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 @@ -278,6 +297,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(); } @@ -381,7 +403,7 @@ public class GeyserSession implements CommandSender { startGamePacket.setBonusChestEnabled(false); startGamePacket.setStartingWithMap(false); startGamePacket.setTrustingPlayers(true); - startGamePacket.setDefaultPlayerPermission(PlayerPermission.OPERATOR); + startGamePacket.setDefaultPlayerPermission(PlayerPermission.MEMBER); startGamePacket.setServerChunkTickRange(4); startGamePacket.setBehaviorPackLocked(false); startGamePacket.setResourcePackLocked(false); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/Registry.java b/connector/src/main/java/org/geysermc/connector/network/translators/Registry.java index cf80cdbfe..70201ba88 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/Registry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/Registry.java @@ -55,6 +55,8 @@ public class Registry { if (MAP.containsKey(clazz)) { ((PacketTranslator

) MAP.get(clazz)).translate(packet, session); return true; + } else { + GeyserConnector.getInstance().getLogger().debug("Could not find packet for " + (packet.toString().length() > 25 ? packet.getClass().getSimpleName() : packet)); } } catch (Throwable ex) { GeyserConnector.getInstance().getLogger().error("Could not translate packet " + packet.getClass().getSimpleName(), ex); 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 { @@ -40,8 +42,12 @@ public class BedrockAnimateTranslator extends PacketTranslator { public void translate(AnimatePacket packet, GeyserSession session) { switch (packet.getAction()) { case SWING_ARM: - ClientPlayerSwingArmPacket swingArmPacket = new ClientPlayerSwingArmPacket(Hand.MAIN_HAND); - session.getDownstream().getSession().send(swingArmPacket); + // Delay so entity damage can be processed first + session.getConnector().getGeneralThreadPool().schedule(() -> + session.getDownstream().getSession().send(new ClientPlayerSwingArmPacket(Hand.MAIN_HAND)), + 25, + TimeUnit.MILLISECONDS + ); break; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index cc399f107..94903246b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -25,6 +25,7 @@ package org.geysermc.connector.network.translators.bedrock; +import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPlaceBlockPacket; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -49,19 +50,33 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator { +public class BedrockSetLocalPlayerAsInitializedTranslator extends PacketTranslator { @Override public void translate(SetLocalPlayerAsInitializedPacket packet, GeyserSession session) { if (session.getPlayerEntity().getGeyserId() == packet.getRuntimeEntityId()) { if (!session.getUpstream().isInitialized()) { session.getUpstream().setInitialized(true); + session.login(); for (PlayerEntity entity : session.getEntityCache().getEntitiesByType(PlayerEntity.class)) { if (!entity.isValid()) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java index 92be5c562..1ac9a9fe1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java @@ -57,6 +57,9 @@ public class BlockTranslator { private static final Int2ObjectMap BEDROCK_TO_JAVA_BLOCK_MAP = new Int2ObjectOpenHashMap<>(); 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 int BLOCK_STATE_VERSION = 17760256; static { @@ -103,7 +106,8 @@ public class BlockTranslator { if ("minecraft:water[level=0]".equals(javaId)) { waterRuntimeId = bedrockRuntimeId; } - boolean waterlogged = entry.getValue().has("waterlogged") && entry.getValue().get("waterlogged").booleanValue(); + boolean waterlogged = entry.getKey().contains("waterlogged=true") + || javaId.contains("minecraft:bubble_column") || javaId.contains("minecraft:kelp") || javaId.contains("seagrass"); if (waterlogged) { BEDROCK_TO_JAVA_BLOCK_MAP.putIfAbsent(bedrockRuntimeId | 1 << 31, new BlockState(javaRuntimeId)); @@ -180,6 +184,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/item/ItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java index 99d416bba..68f88bb21 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 @@ -72,7 +72,18 @@ public class ItemTranslator { if (stack.getNbt() == null) { return ItemData.of(bedrockItem.getBedrockId(), (short) bedrockItem.getBedrockData(), stack.getAmount()); } - return ItemData.of(bedrockItem.getBedrockId(), (short) bedrockItem.getBedrockData(), stack.getAmount(), translateToBedrockNBT(stack.getNbt())); + + // TODO: Create proper transformers instead of shoving everything here + CompoundTag tag = stack.getNbt(); + IntTag mapId = tag.get("map"); + + if (mapId != null) { + tag.put(new StringTag("map_uuid", mapId.getValue().toString())); + tag.put(new IntTag("map_name_index", mapId.getValue())); + } + + + return ItemData.of(bedrockItem.getBedrockId(), (short) bedrockItem.getBedrockData(), stack.getAmount(), translateToBedrockNBT(tag)); } public ItemEntry getItem(ItemStack stack) { 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 1abe1940c..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; @@ -49,7 +48,7 @@ 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/scoreboard/JavaTeamTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java index a832f3d71..c9d1ccfe2 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 @@ -52,12 +52,14 @@ public class JavaTeamTranslator extends PacketTranslator { case CREATE: scoreboard.registerNewTeam(packet.getTeamName(), toPlayerSet(packet.getPlayers())) .setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) + .setColor(packet.getColor()) .setPrefix(MessageUtils.getBedrockMessage(packet.getPrefix())) .setSuffix(MessageUtils.getBedrockMessage(packet.getSuffix())); break; case UPDATE: scoreboard.getTeam(packet.getTeamName()) .setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) + .setColor(packet.getColor()) .setPrefix(MessageUtils.getBedrockMessage(packet.getPrefix())) .setSuffix(MessageUtils.getBedrockMessage(packet.getSuffix())) .setUpdateType(UpdateType.UPDATE); 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 4687dfbbf..bb73c5f02 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 @@ -36,7 +36,6 @@ import org.geysermc.connector.world.chunk.ChunkSection; import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerChunkDataPacket; -import com.nukkitx.math.vector.Vector3i; import com.nukkitx.network.VarInts; import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; @@ -48,63 +47,47 @@ public class JavaChunkDataTranslator extends PacketTranslator { try { - if (packet.getColumn().getBiomeData() != null) { //Full chunk - ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(packet.getColumn()); - ByteBuf byteBuf = Unpooled.buffer(32); - ChunkSection[] sections = chunkData.sections; + ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(packet.getColumn()); + ByteBuf byteBuf = Unpooled.buffer(32); + ChunkSection[] sections = chunkData.sections; - int sectionCount = sections.length - 1; - while (sectionCount >= 0 && sections[sectionCount].isEmpty()) { - sectionCount--; - } - sectionCount++; - - for (int i = 0; i < sectionCount; i++) { - ChunkSection section = chunkData.sections[i]; - section.writeToNetwork(byteBuf); - } - - byte[] bedrockBiome = BiomeTranslator.toBedrockBiome(packet.getColumn().getBiomeData()); - - byteBuf.writeBytes(bedrockBiome); // Biomes - 256 bytes - byteBuf.writeByte(0); // Border blocks - Edu edition only - VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now - - byte[] payload = new byte[byteBuf.writerIndex()]; - byteBuf.readBytes(payload); - - LevelChunkPacket levelChunkPacket = new LevelChunkPacket(); - levelChunkPacket.setSubChunksLength(sectionCount); - levelChunkPacket.setCachingEnabled(false); - levelChunkPacket.setChunkX(packet.getColumn().getX()); - levelChunkPacket.setChunkZ(packet.getColumn().getZ()); - levelChunkPacket.setData(payload); - session.getUpstream().sendPacket(levelChunkPacket); - } else { - final int xOffset = packet.getColumn().getX() << 4; - final int zOffset = packet.getColumn().getZ() << 4; - Chunk[] chunks = packet.getColumn().getChunks(); - for (int i = 0; i < chunks.length; i++) { - Chunk chunk = chunks[i]; - if (chunk == null) continue; - final int yOffset = i * 16; - for (int x = 0; x < 16; x++) { - for (int y = 0; y < 16; y++) { - for (int z = 0; z < 16; z++) { - BlockState blockState = chunk.get(x, y, z); - Vector3i pos = Vector3i.from( - x + xOffset, - y + yOffset, - z + zOffset); - ChunkUtils.updateBlock(session, blockState, pos); - } - } - } - } + int sectionCount = sections.length - 1; + while (sectionCount >= 0 && sections[sectionCount].isEmpty()) { + sectionCount--; } + sectionCount++; + + for (int i = 0; i < sectionCount; i++) { + ChunkSection section = chunkData.sections[i]; + section.writeToNetwork(byteBuf); + } + + byte[] bedrockBiome = BiomeTranslator.toBedrockBiome(packet.getColumn().getBiomeData()); + + byteBuf.writeBytes(bedrockBiome); // Biomes - 256 bytes + byteBuf.writeByte(0); // Border blocks - Edu edition only + VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now + + byte[] payload = new byte[byteBuf.writerIndex()]; + byteBuf.readBytes(payload); + + LevelChunkPacket levelChunkPacket = new LevelChunkPacket(); + levelChunkPacket.setSubChunksLength(sectionCount); + levelChunkPacket.setCachingEnabled(false); + levelChunkPacket.setChunkX(packet.getColumn().getX()); + levelChunkPacket.setChunkZ(packet.getColumn().getZ()); + levelChunkPacket.setData(payload); + session.getUpstream().sendPacket(levelChunkPacket); } catch (Exception 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 new file mode 100644 index 000000000..28022c16d --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.java.world; + +import com.github.steveice10.mc.protocol.data.game.world.map.MapData; +import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerMapDataPacket; +import com.nukkitx.protocol.bedrock.packet.ClientboundMapItemDataPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.utils.MapColor; + +@Translator(packet = ServerMapDataPacket.class) +public class JavaMapDataTranslator extends PacketTranslator { + @Override + public void translate(ServerMapDataPacket packet, GeyserSession session) { + ClientboundMapItemDataPacket mapItemDataPacket = new ClientboundMapItemDataPacket(); + + mapItemDataPacket.setUniqueMapId(packet.getMapId()); + mapItemDataPacket.setDimensionId(session.getPlayerEntity().getDimension()); + mapItemDataPacket.setLocked(packet.isLocked()); + mapItemDataPacket.setScale(packet.getScale()); + + MapData data = packet.getData(); + if (data != null) { + mapItemDataPacket.setXOffset(data.getX()); + mapItemDataPacket.setYOffset(data.getY()); + mapItemDataPacket.setWidth(data.getColumns()); + mapItemDataPacket.setHeight(data.getRows()); + + // Every int entry is an ARGB color + int[] colors = new int[data.getData().length]; + + int idx = 0; + for (byte colorId : data.getData()) { + colors[idx] = MapColor.fromId(colorId).toARGB(); + idx++; + } + + mapItemDataPacket.setColors(colors); + } + + session.getUpstream().getSession().sendPacket(mapItemDataPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java index 3e1904d48..6c7eeaf92 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 @@ -98,7 +98,7 @@ public class JavaNotifyClientTranslator extends PacketTranslator entities = new ObjectOpenHashSet<>(); - public Team(Scoreboard scoreboard, String id) { this.scoreboard = scoreboard; this.id = id; 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 2acda2c2b..a35b2cc58 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -29,8 +29,10 @@ import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; import com.github.steveice10.mc.protocol.data.game.chunk.Column; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.nukkitx.math.vector.Vector2i; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; +import com.nukkitx.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.Translators; @@ -74,6 +76,20 @@ public class ChunkUtils { return chunkData; } + public static void updateChunkPosition(GeyserSession session, Vector3i position) { + Vector2i chunkPos = session.getLastChunkPosition(); + Vector2i newChunkPos = Vector2i.from(position.getX() >> 4, position.getZ() >> 4); + + if (chunkPos == null || !chunkPos.equals(newChunkPos)) { + NetworkChunkPublisherUpdatePacket chunkPublisherUpdatePacket = new NetworkChunkPublisherUpdatePacket(); + chunkPublisherUpdatePacket.setPosition(position); + chunkPublisherUpdatePacket.setRadius(session.getRenderDistance() << 4); + session.getUpstream().sendPacket(chunkPublisherUpdatePacket); + + session.setLastChunkPosition(newChunkPos); + } + } + public static void updateBlock(GeyserSession session, BlockState blockState, Position position) { Vector3i pos = Vector3i.from(position.getX(), position.getY(), position.getZ()); updateBlock(session, blockState, pos); 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 c7ecaafb4..199c5a5c6 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java @@ -52,6 +52,7 @@ public class DimensionUtils { player.setDimension(bedrockDimension); player.setPosition(pos.toFloat()); session.setSpawned(false); + session.setLastChunkPosition(null); //let java server handle portal travel sound StopSoundPacket stopSoundPacket = new StopSoundPacket(); 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 22248db38..eac2fc3f4 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java @@ -72,4 +72,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/MapColor.java b/connector/src/main/java/org/geysermc/connector/utils/MapColor.java new file mode 100644 index 000000000..2c4a13b9f --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/MapColor.java @@ -0,0 +1,244 @@ +package org.geysermc.connector.utils; + +import java.util.Arrays; + +public enum MapColor { + COLOR_0(-1, -1, -1), + COLOR_1(-1, -1, -1), + COLOR_2(-1, -1, -1), + COLOR_3(-1, -1, -1), + COLOR_4(89, 125, 39), + COLOR_5(109, 153, 48), + COLOR_6(127, 178, 56), + COLOR_7(67, 94, 29), + COLOR_8(174, 164, 115), + COLOR_9(213, 201, 140), + COLOR_10(247, 233, 163), + COLOR_11(130, 123, 86), + COLOR_12(140, 140, 140), + COLOR_13(171, 171, 171), + COLOR_14(199, 199, 199), + COLOR_15(105, 105, 105), + COLOR_16(180, 0, 0), + COLOR_17(220, 0, 0), + COLOR_18(255, 0, 0), + COLOR_19(135, 0, 0), + COLOR_20(112, 112, 180), + COLOR_21(138, 138, 220), + COLOR_22(160, 160, 255), + COLOR_23(84, 84, 135), + COLOR_24(117, 117, 117), + COLOR_25(144, 144, 144), + COLOR_26(167, 167, 167), + COLOR_27(88, 88, 88), + COLOR_28(0, 87, 0), + COLOR_29(0, 106, 0), + COLOR_30(0, 124, 0), + COLOR_31(0, 65, 0), + COLOR_32(180, 180, 180), + COLOR_33(220, 220, 220), + COLOR_34(255, 255, 255), + COLOR_35(135, 135, 135), + COLOR_36(115, 118, 129), + COLOR_37(141, 144, 158), + COLOR_38(164, 168, 184), + COLOR_39(86, 88, 97), + COLOR_40(106, 76, 54), + COLOR_41(130, 94, 66), + COLOR_42(151, 109, 77), + COLOR_43(79, 57, 40), + COLOR_44(79, 79, 79), + COLOR_45(96, 96, 96), + COLOR_46(112, 112, 112), + COLOR_47(59, 59, 59), + COLOR_48(45, 45, 180), + COLOR_49(55, 55, 220), + COLOR_50(64, 64, 255), + COLOR_51(33, 33, 135), + COLOR_52(100, 84, 50), + COLOR_53(123, 102, 62), + COLOR_54(143, 119, 72), + COLOR_55(75, 63, 38), + COLOR_56(180, 177, 172), + COLOR_57(220, 217, 211), + COLOR_58(255, 252, 245), + COLOR_59(135, 133, 129), + COLOR_60(152, 89, 36), + COLOR_61(186, 109, 44), + COLOR_62(216, 127, 51), + COLOR_63(114, 67, 27), + COLOR_64(125, 53, 152), + COLOR_65(153, 65, 186), + COLOR_66(178, 76, 216), + COLOR_67(94, 40, 114), + COLOR_68(72, 108, 152), + COLOR_69(88, 132, 186), + COLOR_70(102, 153, 216), + COLOR_71(54, 81, 114), + COLOR_72(161, 161, 36), + COLOR_73(197, 197, 44), + COLOR_74(229, 229, 51), + COLOR_75(121, 121, 27), + COLOR_76(89, 144, 17), + COLOR_77(109, 176, 21), + COLOR_78(127, 204, 25), + COLOR_79(67, 108, 13), + COLOR_80(170, 89, 116), + COLOR_81(208, 109, 142), + COLOR_82(242, 127, 165), + COLOR_83(128, 67, 87), + COLOR_84(53, 53, 53), + COLOR_85(65, 65, 65), + COLOR_86(76, 76, 76), + COLOR_87(40, 40, 40), + COLOR_88(108, 108, 108), + COLOR_89(132, 132, 132), + COLOR_90(153, 153, 153), + COLOR_91(81, 81, 81), + COLOR_92(53, 89, 108), + COLOR_93(65, 109, 132), + COLOR_94(76, 127, 153), + COLOR_95(40, 67, 81), + COLOR_96(89, 44, 125), + COLOR_97(109, 54, 153), + COLOR_98(127, 63, 178), + COLOR_99(67, 33, 94), + COLOR_100(36, 53, 125), + COLOR_101(44, 65, 153), + COLOR_102(51, 76, 178), + COLOR_103(27, 40, 94), + COLOR_104(72, 53, 36), + COLOR_105(88, 65, 44), + COLOR_106(102, 76, 51), + COLOR_107(54, 40, 27), + COLOR_108(72, 89, 36), + COLOR_109(88, 109, 44), + COLOR_110(102, 127, 51), + COLOR_111(54, 67, 27), + COLOR_112(108, 36, 36), + COLOR_113(132, 44, 44), + COLOR_114(153, 51, 51), + COLOR_115(81, 27, 27), + COLOR_116(17, 17, 17), + COLOR_117(21, 21, 21), + COLOR_118(25, 25, 25), + COLOR_119(13, 13, 13), + COLOR_120(176, 168, 54), + COLOR_121(215, 205, 66), + COLOR_122(250, 238, 77), + COLOR_123(132, 126, 40), + COLOR_124(64, 154, 150), + COLOR_125(79, 188, 183), + COLOR_126(92, 219, 213), + COLOR_127(48, 115, 112), + COLOR_128(52, 90, 180), + COLOR_129(63, 110, 220), + COLOR_130(74, 128, 255), + COLOR_131(39, 67, 135), + COLOR_132(0, 153, 40), + COLOR_133(0, 187, 50), + COLOR_134(0, 217, 58), + COLOR_135(0, 114, 30), + COLOR_136(91, 60, 34), + COLOR_137(111, 74, 42), + COLOR_138(129, 86, 49), + COLOR_139(68, 45, 25), + COLOR_140(79, 1, 0), + COLOR_141(96, 1, 0), + COLOR_142(112, 2, 0), + COLOR_143(59, 1, 0), + COLOR_144(147, 124, 113), + COLOR_145(180, 152, 138), + COLOR_146(209, 177, 161), + COLOR_147(110, 93, 85), + COLOR_148(112, 57, 25), + COLOR_149(137, 70, 31), + COLOR_150(159, 82, 36), + COLOR_151(84, 43, 19), + COLOR_152(105, 61, 76), + COLOR_153(128, 75, 93), + COLOR_154(149, 87, 108), + COLOR_155(78, 46, 57), + COLOR_156(79, 76, 97), + COLOR_157(96, 93, 119), + COLOR_158(112, 108, 138), + COLOR_159(59, 57, 73), + COLOR_160(131, 93, 25), + COLOR_161(160, 114, 31), + COLOR_162(186, 133, 36), + COLOR_163(98, 70, 19), + COLOR_164(72, 82, 37), + COLOR_165(88, 100, 45), + COLOR_166(103, 117, 53), + COLOR_167(54, 61, 28), + COLOR_168(112, 54, 55), + COLOR_169(138, 66, 67), + COLOR_170(160, 77, 78), + COLOR_171(84, 40, 41), + COLOR_172(40, 28, 24), + COLOR_173(49, 35, 30), + COLOR_174(57, 41, 35), + COLOR_175(30, 21, 18), + COLOR_176(95, 75, 69), + COLOR_177(116, 92, 84), + COLOR_178(135, 107, 98), + COLOR_179(71, 56, 51), + COLOR_180(61, 64, 64), + COLOR_181(75, 79, 79), + COLOR_182(87, 92, 92), + COLOR_183(46, 48, 48), + COLOR_184(86, 51, 62), + COLOR_185(105, 62, 75), + COLOR_186(122, 73, 88), + COLOR_187(64, 38, 46), + COLOR_188(53, 43, 64), + COLOR_189(65, 53, 79), + COLOR_190(76, 62, 92), + COLOR_191(40, 32, 48), + COLOR_192(53, 35, 24), + COLOR_193(65, 43, 30), + COLOR_194(76, 50, 35), + COLOR_195(40, 26, 18), + COLOR_196(53, 57, 29), + COLOR_197(65, 70, 36), + COLOR_198(76, 82, 42), + COLOR_199(40, 43, 22), + COLOR_200(100, 42, 32), + COLOR_201(122, 51, 39), + COLOR_202(142, 60, 46), + COLOR_203(75, 31, 24), + COLOR_204(26, 15, 11), + COLOR_205(31, 18, 13), + COLOR_206(37, 22, 16), + COLOR_207(19, 11, 8); + + private final int red; + private final int green; + private final int blue; + + MapColor(int red, int green, int blue) { + this.red = red; + this.green = green; + this.blue = blue; + } + + int getId() { + return ordinal(); + } + + public static MapColor fromId(int id) { + return Arrays.stream(values()).filter(color -> color.getId() == id).findFirst().get(); + } + + public int toARGB() { + int alpha = 255; + if (red == -1 && green == -1 && blue == -1) + alpha = 0; // transparent + + long result = red & 0xff; + result |= (green & 0xff) << 8; + result |= (blue & 0xff) << 16; + result |= (alpha & 0xff) << 24; + return (int) (result & 0xFFFFFFFFL); + } +} \ No newline at end of file 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 b5260ab75..a28d6a7a1 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java @@ -25,31 +25,30 @@ package org.geysermc.connector.utils; -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.game.scoreboard.TeamColor; +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.List; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class MessageUtils { - public static List getTranslationParams(Message[] messages) { - List strings = new ArrayList(); - for (int i = 0; i < messages.length; i++) { - if (messages[i] instanceof TranslationMessage) { - TranslationMessage translation = (TranslationMessage) messages[i]; + public static List getTranslationParams(Message[] messages, String locale) { + List strings = new ArrayList<>(); + for (Message message : messages) { + if (message instanceof TranslationMessage) { + TranslationMessage translation = (TranslationMessage) message; - StringBuilder builder = new StringBuilder(""); - builder.append("%"); - builder.append(translation.getTranslationKey()); - strings.add(builder.toString()); + if (locale == null) { + String builder = "%" + translation.getTranslationKey(); + strings.add(builder); + } if (translation.getTranslationKey().equals("commands.gamemode.success.other")) { strings.add(""); @@ -59,47 +58,92 @@ public class MessageUtils { strings.add(" - no permission or invalid command!"); } - for (int j = 0; j < getTranslationParams(translation.getTranslationParams()).size(); j++) { - strings.add(getTranslationParams(translation.getTranslationParams()).get(j)); + List furtherParams = getTranslationParams(translation.getTranslationParams()); + if (locale != null) { + strings.add(insertParams(LocaleUtils.getLocaleString(translation.getTranslationKey(), locale), furtherParams)); + }else{ + strings.addAll(furtherParams); } } else { - StringBuilder builder = new StringBuilder(""); - builder.append(getFormat(messages[i].getStyle().getFormats())); - builder.append(getColor(messages[i].getStyle().getColor())); - builder.append(getBedrockMessage(messages[i])); - strings.add(builder.toString()); + String builder = getFormat(message.getStyle().getFormats()) + + getColorOrParent(message.getStyle()); + builder += getTranslatedBedrockMessage(message, locale, false); + strings.add(builder); } } return strings; } - public static String getTranslationText(TranslationMessage message) { - StringBuilder builder = new StringBuilder(""); - builder.append(getFormat(message.getStyle().getFormats())); - builder.append(getColor(message.getStyle().getColor())); - builder.append("%"); - builder.append(message.getTranslationKey()); - return builder.toString(); + public static List getTranslationParams(Message[] messages) { + return getTranslationParams(messages, null); } - public static String getBedrockMessage(Message message) { + public static String getTranslationText(TranslationMessage message) { + return getFormat(message.getStyle().getFormats()) + getColorOrParent(message.getStyle()) + + "%" + message.getTranslationKey(); + } + + 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 } } - return builder.toString(); + 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) { @@ -206,7 +250,6 @@ public class MessageUtils { } catch (Exception ex) { return false; } - return true; } @@ -231,7 +274,20 @@ public class MessageUtils { formatJson((JsonObject) a.get(i)); } } - return object; } + + public static String toChatColor(TeamColor teamColor) { + for (ChatColor color : ChatColor.values()) { + if (color.name().equals(teamColor.name())) { + return getColor(color); + } + } + for (ChatFormat format : ChatFormat.values()) { + if (format.name().equals(teamColor.name())) { + return getFormat(Collections.singletonList(format)); + } + } + return ""; + } } 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 562fb3c51..7815fd087 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java +++ b/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java @@ -40,7 +40,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.translators.item.ItemEntry; -import java.io.InputStream; +import java.io.*; import java.util.*; public class Toolbox { @@ -52,6 +52,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"); @@ -104,6 +106,9 @@ public class Toolbox { entry.getValue().get("bedrock_id").intValue(), entry.getValue().get("bedrock_data").intValue())); itemIndex++; } + + // Load the locale data + LocaleUtils.init(); } public static InputStream getResource(String resource) { 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/. diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index 278c73449..efc9db6b7 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 278c73449aeeb4064c7513a68f98a49a5f463f0a +Subproject commit efc9db6b7d51bdf145230933ac23b321ac1c132d