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/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..92085a35b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: GeyserMC Discord + url: http://discord.geysermc.org/ + about: If your issue seems like it could possibly be an easy fix due to configuration, please hop on our Discord. diff --git a/.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/README.md b/README.md index 8a0197972..056a83cac 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Geyser is a bridge between Minecraft: Bedrock Edition and Minecraft: Java Editio Geyser is a proxy, bridging the gap between Minecraft: Bedrock Edition and Minecraft: Java Edition servers. The ultimate goal of this project is to allow Minecraft: Bedrock Edition users to join Minecraft: Java Edition servers as seamlessly as possible. **Please note, this project is still a work in progress and should not be used on production. Expect bugs!** -### Currently supporting Minecraft Bedrock v1.14.X and Minecraft Java v1.15.2. +### Currently supporting Minecraft Bedrock v1.14.6(0) and Minecraft Java v1.15.2. ## Setting Up Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser. 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..5b8842b4e 100644 --- a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitConfiguration.java +++ b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -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/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitLogger.java b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitLogger.java index 05fd2c6c8..454ec9f6e 100644 --- a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitLogger.java +++ b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitLogger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitPlugin.java b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitPlugin.java index 3c7ae8391..8b0883efb 100644 --- a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitPlugin.java +++ b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitPlugin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal 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..e0f6a6eff 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -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/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeLogger.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeLogger.java index 2ebc069b1..7aba88bcd 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeLogger.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeLogger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java index d6974dafa..959c78d92 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal 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..be4923e2b 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -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/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeLogger.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeLogger.java index 1df7fbf9b..758ac98d3 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeLogger.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeLogger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java index a2074b158..bc264589d 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal 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/GeyserBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserBootstrap.java index 284d94072..1bdce0bda 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserBootstrap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal 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..afd6179e8 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserConfiguration.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -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/ChatColor.java b/common/src/main/java/org/geysermc/common/ChatColor.java index 20632770c..e60012e77 100644 --- a/common/src/main/java/org/geysermc/common/ChatColor.java +++ b/common/src/main/java/org/geysermc/common/ChatColor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/common/src/main/java/org/geysermc/common/IGeyserConfiguration.java b/common/src/main/java/org/geysermc/common/IGeyserConfiguration.java index db5d831b1..774e3394d 100644 --- a/common/src/main/java/org/geysermc/common/IGeyserConfiguration.java +++ b/common/src/main/java/org/geysermc/common/IGeyserConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -46,6 +46,8 @@ public interface IGeyserConfiguration { boolean isAllowThirdPartyCapes(); + String getDefaultLocale(); + Path getFloodgateKeyFile(); IMetricsInfo getMetrics(); diff --git a/common/src/main/java/org/geysermc/common/bootstrap/IGeyserBootstrap.java b/common/src/main/java/org/geysermc/common/bootstrap/IGeyserBootstrap.java index cf4b3991e..9a9d9c2b9 100644 --- a/common/src/main/java/org/geysermc/common/bootstrap/IGeyserBootstrap.java +++ b/common/src/main/java/org/geysermc/common/bootstrap/IGeyserBootstrap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/common/src/main/java/org/geysermc/common/logger/IGeyserLogger.java b/common/src/main/java/org/geysermc/common/logger/IGeyserLogger.java index b4d143e43..ad571ebbb 100644 --- a/common/src/main/java/org/geysermc/common/logger/IGeyserLogger.java +++ b/common/src/main/java/org/geysermc/common/logger/IGeyserLogger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/common/src/main/java/org/geysermc/common/window/CustomFormBuilder.java b/common/src/main/java/org/geysermc/common/window/CustomFormBuilder.java index fb6794120..004b00a96 100644 --- a/common/src/main/java/org/geysermc/common/window/CustomFormBuilder.java +++ b/common/src/main/java/org/geysermc/common/window/CustomFormBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java b/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java index a6bc72781..8d48fcfaa 100644 --- a/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java +++ b/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/common/src/main/java/org/geysermc/common/window/FormWindow.java b/common/src/main/java/org/geysermc/common/window/FormWindow.java index 968d9349d..3cfaee43a 100644 --- a/common/src/main/java/org/geysermc/common/window/FormWindow.java +++ b/common/src/main/java/org/geysermc/common/window/FormWindow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/common/src/main/java/org/geysermc/common/window/ModalFormWindow.java b/common/src/main/java/org/geysermc/common/window/ModalFormWindow.java index 934660395..66dda0b5d 100644 --- a/common/src/main/java/org/geysermc/common/window/ModalFormWindow.java +++ b/common/src/main/java/org/geysermc/common/window/ModalFormWindow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java b/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java index cc31f061a..381cef39c 100644 --- a/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java +++ b/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/common/src/main/java/org/geysermc/common/window/button/FormButton.java b/common/src/main/java/org/geysermc/common/window/button/FormButton.java index 4f710d483..6daa2feae 100644 --- a/common/src/main/java/org/geysermc/common/window/button/FormButton.java +++ b/common/src/main/java/org/geysermc/common/window/button/FormButton.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -37,6 +37,10 @@ public class FormButton { @Getter private FormImage image; + public FormButton(String text) { + this.text = text; + } + public FormButton(String text, FormImage image) { this.text = text; diff --git a/common/src/main/java/org/geysermc/common/window/button/FormImage.java b/common/src/main/java/org/geysermc/common/window/button/FormImage.java index a3f83a0c5..b700b046b 100644 --- a/common/src/main/java/org/geysermc/common/window/button/FormImage.java +++ b/common/src/main/java/org/geysermc/common/window/button/FormImage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/common/src/main/java/org/geysermc/common/window/component/DropdownComponent.java b/common/src/main/java/org/geysermc/common/window/component/DropdownComponent.java index 8abe72ff8..4dac6b043 100644 --- a/common/src/main/java/org/geysermc/common/window/component/DropdownComponent.java +++ b/common/src/main/java/org/geysermc/common/window/component/DropdownComponent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/common/src/main/java/org/geysermc/common/window/component/FormComponent.java b/common/src/main/java/org/geysermc/common/window/component/FormComponent.java index fb5b9d18c..5a56ae0cc 100644 --- a/common/src/main/java/org/geysermc/common/window/component/FormComponent.java +++ b/common/src/main/java/org/geysermc/common/window/component/FormComponent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/common/src/main/java/org/geysermc/common/window/component/InputComponent.java b/common/src/main/java/org/geysermc/common/window/component/InputComponent.java index 53ec2b5eb..fad6a0fed 100644 --- a/common/src/main/java/org/geysermc/common/window/component/InputComponent.java +++ b/common/src/main/java/org/geysermc/common/window/component/InputComponent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/common/src/main/java/org/geysermc/common/window/component/LabelComponent.java b/common/src/main/java/org/geysermc/common/window/component/LabelComponent.java index 7d2aaa420..a76b313fa 100644 --- a/common/src/main/java/org/geysermc/common/window/component/LabelComponent.java +++ b/common/src/main/java/org/geysermc/common/window/component/LabelComponent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/common/src/main/java/org/geysermc/common/window/component/SliderComponent.java b/common/src/main/java/org/geysermc/common/window/component/SliderComponent.java index fd82b3e26..a7a78362e 100644 --- a/common/src/main/java/org/geysermc/common/window/component/SliderComponent.java +++ b/common/src/main/java/org/geysermc/common/window/component/SliderComponent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/common/src/main/java/org/geysermc/common/window/component/StepSliderComponent.java b/common/src/main/java/org/geysermc/common/window/component/StepSliderComponent.java index b61e416d0..8f128d1a4 100644 --- a/common/src/main/java/org/geysermc/common/window/component/StepSliderComponent.java +++ b/common/src/main/java/org/geysermc/common/window/component/StepSliderComponent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java b/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java index 614ecf8fb..50a5c631a 100644 --- a/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java +++ b/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/common/src/main/java/org/geysermc/common/window/response/CustomFormResponse.java b/common/src/main/java/org/geysermc/common/window/response/CustomFormResponse.java index 36b2922f9..6cdd70978 100644 --- a/common/src/main/java/org/geysermc/common/window/response/CustomFormResponse.java +++ b/common/src/main/java/org/geysermc/common/window/response/CustomFormResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/common/src/main/java/org/geysermc/common/window/response/FormResponse.java b/common/src/main/java/org/geysermc/common/window/response/FormResponse.java index 58c8c8e87..2be646837 100644 --- a/common/src/main/java/org/geysermc/common/window/response/FormResponse.java +++ b/common/src/main/java/org/geysermc/common/window/response/FormResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/common/src/main/java/org/geysermc/common/window/response/FormResponseData.java b/common/src/main/java/org/geysermc/common/window/response/FormResponseData.java index 826aba208..fd40be0fb 100644 --- a/common/src/main/java/org/geysermc/common/window/response/FormResponseData.java +++ b/common/src/main/java/org/geysermc/common/window/response/FormResponseData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/common/src/main/java/org/geysermc/common/window/response/ModalFormResponse.java b/common/src/main/java/org/geysermc/common/window/response/ModalFormResponse.java index 844c9a5b8..e1a14039d 100644 --- a/common/src/main/java/org/geysermc/common/window/response/ModalFormResponse.java +++ b/common/src/main/java/org/geysermc/common/window/response/ModalFormResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/common/src/main/java/org/geysermc/common/window/response/SimpleFormResponse.java b/common/src/main/java/org/geysermc/common/window/response/SimpleFormResponse.java index 5a53d0423..e80d58e78 100644 --- a/common/src/main/java/org/geysermc/common/window/response/SimpleFormResponse.java +++ b/common/src/main/java/org/geysermc/common/window/response/SimpleFormResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/pom.xml b/connector/pom.xml index 25ef50730..5830ce26f 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -30,16 +30,10 @@ 2.9.8 compile - - io.sentry - sentry - 1.7.0 - compile - com.nukkitx.protocol - bedrock-v389 - 2.5.4 + bedrock-v390 + 2.5.5-SNAPSHOT compile diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index fb93a27de..9cea6257b 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -27,7 +27,7 @@ package org.geysermc.connector; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; import com.nukkitx.protocol.bedrock.BedrockServer; -import com.nukkitx.protocol.bedrock.v389.Bedrock_v389; +import com.nukkitx.protocol.bedrock.v390.Bedrock_v390; import lombok.Getter; @@ -49,6 +49,8 @@ import java.net.InetSocketAddress; import java.text.DecimalFormat; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.UUID; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -56,12 +58,12 @@ import java.util.concurrent.TimeUnit; @Getter public class GeyserConnector { - public static final BedrockPacketCodec BEDROCK_PACKET_CODEC = Bedrock_v389.V389_CODEC; + public static final BedrockPacketCodec BEDROCK_PACKET_CODEC = Bedrock_v390.V390_CODEC; public static final String NAME = "Geyser"; public static final String VERSION = "1.0-SNAPSHOT"; - private final Map players = new HashMap<>(); + private final Map players = new HashMap<>(); private static GeyserConnector instance; @@ -141,6 +143,40 @@ public class GeyserConnector { bootstrap.getGeyserLogger().info("Shutting down Geyser."); shuttingDown = true; + if (players.size() >= 1) { + bootstrap.getGeyserLogger().info("Kicking " + players.size() + " player(s)"); + + for (GeyserSession playerSession : players.values()) { + playerSession.disconnect("Geyser Proxy shutting down."); + } + + CompletableFuture future = CompletableFuture.runAsync(new Runnable() { + @Override + public void run() { + // Simulate a long-running Job + try { + while (true) { + if (players.size() == 0) { + return; + } + + TimeUnit.MILLISECONDS.sleep(100); + } + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + } + }); + + // Block and wait for the future to complete + try { + future.get(); + bootstrap.getGeyserLogger().info("Kicked all players"); + } catch (Exception e) { + // Quietly fail + } + } + generalThreadPool.shutdown(); bedrockServer.close(); players.clear(); @@ -148,17 +184,15 @@ public class GeyserConnector { authType = null; commandMap.getCommands().clear(); commandMap = null; + + bootstrap.getGeyserLogger().info("Geyser shutdown successfully."); } public void addPlayer(GeyserSession player) { - players.put(player.getAuthData().getName(), player); - players.put(player.getAuthData().getUUID(), player); players.put(player.getSocketAddress(), player); } public void removePlayer(GeyserSession player) { - players.remove(player.getAuthData().getName()); - players.remove(player.getAuthData().getUUID()); players.remove(player.getSocketAddress()); } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java index 4694d0fd0..2222cdef5 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java @@ -48,6 +48,11 @@ public class StopCommand extends GeyserCommand { if (!sender.isConsole() && connector.getPlatformType() == PlatformType.STANDALONE) { return; } + connector.shutdown(); + + if (connector.getPlatformType() == PlatformType.STANDALONE) { + System.exit(0); + } } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/EnderCrystalEntity.java b/connector/src/main/java/org/geysermc/connector/entity/EnderCrystalEntity.java new file mode 100644 index 000000000..b3ce22783 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/EnderCrystalEntity.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.data.EntityData; +import com.nukkitx.protocol.bedrock.data.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class EnderCrystalEntity extends Entity { + + public EnderCrystalEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + // Show beam + // Usually performed client-side on Bedrock except for Ender Dragon respawn event + if (entityMetadata.getId() == 7) { + if (entityMetadata.getValue() instanceof Position) { + Position pos = (Position) entityMetadata.getValue(); + metadata.put(EntityData.BLOCK_TARGET, Vector3i.from(pos.getX(), pos.getY(), pos.getZ())); + } else { + metadata.put(EntityData.BLOCK_TARGET, Vector3i.ZERO); + } + } + // There is a base located on the ender crystal + if (entityMetadata.getId() == 8) { + metadata.getFlags().setFlag(EntityFlag.SHOW_BOTTOM, (boolean) entityMetadata.getValue()); + } + super.updateBedrockMetadata(entityMetadata, session); + } + + @Override + public void spawnEntity(GeyserSession session) { + AddEntityPacket addEntityPacket = new AddEntityPacket(); + // Not end crystal but ender crystal + addEntityPacket.setIdentifier("minecraft:ender_crystal"); + addEntityPacket.setRuntimeEntityId(geyserId); + addEntityPacket.setUniqueEntityId(geyserId); + addEntityPacket.setPosition(position); + addEntityPacket.setMotion(motion); + addEntityPacket.setRotation(getBedrockRotation()); + addEntityPacket.setEntityType(entityType.getType()); + addEntityPacket.getMetadata().putAll(metadata); + + valid = true; + session.getUpstream().sendPacket(addEntityPacket); + + session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/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/FishingHookEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java new file mode 100644 index 000000000..3a77292fe --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class FishingHookEntity extends Entity { + public FishingHookEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void spawnEntity(GeyserSession session) { + AddEntityPacket addEntityPacket = new AddEntityPacket(); + // Different ID in Bedrock + addEntityPacket.setIdentifier("minecraft:fishing_hook"); + addEntityPacket.setRuntimeEntityId(geyserId); + addEntityPacket.setUniqueEntityId(geyserId); + addEntityPacket.setPosition(position); + addEntityPacket.setMotion(motion); + addEntityPacket.setRotation(getBedrockRotation()); + addEntityPacket.setEntityType(entityType.getType()); + addEntityPacket.getMetadata().putAll(metadata); + + valid = true; + session.getUpstream().sendPacket(addEntityPacket); + + session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/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/living/monster/EnderDragonEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java new file mode 100644 index 000000000..b07377389 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living.monster; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.Attribute; +import com.nukkitx.protocol.bedrock.data.EntityEventType; +import com.nukkitx.protocol.bedrock.data.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; +import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; +import org.geysermc.connector.entity.living.InsentientEntity; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class EnderDragonEntity extends InsentientEntity { + + public EnderDragonEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 15) { + metadata.getFlags().setFlag(EntityFlag.FIRE_IMMUNE, true); + switch ((int) entityMetadata.getValue()) { + // Performing breath attack + case 5: + EntityEventPacket entityEventPacket = new EntityEventPacket(); + entityEventPacket.setType(EntityEventType.DRAGON_FLAMING); + entityEventPacket.setRuntimeEntityId(geyserId); + entityEventPacket.setData(0); + session.getUpstream().sendPacket(entityEventPacket); + case 6: + case 7: + metadata.getFlags().setFlag(EntityFlag.SITTING, true); + break; + } + } + super.updateBedrockMetadata(entityMetadata, session); + } + + @Override + public void spawnEntity(GeyserSession session) { + AddEntityPacket addEntityPacket = new AddEntityPacket(); + addEntityPacket.setIdentifier("minecraft:" + entityType.name().toLowerCase()); + addEntityPacket.setRuntimeEntityId(geyserId); + addEntityPacket.setUniqueEntityId(geyserId); + addEntityPacket.setPosition(position); + addEntityPacket.setMotion(motion); + addEntityPacket.setRotation(getBedrockRotation()); + addEntityPacket.setEntityType(entityType.getType()); + addEntityPacket.getMetadata().putAll(metadata); + + // Otherwise dragon is always 'dying' + addEntityPacket.getAttributes().add(new Attribute("minecraft:health", 0.0f, 200f, 200f, 200f)); + + valid = true; + session.getUpstream().sendPacket(addEntityPacket); + + session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java new file mode 100644 index 000000000..a423013cb --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living.monster; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityData; +import com.nukkitx.protocol.bedrock.data.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.block.BlockTranslator; + +public class EndermanEntity extends MonsterEntity { + + public EndermanEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + // Held block + if (entityMetadata.getId() == 15) { + metadata.put(EntityData.ENDERMAN_HELD_ITEM_ID, BlockTranslator.getBedrockBlockId((BlockState) entityMetadata.getValue())); + } + // 'Angry' - mouth open + if (entityMetadata.getId() == 16) { + metadata.getFlags().setFlag(EntityFlag.ANGRY, (boolean) entityMetadata.getValue()); + } + // TODO: ID 17 is stared at but I don't believe it's used - maybe only for the sound effect. Check after particle merge + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java new file mode 100644 index 000000000..bca9e6891 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living.monster; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.data.EntityData; +import org.geysermc.connector.entity.living.GolemEntity; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class ShulkerEntity extends GolemEntity { + + public ShulkerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 15) { + BlockFace blockFace = (BlockFace) entityMetadata.getValue(); + metadata.put(EntityData.SHULKER_ATTACH_FACE, (byte) blockFace.ordinal()); + } + if (entityMetadata.getId() == 16) { + Position position = (Position) entityMetadata.getValue(); + if (position != null) { + metadata.put(EntityData.SHULKER_ATTACH_POS, Vector3i.from(position.getX(), position.getY(), position.getZ())); + } + } + //TODO Outdated metadata flag SHULKER_PEAK_HEIGHT +// if (entityMetadata.getId() == 17) { +// int height = (byte) entityMetadata.getValue(); +// metadata.put(EntityData.SHULKER_PEAK_HEIGHT, height); +// } + if (entityMetadata.getId() == 18) { + int color = Math.abs((byte) entityMetadata.getValue() - 15); + metadata.put(EntityData.VARIANT, color); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index f6af511cd..8ea3240c4 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), @@ -73,7 +69,7 @@ public enum EntityType { SPIDER(SpiderEntity.class, 35, 0.9f, 1.4f, 1.4f, 1f), ZOMBIE_PIGMAN(MonsterEntity.class, 36, 1.8f, 0.6f, 0.6f, 1.62f), SLIME(InsentientEntity.class, 37, 0.51f), - ENDERMAN(MonsterEntity.class, 38, 2.9f, 0.6f), + ENDERMAN(EndermanEntity.class, 38, 2.9f, 0.6f), SILVERFISH(MonsterEntity.class, 39, 0.3f, 0.4f), CAVE_SPIDER(MonsterEntity.class, 40, 0.5f, 0.7f), GHAST(FlyingEntity.class, 41, 4.0f), @@ -88,8 +84,8 @@ public enum EntityType { ELDER_GUARDIAN(GuardianEntity.class, 50, 1.9975f), NPC(PlayerEntity.class, 51, 1.8f, 0.6f, 0.6f, 1.62f), WITHER(MonsterEntity.class, 52, 3.5f, 0.9f), - ENDER_DRAGON(InsentientEntity.class, 53, 4f, 13f), - SHULKER(GolemEntity.class, 54, 1f, 1f), + ENDER_DRAGON(EnderDragonEntity.class, 53, 4f, 13f), + SHULKER(ShulkerEntity.class, 54, 1f, 1f), ENDERMITE(MonsterEntity.class, 55, 0.3f, 0.4f), AGENT(Entity.class, 56, 0f), VINDICATOR(AbstractIllagerEntity.class, 57, 1.8f, 0.6f, 0.6f, 1.62f), @@ -103,18 +99,18 @@ 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), EYE_OF_ENDER(Entity.class, 70, 0f), - END_CRYSTAL(Entity.class, 71, 0f), + END_CRYSTAL(EnderCrystalEntity.class, 71, 0f), FIREWORK_ROCKET(Entity.class, 72, 0f), TRIDENT(ArrowEntity.class, 73, 0f), TURTLE(AnimalEntity.class, 74, 0.4f, 1.2f), CAT(CatEntity.class, 75, 0.35f, 0.3f), SHULKER_BULLET(Entity.class, 76, 0f), - FISHING_BOBBER(Entity.class, 77, 0f), + FISHING_BOBBER(FishingHookEntity.class, 77, 0f), CHALKBOARD(Entity.class, 78, 0f), DRAGON_FIREBALL(ItemedFireballEntity.class, 79, 0f), ARROW(ArrowEntity.class, 80, 0.25f, 0.25f), diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java index 801f670cd..d969f970e 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java b/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java index 424570b9e..a1db2fcd5 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/metrics/SentryMetrics.java b/connector/src/main/java/org/geysermc/connector/metrics/SentryMetrics.java deleted file mode 100644 index 88926fd5e..000000000 --- a/connector/src/main/java/org/geysermc/connector/metrics/SentryMetrics.java +++ /dev/null @@ -1,107 +0,0 @@ -package org.geysermc.connector.metrics; - -import io.sentry.Sentry; -import io.sentry.SentryClient; -import io.sentry.SentryClientFactory; -import io.sentry.context.Context; -import io.sentry.event.BreadcrumbBuilder; -import io.sentry.event.UserBuilder; - -public class SentryMetrics { - private static SentryClient sentry; - - public static void init() { - /* - It is recommended that you use the DSN detection system, which - will check the environment variable "SENTRY_DSN", the Java - System Property "sentry.dsn", or the "sentry.properties" file - in your classpath. This makes it easier to provide and adjust - your DSN without needing to change your code. See the configuration - page for more information. - */ - Sentry.init(); - - // You can also manually provide the DSN to the ``init`` method. - Sentry.init(); - - /* - It is possible to go around the static ``Sentry`` API, which means - you are responsible for making the SentryClient instance available - to your code. - */ - sentry = SentryClientFactory.sentryClient(); - - SentryMetrics metrics = new SentryMetrics(); - metrics.logWithStaticAPI(); - metrics.logWithInstanceAPI(); - } - - /** - * An example method that throws an exception. - */ - void unsafeMethod() { - throw new UnsupportedOperationException("You shouldn't call this!"); - } - - /** - * Examples using the (recommended) static API. - */ - void logWithStaticAPI() { - // Note that all fields set on the context are optional. Context data is copied onto - // all future events in the current context (until the context is cleared). - - // Record a breadcrumb in the current context. By default the last 100 breadcrumbs are kept. - Sentry.getContext().recordBreadcrumb( - new BreadcrumbBuilder().setMessage("User made an action").build() - ); - - // Set the user in the current context. - Sentry.getContext().setUser( - new UserBuilder().setEmail("hello@sentry.io").build() - ); - - // Add extra data to future events in this context. - Sentry.getContext().addExtra("extra", "thing"); - - // Add an additional tag to future events in this context. - Sentry.getContext().addTag("tagName", "tagValue"); - - /* - This sends a simple event to Sentry using the statically stored instance - that was created in the ``main`` method. - */ - Sentry.capture("This is a test"); - - try { - unsafeMethod(); - } catch (Exception e) { - // This sends an exception event to Sentry using the statically stored instance - // that was created in the ``main`` method. - Sentry.capture(e); - } - } - - /** - * Examples that use the SentryClient instance directly. - */ - void logWithInstanceAPI() { - // Retrieve the current context. - Context context = sentry.getContext(); - - // Record a breadcrumb in the current context. By default the last 100 breadcrumbs are kept. - context.recordBreadcrumb(new BreadcrumbBuilder().setMessage("User made an action").build()); - - // Set the user in the current context. - context.setUser(new UserBuilder().setEmail("geyser.project@gmail.com").build()); - - // This sends a simple event to Sentry. - sentry.sendMessage("This is a test"); - - try { - unsafeMethod(); - } catch (Exception e) { - // This sends an exception event to Sentry. - sentry.sendException(e); - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java index f0ae14a5c..60ad28d47 100644 --- a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java @@ -80,6 +80,13 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler { pong.setMotd(config.getBedrock().getMotd1()); pong.setMotd(config.getBedrock().getMotd2()); } + + //Bedrock will not even attempt a connection if the client thinks the server is full + //so we have to fake it not being full + if (pong.getPlayerCount() >= pong.getMaximumPlayerCount()) { + pong.setMaximumPlayerCount(pong.getPlayerCount() + 1); + } + return pong; } diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index 6fffcda84..2ccb3723e 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; @@ -85,7 +86,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public boolean handle(ModalFormResponsePacket packet) { - return LoginEncryptionUtils.authenticateFromForm(session, connector, packet.getFormData()); + return LoginEncryptionUtils.authenticateFromForm(session, connector, packet.getFormId(), packet.getFormData()); } private boolean couldLoginUserByName(String bedrockUsername) { @@ -107,7 +108,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public boolean handle(MovePlayerPacket packet) { - if (!session.isLoggedIn() && !session.isLoggingIn()) { + if (!session.isLoggedIn() && !session.isLoggingIn() && session.getConnector().getAuthType() == AuthType.ONLINE) { // TODO it is safer to key authentication on something that won't change (UUID, not username) if (!couldLoginUserByName(session.getAuthData().getName())) { LoginEncryptionUtils.showLoginWindow(session); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 4a6f43826..83a945291 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 @@ -255,6 +274,9 @@ public class GeyserSession implements CommandSender { loggingIn = false; loggedIn = false; connector.getLogger().info(authData.getName() + " has disconnected from remote java server on address " + remoteServer.getAddress() + " because of " + event.getReason()); + if (event.getCause() != null) { + event.getCause().printStackTrace(); + } upstream.disconnect(event.getReason()); } @@ -278,6 +300,9 @@ public class GeyserSession implements CommandSender { downstream.getSession().connect(); connector.addPlayer(this); + } catch (InvalidCredentialsException | IllegalArgumentException e) { + connector.getLogger().info("User '" + username + "' entered invalid login info, kicking."); + disconnect("Invalid/incorrect login info"); } catch (RequestException ex) { ex.printStackTrace(); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java new file mode 100644 index 000000000..abb9016a2 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.session.cache; + +import com.github.steveice10.mc.protocol.data.message.Message; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityData; +import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; +import com.nukkitx.protocol.bedrock.packet.BossEventPacket; +import com.nukkitx.protocol.bedrock.packet.RemoveEntityPacket; +import lombok.AllArgsConstructor; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.MessageUtils; + +@AllArgsConstructor +public class BossBar { + + private GeyserSession session; + + private long entityId; + private Message title; + private float health; + private int color; + private int overlay; + private int darkenSky; + + public void addBossBar() { + addBossEntity(); + updateBossBar(); + } + + public void updateBossBar() { + BossEventPacket bossEventPacket = new BossEventPacket(); + bossEventPacket.setBossUniqueEntityId(entityId); + bossEventPacket.setAction(BossEventPacket.Action.SHOW); + bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getClientData().getLanguageCode())); + bossEventPacket.setHealthPercentage(health); + bossEventPacket.setColor(color); //ignored by client + bossEventPacket.setOverlay(overlay); + bossEventPacket.setDarkenSky(darkenSky); + + session.getUpstream().sendPacket(bossEventPacket); + } + + public void updateTitle(Message title) { + this.title = title; + BossEventPacket bossEventPacket = new BossEventPacket(); + bossEventPacket.setBossUniqueEntityId(entityId); + bossEventPacket.setAction(BossEventPacket.Action.TITLE); + bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getClientData().getLanguageCode())); + + session.getUpstream().sendPacket(bossEventPacket); + } + + public void updateHealth(float health) { + this.health = health; + BossEventPacket bossEventPacket = new BossEventPacket(); + bossEventPacket.setBossUniqueEntityId(entityId); + bossEventPacket.setAction(BossEventPacket.Action.HEALTH_PERCENTAGE); + bossEventPacket.setHealthPercentage(health); + + session.getUpstream().sendPacket(bossEventPacket); + } + + public void removeBossBar() { + BossEventPacket bossEventPacket = new BossEventPacket(); + bossEventPacket.setBossUniqueEntityId(entityId); + bossEventPacket.setAction(BossEventPacket.Action.HIDE); + + session.getUpstream().sendPacket(bossEventPacket); + removeBossEntity(); + } + + /** + * Bedrock still needs an entity to display the BossBar.
+ * Just like 1.8 but it doesn't care about which entity + */ + private void addBossEntity() { + AddEntityPacket addEntityPacket = new AddEntityPacket(); + addEntityPacket.setUniqueEntityId(entityId); + addEntityPacket.setRuntimeEntityId(entityId); + addEntityPacket.setIdentifier("minecraft:creeper"); + addEntityPacket.setEntityType(33); + addEntityPacket.setPosition(session.getPlayerEntity().getPosition()); + addEntityPacket.setRotation(Vector3f.ZERO); + addEntityPacket.setMotion(Vector3f.ZERO); + addEntityPacket.getMetadata().put(EntityData.SCALE, 0.01F); // scale = 0 doesn't work? + + session.getUpstream().sendPacket(addEntityPacket); + } + + private void removeBossEntity() { + RemoveEntityPacket removeEntityPacket = new RemoveEntityPacket(); + removeEntityPacket.setUniqueEntityId(entityId); + + session.getUpstream().sendPacket(removeEntityPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java index f32ee2a5c..f0b394fd5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java @@ -48,7 +48,7 @@ public class EntityCache { private Long2ObjectMap entities = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); private Long2LongMap entityIdTranslations = Long2LongMaps.synchronize(new Long2LongOpenHashMap()); private Map playerEntities = Collections.synchronizedMap(new HashMap<>()); - private Object2LongMap bossbars = new Object2LongOpenHashMap<>(); + private Map bossBars = Collections.synchronizedMap(new HashMap<>()); @Getter private AtomicLong nextEntityId = new AtomicLong(2L); @@ -116,24 +116,30 @@ public class EntityCache { playerEntities.remove(uuid); } - public long addBossBar(UUID uuid) { - long entityId = getNextEntityId().incrementAndGet(); - bossbars.put(uuid, entityId); - return entityId; + public void addBossBar(UUID uuid, BossBar bossBar) { + bossBars.put(uuid, bossBar); + bossBar.addBossBar(); } - public long getBossBar(UUID uuid) { - return bossbars.containsKey(uuid) ? bossbars.get(uuid) : -1; + public BossBar getBossBar(UUID uuid) { + return bossBars.get(uuid); } - public long removeBossBar(UUID uuid) { - return bossbars.remove(uuid); + public void removeBossBar(UUID uuid) { + BossBar bossBar = bossBars.remove(uuid); + if (bossBar != null) { + bossBar.removeBossBar(); + } + } + + public void updateBossBars() { + bossBars.values().forEach(BossBar::updateBossBar); } public void clear() { entities = null; entityIdTranslations = null; playerEntities = null; - bossbars = null; + bossBars = null; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/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..7ab713893 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java @@ -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 { @Override public void translate(AnimatePacket packet, GeyserSession session) { + // Stop the player sending animations before they have fully spawned into the server + if (!session.isSpawned()) { + return; + } + switch (packet.getAction()) { case SWING_ARM: - 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/BedrockCommandRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java index 28cbf4c41..e7d4c7486 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java @@ -34,6 +34,7 @@ import org.geysermc.connector.network.translators.Translator; import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket; import com.nukkitx.protocol.bedrock.packet.CommandRequestPacket; +import org.geysermc.connector.utils.MessageUtils; @Translator(packet = CommandRequestPacket.class) public class BedrockCommandRequestTranslator extends PacketTranslator { @@ -45,7 +46,13 @@ public class BedrockCommandRequestTranslator 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/bedrock/BedrockTextTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockTextTranslator.java index 1c8416310..4dfe4c76c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockTextTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockTextTranslator.java @@ -31,6 +31,7 @@ import org.geysermc.connector.network.translators.Translator; import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket; import com.nukkitx.protocol.bedrock.packet.TextPacket; +import org.geysermc.connector.utils.MessageUtils; @Translator(packet = TextPacket.class) public class BedrockTextTranslator extends PacketTranslator { @@ -38,12 +39,24 @@ public class BedrockTextTranslator extends PacketTranslator { @Override public void translate(TextPacket packet, GeyserSession session) { if (packet.getMessage().charAt(0) == '.') { - ClientChatPacket chatPacket = new ClientChatPacket(packet.getMessage().replace(".", "/")); + String message = packet.getMessage().replace(".", "/").trim(); + + if (MessageUtils.isTooLong(message, session)) { + return; + } + + ClientChatPacket chatPacket = new ClientChatPacket(message); session.getDownstream().getSession().send(chatPacket); return; } - ClientChatPacket chatPacket = new ClientChatPacket(packet.getMessage()); + String message = packet.getMessage().trim(); + + if (MessageUtils.isTooLong(message, session)) { + return; + } + + ClientChatPacket chatPacket = new ClientChatPacket(message); session.getDownstream().getSession().send(chatPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java index c782e099a..58f6ff4e7 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 @@ -55,6 +55,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 { @@ -100,7 +103,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)); @@ -177,6 +181,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..2c32ef6fe 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaBossBarTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaBossBarTranslator.java @@ -26,80 +26,38 @@ package org.geysermc.connector.network.translators.java; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.session.cache.BossBar; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.MessageUtils; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerBossBarPacket; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.EntityData; -import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; -import com.nukkitx.protocol.bedrock.packet.BossEventPacket; -import com.nukkitx.protocol.bedrock.packet.RemoveEntityPacket; + +import java.awt.*; @Translator(packet = ServerBossBarPacket.class) public class JavaBossBarTranslator extends PacketTranslator { @Override public void translate(ServerBossBarPacket packet, GeyserSession session) { - BossEventPacket bossEventPacket = new BossEventPacket(); - bossEventPacket.setBossUniqueEntityId(session.getEntityCache().getBossBar(packet.getUuid())); - + BossBar bossBar = session.getEntityCache().getBossBar(packet.getUuid()); switch (packet.getAction()) { case ADD: - long entityId = session.getEntityCache().addBossBar(packet.getUuid()); - addBossEntity(session, entityId); - - bossEventPacket.setAction(BossEventPacket.Action.SHOW); - bossEventPacket.setBossUniqueEntityId(entityId); - bossEventPacket.setTitle(MessageUtils.getBedrockMessage(packet.getTitle())); - bossEventPacket.setHealthPercentage(packet.getHealth()); - bossEventPacket.setColor(0); //ignored by client - bossEventPacket.setOverlay(1); - bossEventPacket.setDarkenSky(0); + long entityId = session.getEntityCache().getNextEntityId().incrementAndGet(); + bossBar = new BossBar(session, entityId, packet.getTitle(), packet.getHealth(), 0, 1, 0); + session.getEntityCache().addBossBar(packet.getUuid(), bossBar); break; case UPDATE_TITLE: - bossEventPacket.setAction(BossEventPacket.Action.TITLE); - bossEventPacket.setTitle(MessageUtils.getBedrockMessage(packet.getTitle())); + if (bossBar != null) bossBar.updateTitle(packet.getTitle()); break; case UPDATE_HEALTH: - bossEventPacket.setAction(BossEventPacket.Action.HEALTH_PERCENTAGE); - bossEventPacket.setHealthPercentage(packet.getHealth()); + if (bossBar != null) bossBar.updateHealth(packet.getHealth()); break; case REMOVE: - bossEventPacket.setAction(BossEventPacket.Action.HIDE); - removeBossEntity(session, session.getEntityCache().removeBossBar(packet.getUuid())); + session.getEntityCache().removeBossBar(packet.getUuid()); break; case UPDATE_STYLE: case UPDATE_FLAGS: //todo return; } - - session.getUpstream().sendPacket(bossEventPacket); - } - - /** - * Bedrock still needs an entity to display the BossBar.
- * Just like 1.8 but it doesn't care about which entity - */ - private void addBossEntity(GeyserSession session, long entityId) { - AddEntityPacket addEntityPacket = new AddEntityPacket(); - addEntityPacket.setUniqueEntityId(entityId); - addEntityPacket.setRuntimeEntityId(entityId); - addEntityPacket.setIdentifier("minecraft:creeper"); - addEntityPacket.setEntityType(33); - addEntityPacket.setPosition(session.getPlayerEntity().getPosition()); - addEntityPacket.setRotation(Vector3f.ZERO); - addEntityPacket.setMotion(Vector3f.ZERO); - addEntityPacket.getMetadata().put(EntityData.SCALE, 0.01F); // scale = 0 doesn't work? - - session.getUpstream().sendPacket(addEntityPacket); - } - - private void removeBossEntity(GeyserSession session, long entityId) { - RemoveEntityPacket removeEntityPacket = new RemoveEntityPacket(); - removeEntityPacket.setUniqueEntityId(entityId); - - session.getUpstream().sendPacket(removeEntityPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java index 226bd9714..a527866c9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java @@ -34,6 +34,8 @@ import com.github.steveice10.mc.protocol.data.message.TranslationMessage; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerChatPacket; import com.nukkitx.protocol.bedrock.packet.TextPacket; +import java.util.List; + @Translator(packet = ServerChatPacket.class) public class JavaChatTranslator extends PacketTranslator { @@ -58,14 +60,20 @@ public class JavaChatTranslator extends PacketTranslator { break; } + String locale = session.getClientData().getLanguageCode(); + if (packet.getMessage() instanceof TranslationMessage) { textPacket.setType(TextPacket.Type.TRANSLATION); textPacket.setNeedsTranslation(true); - textPacket.setParameters(MessageUtils.getTranslationParams(((TranslationMessage) packet.getMessage()).getTranslationParams())); - textPacket.setMessage(MessageUtils.getBedrockMessage(packet.getMessage())); + + List paramsTranslated = MessageUtils.getTranslationParams(((TranslationMessage) packet.getMessage()).getTranslationParams(), locale); + textPacket.setParameters(paramsTranslated); + + textPacket.setMessage(MessageUtils.insertParams(MessageUtils.getTranslatedBedrockMessage(packet.getMessage(), locale, true), paramsTranslated)); } else { textPacket.setNeedsTranslation(false); - textPacket.setMessage(MessageUtils.getBedrockMessage(packet.getMessage())); + + textPacket.setMessage(MessageUtils.getTranslatedBedrockMessage(packet.getMessage(), locale, false)); } session.getUpstream().sendPacket(textPacket); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java index 0d6caaed0..34fe2271d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java @@ -29,7 +29,6 @@ import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.ChunkUtils; import org.geysermc.connector.utils.DimensionUtils; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerJoinGamePacket; @@ -69,7 +68,6 @@ public class JavaJoinGameTranslator extends PacketTranslator { + + private static byte[] brandData; + + static { + byte[] data = GeyserConnector.NAME.getBytes(StandardCharsets.UTF_8); + byte[] varInt = writeVarInt(data.length); + brandData = new byte[varInt.length + data.length]; + System.arraycopy(varInt, 0, brandData, 0, varInt.length); + System.arraycopy(data, 0, brandData, varInt.length, data.length); + } + + @Override public void translate(ServerPluginMessagePacket packet, GeyserSession session) { if (packet.getChannel().equals("minecraft:brand")) { session.getDownstream().getSession().send( - new ClientPluginMessagePacket(packet.getChannel(), GeyserConnector.NAME.getBytes()) + new ClientPluginMessagePacket(packet.getChannel(), brandData) ); } } + + private static byte[] writeVarInt(int value) { + byte[] data = new byte[getVarIntLength(value)]; + int index = 0; + do { + byte temp = (byte)(value & 0b01111111); + value >>>= 7; + if (value != 0) { + temp |= 0b10000000; + } + data[index] = temp; + index++; + } while (value != 0); + return data; + } + + private static int getVarIntLength(int number) { + if ((number & 0xFFFFFF80) == 0) { + return 1; + } else if ((number & 0xFFFFC000) == 0) { + return 2; + } else if ((number & 0xFFE00000) == 0) { + return 3; + } else if ((number & 0xF0000000) == 0) { + return 4; + } + return 5; + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java index 185aab540..b28e90108 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java @@ -25,6 +25,9 @@ package org.geysermc.connector.network.translators.java; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.attribute.AttributeType; import org.geysermc.connector.network.session.GeyserSession; @@ -35,6 +38,8 @@ import org.geysermc.connector.utils.DimensionUtils; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket; import com.nukkitx.protocol.bedrock.packet.SetPlayerGameTypePacket; +import java.util.concurrent.ThreadLocalRandom; + @Translator(packet = ServerRespawnPacket.class) public class JavaRespawnTranslator extends PacketTranslator { @@ -53,6 +58,12 @@ public class JavaRespawnTranslator extends PacketTranslator session.getUpstream().sendPacket(playerGameTypePacket); session.setGameMode(packet.getGamemode()); + LevelEventPacket stopRainPacket = new LevelEventPacket(); + stopRainPacket.setType(LevelEventType.STOP_RAIN); + stopRainPacket.setData(ThreadLocalRandom.current().nextInt(50000) + 10000); + stopRainPacket.setPosition(Vector3f.ZERO); + session.getUpstream().sendPacket(stopRainPacket); + if (entity.getDimension() != DimensionUtils.javaToBedrock(packet.getDimension())) { DimensionUtils.switchDimension(session, packet.getDimension()); } else { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaServerDeclareCommandsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaServerDeclareCommandsTranslator.java new file mode 100644 index 000000000..195da60c3 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaServerDeclareCommandsTranslator.java @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.java; + +import com.github.steveice10.mc.protocol.data.game.command.CommandNode; +import com.github.steveice10.mc.protocol.data.game.command.CommandParser; +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerDeclareCommandsPacket; +import com.nukkitx.protocol.bedrock.data.CommandData; +import com.nukkitx.protocol.bedrock.data.CommandEnumData; +import com.nukkitx.protocol.bedrock.data.CommandParamData; +import com.nukkitx.protocol.bedrock.packet.AvailableCommandsPacket; +import lombok.Getter; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +import java.util.*; + +@Translator(packet = ServerDeclareCommandsPacket.class) +public class JavaServerDeclareCommandsTranslator extends PacketTranslator { + @Override + public void translate(ServerDeclareCommandsPacket packet, GeyserSession session) { + List commandData = new ArrayList<>(); + Map commands = new HashMap<>(); + Map> commandArgs = new HashMap<>(); + + // Get the first node, it should be a root node + CommandNode rootNode = packet.getNodes()[packet.getFirstNodeIndex()]; + + // Loop through the root nodes to get all commands + for (int nodeIndex : rootNode.getChildIndices()) { + CommandNode node = packet.getNodes()[nodeIndex]; + + // Make sure we dont have duplicated commands (happens if there is more than 1 root node) + if (commands.containsKey(nodeIndex)) { continue; } + + // Get and update the commandArgs list with the found arguments + if (node.getChildIndices().length >= 1) { + for (int childIndex : node.getChildIndices()) { + commandArgs.putIfAbsent(nodeIndex, new ArrayList<>()); + commandArgs.get(nodeIndex).add(packet.getNodes()[childIndex]); + } + } + + // Insert the command name into the list + commands.put(nodeIndex, node.getName()); + } + + // The command flags, not sure what these do apart from break things + List flags = new ArrayList<>(); + + // Loop through all the found commands + for (int commandID : commands.keySet()) { + String commandName = commands.get(commandID); + + // Create a basic alias + CommandEnumData aliases = new CommandEnumData( commandName + "Aliases", new String[] { commandName.toLowerCase() }, false); + + // Get and parse all params + CommandParamData[][] params = getParams(commandID, packet.getNodes()[commandID], packet.getNodes()); + + // Build the completed command and add it to the final list + CommandData data = new CommandData(commandName, "", flags, (byte) 0, aliases, params); + commandData.add(data); + } + + // Add our commands to the AvailableCommandsPacket for the bedrock client + AvailableCommandsPacket availableCommandsPacket = new AvailableCommandsPacket(); + for (CommandData data : commandData) { + availableCommandsPacket.getCommands().add(data); + } + + GeyserConnector.getInstance().getLogger().debug("Sending command packet of " + commandData.size() + " commands"); + + // Finally, send the commands to the client + session.getUpstream().sendPacket(availableCommandsPacket); + } + + private CommandParamData[][] getParams(int commandID, CommandNode commandNode, CommandNode[] allNodes) { + // Check if the command is an alias and redirect it + if (commandNode.getRedirectIndex() != -1) { + GeyserConnector.getInstance().getLogger().debug("Redirecting command " + commandNode.getName() + " to " + allNodes[commandNode.getRedirectIndex()].getName()); + commandNode = allNodes[commandNode.getRedirectIndex()]; + } + + if (commandNode.getChildIndices().length >= 1) { + // Create the root param node and build all the children + ParamInfo rootParam = new ParamInfo(commandNode, null); + rootParam.buildChildren(allNodes); + + List treeData = rootParam.getTree(); + CommandParamData[][] params = new CommandParamData[treeData.size()][]; + + // Fill the nested params array + int i = 0; + for (CommandParamData[] tree : treeData) { + params[i] = tree; + i++; + } + + return params; + } + + return new CommandParamData[0][0]; + } + + private CommandParamData.Type mapCommandType(CommandParser parser) { + if (parser == null) { return CommandParamData.Type.STRING; } + + switch (parser) { + case FLOAT: + return CommandParamData.Type.FLOAT; + + case INTEGER: + return CommandParamData.Type.INT; + + case ENTITY: + case GAME_PROFILE: + return CommandParamData.Type.TARGET; + + case BLOCK_POS: + return CommandParamData.Type.BLOCK_POSITION; + + case COLUMN_POS: + case VEC3: + return CommandParamData.Type.POSITION; + + case MESSAGE: + return CommandParamData.Type.MESSAGE; + + case NBT: + case NBT_COMPOUND_TAG: + case NBT_TAG: + case NBT_PATH: + return CommandParamData.Type.JSON; + + case RESOURCE_LOCATION: + return CommandParamData.Type.FILE_PATH; + + case INT_RANGE: + return CommandParamData.Type.INT_RANGE; + + case BOOL: + case DOUBLE: + case STRING: + case VEC2: + case BLOCK_STATE: + case BLOCK_PREDICATE: + case ITEM_STACK: + case ITEM_PREDICATE: + case COLOR: + case COMPONENT: + case OBJECTIVE: + case OBJECTIVE_CRITERIA: + case OPERATION: // Possibly OPERATOR + case PARTICLE: + case ROTATION: + case SCOREBOARD_SLOT: + case SCORE_HOLDER: + case SWIZZLE: + case TEAM: + case ITEM_SLOT: + case MOB_EFFECT: + case FUNCTION: + case ENTITY_ANCHOR: + case RANGE: + case FLOAT_RANGE: + case ITEM_ENCHANTMENT: + case ENTITY_SUMMON: + case DIMENSION: + case TIME: + default: + return CommandParamData.Type.STRING; + } + } + + @Getter + private class ParamInfo { + private CommandNode paramNode; + private CommandParamData paramData; + private List children; + + public ParamInfo(CommandNode paramNode, CommandParamData paramData) { + this.paramNode = paramNode; + this.paramData = paramData; + this.children = new ArrayList<>(); + } + + public void buildChildren(CommandNode[] allNodes) { + int enumIndex = -1; + + for (int paramID : paramNode.getChildIndices()) { + CommandNode paramNode = allNodes[paramID]; + + if (paramNode.getParser() == null) { + if (enumIndex == -1) { + enumIndex = children.size(); + + // Create the new enum command + CommandEnumData enumData = new CommandEnumData(paramNode.getName(), new String[] { paramNode.getName() }, false); + children.add(new ParamInfo(paramNode, new CommandParamData(paramNode.getName(), false, enumData, mapCommandType(paramNode.getParser()), null, Collections.emptyList()))); + } else { + // Get the existing enum + ParamInfo enumParamInfo = children.get(enumIndex); + + // Extend the current list of enum values + String[] enumOptions = Arrays.copyOf(enumParamInfo.getParamData().getEnumData().getValues(), enumParamInfo.getParamData().getEnumData().getValues().length + 1); + enumOptions[enumOptions.length - 1] = paramNode.getName(); + + // Re-create the command using the updated values + CommandEnumData enumData = new CommandEnumData(enumParamInfo.getParamData().getEnumData().getName(), enumOptions, false); + children.set(enumIndex, new ParamInfo(enumParamInfo.getParamNode(), new CommandParamData(enumParamInfo.getParamData().getName(), false, enumData, enumParamInfo.getParamData().getType(), null, Collections.emptyList()))); + } + }else{ + // Put the non-enum param into the list + children.add(new ParamInfo(paramNode, new CommandParamData(paramNode.getName(), false, null, mapCommandType(paramNode.getParser()), null, Collections.emptyList()))); + } + } + + // Recursively build all child options + for (ParamInfo child : children) { + child.buildChildren(allNodes); + } + } + + public List getTree() { + List treeParamData = new ArrayList<>(); + + for (ParamInfo child : children) { + // Get the tree from the child + List childTree = child.getTree(); + + // Un-pack the tree append the child node to it and push into the list + for (CommandParamData[] subchild : childTree) { + CommandParamData[] tmpTree = new ArrayList() { + { + add(child.getParamData()); + addAll(Arrays.asList(subchild)); + } + }.toArray(new CommandParamData[0]); + + treeParamData.add(tmpTree); + } + + // If we have no more child parameters just the child + if (childTree.size() == 0) { + treeParamData.add(new CommandParamData[] { child.getParamData() }); + } + } + + return treeParamData; + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java index 0f56bc935..cfeb2371f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java @@ -30,6 +30,7 @@ import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.utils.ChunkUtils; import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerPositionRotationPacket; @@ -86,6 +87,8 @@ public class JavaPlayerPositionRotationTranslator 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/JavaUpdateTimeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java index ccb69856c..365338443 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java @@ -25,6 +25,10 @@ package org.geysermc.connector.network.translators.java.world; +import com.nukkitx.protocol.bedrock.data.GameRuleData; +import com.nukkitx.protocol.bedrock.packet.GameRulesChangedPacket; +import it.unimi.dsi.fastutil.longs.Long2LongMap; +import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -35,11 +39,37 @@ import com.nukkitx.protocol.bedrock.packet.SetTimePacket; @Translator(packet = ServerUpdateTimePacket.class) public class JavaUpdateTimeTranslator extends PacketTranslator { + // If negative, the last time is stored so we know it's not some plugin behavior doing weird things. + // Per-player for multi-world support + private static final Long2LongMap LAST_RECORDED_TIMES = new Long2LongOpenHashMap(); + @Override public void translate(ServerUpdateTimePacket packet, GeyserSession session) { - // https://minecraft.gamepedia.com/Day-night_cycle#24-hour_Minecraft_day - SetTimePacket setTimePacket = new SetTimePacket(); - setTimePacket.setTime((int) Math.abs(packet.getTime()) % 24000); - session.getUpstream().sendPacket(setTimePacket); + + // Bedrock sends a GameRulesChangedPacket if there is no daylight cycle + // Java just sends a negative long if there is no daylight cycle + long lastTime = LAST_RECORDED_TIMES.getOrDefault(session.getPlayerEntity().getEntityId(), 0); + long time = packet.getTime(); + + if (lastTime != time) { + // https://minecraft.gamepedia.com/Day-night_cycle#24-hour_Minecraft_day + SetTimePacket setTimePacket = new SetTimePacket(); + setTimePacket.setTime((int) Math.abs(time) % 24000); + session.getUpstream().sendPacket(setTimePacket); + // TODO: Performance efficient to always do this? + LAST_RECORDED_TIMES.put(session.getPlayerEntity().getEntityId(), time); + } + if (lastTime < 0 && time >= 0) { + setDoDaylightCycleGamerule(session, true); + } else if (lastTime != time && time < 0) { + setDoDaylightCycleGamerule(session, false); + } } + + private void setDoDaylightCycleGamerule(GeyserSession session, boolean doCycle) { + GameRulesChangedPacket gameRulesChangedPacket = new GameRulesChangedPacket(); + gameRulesChangedPacket.getGameRules().add(new GameRuleData<>("dodaylightcycle", doCycle)); + session.getUpstream().sendPacket(gameRulesChangedPacket); + } + } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateViewPositionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateViewPositionTranslator.java index 452e86e4f..63ade1533 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateViewPositionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateViewPositionTranslator.java @@ -28,6 +28,7 @@ package org.geysermc.connector.network.translators.java.world; 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 com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdateViewPositionPacket; import com.nukkitx.math.vector.Vector3i; @@ -38,9 +39,8 @@ public class JavaUpdateViewPositionTranslator 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 3070e743d..0938fa7c0 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java @@ -65,4 +65,23 @@ public class FileUtils { return file; } + + public static void writeFile(File file, char[] data) throws IOException { + if (!file.exists()) { + file.createNewFile(); + } + + FileOutputStream fos = new FileOutputStream(file); + + for (char c : data) { + fos.write(c); + } + + fos.flush(); + fos.close(); + } + + public static void writeFile(String name, char[] data) throws IOException { + writeFile(new File(name), data); + } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java new file mode 100644 index 000000000..e8555eb02 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.utils; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Getter; +import org.geysermc.connector.GeyserConnector; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.*; +import java.util.zip.ZipFile; + +public class LocaleUtils { + + public static final Map> LOCALE_MAPPINGS = new HashMap<>(); + + private static final Map ASSET_MAP = new HashMap<>(); + + private static final String DEFAULT_LOCALE = (GeyserConnector.getInstance().getConfig().getDefaultLocale() != null ? GeyserConnector.getInstance().getConfig().getDefaultLocale() : "en_us"); + + private static String smallestURL = ""; + + static { + // Create the locales folder + File localesFolder = new File("locales/"); + localesFolder.mkdir(); + + // Download the latest asset list and cache it + generateAssetCache(); + downloadAndLoadLocale(DEFAULT_LOCALE); + } + + private static void generateAssetCache() { + try { + VersionManifest versionManifest = Toolbox.JSON_MAPPER.readValue(WebUtils.getBody("https://launchermeta.mojang.com/mc/game/version_manifest.json"), VersionManifest.class); + String latestInfoURL = ""; + for (Version version : versionManifest.getVersions()) { + if (version.getId().equals(versionManifest.getLatestVersion().getRelease())) { + latestInfoURL = version.getUrl(); + break; + } + } + + if (latestInfoURL.isEmpty()) { + throw new Exception("Unable to get latest Minecraft version"); + } + + VersionInfo versionInfo = Toolbox.JSON_MAPPER.readValue(WebUtils.getBody(latestInfoURL), VersionInfo.class); + + int currentSize = Integer.MAX_VALUE; + for (VersionDownload download : versionInfo.getDownloads().values()) { + if (download.getUrl().endsWith(".jar") && download.getSize() < currentSize) { + smallestURL = download.getUrl(); + currentSize = download.getSize(); + } + } + + JsonNode assets = Toolbox.JSON_MAPPER.readTree(WebUtils.getBody(versionInfo.getAssetIndex().getUrl())).get("objects"); + + Iterator> assetIterator = assets.fields(); + while (assetIterator.hasNext()) { + Map.Entry entry = assetIterator.next(); + Asset asset = Toolbox.JSON_MAPPER.treeToValue(entry.getValue(), Asset.class); + ASSET_MAP.put(entry.getKey(), asset); + } + } catch (Exception e) { + GeyserConnector.getInstance().getLogger().info("Failed to load locale asset cache: " + (!e.getMessage().isEmpty() ? e.getMessage() : e.getStackTrace())); + } + } + + public static void downloadAndLoadLocale(String locale) { + locale = locale.toLowerCase(); + if (!ASSET_MAP.containsKey("minecraft/lang/" + locale + ".json") && !locale.equals("en_us")) { + GeyserConnector.getInstance().getLogger().warning("Invalid locale requested to download and load: " + locale); + return; + } + + GeyserConnector.getInstance().getLogger().debug("Downloading and loading locale: " + locale); + + downloadLocale(locale); + loadLocale(locale); + } + + private static void downloadLocale(String locale) { + File localeFile = new File("locales/" + locale + ".json"); + + if (localeFile.exists()) { + GeyserConnector.getInstance().getLogger().debug("Locale already downloaded: " + locale); + return; + } + + // Create the en_us locale + if (locale.equals("en_us")) { + downloadEN_US(localeFile); + + return; + } + + // Get the hash and download the locale + String hash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash(); + WebUtils.downloadFile("http://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, "locales/" + locale + ".json"); + } + + private static void loadLocale(String locale) { + File localeFile = new File("locales/" + locale + ".json"); + + // Load the locale + if (localeFile.exists()) { + // Read the localefile + InputStream localeStream; + try { + localeStream = new FileInputStream(localeFile); + } catch (FileNotFoundException e) { + throw new AssertionError("Unable to load locale: " + locale + " (" + e.getMessage() + ")"); + } + + // Parse the file as json + JsonNode localeObj; + try { + localeObj = Toolbox.JSON_MAPPER.readTree(localeStream); + } catch (Exception e) { + throw new AssertionError("Unable to load Java lang map for " + locale, e); + } + + // Parse all the locale fields + Iterator> localeIterator = localeObj.fields(); + Map langMap = new HashMap<>(); + while (localeIterator.hasNext()) { + Map.Entry entry = localeIterator.next(); + langMap.put(entry.getKey(), entry.getValue().asText()); + } + + // Insert the locale into the mappings + LOCALE_MAPPINGS.put(locale.toLowerCase(), langMap); + } else { + GeyserConnector.getInstance().getLogger().warning("Missing locale file: " + locale); + } + } + + private static void downloadEN_US(File localeFile) { + try { + // Let the user know we are downloading the JAR + GeyserConnector.getInstance().getLogger().info("Downloading Minecraft JAR to extract en_us locale, please wait... (this may take some time depending on the speed of your internet connection)"); + GeyserConnector.getInstance().getLogger().debug("Download URL: " + smallestURL); + + // Download the smallest JAR (client or server) + WebUtils.downloadFile(smallestURL, "tmp_locale.jar"); + + // Load in the JAR as a zip and extract the file + ZipFile localeJar = new ZipFile("tmp_locale.jar"); + InputStream inputStream = localeJar.getInputStream(localeJar.getEntry("assets/minecraft/lang/en_us.json")); + FileOutputStream outputStream = new FileOutputStream(localeFile); + + // Write the file to the locale dir + int data = inputStream.read(); + while(data != -1){ + outputStream.write(data); + data = inputStream.read(); + } + + // Flush all changes to disk and cleanup + outputStream.flush(); + outputStream.close(); + + inputStream.close(); + localeJar.close(); + + // Delete the nolonger needed client/server jar + Files.delete(Paths.get("tmp_locale.jar")); + } catch (Exception e) { + throw new AssertionError("Unable to download and extract en_us locale!", e); + } + } + + public static String getLocaleString(String messageText, String locale) { + Map localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(locale.toLowerCase()); + if (localeStrings == null) + localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(DEFAULT_LOCALE); + + return localeStrings.getOrDefault(messageText, messageText); + } + + public static void init() { + // no-op + } +} + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +class VersionManifest { + @JsonProperty("latest") + private LatestVersion latestVersion; + + @JsonProperty("versions") + private List versions; +} + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +class LatestVersion { + @JsonProperty("release") + private String release; + + @JsonProperty("snapshot") + private String snapshot; +} + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +class Version { + @JsonProperty("id") + private String id; + + @JsonProperty("type") + private String type; + + @JsonProperty("url") + private String url; + + @JsonProperty("time") + private String time; + + @JsonProperty("releaseTime") + private String releaseTime; +} + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +class VersionInfo { + @JsonProperty("id") + private String id; + + @JsonProperty("type") + private String type; + + @JsonProperty("time") + private String time; + + @JsonProperty("releaseTime") + private String releaseTime; + + @JsonProperty("assetIndex") + private AssetIndex assetIndex; + + @JsonProperty("downloads") + private Map downloads; +} + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +class VersionDownload { + @JsonProperty("sha1") + private String sha1; + + @JsonProperty("size") + private int size; + + @JsonProperty("url") + private String url; +} + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +class AssetIndex { + @JsonProperty("id") + private String id; + + @JsonProperty("sha1") + private String sha1; + + @JsonProperty("size") + private int size; + + @JsonProperty("totalSize") + private int totalSize; + + @JsonProperty("url") + private String url; +} + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +class Asset { + @JsonProperty("hash") + private String hash; + + @JsonProperty("size") + private int size; +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java index 7f6c8eda1..300294a2f 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -40,9 +40,12 @@ import net.minidev.json.JSONObject; import org.geysermc.common.window.CustomFormBuilder; import org.geysermc.common.window.CustomFormWindow; import org.geysermc.common.window.FormWindow; +import org.geysermc.common.window.SimpleFormWindow; +import org.geysermc.common.window.button.FormButton; import org.geysermc.common.window.component.InputComponent; import org.geysermc.common.window.component.LabelComponent; import org.geysermc.common.window.response.CustomFormResponse; +import org.geysermc.common.window.response.SimpleFormResponse; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.auth.AuthData; @@ -152,43 +155,60 @@ public class LoginEncryptionUtils { session.getUpstream().sendPacketImmediately(packet); } - private static int AUTH_FORM_ID = 1337; + private static int AUTH_FORM_ID = 1336; + private static int AUTH_DETAILS_FORM_ID = 1337; public static void showLoginWindow(GeyserSession session) { - CustomFormWindow window = new CustomFormBuilder("Login") - .addComponent(new LabelComponent("Minecraft: Java Edition account authentication.")) + SimpleFormWindow window = new SimpleFormWindow("Login", "You need a Java Edition account to play on this server."); + window.getButtons().add(new FormButton("Login with Minecraft")); + window.getButtons().add(new FormButton("Disconnect")); + + session.sendForm(window, AUTH_FORM_ID); + } + + public static void showLoginDetailsWindow(GeyserSession session) { + CustomFormWindow window = new CustomFormBuilder("Login Details") .addComponent(new LabelComponent("Enter the credentials for your Minecraft: Java Edition account below.")) .addComponent(new InputComponent("Email/Username", "account@geysermc.org", "")) .addComponent(new InputComponent("Password", "123456", "")) .build(); - session.sendForm(window, AUTH_FORM_ID); + session.sendForm(window, AUTH_DETAILS_FORM_ID); } - public static boolean authenticateFromForm(GeyserSession session, GeyserConnector connector, String formData) { + public static boolean authenticateFromForm(GeyserSession session, GeyserConnector connector, int formId, String formData) { WindowCache windowCache = session.getWindowCache(); - if (!windowCache.getWindows().containsKey(AUTH_FORM_ID)) + if (!windowCache.getWindows().containsKey(formId)) return false; - FormWindow window = windowCache.getWindows().remove(AUTH_FORM_ID); - window.setResponse(formData.trim()); + if(formId == AUTH_FORM_ID || formId == AUTH_DETAILS_FORM_ID) { + FormWindow window = windowCache.getWindows().remove(formId); + window.setResponse(formData.trim()); - if (!session.isLoggedIn()) { - if (window instanceof CustomFormWindow) { - CustomFormWindow customFormWindow = (CustomFormWindow) window; - if (!customFormWindow.getTitle().equals("Login")) - return false; + if (!session.isLoggedIn()) { + if (formId == AUTH_DETAILS_FORM_ID && window instanceof CustomFormWindow) { + CustomFormWindow customFormWindow = (CustomFormWindow) window; - CustomFormResponse response = (CustomFormResponse) customFormWindow.getResponse(); - if (response != null) { - String email = response.getInputResponses().get(2); - String password = response.getInputResponses().get(3); + CustomFormResponse response = (CustomFormResponse) customFormWindow.getResponse(); + if (response != null) { + String email = response.getInputResponses().get(1); + String password = response.getInputResponses().get(2); - session.authenticate(email, password); + session.authenticate(email, password); + } + + // Clear windows so authentication data isn't accidentally cached + windowCache.getWindows().clear(); + } else if (formId == AUTH_FORM_ID && window instanceof SimpleFormWindow) { + SimpleFormResponse response = (SimpleFormResponse) window.getResponse(); + if(response != null) { + if(response.getClickedButtonId() == 0) { + showLoginDetailsWindow(session); + } else if(response.getClickedButtonId() == 1) { + session.disconnect("Login is required"); + } + } } - - // Clear windows so authentication data isn't accidentally cached - windowCache.getWindows().clear(); } } return true; diff --git a/connector/src/main/java/org/geysermc/connector/utils/MapColor.java b/connector/src/main/java/org/geysermc/connector/utils/MapColor.java new file mode 100644 index 000000000..2db144648 --- /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().orElse(COLOR_0); + } + + 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..5be8eab1d 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,31 @@ 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 org.geysermc.connector.network.session.GeyserSession; -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 +59,96 @@ 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(), locale); + 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(); + builder.append(getFormat(message.getStyle().getFormats())); + builder.append(getColorOrParent(message.getStyle())); + builder.append(messageText); + for (Message msg : message.getExtra()) { builder.append(getFormat(msg.getStyle().getFormats())); - builder.append(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 && style.getParent() != null) { + return getColorOrParent(style.getParent()); + } + + return getColor(chatColor); } private static String getColor(ChatColor color) { @@ -206,7 +255,6 @@ public class MessageUtils { } catch (Exception ex) { return false; } - return true; } @@ -231,7 +279,30 @@ 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 ""; + } + + public static boolean isTooLong(String message, GeyserSession session) { + if (message.length() > 256) { + // TODO: Add Geyser localization and translate this based on language + session.sendMessage("Your message is bigger than 256 characters (" + message.length() + ") so it has not been sent."); + return true; + } + + return false; + } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java b/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java index db19ed2e1..28e185210 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java +++ b/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java @@ -42,7 +42,7 @@ import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.sound.SoundMap; -import java.io.InputStream; +import java.io.*; import java.util.*; public class Toolbox { @@ -54,6 +54,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"); @@ -105,9 +107,11 @@ public class Toolbox { entry.getValue().get("bedrock_id").intValue(), entry.getValue().get("bedrock_data").intValue())); itemIndex++; } - - /* Load sound mappings */ + + // Load sound mappings SoundMap.get(); + // 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