mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-12-23 15:00:37 +01:00
Merge remote-tracking branch 'origin/master' into floodgate-2.0
# Conflicts: # connector/pom.xml # connector/src/main/java/org/geysermc/connector/GeyserConnector.java # connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java # connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java # connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java # connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java
This commit is contained in:
commit
c16c66b860
66 changed files with 1231 additions and 478 deletions
57
.github/ISSUE_TEMPLATE/bug_report.md
vendored
57
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -1,57 +0,0 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--- DELETING THIS TEMPLATE WILL GET YOUR ISSUE CLOSED! --->
|
||||
|
||||
<!--- Please follow this format COMPLETELY and make sure the bug you are reporting has not been reported yet. Reports should contain as much information or context as possible to help us find the problem. Simply creating an issue on a vague topic will not help us at all, and if you are unsure if something should belong here, please contact us on [Discord](http://discord.geysermc.org).-->
|
||||
|
||||
<!--- Issues pertaining to connection problems, or anything of that covered on the [Common Issues](https://github.com/GeyserMC/Geyser/wiki/Common-Issues) do not belong here and only clutter this issue tracker. -->
|
||||
|
||||
**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 / Videos**
|
||||
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Server Version and Plugins**
|
||||
|
||||
If you just run Geyser-Spigot, you can leave this area blank as the next section covers this information.
|
||||
|
||||
If you're running a multi-server instance, or using Geyser Standalone:
|
||||
|
||||
- Give us the exact output from `/version` on all servers involved. Saying "latest" does not help us at all.
|
||||
- Please list all plugins on all servers involved.
|
||||
|
||||
If this bug occurs on a server you do not control, please fill this in to the best of your knowledge.
|
||||
|
||||
**Geyser Dump**
|
||||
|
||||
If Geyser starts correctly, please also include the link to a dump by using `/geyser dump`. If you use the Standalone GUI, the option can be found under `Commands` => `Dump`. This provides us information about your server that we can use to debug your issue.
|
||||
|
||||
**Minecraft: Bedrock Edition Version**
|
||||
|
||||
The version of your Minecraft: Bedrock Edition client you tested with, along with your device type (e.g. Windows 10, Switch...).
|
||||
|
||||
**Additional Context**
|
||||
|
||||
Add any other context about the problem here.
|
64
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
64
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
|
@ -0,0 +1,64 @@
|
|||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report for Geyser! Fill out the following form to your best ability to help us fix the problem.
|
||||
Only use this if you're absolutely sure that you found a bug and can reproduce it. For anything else, use: [our Discord server](https://discord.gg/geysermc), [the FAQ](https://github.com/GeyserMC/Geyser/wiki/FAQ) or the [Common Issues](https://github.com/GeyserMC/Geyser/wiki/Common-Issues).
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of what the bug is.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: To Reproduce
|
||||
description: Steps to reproduce this behaviour
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See error
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected behaviour
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Screenshots / Videos
|
||||
description: If applicable, add screenshots to help explain your problem.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Server Version and Plugins
|
||||
description: |
|
||||
If you just run Geyser-Spigot, you can leave this area blank as the next section covers this information.
|
||||
If you're running a multi-server instance or using Geyser Standalone:
|
||||
* Give us the exact output from `/version` on all servers involved. Saying "latest" does not help us at all.
|
||||
* Please list all plugins on all servers involved.
|
||||
If this bug occurs on a server you do not control, please full this in to the best of your knowledge.
|
||||
- type: input
|
||||
attributes:
|
||||
label: Geyser Dump
|
||||
description: If Geyser starts correctly, please also include the link to a dump by using `/geyser dump`. If you're using the Standalone GUI, the option can be found under `Commands` => `Dump`. This provides us information about your server that we can use to debug your issue.
|
||||
- type: input
|
||||
attributes:
|
||||
label: Geyser Version
|
||||
description: What version of Geyser are you running?
|
||||
placeholder: "For example: 1.2.0-SNAPSHOT (git-master-2d9baf1)"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: "Minecraft: Bedrock Edition Version"
|
||||
description: "What version of Minecraft: Bedrock Edition are you using? Leave empty if the bug happens before you can connect with Minecraft: Bedrock Edition."
|
||||
placeholder: "For example: 1.16.201"
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Add any other context about the problem here
|
10
.github/ISSUE_TEMPLATE/config.yml
vendored
10
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -1,5 +1,11 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: GeyserMC Discord
|
||||
url: http://discord.geysermc.org/
|
||||
- name: Common Issues
|
||||
url: https://github.com/GeyserMC/Geyser/wiki/Common-Issues
|
||||
about: Check the common issues to see if you are not alone with that issue and see how you can fix them.
|
||||
- name: Frequently Asked Questions
|
||||
url: https://github.com/GeyserMC/Geyser/wiki/FAQ
|
||||
about: Look at the FAQ page for answers for frequently asked questions.
|
||||
- name: Get help on the GeyserMC Discord server
|
||||
url: https://discord.gg/geysermc
|
||||
about: If your issue seems like it could possibly be an easy fix due to configuration, please hop on our Discord.
|
||||
|
|
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -1,14 +0,0 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**What feature do you want?**
|
||||
Add a description
|
||||
|
||||
**Alternatives?**
|
||||
List any alternatives you might have tried
|
21
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
21
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
labels: "Feature Request"
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this feature request for Geyser! Fill out the following form to your best ability to help us understand your feature request and greately improve the change of it getting added.
|
||||
For anything else than a feature request, use: [our Discord server](https://discord.gg/geysermc), [the FAQ](https://github.com/GeyserMC/Geyser/wiki/FAQ) or [the Common Issues](https://github.com/GeyserMC/Geyser/wiki/Common-Issues).
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What feature do you want to see added?
|
||||
description: A clear and concise description of your feature request.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Are there any alternatives?
|
||||
description: List any alternatives you might have tried
|
||||
validations:
|
||||
required: true
|
|
@ -22,7 +22,7 @@
|
|||
</repository>
|
||||
<repository>
|
||||
<id>sponge-repo</id>
|
||||
<url>https://repo.spongepowered.org/maven</url>
|
||||
<url>https://repo.spongepowered.org/repository/maven-public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>bungeecord-repo</id>
|
||||
|
|
|
@ -30,9 +30,9 @@
|
|||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.geysermc.adapters</groupId>
|
||||
<groupId>org.geysermc.geyser.adapters</groupId>
|
||||
<artifactId>spigot-all</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<version>1.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
|
|
|
@ -28,7 +28,6 @@ package org.geysermc.platform.spigot;
|
|||
import com.github.steveice10.mc.protocol.MinecraftConstants;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.geysermc.adapters.spigot.SpigotAdapters;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.bootstrap.GeyserBootstrap;
|
||||
|
@ -40,6 +39,7 @@ import org.geysermc.connector.ping.GeyserLegacyPingPassthrough;
|
|||
import org.geysermc.connector.ping.IGeyserPingPassthrough;
|
||||
import org.geysermc.connector.utils.FileUtils;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
import org.geysermc.geyser.adapters.spigot.SpigotAdapters;
|
||||
import org.geysermc.platform.spigot.command.GeyserSpigotCommandExecutor;
|
||||
import org.geysermc.platform.spigot.command.GeyserSpigotCommandManager;
|
||||
import org.geysermc.platform.spigot.command.SpigotCommandSender;
|
||||
|
@ -69,6 +69,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||
|
||||
private GeyserConnector connector;
|
||||
|
||||
/**
|
||||
* The Minecraft server version, formatted as <code>1.#.#</code>
|
||||
*/
|
||||
private String minecraftVersion;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
// This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed
|
||||
|
@ -118,6 +123,9 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||
|
||||
geyserConfig.loadFloodgate(this);
|
||||
|
||||
// Turn "(MC: 1.16.4)" into 1.16.4.
|
||||
this.minecraftVersion = Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0];
|
||||
|
||||
this.connector = GeyserConnector.start(PlatformType.SPIGOT, this);
|
||||
|
||||
if (geyserConfig.isLegacyPingPassthrough()) {
|
||||
|
@ -239,6 +247,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||
return new GeyserSpigotDumpInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMinecraftServerVersion() {
|
||||
return this.minecraftVersion;
|
||||
}
|
||||
|
||||
public boolean isCompatible(String version, String whichVersion) {
|
||||
int[] currentVersion = parseVersion(version);
|
||||
int[] otherVersion = parseVersion(whichVersion);
|
||||
|
@ -277,10 +290,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||
* @return the server version before ViaVersion finishes initializing
|
||||
*/
|
||||
public ProtocolVersion getServerProtocolVersion() {
|
||||
String bukkitVersion = Bukkit.getServer().getVersion();
|
||||
// Turn "(MC: 1.16.4)" into 1.16.4.
|
||||
String version = bukkitVersion.split("\\(MC: ")[1].split("\\)")[0];
|
||||
return ProtocolVersion.getClosest(version);
|
||||
return ProtocolVersion.getClosest(this.minecraftVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -27,10 +27,10 @@ package org.geysermc.platform.spigot.world.manager;
|
|||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.geysermc.adapters.spigot.SpigotAdapters;
|
||||
import org.geysermc.adapters.spigot.SpigotWorldAdapter;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
||||
import org.geysermc.geyser.adapters.spigot.SpigotAdapters;
|
||||
import org.geysermc.geyser.adapters.spigot.SpigotWorldAdapter;
|
||||
import us.myles.ViaVersion.api.Via;
|
||||
import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage;
|
||||
|
||||
|
|
|
@ -75,6 +75,10 @@ public class GeyserSpigot1_12WorldManager extends GeyserSpigotWorldManager {
|
|||
if (player == null) {
|
||||
return BlockTranslator.JAVA_AIR_ID;
|
||||
}
|
||||
if (!player.getWorld().isChunkLoaded(x >> 4, z >> 4)) {
|
||||
// Prevent nasty async errors if a player is loading in
|
||||
return BlockTranslator.JAVA_AIR_ID;
|
||||
}
|
||||
// Get block entity storage
|
||||
BlockStorage storage = Via.getManager().getConnection(player.getUniqueId()).get(BlockStorage.class);
|
||||
Block block = player.getWorld().getBlockAt(x, y, z);
|
||||
|
|
|
@ -27,10 +27,10 @@ package org.geysermc.platform.spigot.world.manager;
|
|||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.geysermc.adapters.spigot.SpigotAdapters;
|
||||
import org.geysermc.adapters.spigot.SpigotWorldAdapter;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
||||
import org.geysermc.geyser.adapters.spigot.SpigotAdapters;
|
||||
import org.geysermc.geyser.adapters.spigot.SpigotWorldAdapter;
|
||||
|
||||
public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager {
|
||||
protected final SpigotWorldAdapter adapter;
|
||||
|
|
|
@ -101,7 +101,7 @@ public class GeyserSpongePlugin implements GeyserBootstrap {
|
|||
}
|
||||
}
|
||||
|
||||
if (geyserConfig.getBedrock().isCloneRemotePort()){
|
||||
if (geyserConfig.getBedrock().isCloneRemotePort()) {
|
||||
geyserConfig.getBedrock().setPort(geyserConfig.getRemote().getPort());
|
||||
}
|
||||
|
||||
|
@ -163,4 +163,9 @@ public class GeyserSpongePlugin implements GeyserBootstrap {
|
|||
public BootstrapDumpInfo getDumpInfo() {
|
||||
return new GeyserSpongeDumpInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMinecraftServerVersion() {
|
||||
return Sponge.getPlatform().getMinecraftVersion().getName();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import com.fasterxml.jackson.databind.introspect.AnnotatedField;
|
|||
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
|
||||
import lombok.Getter;
|
||||
import net.minecrell.terminalconsole.TerminalConsoleAppender;
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.core.Appender;
|
||||
import org.apache.logging.log4j.core.Logger;
|
||||
|
@ -167,11 +168,6 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
|||
this.onEnable();
|
||||
}
|
||||
|
||||
public void onEnable(boolean useGui) {
|
||||
this.useGui = useGui;
|
||||
this.onEnable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
Logger logger = (Logger) LogManager.getRootLogger();
|
||||
|
@ -213,6 +209,9 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
|||
}
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
|
||||
// Allow libraries like Protocol to have their debug information passthrough
|
||||
logger.get().setLevel(geyserConfig.isDebugMode() ? Level.DEBUG : Level.INFO);
|
||||
|
||||
connector = GeyserConnector.start(PlatformType.STANDALONE, this);
|
||||
geyserCommandManager = new GeyserCommandManager(connector);
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ public class LoopbackUtil {
|
|||
|
||||
if (!result.contains("minecraftuwp")) {
|
||||
Files.write(Paths.get(System.getenv("temp") + "/loopback_minecraft.bat"), loopbackCommand.getBytes(), new OpenOption[0]);
|
||||
process = Runtime.getRuntime().exec(startScript);
|
||||
Runtime.getRuntime().exec(startScript);
|
||||
|
||||
geyserLogger.info(ChatColor.AQUA + LanguageUtils.getLocaleStringLog("geyser.bootstrap.loopback.added"));
|
||||
}
|
||||
|
|
|
@ -10,16 +10,21 @@
|
|||
</parent>
|
||||
<artifactId>connector</artifactId>
|
||||
|
||||
<properties>
|
||||
<netty.version>4.1.59.Final</netty.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>1.2.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-yaml</artifactId>
|
||||
<version>2.9.8</version>
|
||||
<version>2.10.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -27,23 +32,16 @@
|
|||
<artifactId>Java-Websocket</artifactId>
|
||||
<version>1.5.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<version>2.9.8</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.CloudburstMC.Protocol</groupId>
|
||||
<artifactId>bedrock-v422</artifactId>
|
||||
<version>d41b84e86c</version>
|
||||
<version>294e7e5</version>
|
||||
<scope>compile</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>net.sf.trove4j</groupId>
|
||||
<artifactId>trove</artifactId>
|
||||
</exclusion>
|
||||
<!-- Stay on the older version of Network while it's rewritten -->
|
||||
<exclusion>
|
||||
<groupId>com.nukkitx.network</groupId>
|
||||
<artifactId>raknet</artifactId>
|
||||
|
@ -51,10 +49,16 @@
|
|||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.nukkitx.network</groupId>
|
||||
<groupId>com.github.CloudburstMC.Network</groupId>
|
||||
<artifactId>raknet</artifactId>
|
||||
<version>1.6.20</version>
|
||||
<version>a94d2dd</version>
|
||||
<scope>compile</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.nukkitx.fastutil</groupId>
|
||||
|
@ -157,15 +161,51 @@
|
|||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-resolver-dns</artifactId>
|
||||
<version>4.1.43.Final</version>
|
||||
<version>${netty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-resolver-dns-native-macos</artifactId>
|
||||
<version>${netty.version}</version>
|
||||
<scope>compile</scope>
|
||||
<classifier>osx-x86_64</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-codec-haproxy</artifactId>
|
||||
<version>4.1.56.Final</version>
|
||||
<version>${netty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- Network dependencies we are updating ourselves -->
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-handler</artifactId>
|
||||
<version>${netty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-transport-native-epoll</artifactId>
|
||||
<version>${netty.version}</version>
|
||||
<scope>compile</scope>
|
||||
<classifier>linux-x86_64</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-transport-native-epoll</artifactId>
|
||||
<version>${netty.version}</version>
|
||||
<scope>compile</scope>
|
||||
<classifier>linux-aarch_64</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-transport-native-kqueue</artifactId>
|
||||
<version>${netty.version}</version>
|
||||
<scope>compile</scope>
|
||||
<classifier>osx-x86_64</classifier>
|
||||
</dependency>
|
||||
<!-- End -->
|
||||
<dependency>
|
||||
<groupId>org.reflections</groupId>
|
||||
<artifactId>reflections</artifactId>
|
||||
|
@ -179,25 +219,25 @@
|
|||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>4.3.0</version>
|
||||
<version>4.5.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>4.3.0</version>
|
||||
<version>4.5.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>4.3.0</version>
|
||||
<version>4.5.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson-legacy-impl</artifactId>
|
||||
<version>4.3.0</version>
|
||||
<version>4.5.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -29,6 +29,7 @@ import com.fasterxml.jackson.core.JsonParser;
|
|||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.nukkitx.network.raknet.RakNetConstants;
|
||||
import com.nukkitx.network.util.EventLoops;
|
||||
import com.nukkitx.protocol.bedrock.BedrockServer;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
@ -57,10 +58,7 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
|||
import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator;
|
||||
import org.geysermc.connector.network.translators.world.block.entity.SkullBlockEntityTranslator;
|
||||
import org.geysermc.connector.skin.FloodgateSkinUploader;
|
||||
import org.geysermc.connector.utils.DimensionUtils;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
import org.geysermc.connector.utils.LocaleUtils;
|
||||
import org.geysermc.connector.utils.ResourcePack;
|
||||
import org.geysermc.connector.utils.*;
|
||||
import org.geysermc.floodgate.crypto.AesCipher;
|
||||
import org.geysermc.floodgate.crypto.AesKeyProducer;
|
||||
import org.geysermc.floodgate.crypto.Base64Topping;
|
||||
|
@ -86,7 +84,8 @@ public class GeyserConnector {
|
|||
.enable(JsonParser.Feature.IGNORE_UNDEFINED)
|
||||
.enable(JsonParser.Feature.ALLOW_COMMENTS)
|
||||
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
|
||||
.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
|
||||
.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES)
|
||||
.enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES);
|
||||
|
||||
public static final String NAME = "Geyser";
|
||||
public static final String GIT_VERSION = "DEV"; // A fallback for running in IDEs
|
||||
|
@ -211,6 +210,7 @@ public class GeyserConnector {
|
|||
}
|
||||
}
|
||||
|
||||
CooldownUtils.setShowCooldown(config.isShowCooldown());
|
||||
DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether
|
||||
SkullBlockEntityTranslator.ALLOW_CUSTOM_SKULLS = config.isAllowCustomSkulls();
|
||||
|
||||
|
@ -218,7 +218,13 @@ public class GeyserConnector {
|
|||
RakNetConstants.MAXIMUM_MTU_SIZE = (short) config.getMtu();
|
||||
logger.debug("Setting MTU to " + config.getMtu());
|
||||
|
||||
bedrockServer = new BedrockServer(new InetSocketAddress(config.getBedrock().getAddress(), config.getBedrock().getPort()));
|
||||
boolean enableProxyProtocol = config.getBedrock().isEnableProxyProtocol();
|
||||
bedrockServer = new BedrockServer(
|
||||
new InetSocketAddress(config.getBedrock().getAddress(), config.getBedrock().getPort()),
|
||||
1,
|
||||
EventLoops.commonGroup(),
|
||||
enableProxyProtocol
|
||||
);
|
||||
bedrockServer.setHandler(new ConnectorServerEventHandler(this));
|
||||
bedrockServer.bind().whenComplete((avoid, throwable) -> {
|
||||
if (throwable == null) {
|
||||
|
@ -265,6 +271,20 @@ public class GeyserConnector {
|
|||
}
|
||||
return valueMap;
|
||||
}));
|
||||
|
||||
String minecraftVersion = bootstrap.getMinecraftServerVersion();
|
||||
if (minecraftVersion != null) {
|
||||
Map<String, Map<String, Integer>> versionMap = new HashMap<>();
|
||||
Map<String, Integer> platformMap = new HashMap<>();
|
||||
platformMap.put(platformType.getPlatformName(), 1);
|
||||
versionMap.put(minecraftVersion, platformMap);
|
||||
|
||||
metrics.addCustomChart(new Metrics.DrilldownPie("minecraftServerVersion", () -> {
|
||||
// By the end, we should return, for example:
|
||||
// 1.16.5 => (Spigot, 1)
|
||||
return versionMap;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
boolean isGui = false;
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.geysermc.connector.command.CommandManager;
|
|||
import org.geysermc.connector.network.translators.world.GeyserWorldManager;
|
||||
import org.geysermc.connector.network.translators.world.WorldManager;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public interface GeyserBootstrap {
|
||||
|
@ -99,4 +100,18 @@ public interface GeyserBootstrap {
|
|||
* @return The info about the bootstrap
|
||||
*/
|
||||
BootstrapDumpInfo getDumpInfo();
|
||||
|
||||
/**
|
||||
* Returns the Minecraft version currently being used on the server. This should be only be implemented on platforms
|
||||
* that have direct server access - platforms such as proxies always have to be on their latest version to support
|
||||
* the newest Minecraft version, but older servers can use ViaVersion to enable newer versions to join.
|
||||
* <br>
|
||||
* If used, this should not be null before {@link org.geysermc.connector.GeyserConnector} initialization.
|
||||
*
|
||||
* @return the Minecraft version being used on the server, or <code>null</code> if not applicable
|
||||
*/
|
||||
@Nullable
|
||||
default String getMinecraftServerVersion() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ public class IGeyserMain {
|
|||
* @return The formatted message
|
||||
*/
|
||||
private String createMessage() {
|
||||
String message = "";
|
||||
StringBuilder message = new StringBuilder();
|
||||
|
||||
InputStream helpStream = IGeyserMain.class.getClassLoader().getResourceAsStream("languages/run-help/" + Locale.getDefault().toString() + ".txt");
|
||||
|
||||
|
@ -68,10 +68,10 @@ public class IGeyserMain {
|
|||
line = line.replace("${plugin_type}", this.getPluginType());
|
||||
line = line.replace("${plugin_folder}", this.getPluginFolder());
|
||||
|
||||
message += line + "\n";
|
||||
message.append(line).append("\n");
|
||||
}
|
||||
|
||||
return message;
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -27,9 +27,11 @@ package org.geysermc.connector.configuration;
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import org.geysermc.connector.GeyserLogger;
|
||||
import org.geysermc.connector.network.CIDRMatcher;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface GeyserConfiguration {
|
||||
|
@ -59,6 +61,8 @@ public interface GeyserConfiguration {
|
|||
|
||||
int getPingPassthroughInterval();
|
||||
|
||||
boolean isForwardPlayerPing();
|
||||
|
||||
int getMaxPlayers();
|
||||
|
||||
boolean isDebugMode();
|
||||
|
@ -104,6 +108,15 @@ public interface GeyserConfiguration {
|
|||
String getMotd2();
|
||||
|
||||
String getServerName();
|
||||
|
||||
boolean isEnableProxyProtocol();
|
||||
|
||||
List<String> getProxyProtocolWhitelistedIPs();
|
||||
|
||||
/**
|
||||
* @return Unmodifiable list of {@link CIDRMatcher}s from {@link #getProxyProtocolWhitelistedIPs()}
|
||||
*/
|
||||
List<CIDRMatcher> getWhitelistedIPsMatchers();
|
||||
}
|
||||
|
||||
interface IRemoteConfiguration {
|
||||
|
|
|
@ -25,16 +25,21 @@
|
|||
|
||||
package org.geysermc.connector.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.common.serializer.AsteriskSerializer;
|
||||
import org.geysermc.connector.network.CIDRMatcher;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
|
@ -74,6 +79,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
|||
@JsonProperty("ping-passthrough-interval")
|
||||
private int pingPassthroughInterval = 3;
|
||||
|
||||
@JsonProperty("forward-player-ping")
|
||||
private boolean forwardPlayerPing = false;
|
||||
|
||||
@JsonProperty("max-players")
|
||||
private int maxPlayers = 100;
|
||||
|
||||
|
@ -119,6 +127,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
|||
private MetricsInfo metrics = new MetricsInfo();
|
||||
|
||||
@Getter
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class BedrockConfiguration implements IBedrockConfiguration {
|
||||
@AsteriskSerializer.Asterisk(sensitive = true)
|
||||
private String address = "0.0.0.0";
|
||||
|
@ -134,9 +143,33 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
|||
|
||||
@JsonProperty("server-name")
|
||||
private String serverName = GeyserConnector.NAME;
|
||||
|
||||
@JsonProperty("enable-proxy-protocol")
|
||||
private boolean enableProxyProtocol = false;
|
||||
|
||||
@JsonProperty("proxy-protocol-whitelisted-ips")
|
||||
private List<String> proxyProtocolWhitelistedIPs = Collections.emptyList();
|
||||
|
||||
@JsonIgnore
|
||||
private List<CIDRMatcher> whitelistedIPsMatchers = null;
|
||||
|
||||
@Override
|
||||
public List<CIDRMatcher> getWhitelistedIPsMatchers() {
|
||||
// Effective Java, Third Edition; Item 83: Use lazy initialization judiciously
|
||||
List<CIDRMatcher> matchers = this.whitelistedIPsMatchers;
|
||||
if (matchers == null) {
|
||||
synchronized (this) {
|
||||
this.whitelistedIPsMatchers = matchers = proxyProtocolWhitelistedIPs.stream()
|
||||
.map(CIDRMatcher::new)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableList(matchers);
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class RemoteConfiguration implements IRemoteConfiguration {
|
||||
@Setter
|
||||
@AsteriskSerializer.Asterisk(sensitive = true)
|
||||
|
@ -170,6 +203,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
|||
}
|
||||
|
||||
@Getter
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class MetricsInfo implements IMetricsInfo {
|
||||
private boolean enabled = true;
|
||||
|
||||
|
|
|
@ -35,6 +35,8 @@ public class AbstractArrowEntity extends Entity {
|
|||
|
||||
public AbstractArrowEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
|
||||
super(entityId, geyserId, entityType, position, motion, rotation);
|
||||
|
||||
setMotion(motion);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -47,4 +49,20 @@ public class AbstractArrowEntity extends Entity {
|
|||
|
||||
super.updateBedrockMetadata(entityMetadata, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRotation(Vector3f rotation) {
|
||||
// Ignore the rotation sent by the Java server since the
|
||||
// Java client calculates the rotation from the motion
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMotion(Vector3f motion) {
|
||||
super.setMotion(motion);
|
||||
|
||||
double horizontalSpeed = Math.sqrt(motion.getX() * motion.getX() + motion.getZ() * motion.getZ());
|
||||
float yaw = (float) Math.toDegrees(Math.atan2(motion.getX(), motion.getZ()));
|
||||
float pitch = (float) Math.toDegrees(Math.atan2(motion.getY(), horizontalSpeed));
|
||||
rotation = Vector3f.from(yaw, pitch, yaw);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ public class BoatEntity extends Entity {
|
|||
*/
|
||||
private static final String BUOYANCY_DATA = "{\"apply_gravity\":true,\"base_buoyancy\":1.0,\"big_wave_probability\":0.02999999932944775," +
|
||||
"\"big_wave_speed\":10.0,\"drag_down_on_buoyancy_removed\":0.0,\"liquid_blocks\":[\"minecraft:water\"," +
|
||||
"\"minecraft:flowing_water\"],\"simulate_waves\":false}}";
|
||||
"\"minecraft:flowing_water\"],\"simulate_waves\":false}";
|
||||
|
||||
private boolean isPaddlingLeft;
|
||||
private float paddleTimeLeft;
|
||||
|
|
|
@ -26,43 +26,167 @@
|
|||
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.object.ProjectileData;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import com.nukkitx.protocol.bedrock.packet.PlaySoundPacket;
|
||||
import org.geysermc.connector.entity.player.PlayerEntity;
|
||||
import org.geysermc.connector.entity.type.EntityType;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.collision.BoundingBox;
|
||||
import org.geysermc.connector.network.translators.collision.CollisionManager;
|
||||
import org.geysermc.connector.network.translators.collision.CollisionTranslator;
|
||||
import org.geysermc.connector.network.translators.collision.translators.BlockCollision;
|
||||
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
|
||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
||||
|
||||
public class FishingHookEntity extends Entity {
|
||||
public FishingHookEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, ProjectileData data) {
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class FishingHookEntity extends ThrowableEntity {
|
||||
|
||||
private boolean hooked = false;
|
||||
|
||||
private final BoundingBox boundingBox;
|
||||
|
||||
private boolean inWater = false;
|
||||
|
||||
public FishingHookEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, PlayerEntity owner) {
|
||||
super(entityId, geyserId, entityType, position, motion, rotation);
|
||||
|
||||
for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) {
|
||||
Entity entity = session.getEntityCache().getEntityByJavaId(data.getOwnerId());
|
||||
if (entity == null && session.getPlayerEntity().getEntityId() == data.getOwnerId()) {
|
||||
entity = session.getPlayerEntity();
|
||||
}
|
||||
this.boundingBox = new BoundingBox(0.125, 0.125, 0.125, 0.25, 0.25, 0.25);
|
||||
|
||||
if (entity != null) {
|
||||
this.metadata.put(EntityData.OWNER_EID, entity.getGeyserId());
|
||||
return;
|
||||
}
|
||||
}
|
||||
// In Java, the splash sound depends on the entity's velocity, but in Bedrock the volume doesn't change.
|
||||
// This splash can be confused with the sound from catching a fish. This silences the splash from Bedrock,
|
||||
// so that it can be handled by moveAbsoluteImmediate.
|
||||
this.metadata.putFloat(EntityData.BOUNDING_BOX_HEIGHT, 128);
|
||||
|
||||
this.metadata.put(EntityData.OWNER_EID, owner.getGeyserId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
|
||||
if (entityMetadata.getId() == 7) {
|
||||
Entity entity = session.getEntityCache().getEntityByJavaId((Integer) entityMetadata.getValue() - 1);
|
||||
if (entity == null && session.getPlayerEntity().getEntityId() == (Integer) entityMetadata.getValue() - 1) {
|
||||
if (entityMetadata.getId() == 7) { // Hooked entity
|
||||
int hookedEntityId = (int) entityMetadata.getValue() - 1;
|
||||
Entity entity = session.getEntityCache().getEntityByJavaId(hookedEntityId);
|
||||
if (entity == null && session.getPlayerEntity().getEntityId() == hookedEntityId) {
|
||||
entity = session.getPlayerEntity();
|
||||
}
|
||||
|
||||
if (entity != null) {
|
||||
metadata.put(EntityData.TARGET_EID, entity.getGeyserId());
|
||||
hooked = true;
|
||||
} else {
|
||||
hooked = false;
|
||||
}
|
||||
}
|
||||
|
||||
super.updateBedrockMetadata(entityMetadata, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
|
||||
boundingBox.setMiddleX(position.getX());
|
||||
boundingBox.setMiddleY(position.getY() + boundingBox.getSizeY() / 2);
|
||||
boundingBox.setMiddleZ(position.getZ());
|
||||
|
||||
CollisionManager collisionManager = session.getCollisionManager();
|
||||
List<Vector3i> collidableBlocks = collisionManager.getCollidableBlocks(boundingBox);
|
||||
boolean touchingWater = false;
|
||||
boolean collided = false;
|
||||
for (Vector3i blockPos : collidableBlocks) {
|
||||
if (0 <= blockPos.getY() && blockPos.getY() <= 255) {
|
||||
int blockID = session.getConnector().getWorldManager().getBlockAt(session, blockPos);
|
||||
BlockCollision blockCollision = CollisionTranslator.getCollision(blockID, blockPos.getX(), blockPos.getY(), blockPos.getZ());
|
||||
if (blockCollision != null && blockCollision.checkIntersection(boundingBox)) {
|
||||
// TODO Push bounding box out of collision to improve movement
|
||||
collided = true;
|
||||
}
|
||||
|
||||
int waterLevel = BlockStateValues.getWaterLevel(blockID);
|
||||
if (BlockTranslator.isWaterlogged(blockID)) {
|
||||
waterLevel = 0;
|
||||
}
|
||||
if (waterLevel >= 0) {
|
||||
double waterMaxY = blockPos.getY() + 1 - (waterLevel + 1) / 9.0;
|
||||
// Falling water is a full block
|
||||
if (waterLevel >= 8) {
|
||||
waterMaxY = blockPos.getY() + 1;
|
||||
}
|
||||
if (position.getY() <= waterMaxY) {
|
||||
touchingWater = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!inWater && touchingWater) {
|
||||
sendSplashSound(session);
|
||||
}
|
||||
inWater = touchingWater;
|
||||
|
||||
if (!collided) {
|
||||
super.moveAbsoluteImmediate(session, position, rotation, isOnGround, teleported);
|
||||
} else {
|
||||
super.moveAbsoluteImmediate(session, this.position, rotation, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendSplashSound(GeyserSession session) {
|
||||
if (!metadata.getFlags().getFlag(EntityFlag.SILENT)) {
|
||||
float volume = (float) (0.2f * Math.sqrt(0.2 * (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) + motion.getY() * motion.getY()));
|
||||
if (volume > 1) {
|
||||
volume = 1;
|
||||
}
|
||||
PlaySoundPacket playSoundPacket = new PlaySoundPacket();
|
||||
playSoundPacket.setSound("random.splash");
|
||||
playSoundPacket.setPosition(position);
|
||||
playSoundPacket.setVolume(volume);
|
||||
playSoundPacket.setPitch(1f + ThreadLocalRandom.current().nextFloat() * 0.3f);
|
||||
session.sendUpstreamPacket(playSoundPacket);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(GeyserSession session) {
|
||||
if (hooked || !isInAir(session) && !isInWater(session) || isOnGround()) {
|
||||
motion = Vector3f.ZERO;
|
||||
return;
|
||||
}
|
||||
float gravity = getGravity(session);
|
||||
motion = motion.down(gravity);
|
||||
|
||||
moveAbsoluteImmediate(session, position.add(motion), rotation, onGround, false);
|
||||
|
||||
float drag = getDrag(session);
|
||||
motion = motion.mul(drag);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getGravity(GeyserSession session) {
|
||||
if (!isInWater(session) && !onGround) {
|
||||
return 0.03f;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param session the session of the Bedrock client.
|
||||
* @return true if this entity is currently in air.
|
||||
*/
|
||||
protected boolean isInAir(GeyserSession session) {
|
||||
if (session.getConnector().getConfig().isCacheChunks()) {
|
||||
if (0 <= position.getFloorY() && position.getFloorY() <= 255) {
|
||||
int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt());
|
||||
return block == BlockTranslator.JAVA_AIR_ID;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getDrag(GeyserSession session) {
|
||||
return 0.92f;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,19 +32,44 @@ import org.geysermc.connector.network.session.GeyserSession;
|
|||
public class ItemedFireballEntity extends ThrowableEntity {
|
||||
private final Vector3f acceleration;
|
||||
|
||||
/**
|
||||
* The number of ticks to advance movement before sending to Bedrock
|
||||
*/
|
||||
protected int futureTicks = 3;
|
||||
|
||||
public ItemedFireballEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
|
||||
super(entityId, geyserId, entityType, position, Vector3f.ZERO, rotation);
|
||||
acceleration = motion;
|
||||
|
||||
float magnitude = motion.length();
|
||||
if (magnitude != 0) {
|
||||
acceleration = motion.div(magnitude).mul(0.1f);
|
||||
} else {
|
||||
acceleration = Vector3f.ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
private Vector3f tickMovement(GeyserSession session, Vector3f position) {
|
||||
position = position.add(motion);
|
||||
float drag = getDrag(session);
|
||||
motion = motion.add(acceleration).mul(drag);
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
|
||||
// Advance the position by a few ticks before sending it to Bedrock
|
||||
Vector3f lastMotion = motion;
|
||||
Vector3f newPosition = position;
|
||||
for (int i = 0; i < futureTicks; i++) {
|
||||
newPosition = tickMovement(session, newPosition);
|
||||
}
|
||||
super.moveAbsoluteImmediate(session, newPosition, rotation, isOnGround, teleported);
|
||||
this.position = position;
|
||||
this.motion = lastMotion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(GeyserSession session) {
|
||||
position = position.add(motion);
|
||||
// TODO: While this reduces latency in position updating (needed for better fireball reflecting),
|
||||
// TODO: movement is incredibly stiff.
|
||||
// TODO: Only use this laggy movement for fireballs that be reflected
|
||||
moveAbsoluteImmediate(session, position, rotation, false, true);
|
||||
float drag = getDrag(session);
|
||||
motion = motion.add(acceleration).mul(drag);
|
||||
moveAbsoluteImmediate(session, tickMovement(session, position), rotation, false, false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,20 +29,21 @@ import com.nukkitx.math.vector.Vector3f;
|
|||
import com.nukkitx.protocol.bedrock.data.LevelEventType;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.MoveEntityDeltaPacket;
|
||||
import org.geysermc.connector.entity.type.EntityType;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
||||
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
|
||||
|
||||
/**
|
||||
* Used as a class for any object-like entity that moves as a projectile
|
||||
*/
|
||||
public class ThrowableEntity extends Entity implements Tickable {
|
||||
|
||||
private Vector3f lastPosition;
|
||||
protected Vector3f lastJavaPosition;
|
||||
|
||||
public ThrowableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
|
||||
super(entityId, geyserId, entityType, position, motion, rotation);
|
||||
this.lastPosition = position;
|
||||
this.lastJavaPosition = position;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -52,22 +53,65 @@ public class ThrowableEntity extends Entity implements Tickable {
|
|||
*/
|
||||
@Override
|
||||
public void tick(GeyserSession session) {
|
||||
super.moveRelative(session, motion.getX(), motion.getY(), motion.getZ(), rotation, onGround);
|
||||
moveAbsoluteImmediate(session, position.add(motion), rotation, onGround, false);
|
||||
float drag = getDrag(session);
|
||||
float gravity = getGravity();
|
||||
float gravity = getGravity(session);
|
||||
motion = motion.mul(drag).down(gravity);
|
||||
}
|
||||
|
||||
protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
|
||||
super.moveAbsolute(session, position, rotation, isOnGround, teleported);
|
||||
MoveEntityDeltaPacket moveEntityDeltaPacket = new MoveEntityDeltaPacket();
|
||||
moveEntityDeltaPacket.setRuntimeEntityId(geyserId);
|
||||
|
||||
if (isOnGround) {
|
||||
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.ON_GROUND);
|
||||
}
|
||||
setOnGround(isOnGround);
|
||||
|
||||
if (teleported) {
|
||||
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.TELEPORTING);
|
||||
}
|
||||
|
||||
if (this.position.getX() != position.getX()) {
|
||||
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_X);
|
||||
moveEntityDeltaPacket.setX(position.getX());
|
||||
}
|
||||
if (this.position.getY() != position.getY()) {
|
||||
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Y);
|
||||
moveEntityDeltaPacket.setY(position.getY());
|
||||
}
|
||||
if (this.position.getZ() != position.getZ()) {
|
||||
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Z);
|
||||
moveEntityDeltaPacket.setZ(position.getZ());
|
||||
}
|
||||
setPosition(position);
|
||||
|
||||
if (this.rotation.getX() != rotation.getX()) {
|
||||
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_YAW);
|
||||
moveEntityDeltaPacket.setYaw(rotation.getX());
|
||||
}
|
||||
if (this.rotation.getY() != rotation.getY()) {
|
||||
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_PITCH);
|
||||
moveEntityDeltaPacket.setPitch(rotation.getY());
|
||||
}
|
||||
if (this.rotation.getZ() != rotation.getZ()) {
|
||||
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_HEAD_YAW);
|
||||
moveEntityDeltaPacket.setHeadYaw(rotation.getZ());
|
||||
}
|
||||
setRotation(rotation);
|
||||
|
||||
if (!moveEntityDeltaPacket.getFlags().isEmpty()) {
|
||||
session.sendUpstreamPacket(moveEntityDeltaPacket);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the gravity of this entity type. Used for applying gravity while the entity is in motion.
|
||||
*
|
||||
* @param session the session of the Bedrock client.
|
||||
* @return the amount of gravity to apply to this entity while in motion.
|
||||
*/
|
||||
protected float getGravity() {
|
||||
protected float getGravity(GeyserSession session) {
|
||||
if (metadata.getFlags().getFlag(EntityFlag.HAS_GRAVITY)) {
|
||||
switch (entityType) {
|
||||
case THROWN_POTION:
|
||||
|
@ -76,11 +120,14 @@ public class ThrowableEntity extends Entity implements Tickable {
|
|||
case THROWN_EXP_BOTTLE:
|
||||
return 0.07f;
|
||||
case FIREBALL:
|
||||
case SHULKER_BULLET:
|
||||
return 0;
|
||||
case SNOWBALL:
|
||||
case THROWN_EGG:
|
||||
case THROWN_ENDERPEARL:
|
||||
return 0.03f;
|
||||
case LLAMA_SPIT:
|
||||
return 0.06f;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
@ -101,11 +148,14 @@ public class ThrowableEntity extends Entity implements Tickable {
|
|||
case SNOWBALL:
|
||||
case THROWN_EGG:
|
||||
case THROWN_ENDERPEARL:
|
||||
case LLAMA_SPIT:
|
||||
return 0.99f;
|
||||
case FIREBALL:
|
||||
case SMALL_FIREBALL:
|
||||
case DRAGON_FIREBALL:
|
||||
return 0.95f;
|
||||
case SHULKER_BULLET:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
|
@ -117,8 +167,10 @@ public class ThrowableEntity extends Entity implements Tickable {
|
|||
*/
|
||||
protected boolean isInWater(GeyserSession session) {
|
||||
if (session.getConnector().getConfig().isCacheChunks()) {
|
||||
int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt());
|
||||
return block == BlockTranslator.BEDROCK_WATER_ID;
|
||||
if (0 <= position.getFloorY() && position.getFloorY() <= 255) {
|
||||
int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt());
|
||||
return BlockStateValues.getWaterLevel(block) != -1;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -136,14 +188,13 @@ public class ThrowableEntity extends Entity implements Tickable {
|
|||
|
||||
@Override
|
||||
public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) {
|
||||
position = lastPosition;
|
||||
super.moveRelative(session, relX, relY, relZ, rotation, isOnGround);
|
||||
lastPosition = position;
|
||||
moveAbsoluteImmediate(session, lastJavaPosition.add(relX, relY, relZ), rotation, isOnGround, false);
|
||||
lastJavaPosition = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
|
||||
super.moveAbsolute(session, position, rotation, isOnGround, teleported);
|
||||
lastPosition = position;
|
||||
moveAbsoluteImmediate(session, position, rotation, isOnGround, teleported);
|
||||
lastJavaPosition = position;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,8 @@ public class WitherSkullEntity extends ItemedFireballEntity {
|
|||
|
||||
public WitherSkullEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
|
||||
super(entityId, geyserId, entityType, position, motion, rotation);
|
||||
|
||||
this.futureTicks = 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -44,7 +44,7 @@ public class RabbitEntity extends AnimalEntity {
|
|||
if (entityMetadata.getId() == 15) {
|
||||
metadata.put(EntityData.SCALE, .55f);
|
||||
boolean isBaby = (boolean) entityMetadata.getValue();
|
||||
if(isBaby) {
|
||||
if (isBaby) {
|
||||
metadata.put(EntityData.SCALE, .35f);
|
||||
metadata.getFlags().setFlag(EntityFlag.BABY, true);
|
||||
}
|
||||
|
|
|
@ -125,7 +125,7 @@ public class VillagerEntity extends AbstractMerchantEntity {
|
|||
Pattern r = Pattern.compile("facing=([a-z]+)");
|
||||
Matcher m = r.matcher(bedRotationZ);
|
||||
if (m.find()) {
|
||||
switch (m.group(0)){
|
||||
switch (m.group(0)) {
|
||||
case "facing=south":
|
||||
//bed is facing south
|
||||
z = 180;
|
||||
|
|
|
@ -46,7 +46,7 @@ public class WitherEntity extends MonsterEntity {
|
|||
|
||||
if (entityMetadata.getId() >= 15 && entityMetadata.getId() <= 17) {
|
||||
Entity entity = session.getEntityCache().getEntityByJavaId((int) entityMetadata.getValue());
|
||||
if (entity == null && session.getPlayerEntity().getEntityId() == (Integer) entityMetadata.getValue()) {
|
||||
if (entity == null && session.getPlayerEntity().getEntityId() == (int) entityMetadata.getValue()) {
|
||||
entity = session.getPlayerEntity();
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,7 @@ public class WitherEntity extends MonsterEntity {
|
|||
} else if (entityMetadata.getId() == 17) {
|
||||
metadata.put(EntityData.WITHER_TARGET_3, targetID);
|
||||
} else if (entityMetadata.getId() == 18) {
|
||||
metadata.put(EntityData.WITHER_INVULNERABLE_TICKS, (int) entityMetadata.getValue());
|
||||
metadata.put(EntityData.WITHER_INVULNERABLE_TICKS, entityMetadata.getValue());
|
||||
|
||||
// Show the shield for the first few seconds of spawning (like Java)
|
||||
if ((int) entityMetadata.getValue() >= 165) {
|
||||
|
|
|
@ -30,8 +30,11 @@ 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.horse.*;
|
||||
import org.geysermc.connector.entity.living.animal.tameable.*;
|
||||
import org.geysermc.connector.entity.living.merchant.*;
|
||||
import org.geysermc.connector.entity.living.animal.tameable.CatEntity;
|
||||
import org.geysermc.connector.entity.living.animal.tameable.ParrotEntity;
|
||||
import org.geysermc.connector.entity.living.animal.tameable.WolfEntity;
|
||||
import org.geysermc.connector.entity.living.merchant.AbstractMerchantEntity;
|
||||
import org.geysermc.connector.entity.living.merchant.VillagerEntity;
|
||||
import org.geysermc.connector.entity.living.monster.*;
|
||||
import org.geysermc.connector.entity.living.monster.raid.AbstractIllagerEntity;
|
||||
import org.geysermc.connector.entity.living.monster.raid.PillagerEntity;
|
||||
|
@ -39,6 +42,9 @@ import org.geysermc.connector.entity.living.monster.raid.RaidParticipantEntity;
|
|||
import org.geysermc.connector.entity.living.monster.raid.SpellcasterIllagerEntity;
|
||||
import org.geysermc.connector.entity.player.PlayerEntity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
public enum EntityType {
|
||||
|
||||
|
@ -112,7 +118,7 @@ public enum EntityType {
|
|||
TRIDENT(TridentEntity.class, 73, 0f, 0f, 0f, 0f, "minecraft:thrown_trident"),
|
||||
TURTLE(TurtleEntity.class, 74, 0.4f, 1.2f),
|
||||
CAT(CatEntity.class, 75, 0.35f, 0.3f),
|
||||
SHULKER_BULLET(Entity.class, 76, 0.3125f),
|
||||
SHULKER_BULLET(ThrowableEntity.class, 76, 0.3125f),
|
||||
FISHING_BOBBER(FishingHookEntity.class, 77, 0f, 0f, 0f, 0f, "minecraft:fishing_hook"),
|
||||
CHALKBOARD(Entity.class, 78, 0f),
|
||||
DRAGON_FIREBALL(ItemedFireballEntity.class, 79, 1.0f),
|
||||
|
@ -139,7 +145,7 @@ public enum EntityType {
|
|||
MINECART_SPAWNER(SpawnerMinecartEntity.class, 98, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:minecart"),
|
||||
MINECART_COMMAND_BLOCK(CommandBlockMinecartEntity.class, 100, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:command_block_minecart"),
|
||||
LINGERING_POTION(ThrowableEntity.class, 101, 0f),
|
||||
LLAMA_SPIT(Entity.class, 102, 0.25f),
|
||||
LLAMA_SPIT(ThrowableEntity.class, 102, 0.25f),
|
||||
EVOKER_FANGS(Entity.class, 103, 0.8f, 0.5f, 0.5f, 0f, "minecraft:evocation_fang"),
|
||||
EVOKER(SpellcasterIllagerEntity.class, 104, 1.95f, 0.6f, 0.6f, 0f, "minecraft:evocation_illager"),
|
||||
VEX(VexEntity.class, 105, 0.8f, 0.4f),
|
||||
|
@ -174,17 +180,33 @@ public enum EntityType {
|
|||
*/
|
||||
ENDER_DRAGON_PART(EnderDragonPartEntity.class, 32, 0, 0, 0, 0, "minecraft:armor_stand");
|
||||
|
||||
/**
|
||||
* A list of all Java identifiers for use with command suggestions
|
||||
*/
|
||||
public static final String[] ALL_JAVA_IDENTIFIERS;
|
||||
private static final EntityType[] VALUES = values();
|
||||
|
||||
private Class<? extends Entity> entityClass;
|
||||
static {
|
||||
List<String> allJavaIdentifiers = new ArrayList<>();
|
||||
for (EntityType type : values()) {
|
||||
if (type == AGENT || type == BALLOON || type == CHALKBOARD || type == NPC || type == TRIPOD_CAMERA || type == ENDER_DRAGON_PART) {
|
||||
continue;
|
||||
}
|
||||
allJavaIdentifiers.add("minecraft:" + type.name().toLowerCase());
|
||||
}
|
||||
ALL_JAVA_IDENTIFIERS = allJavaIdentifiers.toArray(new String[0]);
|
||||
}
|
||||
|
||||
private final Class<? extends Entity> entityClass;
|
||||
private final int type;
|
||||
private final float height;
|
||||
private final float width;
|
||||
private final float length;
|
||||
private final float offset;
|
||||
private String identifier;
|
||||
private final String identifier;
|
||||
|
||||
EntityType(Class<? extends Entity> entityClass, int type, float height) {
|
||||
//noinspection SuspiciousNameCombination
|
||||
this(entityClass, type, height, height);
|
||||
}
|
||||
|
||||
|
@ -198,8 +220,6 @@ public enum EntityType {
|
|||
|
||||
EntityType(Class<? extends Entity> entityClass, int type, float height, float width, float length, float offset) {
|
||||
this(entityClass, type, height, width, length, offset, null);
|
||||
|
||||
this.identifier = "minecraft:" + name().toLowerCase();
|
||||
}
|
||||
|
||||
EntityType(Class<? extends Entity> entityClass, int type, float height, float width, float length, float offset, String identifier) {
|
||||
|
@ -209,7 +229,7 @@ public enum EntityType {
|
|||
this.width = width;
|
||||
this.length = length;
|
||||
this.offset = offset + 0.00001f;
|
||||
this.identifier = identifier;
|
||||
this.identifier = identifier == null ? "minecraft:" + name().toLowerCase() : identifier;
|
||||
}
|
||||
|
||||
public static EntityType getFromIdentifier(String identifier) {
|
||||
|
|
|
@ -36,6 +36,7 @@ import java.io.ByteArrayOutputStream;
|
|||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -75,7 +76,7 @@ public class Metrics {
|
|||
|
||||
private final static ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
private GeyserConnector connector;
|
||||
private final GeyserConnector connector;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
|
@ -156,6 +157,7 @@ public class Metrics {
|
|||
String osName = System.getProperty("os.name");
|
||||
String osArch = System.getProperty("os.arch");
|
||||
String osVersion = System.getProperty("os.version");
|
||||
String javaVersion = System.getProperty("java.version");
|
||||
int coreCount = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
ObjectNode data = mapper.createObjectNode();
|
||||
|
@ -163,6 +165,7 @@ public class Metrics {
|
|||
data.put("serverUUID", serverUUID);
|
||||
|
||||
data.put("playerAmount", playerAmount);
|
||||
data.put("javaVersion", javaVersion);
|
||||
data.put("osName", osName);
|
||||
data.put("osArch", osArch);
|
||||
data.put("osVersion", osVersion);
|
||||
|
@ -241,7 +244,7 @@ public class Metrics {
|
|||
}
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
GZIPOutputStream gzip = new GZIPOutputStream(outputStream);
|
||||
gzip.write(str.getBytes("UTF-8"));
|
||||
gzip.write(str.getBytes(StandardCharsets.UTF_8));
|
||||
gzip.close();
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2021 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;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/*
|
||||
* Taken & modified from TCPShield, licensed under MIT. See https://github.com/TCPShield/RealIP/blob/master/LICENSE
|
||||
*
|
||||
* https://github.com/TCPShield/RealIP/blob/32d422a9523cb6e25b571072851f3306bb8bbc4f/src/main/java/net/tcpshield/tcpshield/validation/cidr/CIDRMatcher.java
|
||||
*/
|
||||
public class CIDRMatcher {
|
||||
private final int maskBits;
|
||||
private final int maskBytes;
|
||||
private final boolean simpleCIDR;
|
||||
private final InetAddress cidrAddress;
|
||||
|
||||
public CIDRMatcher(String ipAddress) {
|
||||
String[] split = ipAddress.split("/", 2);
|
||||
|
||||
String parsedIPAddress;
|
||||
if (split.length == 2) {
|
||||
parsedIPAddress = split[0];
|
||||
|
||||
this.maskBits = Integer.parseInt(split[1]);
|
||||
this.simpleCIDR = maskBits == 32;
|
||||
} else {
|
||||
parsedIPAddress = ipAddress;
|
||||
|
||||
this.maskBits = -1;
|
||||
this.simpleCIDR = true;
|
||||
}
|
||||
|
||||
this.maskBytes = simpleCIDR ? -1 : maskBits / 8;
|
||||
|
||||
try {
|
||||
cidrAddress = InetAddress.getByName(parsedIPAddress);
|
||||
} catch (UnknownHostException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean matches(InetAddress inetAddress) {
|
||||
// check if IP is IPv4 or IPv6
|
||||
if (cidrAddress.getClass() != inetAddress.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for equality if it's a simple CIDR
|
||||
if (simpleCIDR) {
|
||||
return inetAddress.equals(cidrAddress);
|
||||
}
|
||||
|
||||
byte[] inetAddressBytes = inetAddress.getAddress();
|
||||
byte[] requiredAddressBytes = cidrAddress.getAddress();
|
||||
|
||||
byte finalByte = (byte) (0xFF00 >> (maskBits & 0x07));
|
||||
|
||||
for (int i = 0; i < maskBytes; i++) {
|
||||
if (inetAddressBytes[i] != requiredAddressBytes[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (finalByte != 0) {
|
||||
return (inetAddressBytes[maskBytes] & finalByte) == (requiredAddressBytes[maskBytes] & finalByte);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -40,8 +40,18 @@ import org.geysermc.connector.utils.LanguageUtils;
|
|||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
public class ConnectorServerEventHandler implements BedrockServerEventHandler {
|
||||
/*
|
||||
The following constants are all used to ensure the ping does not reach a length where it is unparsable by the Bedrock client
|
||||
*/
|
||||
private static final int MINECRAFT_VERSION_BYTES_LENGTH = BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion().getBytes(StandardCharsets.UTF_8).length;
|
||||
private static final int BRAND_BYTES_LENGTH = GeyserConnector.NAME.getBytes(StandardCharsets.UTF_8).length;
|
||||
/**
|
||||
* The MOTD, sub-MOTD and Minecraft version ({@link #MINECRAFT_VERSION_BYTES_LENGTH}) combined cannot reach this length.
|
||||
*/
|
||||
private static final int MAGIC_RAKNET_LENGTH = 338;
|
||||
|
||||
private final GeyserConnector connector;
|
||||
|
||||
|
@ -51,6 +61,21 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
|
|||
|
||||
@Override
|
||||
public boolean onConnectionRequest(InetSocketAddress inetSocketAddress) {
|
||||
List<String> allowedProxyIPs = connector.getConfig().getBedrock().getProxyProtocolWhitelistedIPs();
|
||||
if (connector.getConfig().getBedrock().isEnableProxyProtocol() && !allowedProxyIPs.isEmpty()) {
|
||||
boolean isWhitelistedIP = false;
|
||||
for (CIDRMatcher matcher : connector.getConfig().getBedrock().getWhitelistedIPsMatchers()) {
|
||||
if (matcher.matches(inetSocketAddress.getAddress())) {
|
||||
isWhitelistedIP = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isWhitelistedIP) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.attempt_connect", inetSocketAddress));
|
||||
return true;
|
||||
}
|
||||
|
@ -69,16 +94,16 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
|
|||
|
||||
BedrockPong pong = new BedrockPong();
|
||||
pong.setEdition("MCPE");
|
||||
pong.setGameType("Default");
|
||||
pong.setGameType("Survival"); // Can only be Survival or Creative as of 1.16.210.59
|
||||
pong.setNintendoLimited(false);
|
||||
pong.setProtocolVersion(BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion());
|
||||
pong.setVersion(null); // Server tries to connect either way and it looks better
|
||||
pong.setVersion(BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()); // Required to not be empty as of 1.16.210.59. Can only contain . and numbers.
|
||||
pong.setIpv4Port(config.getBedrock().getPort());
|
||||
|
||||
if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) {
|
||||
String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n");
|
||||
String mainMotd = motd[0]; // First line of the motd.
|
||||
String subMotd = (motd.length != 1) ? motd[1] : ""; // Second line of the motd if present, otherwise blank.
|
||||
String subMotd = (motd.length != 1) ? motd[1] : GeyserConnector.NAME; // Second line of the motd if present, otherwise default.
|
||||
|
||||
pong.setMotd(mainMotd.trim());
|
||||
pong.setSubMotd(subMotd.trim()); // Trimmed to shift it to the left, prevents the universe from collapsing on us just because we went 2 characters over the text box's limit.
|
||||
|
@ -95,15 +120,28 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
|
|||
pong.setMaximumPlayerCount(config.getMaxPlayers());
|
||||
}
|
||||
|
||||
// Fallbacks to prevent errors and allow Bedrock to see the server
|
||||
if (pong.getMotd() == null || pong.getMotd().trim().isEmpty()) {
|
||||
pong.setMotd(GeyserConnector.NAME);
|
||||
}
|
||||
if (pong.getSubMotd() == null || pong.getSubMotd().trim().isEmpty()) {
|
||||
// Sub-MOTD cannot be empty as of 1.16.210.59
|
||||
pong.setSubMotd(GeyserConnector.NAME);
|
||||
}
|
||||
|
||||
// The ping will not appear if the MOTD + sub-MOTD is of a certain length.
|
||||
// We don't know why, though
|
||||
byte[] motdArray = pong.getMotd().getBytes(StandardCharsets.UTF_8);
|
||||
if (motdArray.length + pong.getSubMotd().getBytes(StandardCharsets.UTF_8).length > 338) {
|
||||
// Remove the sub-MOTD first since that only appears locally
|
||||
pong.setSubMotd("");
|
||||
if (motdArray.length > 338) {
|
||||
int subMotdLength = pong.getSubMotd().getBytes(StandardCharsets.UTF_8).length;
|
||||
if (motdArray.length + subMotdLength > (MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH)) {
|
||||
// Shorten the sub-MOTD first since that only appears locally
|
||||
if (subMotdLength > BRAND_BYTES_LENGTH) {
|
||||
pong.setSubMotd(GeyserConnector.NAME);
|
||||
subMotdLength = BRAND_BYTES_LENGTH;
|
||||
}
|
||||
if (motdArray.length > (MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH - subMotdLength)) {
|
||||
// If the top MOTD is still too long, we chop it down
|
||||
byte[] newMotdArray = new byte[339];
|
||||
byte[] newMotdArray = new byte[MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH - subMotdLength];
|
||||
System.arraycopy(motdArray, 0, newMotdArray, 0, newMotdArray.length);
|
||||
pong.setMotd(new String(newMotdArray, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ public class QueryPacketHandler {
|
|||
* @param buffer The Query data
|
||||
*/
|
||||
public QueryPacketHandler(GeyserConnector connector, InetSocketAddress sender, ByteBuf buffer) {
|
||||
if(!isQueryPacket(buffer))
|
||||
if (!isQueryPacket(buffer))
|
||||
return;
|
||||
|
||||
this.connector = connector;
|
||||
|
@ -225,7 +225,7 @@ public class QueryPacketHandler {
|
|||
query.write(new byte[] { 0x00, 0x00 });
|
||||
|
||||
// Fill player names
|
||||
if(pingInfo != null) {
|
||||
if (pingInfo != null) {
|
||||
for (String username : pingInfo.getPlayerList()) {
|
||||
query.write(username.getBytes());
|
||||
query.write((byte) 0x00);
|
||||
|
|
|
@ -93,6 +93,8 @@ import org.geysermc.cumulus.util.FormBuilder;
|
|||
import org.geysermc.floodgate.crypto.FloodgateCipher;
|
||||
import org.geysermc.floodgate.util.BedrockData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
@ -168,6 +170,9 @@ public class GeyserSession implements CommandSender {
|
|||
@Setter
|
||||
private boolean sprinting;
|
||||
|
||||
/**
|
||||
* Not updated if cache chunks is enabled.
|
||||
*/
|
||||
@Setter
|
||||
private boolean jumping;
|
||||
|
||||
|
@ -370,7 +375,8 @@ public class GeyserSession implements CommandSender {
|
|||
tmpPlayers.forEach(player -> this.emotes.addAll(player.getEmotes()));
|
||||
|
||||
bedrockServerSession.addDisconnectHandler(disconnectReason -> {
|
||||
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.disconnect", bedrockServerSession.getAddress().getAddress(), disconnectReason));
|
||||
InetAddress address = bedrockServerSession.getRealAddress().getAddress();
|
||||
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.disconnect", address, disconnectReason));
|
||||
|
||||
disconnect(disconnectReason.name());
|
||||
connector.removePlayer(this);
|
||||
|
@ -544,8 +550,10 @@ public class GeyserSession implements CommandSender {
|
|||
downstream.getSession().setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true);
|
||||
downstream.getSession().setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress());
|
||||
}
|
||||
// Let Geyser handle sending the keep alive
|
||||
downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false);
|
||||
if (connector.getConfig().isForwardPlayerPing()) {
|
||||
// Let Geyser handle sending the keep alive
|
||||
downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false);
|
||||
}
|
||||
downstream.getSession().addListener(new SessionAdapter() {
|
||||
@Override
|
||||
public void packetSending(PacketSendingEvent event) {
|
||||
|
@ -565,7 +573,7 @@ public class GeyserSession implements CommandSender {
|
|||
clientData.getLanguageCode(),
|
||||
clientData.getUiProfile().ordinal(),
|
||||
clientData.getCurrentInputMode().ordinal(),
|
||||
upstream.getSession().getAddress().getAddress().getHostAddress(),
|
||||
upstream.getAddress().getAddress().getHostAddress(),
|
||||
skinUploader.getId(),
|
||||
skinUploader.getVerifyCode()
|
||||
).toString());
|
||||
|
@ -826,7 +834,14 @@ public class GeyserSession implements CommandSender {
|
|||
startGamePacket.setMultiplayerCorrelationId("");
|
||||
startGamePacket.setItemEntries(ItemRegistry.ITEMS);
|
||||
startGamePacket.setVanillaVersion("*");
|
||||
startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT);
|
||||
startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT); // can be removed once 1.16.200 support is dropped
|
||||
|
||||
SyncedPlayerMovementSettings settings = new SyncedPlayerMovementSettings();
|
||||
settings.setMovementMode(AuthoritativeMovementMode.CLIENT);
|
||||
settings.setRewindHistorySize(0);
|
||||
settings.setServerAuthoritativeBlockBreaking(false);
|
||||
startGamePacket.setPlayerMovementSettings(settings);
|
||||
|
||||
upstream.sendPacket(startGamePacket);
|
||||
}
|
||||
|
||||
|
|
|
@ -61,6 +61,6 @@ public class UpstreamSession {
|
|||
}
|
||||
|
||||
public InetSocketAddress getAddress() {
|
||||
return session.getAddress();
|
||||
return session.getRealAddress();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,6 +112,8 @@ public final class BedrockClientData {
|
|||
private String skinColor;
|
||||
@JsonProperty(value = "ThirdPartyNameOnly")
|
||||
private boolean thirdPartyNameOnly;
|
||||
@JsonProperty(value = "PlayFabId")
|
||||
private String playFabId;
|
||||
|
||||
public void setJsonData(JsonNode data) {
|
||||
if (this.jsonData == null && data != null) {
|
||||
|
|
|
@ -38,7 +38,7 @@ import java.io.InputStream;
|
|||
*/
|
||||
public class EntityIdentifierRegistry {
|
||||
|
||||
public static NbtMap ENTITY_IDENTIFIERS;
|
||||
public static final NbtMap ENTITY_IDENTIFIERS;
|
||||
|
||||
private EntityIdentifierRegistry() {
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdate
|
|||
import com.github.steveice10.packetlib.packet.Packet;
|
||||
import com.nukkitx.protocol.bedrock.BedrockPacket;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.utils.FileUtils;
|
||||
|
@ -89,12 +90,15 @@ public class PacketTranslatorRegistry<T> {
|
|||
public <P extends T> boolean translate(Class<? extends P> clazz, P packet, GeyserSession session) {
|
||||
if (!session.getUpstream().isClosed() && !session.isClosed()) {
|
||||
try {
|
||||
if (translators.containsKey(clazz)) {
|
||||
((PacketTranslator<P>) translators.get(clazz)).translate(packet, session);
|
||||
PacketTranslator<P> translator = (PacketTranslator<P>) translators.get(clazz);
|
||||
if (translator != null) {
|
||||
translator.translate(packet, session);
|
||||
return true;
|
||||
} else {
|
||||
if (!IGNORED_PACKETS.contains(clazz))
|
||||
if ((GeyserConnector.getInstance().getPlatformType() != PlatformType.STANDALONE || !(packet instanceof BedrockPacket)) && !IGNORED_PACKETS.contains(clazz)) {
|
||||
// Other debug logs already take care of Bedrock packets for us if on standalone
|
||||
GeyserConnector.getInstance().getLogger().debug("Could not find packet for " + (packet.toString().length() > 25 ? packet.getClass().getSimpleName() : packet));
|
||||
}
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.packet.failed", packet.getClass().getSimpleName()), ex);
|
||||
|
|
|
@ -40,7 +40,7 @@ import java.util.Collections;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Used to send the keep alive packet back to the server
|
||||
* Used to send the forwarded keep alive packet back to the server
|
||||
*/
|
||||
@Translator(packet = NetworkStackLatencyPacket.class)
|
||||
public class BedrockNetworkStackLatencyTranslator extends PacketTranslator<NetworkStackLatencyPacket> {
|
||||
|
@ -60,8 +60,10 @@ public class BedrockNetworkStackLatencyTranslator extends PacketTranslator<Netwo
|
|||
|
||||
// negative timestamps are used as hack to fix the url image loading bug
|
||||
if (packet.getTimestamp() > 0) {
|
||||
ClientKeepAlivePacket keepAlivePacket = new ClientKeepAlivePacket(pingId);
|
||||
session.sendDownstreamPacket(keepAlivePacket);
|
||||
if (session.getConnector().getConfig().isForwardPlayerPing()) {
|
||||
ClientKeepAlivePacket keepAlivePacket = new ClientKeepAlivePacket(pingId);
|
||||
session.sendDownstreamPacket(keepAlivePacket);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlaye
|
|||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import com.nukkitx.protocol.bedrock.data.LevelEventType;
|
||||
import com.nukkitx.protocol.bedrock.data.PlayerActionType;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
|
||||
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
|
||||
|
@ -64,7 +65,7 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
|||
return;
|
||||
|
||||
// Send book update before any player action
|
||||
if (packet.getAction() != PlayerActionPacket.Action.RESPAWN) {
|
||||
if (packet.getAction() != PlayerActionType.RESPAWN) {
|
||||
session.getBookEditCache().checkForSend();
|
||||
}
|
||||
|
||||
|
@ -205,10 +206,11 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
|||
session.getEntityCache().updateBossBars();
|
||||
break;
|
||||
case JUMP:
|
||||
session.setJumping(true);
|
||||
session.getConnector().getGeneralThreadPool().schedule(() -> {
|
||||
session.setJumping(false);
|
||||
}, 1, TimeUnit.SECONDS);
|
||||
if (!session.getConnector().getConfig().isCacheChunks()) {
|
||||
// Save the jumping status for determining teleport status
|
||||
session.setJumping(true);
|
||||
session.getConnector().getGeneralThreadPool().schedule(() -> session.setJumping(false), 1, TimeUnit.SECONDS);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -177,24 +177,24 @@ public class CollisionManager {
|
|||
session.sendUpstreamPacket(movePlayerPacket);
|
||||
}
|
||||
|
||||
public List<Vector3i> getPlayerCollidableBlocks() {
|
||||
public List<Vector3i> getCollidableBlocks(BoundingBox box) {
|
||||
List<Vector3i> blocks = new ArrayList<>();
|
||||
|
||||
Vector3d position = Vector3d.from(playerBoundingBox.getMiddleX(),
|
||||
playerBoundingBox.getMiddleY() - (playerBoundingBox.getSizeY() / 2),
|
||||
playerBoundingBox.getMiddleZ());
|
||||
Vector3d position = Vector3d.from(box.getMiddleX(),
|
||||
box.getMiddleY() - (box.getSizeY() / 2),
|
||||
box.getMiddleZ());
|
||||
|
||||
// Loop through all blocks that could collide with the player
|
||||
int minCollisionX = (int) Math.floor(position.getX() - ((playerBoundingBox.getSizeX() / 2) + COLLISION_TOLERANCE));
|
||||
int maxCollisionX = (int) Math.floor(position.getX() + (playerBoundingBox.getSizeX() / 2) + COLLISION_TOLERANCE);
|
||||
// Loop through all blocks that could collide
|
||||
int minCollisionX = (int) Math.floor(position.getX() - ((box.getSizeX() / 2) + COLLISION_TOLERANCE));
|
||||
int maxCollisionX = (int) Math.floor(position.getX() + (box.getSizeX() / 2) + COLLISION_TOLERANCE);
|
||||
|
||||
// Y extends 0.5 blocks down because of fence hitboxes
|
||||
int minCollisionY = (int) Math.floor(position.getY() - 0.5);
|
||||
|
||||
int maxCollisionY = (int) Math.floor(position.getY() + playerBoundingBox.getSizeY());
|
||||
int maxCollisionY = (int) Math.floor(position.getY() + box.getSizeY());
|
||||
|
||||
int minCollisionZ = (int) Math.floor(position.getZ() - ((playerBoundingBox.getSizeZ() / 2) + COLLISION_TOLERANCE));
|
||||
int maxCollisionZ = (int) Math.floor(position.getZ() + (playerBoundingBox.getSizeZ() / 2) + COLLISION_TOLERANCE);
|
||||
int minCollisionZ = (int) Math.floor(position.getZ() - ((box.getSizeZ() / 2) + COLLISION_TOLERANCE));
|
||||
int maxCollisionZ = (int) Math.floor(position.getZ() + (box.getSizeZ() / 2) + COLLISION_TOLERANCE);
|
||||
|
||||
for (int y = minCollisionY; y < maxCollisionY + 1; y++) {
|
||||
for (int x = minCollisionX; x < maxCollisionX + 1; x++) {
|
||||
|
@ -207,6 +207,10 @@ public class CollisionManager {
|
|||
return blocks;
|
||||
}
|
||||
|
||||
public List<Vector3i> getPlayerCollidableBlocks() {
|
||||
return getCollidableBlocks(playerBoundingBox);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false if the movement is invalid, and in this case it shouldn't be sent to the server and should be
|
||||
* cancelled
|
||||
|
|
|
@ -69,6 +69,18 @@ public enum Enchantment {
|
|||
QUICK_CHARGE,
|
||||
SOUL_SPEED;
|
||||
|
||||
/**
|
||||
* A list of all enchantment Java identifiers for use with command suggestions.
|
||||
*/
|
||||
public static final String[] ALL_JAVA_IDENTIFIERS;
|
||||
|
||||
static {
|
||||
ALL_JAVA_IDENTIFIERS = new String[values().length];
|
||||
for (int i = 0; i < ALL_JAVA_IDENTIFIERS.length; i++) {
|
||||
ALL_JAVA_IDENTIFIERS[i] = values()[i].javaIdentifier;
|
||||
}
|
||||
}
|
||||
|
||||
private final String javaIdentifier;
|
||||
|
||||
Enchantment() {
|
||||
|
|
|
@ -63,6 +63,11 @@ public class ItemRegistry {
|
|||
public static final List<StartGamePacket.ItemEntry> ITEMS = new ArrayList<>();
|
||||
public static final Int2ObjectMap<ItemEntry> ITEM_ENTRIES = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
/**
|
||||
* A list of all Java item names.
|
||||
*/
|
||||
public static final String[] ITEM_NAMES;
|
||||
|
||||
/**
|
||||
* Bamboo item entry, used in PandaEntity.java
|
||||
*/
|
||||
|
@ -116,6 +121,8 @@ public class ItemRegistry {
|
|||
// Used to get the Bedrock namespaced ID (in instances where there are small differences)
|
||||
Int2ObjectMap<String> bedrockIdToIdentifier = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
List<String> itemNames = new ArrayList<>();
|
||||
|
||||
List<JsonNode> itemEntries;
|
||||
try {
|
||||
itemEntries = GeyserConnector.JSON_MAPPER.readValue(stream, itemEntriesType);
|
||||
|
@ -207,6 +214,8 @@ public class ItemRegistry {
|
|||
BUCKETS.add(entry.getValue().get("bedrock_id").intValue());
|
||||
}
|
||||
|
||||
itemNames.add(entry.getKey());
|
||||
|
||||
itemIndex++;
|
||||
}
|
||||
|
||||
|
@ -235,6 +244,8 @@ public class ItemRegistry {
|
|||
creativeItems.add(ItemData.fromNet(netId++, item.getId(), item.getDamage(), item.getCount(), item.getTag()));
|
||||
}
|
||||
CREATIVE_ITEMS = creativeItems.toArray(new ItemData[0]);
|
||||
|
||||
ITEM_NAMES = itemNames.toArray(new String[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -303,9 +303,8 @@ public abstract class ItemTranslator {
|
|||
CompoundTag javaTag = new CompoundTag(name);
|
||||
Map<String, Tag> javaValue = javaTag.getValue();
|
||||
if (tag != null && !tag.isEmpty()) {
|
||||
for (String str : tag.keySet()) {
|
||||
Object bedrockTag = tag.get(str);
|
||||
Tag translatedTag = translateToJavaNBT(str, bedrockTag);
|
||||
for (Map.Entry<String, Object> entry : tag.entrySet()) {
|
||||
Tag translatedTag = translateToJavaNBT(entry.getKey(), entry.getValue());
|
||||
if (translatedTag == null)
|
||||
continue;
|
||||
|
||||
|
|
|
@ -33,21 +33,69 @@ import com.nukkitx.protocol.bedrock.data.command.CommandEnumData;
|
|||
import com.nukkitx.protocol.bedrock.data.command.CommandParamData;
|
||||
import com.nukkitx.protocol.bedrock.data.command.CommandParamType;
|
||||
import com.nukkitx.protocol.bedrock.packet.AvailableCommandsPacket;
|
||||
import it.unimi.dsi.fastutil.Hash;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
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.network.translators.item.Enchantment;
|
||||
import org.geysermc.connector.network.translators.item.ItemRegistry;
|
||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
@Translator(packet = ServerDeclareCommandsPacket.class)
|
||||
public class JavaDeclareCommandsTranslator extends PacketTranslator<ServerDeclareCommandsPacket> {
|
||||
|
||||
private static final String[] ENUM_BOOLEAN = {"true", "false"};
|
||||
private static final String[] VALID_COLORS;
|
||||
private static final String[] VALID_SCOREBOARD_SLOTS;
|
||||
|
||||
private static final Hash.Strategy<CommandParamData[][]> PARAM_STRATEGY = new Hash.Strategy<CommandParamData[][]>() {
|
||||
@Override
|
||||
public int hashCode(CommandParamData[][] o) {
|
||||
return Arrays.deepHashCode(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(CommandParamData[][] a, CommandParamData[][] b) {
|
||||
if (a == b) return true;
|
||||
if (a == null || b == null) return false;
|
||||
if (a.length != b.length) return false;
|
||||
for (int i = 0; i < a.length; i++) {
|
||||
CommandParamData[] a1 = a[i];
|
||||
CommandParamData[] b1 = b[i];
|
||||
if (a1.length != b1.length) return false;
|
||||
|
||||
for (int j = 0; j < a1.length; j++) {
|
||||
if (!a1[j].equals(b1[j])) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
static {
|
||||
List<String> validColors = new ArrayList<>(NamedTextColor.NAMES.keys());
|
||||
validColors.add("reset");
|
||||
VALID_COLORS = validColors.toArray(new String[0]);
|
||||
|
||||
List<String> teamOptions = new ArrayList<>(Arrays.asList("list", "sidebar", "belowName"));
|
||||
for (String color : NamedTextColor.NAMES.keys()) {
|
||||
teamOptions.add("sidebar.team." + color);
|
||||
}
|
||||
VALID_SCOREBOARD_SLOTS = teamOptions.toArray(new String[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translate(ServerDeclareCommandsPacket packet, GeyserSession session) {
|
||||
// Don't send command suggestions if they are disabled
|
||||
|
@ -60,48 +108,50 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator<ServerDeclar
|
|||
return;
|
||||
}
|
||||
|
||||
CommandNode[] nodes = packet.getNodes();
|
||||
List<CommandData> commandData = new ArrayList<>();
|
||||
Int2ObjectMap<String> commands = new Int2ObjectOpenHashMap<>();
|
||||
IntSet commandNodes = new IntOpenHashSet();
|
||||
Set<String> knownAliases = new HashSet<>();
|
||||
Map<CommandParamData[][], Set<String>> commands = new Object2ObjectOpenCustomHashMap<>(PARAM_STRATEGY);
|
||||
Int2ObjectMap<List<CommandNode>> commandArgs = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
// Get the first node, it should be a root node
|
||||
CommandNode rootNode = packet.getNodes()[packet.getFirstNodeIndex()];
|
||||
CommandNode rootNode = nodes[packet.getFirstNodeIndex()];
|
||||
|
||||
// Loop through the root nodes to get all commands
|
||||
for (int nodeIndex : rootNode.getChildIndices()) {
|
||||
CommandNode node = packet.getNodes()[nodeIndex];
|
||||
CommandNode node = nodes[nodeIndex];
|
||||
|
||||
// Make sure we don't have duplicated commands (happens if there is more than 1 root node)
|
||||
if (commands.containsKey(nodeIndex)) { continue; }
|
||||
if (commands.containsValue(node.getName())) { continue; }
|
||||
if (!commandNodes.add(nodeIndex) || !knownAliases.add(node.getName().toLowerCase())) 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]);
|
||||
commandArgs.computeIfAbsent(nodeIndex, ArrayList::new).add(nodes[childIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
// Insert the command name into the list
|
||||
commands.put(nodeIndex, node.getName());
|
||||
// Get and parse all params
|
||||
CommandParamData[][] params = getParams(nodes[nodeIndex], nodes);
|
||||
|
||||
// Insert the alias name into the command list
|
||||
commands.computeIfAbsent(params, index -> new HashSet<>()).add(node.getName().toLowerCase());
|
||||
}
|
||||
|
||||
// The command flags, not sure what these do apart from break things
|
||||
List<CommandData.Flag> flags = Collections.emptyList();
|
||||
|
||||
// Loop through all the found commands
|
||||
for (int commandID : commands.keySet()) {
|
||||
String commandName = commands.get(commandID);
|
||||
|
||||
for (Map.Entry<CommandParamData[][], Set<String>> entry : commands.entrySet()) {
|
||||
String commandName = entry.getValue().iterator().next(); // We know this has a value
|
||||
|
||||
// Create a basic alias
|
||||
CommandEnumData aliases = new CommandEnumData(commandName + "Aliases", new String[] { commandName.toLowerCase() }, false);
|
||||
|
||||
// Get and parse all params
|
||||
CommandParamData[][] params = getParams(packet.getNodes()[commandID], packet.getNodes());
|
||||
CommandEnumData aliases = new CommandEnumData(commandName + "Aliases", entry.getValue().toArray(new String[0]), false);
|
||||
|
||||
// Build the completed command and add it to the final list
|
||||
CommandData data = new CommandData(commandName, session.getConnector().getCommandManager().getDescription(commandName), flags, (byte) 0, aliases, params);
|
||||
CommandData data = new CommandData(commandName, session.getConnector().getCommandManager().getDescription(commandName), flags, (byte) 0, aliases, entry.getKey());
|
||||
commandData.add(data);
|
||||
}
|
||||
|
||||
|
@ -109,7 +159,7 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator<ServerDeclar
|
|||
AvailableCommandsPacket availableCommandsPacket = new AvailableCommandsPacket();
|
||||
availableCommandsPacket.getCommands().addAll(commandData);
|
||||
|
||||
GeyserConnector.getInstance().getLogger().debug("Sending command packet of " + commandData.size() + " commands");
|
||||
session.getConnector().getLogger().debug("Sending command packet of " + commandData.size() + " commands");
|
||||
|
||||
// Finally, send the commands to the client
|
||||
session.sendUpstreamPacket(availableCommandsPacket);
|
||||
|
@ -119,11 +169,10 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator<ServerDeclar
|
|||
* Build the command parameter array for the given command
|
||||
*
|
||||
* @param commandNode The command to build the parameters for
|
||||
* @param allNodes Every command node
|
||||
*
|
||||
* @param allNodes Every command node
|
||||
* @return An array of parameter option arrays
|
||||
*/
|
||||
private CommandParamData[][] getParams(CommandNode commandNode, CommandNode[] allNodes) {
|
||||
private static CommandParamData[][] getParams(CommandNode commandNode, CommandNode[] allNodes) {
|
||||
// Check if the command is an alias and redirect it
|
||||
if (commandNode.getRedirectIndex() != -1) {
|
||||
GeyserConnector.getInstance().getLogger().debug("Redirecting command " + commandNode.getName() + " to " + allNodes[commandNode.getRedirectIndex()].getName());
|
||||
|
@ -136,16 +185,8 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator<ServerDeclar
|
|||
rootParam.buildChildren(allNodes);
|
||||
|
||||
List<CommandParamData[]> 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 treeData.toArray(new CommandParamData[0][]);
|
||||
}
|
||||
|
||||
return new CommandParamData[0][0];
|
||||
|
@ -155,14 +196,17 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator<ServerDeclar
|
|||
* Convert Java edition command types to Bedrock edition
|
||||
*
|
||||
* @param parser Command type to convert
|
||||
*
|
||||
* @return Bedrock parameter data type
|
||||
*/
|
||||
private CommandParamType mapCommandType(CommandParser parser) {
|
||||
if (parser == null) { return CommandParamType.STRING; }
|
||||
private static Object mapCommandType(CommandParser parser) {
|
||||
if (parser == null) {
|
||||
return CommandParamType.STRING;
|
||||
}
|
||||
|
||||
switch (parser) {
|
||||
case FLOAT:
|
||||
case ROTATION:
|
||||
case DOUBLE:
|
||||
return CommandParamType.FLOAT;
|
||||
|
||||
case INTEGER:
|
||||
|
@ -189,50 +233,44 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator<ServerDeclar
|
|||
return CommandParamType.JSON;
|
||||
|
||||
case RESOURCE_LOCATION:
|
||||
case FUNCTION:
|
||||
return CommandParamType.FILE_PATH;
|
||||
|
||||
case INT_RANGE:
|
||||
return CommandParamType.INT_RANGE;
|
||||
|
||||
case BOOL:
|
||||
case DOUBLE:
|
||||
case STRING:
|
||||
case VEC2:
|
||||
return ENUM_BOOLEAN;
|
||||
|
||||
case OPERATION: // ">=", "==", etc
|
||||
return CommandParamType.OPERATOR;
|
||||
|
||||
case BLOCK_STATE:
|
||||
case BLOCK_PREDICATE:
|
||||
return BlockTranslator.getAllBlockIdentifiers();
|
||||
|
||||
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:
|
||||
return ItemRegistry.ITEM_NAMES;
|
||||
|
||||
case ITEM_ENCHANTMENT:
|
||||
return Enchantment.ALL_JAVA_IDENTIFIERS; //TODO: inventory branch use Java enums
|
||||
|
||||
case ENTITY_SUMMON:
|
||||
case DIMENSION:
|
||||
case TIME:
|
||||
return EntityType.ALL_JAVA_IDENTIFIERS;
|
||||
|
||||
case COLOR:
|
||||
return VALID_COLORS;
|
||||
|
||||
case SCOREBOARD_SLOT:
|
||||
return VALID_SCOREBOARD_SLOTS;
|
||||
|
||||
default:
|
||||
return CommandParamType.STRING;
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
private class ParamInfo {
|
||||
private CommandNode paramNode;
|
||||
private CommandParamData paramData;
|
||||
private List<ParamInfo> children;
|
||||
@ToString
|
||||
private static class ParamInfo {
|
||||
private final CommandNode paramNode;
|
||||
private final CommandParamData paramData;
|
||||
private final List<ParamInfo> children;
|
||||
|
||||
/**
|
||||
* Create a new parameter info object
|
||||
|
@ -252,33 +290,50 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator<ServerDeclar
|
|||
* @param allNodes Every command node
|
||||
*/
|
||||
public void buildChildren(CommandNode[] allNodes) {
|
||||
int enumIndex = -1;
|
||||
|
||||
for (int paramID : paramNode.getChildIndices()) {
|
||||
CommandNode paramNode = allNodes[paramID];
|
||||
|
||||
if (paramNode.getParser() == null) {
|
||||
if (enumIndex == -1) {
|
||||
enumIndex = children.size();
|
||||
boolean foundCompatible = false;
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
ParamInfo enumParamInfo = children.get(i);
|
||||
// Check to make sure all descending nodes of this command are compatible - otherwise, create a new overload
|
||||
if (isCompatible(allNodes, enumParamInfo.getParamNode(), paramNode)) {
|
||||
foundCompatible = true;
|
||||
// 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();
|
||||
|
||||
// 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())));
|
||||
// Re-create the command using the updated values
|
||||
CommandEnumData enumData = new CommandEnumData(enumParamInfo.getParamData().getEnumData().getName(), enumOptions, false);
|
||||
children.set(i, new ParamInfo(enumParamInfo.getParamNode(), new CommandParamData(enumParamInfo.getParamData().getName(), this.paramNode.isExecutable(), enumData, null, null, Collections.emptyList())));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
|
||||
if (!foundCompatible) {
|
||||
// Create a new subcommand with this exact type
|
||||
CommandEnumData enumData = new CommandEnumData(paramNode.getName(), new String[]{paramNode.getName()}, false);
|
||||
|
||||
// On setting optional:
|
||||
// isExecutable is defined as a node "constitutes a valid command."
|
||||
// Therefore, any children of the parameter must simply be optional.
|
||||
children.add(new ParamInfo(paramNode, new CommandParamData(paramNode.getName(), this.paramNode.isExecutable(), enumData, null, 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())));
|
||||
Object mappedType = mapCommandType(paramNode.getParser());
|
||||
CommandEnumData enumData = null;
|
||||
CommandParamType type = null;
|
||||
if (mappedType instanceof String[]) {
|
||||
enumData = new CommandEnumData(paramNode.getParser().name().toLowerCase(), (String[]) mappedType, false);
|
||||
} else {
|
||||
type = (CommandParamType) mappedType;
|
||||
}
|
||||
// IF enumData != null:
|
||||
// In game, this will show up like <paramNode.getName(): enumData.getName()>
|
||||
// So if paramNode.getName() == "value" and enumData.getName() == "bool": <value: bool>
|
||||
children.add(new ParamInfo(paramNode, new CommandParamData(paramNode.getName(), this.paramNode.isExecutable(), enumData, type, null, Collections.emptyList())));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -288,6 +343,64 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator<ServerDeclar
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparing CommandNode type a and b, determine if they are in the same overload.
|
||||
* <p>
|
||||
* Take the <code>gamerule</code> command, and let's present three "subcommands" you can perform:
|
||||
*
|
||||
* <ul>
|
||||
* <li><code>gamerule doDaylightCycle true</code></li>
|
||||
* <li><code>gamerule announceAdvancements false</code></li>
|
||||
* <li><code>gamerule randomTickSpeed 3</code></li>
|
||||
* </ul>
|
||||
*
|
||||
* While all three of them are indeed part of the same command, the command setting randomTickSpeed parses an int,
|
||||
* while the others use boolean. In Bedrock, this should be presented as a separate overload to indicate that this
|
||||
* does something a little different.
|
||||
* <p>
|
||||
* Therefore, this function will return <code>true</code> if the first two are compared, as they use the same
|
||||
* parsers. If the third is compared with either of the others, this function will return <code>false</code>.
|
||||
* <p>
|
||||
* Here's an example of how the above would be presented to Bedrock (as of 1.16.200). Notice how the top two <code>CommandParamData</code>
|
||||
* classes of each array are identical in type, but the following class is different:
|
||||
* <pre>
|
||||
* overloads=[
|
||||
* [
|
||||
* CommandParamData(name=doDaylightCycle, optional=false, enumData=CommandEnumData(name=announceAdvancements, values=[announceAdvancements, doDaylightCycle], isSoft=false), type=STRING, postfix=null, options=[])
|
||||
* CommandParamData(name=value, optional=false, enumData=CommandEnumData(name=value, values=[true, false], isSoft=false), type=null, postfix=null, options=[])
|
||||
* ]
|
||||
* [
|
||||
* CommandParamData(name=randomTickSpeed, optional=false, enumData=CommandEnumData(name=randomTickSpeed, values=[randomTickSpeed], isSoft=false), type=STRING, postfix=null, options=[])
|
||||
* CommandParamData(name=value, optional=false, enumData=null, type=INT, postfix=null, options=[])
|
||||
* ]
|
||||
* ]
|
||||
* </pre>
|
||||
*
|
||||
* @return if these two can be merged into one overload.
|
||||
*/
|
||||
private boolean isCompatible(CommandNode[] allNodes, CommandNode a, CommandNode b) {
|
||||
if (a == b) return true;
|
||||
if (a.getParser() != b.getParser()) return false;
|
||||
if (a.getChildIndices().length != b.getChildIndices().length) return false;
|
||||
|
||||
for (int i = 0; i < a.getChildIndices().length; i++) {
|
||||
boolean hasSimilarity = false;
|
||||
CommandNode a1 = allNodes[a.getChildIndices()[i]];
|
||||
// Search "b" until we find a child that matches this one
|
||||
for (int j = 0; j < b.getChildIndices().length; j++) {
|
||||
if (isCompatible(allNodes, a1, allNodes[b.getChildIndices()[j]])) {
|
||||
hasSimilarity = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasSimilarity) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tree of every parameter node (recursive)
|
||||
*
|
||||
|
@ -301,13 +414,10 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator<ServerDeclar
|
|||
List<CommandParamData[]> 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<CommandParamData>() {
|
||||
{
|
||||
add(child.getParamData());
|
||||
addAll(Arrays.asList(subchild));
|
||||
}
|
||||
}.toArray(new CommandParamData[0]);
|
||||
for (CommandParamData[] subChild : childTree) {
|
||||
CommandParamData[] tmpTree = new CommandParamData[subChild.length + 1];
|
||||
tmpTree[0] = child.getParamData();
|
||||
System.arraycopy(subChild, 0, tmpTree, 1, subChild.length);
|
||||
|
||||
treeParamData.add(tmpTree);
|
||||
}
|
||||
|
|
|
@ -39,6 +39,9 @@ public class JavaKeepAliveTranslator extends PacketTranslator<ServerKeepAlivePac
|
|||
|
||||
@Override
|
||||
public void translate(ServerKeepAlivePacket packet, GeyserSession session) {
|
||||
if (!session.getConnector().getConfig().isForwardPlayerPing()) {
|
||||
return;
|
||||
}
|
||||
NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket();
|
||||
latencyPacket.setFromServer(true);
|
||||
latencyPacket.setTimestamp(packet.getPingId() * 1000);
|
||||
|
|
|
@ -71,6 +71,7 @@ public class JavaTitleTranslator extends PacketTranslator<ServerTitlePacket> {
|
|||
titlePacket.setFadeInTime(packet.getFadeIn());
|
||||
titlePacket.setFadeOutTime(packet.getFadeOut());
|
||||
titlePacket.setStayTime(packet.getStay());
|
||||
titlePacket.setText("");
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -60,6 +60,9 @@ public class JavaEntityAnimationTranslator extends PacketTranslator<ServerEntity
|
|||
case LEAVE_BED:
|
||||
animatePacket.setAction(AnimatePacket.Action.WAKE_UP);
|
||||
break;
|
||||
default:
|
||||
// Unknown Animation
|
||||
return;
|
||||
}
|
||||
|
||||
session.sendUpstreamPacket(animatePacket);
|
||||
|
|
|
@ -32,6 +32,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
|
|||
import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnEntityPacket;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import org.geysermc.connector.entity.*;
|
||||
import org.geysermc.connector.entity.player.PlayerEntity;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||
import org.geysermc.connector.network.translators.Translator;
|
||||
|
@ -69,8 +70,18 @@ public class JavaSpawnEntityTranslator extends PacketTranslator<ServerSpawnEntit
|
|||
type, position, motion, rotation, (HangingDirection) packet.getData());
|
||||
} else if (packet.getType() == EntityType.FISHING_BOBBER) {
|
||||
// Fishing bobbers need the owner for the line
|
||||
entity = new FishingHookEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(),
|
||||
type, position, motion, rotation, (ProjectileData) packet.getData());
|
||||
int ownerEntityId = ((ProjectileData) packet.getData()).getOwnerId();
|
||||
Entity owner = session.getEntityCache().getEntityByJavaId(ownerEntityId);
|
||||
if (owner == null && session.getPlayerEntity().getEntityId() == ownerEntityId) {
|
||||
owner = session.getPlayerEntity();
|
||||
}
|
||||
// Java clients only spawn fishing hooks with a player as its owner
|
||||
if (owner instanceof PlayerEntity) {
|
||||
entity = new FishingHookEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(),
|
||||
type, position, motion, rotation, (PlayerEntity) owner);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (packet.getType() == EntityType.BOAT) {
|
||||
// Initial rotation is incorrect
|
||||
entity = new BoatEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(),
|
||||
|
|
|
@ -52,7 +52,7 @@ public class JavaPlaySoundTranslator extends PacketTranslator<ServerPlaySoundPac
|
|||
|
||||
SoundRegistry.SoundMapping soundMapping = SoundRegistry.fromJava(packetSound.replace("minecraft:", ""));
|
||||
String playsound;
|
||||
if(soundMapping == null || soundMapping.getPlaysound() == null) {
|
||||
if (soundMapping == null || soundMapping.getPlaysound() == null) {
|
||||
// no mapping
|
||||
session.getConnector().getLogger()
|
||||
.debug("[PlaySound] Defaulting to sound server gave us for " + packet.toString());
|
||||
|
|
|
@ -44,7 +44,7 @@ public class JavaUnloadChunkTranslator extends PacketTranslator<ServerUnloadChun
|
|||
Iterator<Vector3i> iterator = session.getSkullCache().keySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Vector3i position = iterator.next();
|
||||
if (Math.floor(position.getX() / 16) == packet.getX() && Math.floor(position.getZ() / 16) == packet.getZ()) {
|
||||
if ((position.getX() >> 4) == packet.getX() && (position.getZ() >> 4) == packet.getZ()) {
|
||||
session.getSkullCache().get(position).despawnEntity(session);
|
||||
iterator.remove();
|
||||
}
|
||||
|
|
|
@ -49,70 +49,72 @@ public class BlockStateValues {
|
|||
private static final Int2ByteMap SKULL_ROTATIONS = new Int2ByteOpenHashMap();
|
||||
private static final Int2IntMap SKULL_WALL_DIRECTIONS = new Int2IntOpenHashMap();
|
||||
private static final Int2ByteMap SHULKERBOX_DIRECTIONS = new Int2ByteOpenHashMap();
|
||||
private static final Int2IntMap WATER_LEVEL = new Int2IntOpenHashMap();
|
||||
|
||||
/**
|
||||
* Determines if the block state contains Bedrock block information
|
||||
*
|
||||
* @param entry The String to JsonNode map used in BlockTranslator
|
||||
* @param javaId The Java Identifier of the block
|
||||
* @param javaBlockState the Java Block State of the block
|
||||
* @param blockData JsonNode of info about the block from blocks.json
|
||||
*/
|
||||
public static void storeBlockStateValues(Map.Entry<String, JsonNode> entry, int javaBlockState) {
|
||||
JsonNode bannerColor = entry.getValue().get("banner_color");
|
||||
public static void storeBlockStateValues(String javaId, int javaBlockState, JsonNode blockData) {
|
||||
JsonNode bannerColor = blockData.get("banner_color");
|
||||
if (bannerColor != null) {
|
||||
BANNER_COLORS.put(javaBlockState, (byte) bannerColor.intValue());
|
||||
return; // There will never be a banner color and a skull variant
|
||||
}
|
||||
|
||||
JsonNode bedColor = entry.getValue().get("bed_color");
|
||||
JsonNode bedColor = blockData.get("bed_color");
|
||||
if (bedColor != null) {
|
||||
BED_COLORS.put(javaBlockState, (byte) bedColor.intValue());
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.getKey().contains("command_block")) {
|
||||
COMMAND_BLOCK_VALUES.put(javaBlockState, entry.getKey().contains("conditional=true") ? (byte) 1 : (byte) 0);
|
||||
if (javaId.contains("command_block")) {
|
||||
COMMAND_BLOCK_VALUES.put(javaBlockState, javaId.contains("conditional=true") ? (byte) 1 : (byte) 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.getValue().get("double_chest_position") != null) {
|
||||
boolean isX = (entry.getValue().get("x") != null);
|
||||
boolean isDirectionPositive = ((entry.getValue().get("x") != null && entry.getValue().get("x").asBoolean()) ||
|
||||
(entry.getValue().get("z") != null && entry.getValue().get("z").asBoolean()));
|
||||
boolean isLeft = (entry.getValue().get("double_chest_position").asText().contains("left"));
|
||||
if (blockData.get("double_chest_position") != null) {
|
||||
boolean isX = (blockData.get("x") != null);
|
||||
boolean isDirectionPositive = ((blockData.get("x") != null && blockData.get("x").asBoolean()) ||
|
||||
(blockData.get("z") != null && blockData.get("z").asBoolean()));
|
||||
boolean isLeft = (blockData.get("double_chest_position").asText().contains("left"));
|
||||
DOUBLE_CHEST_VALUES.put(javaBlockState, new DoubleChestValue(isX, isDirectionPositive, isLeft));
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.getKey().contains("potted_") || entry.getKey().contains("flower_pot")) {
|
||||
FLOWER_POT_VALUES.put(javaBlockState, entry.getKey().replace("potted_", ""));
|
||||
if (javaId.contains("potted_") || javaId.contains("flower_pot")) {
|
||||
FLOWER_POT_VALUES.put(javaBlockState, javaId.replace("potted_", ""));
|
||||
return;
|
||||
}
|
||||
|
||||
JsonNode notePitch = entry.getValue().get("note_pitch");
|
||||
JsonNode notePitch = blockData.get("note_pitch");
|
||||
if (notePitch != null) {
|
||||
NOTEBLOCK_PITCHES.put(javaBlockState, entry.getValue().get("note_pitch").intValue());
|
||||
NOTEBLOCK_PITCHES.put(javaBlockState, blockData.get("note_pitch").intValue());
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.getKey().contains("piston")) {
|
||||
if (javaId.contains("piston")) {
|
||||
// True if extended, false if not
|
||||
PISTON_VALUES.put(javaBlockState, entry.getKey().contains("extended=true"));
|
||||
IS_STICKY_PISTON.put(javaBlockState, entry.getKey().contains("sticky"));
|
||||
PISTON_VALUES.put(javaBlockState, javaId.contains("extended=true"));
|
||||
IS_STICKY_PISTON.put(javaBlockState, javaId.contains("sticky"));
|
||||
return;
|
||||
}
|
||||
|
||||
JsonNode skullVariation = entry.getValue().get("variation");
|
||||
JsonNode skullVariation = blockData.get("variation");
|
||||
if (skullVariation != null) {
|
||||
SKULL_VARIANTS.put(javaBlockState, (byte) skullVariation.intValue());
|
||||
}
|
||||
|
||||
JsonNode skullRotation = entry.getValue().get("skull_rotation");
|
||||
JsonNode skullRotation = blockData.get("skull_rotation");
|
||||
if (skullRotation != null) {
|
||||
SKULL_ROTATIONS.put(javaBlockState, (byte) skullRotation.intValue());
|
||||
}
|
||||
|
||||
if (entry.getKey().contains("wall_skull") || entry.getKey().contains("wall_head")) {
|
||||
String direction = entry.getKey().substring(entry.getKey().lastIndexOf("facing=") + 7);
|
||||
if (javaId.contains("wall_skull") || javaId.contains("wall_head")) {
|
||||
String direction = javaId.substring(javaId.lastIndexOf("facing=") + 7);
|
||||
int rotation = 0;
|
||||
switch (direction.substring(0, direction.length() - 1)) {
|
||||
case "north":
|
||||
|
@ -131,10 +133,16 @@ public class BlockStateValues {
|
|||
SKULL_WALL_DIRECTIONS.put(javaBlockState, rotation);
|
||||
}
|
||||
|
||||
JsonNode shulkerDirection = entry.getValue().get("shulker_direction");
|
||||
JsonNode shulkerDirection = blockData.get("shulker_direction");
|
||||
if (shulkerDirection != null) {
|
||||
BlockStateValues.SHULKERBOX_DIRECTIONS.put(javaBlockState, (byte) shulkerDirection.intValue());
|
||||
}
|
||||
|
||||
if (javaId.startsWith("minecraft:water")) {
|
||||
String strLevel = javaId.substring(javaId.lastIndexOf("level=") + 6, javaId.length() - 1);
|
||||
int level = Integer.parseInt(strLevel);
|
||||
WATER_LEVEL.put(javaBlockState, level);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -263,4 +271,15 @@ public class BlockStateValues {
|
|||
public static byte getShulkerBoxDirection(int state) {
|
||||
return SHULKERBOX_DIRECTIONS.getOrDefault(state, (byte) -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the level of water from the block state.
|
||||
* This is used in FishingHookEntity to create splash sounds when the hook hits the water.
|
||||
*
|
||||
* @param state BlockState of the block
|
||||
* @return The water level or -1 if the block isn't water
|
||||
*/
|
||||
public static int getWaterLevel(int state) {
|
||||
return WATER_LEVEL.getOrDefault(state, -1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -185,7 +185,7 @@ public class BlockTranslator {
|
|||
|
||||
JAVA_ID_BLOCK_MAP.put(javaId, javaRuntimeId);
|
||||
|
||||
BlockStateValues.storeBlockStateValues(entry, javaRuntimeId);
|
||||
BlockStateValues.storeBlockStateValues(entry.getKey(), javaRuntimeId, entry.getValue());
|
||||
|
||||
String cleanJavaIdentifier = entry.getKey().split("\\[")[0];
|
||||
|
||||
|
@ -386,4 +386,11 @@ public class BlockTranslator {
|
|||
}
|
||||
return itemIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a list of all Java block identifiers. For use with command suggestions.
|
||||
*/
|
||||
public static String[] getAllBlockIdentifiers() {
|
||||
return JAVA_ID_TO_JAVA_IDENTIFIER_MAP.values().toArray(new String[0]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,8 @@ package org.geysermc.connector.network.translators.world.block.entity;
|
|||
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.IntTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.LongTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import com.nukkitx.nbt.NbtList;
|
||||
import com.nukkitx.nbt.NbtMapBuilder;
|
||||
import com.nukkitx.nbt.NbtType;
|
||||
|
@ -39,7 +41,10 @@ import java.util.LinkedHashMap;
|
|||
public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator {
|
||||
@Override
|
||||
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
|
||||
builder.put("Age", (int) ((long) tag.get("Age").getValue()));
|
||||
Tag ageTag = tag.get("Age");
|
||||
if (ageTag instanceof LongTag) {
|
||||
builder.put("Age", (int) ((long) ageTag.getValue()));
|
||||
}
|
||||
// Java sometimes does not provide this tag, but Bedrock crashes if it doesn't exist
|
||||
// Linked coordinates
|
||||
IntList tagsList = new IntArrayList();
|
||||
|
|
|
@ -33,6 +33,7 @@ import com.nukkitx.protocol.bedrock.packet.PlayerListPacket;
|
|||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.common.AuthType;
|
||||
import org.geysermc.connector.entity.player.PlayerEntity;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.session.auth.BedrockClientData;
|
||||
|
@ -80,7 +81,7 @@ public class SkinManager {
|
|||
String capeId, byte[] capeData,
|
||||
SkinProvider.SkinGeometry geometry) {
|
||||
SerializedSkin serializedSkin = SerializedSkin.of(
|
||||
skinId, geometry.getGeometryName(), ImageData.of(skinData), Collections.emptyList(),
|
||||
skinId, "", geometry.getGeometryName(), ImageData.of(skinData), Collections.emptyList(),
|
||||
ImageData.of(capeData), geometry.getGeometryData(), "", true, false,
|
||||
!capeId.equals(SkinProvider.EMPTY_CAPE.getCapeId()), capeId, skinId
|
||||
);
|
||||
|
@ -163,7 +164,7 @@ public class SkinManager {
|
|||
geometry = SkinProvider.SkinGeometry.getEars(data.isAlex());
|
||||
|
||||
// Store the skin and geometry for the ears
|
||||
SkinProvider.storeEarSkin(entity.getUuid(), skin);
|
||||
SkinProvider.storeEarSkin(skin);
|
||||
SkinProvider.storeEarGeometry(entity.getUuid(), data.isAlex());
|
||||
}
|
||||
}
|
||||
|
@ -267,7 +268,10 @@ public class SkinManager {
|
|||
|
||||
return new GameProfileData(skinUrl, capeUrl, isAlex);
|
||||
} catch (Exception exception) {
|
||||
GeyserConnector.getInstance().getLogger().debug("Something went wrong while processing skin for " + profile.getName() + ": " + exception.getMessage());
|
||||
GeyserConnector.getInstance().getLogger().debug("Something went wrong while processing skin for " + profile.getName());
|
||||
if (GeyserConnector.getInstance().getConfig().isDebugMode()) {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
return loadBedrockOrOfflineSkin(profile);
|
||||
}
|
||||
}
|
||||
|
@ -282,7 +286,7 @@ public class SkinManager {
|
|||
|
||||
String skinUrl = isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl();
|
||||
String capeUrl = SkinProvider.EMPTY_CAPE.getTextureUrl();
|
||||
if ("steve".equals(skinUrl) || "alex".equals(skinUrl)) {
|
||||
if (("steve".equals(skinUrl) || "alex".equals(skinUrl)) && GeyserConnector.getInstance().getAuthType() != AuthType.ONLINE) {
|
||||
GeyserSession session = GeyserConnector.getInstance().getPlayerByUuid(profile.getId());
|
||||
|
||||
if (session != null) {
|
||||
|
|
|
@ -79,13 +79,12 @@ public class SkinProvider {
|
|||
.build();
|
||||
private static final Map<String, CompletableFuture<Cape>> requestedCapes = new ConcurrentHashMap<>();
|
||||
|
||||
public static final SkinGeometry EMPTY_GEOMETRY = SkinProvider.SkinGeometry.getLegacy(false);
|
||||
private static final Map<UUID, SkinGeometry> cachedGeometry = new ConcurrentHashMap<>();
|
||||
|
||||
public static final boolean ALLOW_THIRD_PARTY_EARS = GeyserConnector.getInstance().getConfig().isAllowThirdPartyEars();
|
||||
public static String EARS_GEOMETRY;
|
||||
public static String EARS_GEOMETRY_SLIM;
|
||||
public static SkinGeometry SKULL_GEOMETRY;
|
||||
public static final String EARS_GEOMETRY;
|
||||
public static final String EARS_GEOMETRY_SLIM;
|
||||
public static final SkinGeometry SKULL_GEOMETRY;
|
||||
|
||||
public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
|
@ -229,15 +228,15 @@ public class SkinProvider {
|
|||
return CompletableFuture.completedFuture(officialCape);
|
||||
}
|
||||
|
||||
public static CompletableFuture<Skin> requestEars(String earsUrl, EarsProvider provider, boolean newThread, Skin skin) {
|
||||
public static CompletableFuture<Skin> requestEars(String earsUrl, boolean newThread, Skin skin) {
|
||||
if (earsUrl == null || earsUrl.isEmpty()) return CompletableFuture.completedFuture(skin);
|
||||
|
||||
CompletableFuture<Skin> future;
|
||||
if (newThread) {
|
||||
future = CompletableFuture.supplyAsync(() -> supplyEars(skin, earsUrl, provider), EXECUTOR_SERVICE)
|
||||
future = CompletableFuture.supplyAsync(() -> supplyEars(skin, earsUrl), EXECUTOR_SERVICE)
|
||||
.whenCompleteAsync((outSkin, throwable) -> { });
|
||||
} else {
|
||||
Skin ears = supplyEars(skin, earsUrl, provider); // blocking
|
||||
Skin ears = supplyEars(skin, earsUrl); // blocking
|
||||
future = CompletableFuture.completedFuture(ears);
|
||||
}
|
||||
return future;
|
||||
|
@ -255,7 +254,7 @@ public class SkinProvider {
|
|||
public static CompletableFuture<Skin> requestUnofficialEars(Skin officialSkin, UUID playerId, String username, boolean newThread) {
|
||||
for (EarsProvider provider : EarsProvider.VALUES) {
|
||||
Skin skin1 = getOrDefault(
|
||||
requestEars(provider.getUrlFor(playerId, username), provider, newThread, officialSkin),
|
||||
requestEars(provider.getUrlFor(playerId, username), newThread, officialSkin),
|
||||
officialSkin, 4
|
||||
);
|
||||
if (skin1.isEars()) {
|
||||
|
@ -295,12 +294,11 @@ public class SkinProvider {
|
|||
}
|
||||
|
||||
/**
|
||||
* Stores the ajusted skin with the ear texture to the cache
|
||||
* Stores the adjusted skin with the ear texture to the cache
|
||||
*
|
||||
* @param playerID The UUID to cache it against
|
||||
* @param skin The skin to cache
|
||||
*/
|
||||
public static void storeEarSkin(UUID playerID, Skin skin) {
|
||||
public static void storeEarSkin(Skin skin) {
|
||||
cachedSkins.put(skin.getTextureUrl(), skin);
|
||||
}
|
||||
|
||||
|
@ -324,7 +322,7 @@ public class SkinProvider {
|
|||
}
|
||||
|
||||
private static Cape supplyCape(String capeUrl, CapeProvider provider) {
|
||||
byte[] cape = new byte[0];
|
||||
byte[] cape = EMPTY_CAPE.getCapeData();
|
||||
try {
|
||||
cape = requestImage(capeUrl, provider);
|
||||
} catch (Exception ignored) {} // just ignore I guess
|
||||
|
@ -334,7 +332,7 @@ public class SkinProvider {
|
|||
return new Cape(
|
||||
capeUrl,
|
||||
urlSection[urlSection.length - 1], // get the texture id and use it as cape id
|
||||
cape.length > 0 ? cape : EMPTY_CAPE.getCapeData(),
|
||||
cape,
|
||||
System.currentTimeMillis(),
|
||||
cape.length == 0
|
||||
);
|
||||
|
@ -345,10 +343,9 @@ public class SkinProvider {
|
|||
*
|
||||
* @param existingSkin The players current skin
|
||||
* @param earsUrl The URL to get the ears texture from
|
||||
* @param provider The ears texture provider
|
||||
* @return The updated skin with ears
|
||||
*/
|
||||
private static Skin supplyEars(Skin existingSkin, String earsUrl, EarsProvider provider) {
|
||||
private static Skin supplyEars(Skin existingSkin, String earsUrl) {
|
||||
try {
|
||||
// Get the ears texture
|
||||
BufferedImage ears = ImageIO.read(new URL(earsUrl));
|
||||
|
@ -415,14 +412,15 @@ public class SkinProvider {
|
|||
|
||||
// if the requested image is a cape
|
||||
if (provider != null) {
|
||||
while(image.getWidth() > 64) {
|
||||
image = scale(image);
|
||||
if (image.getWidth() > 64) {
|
||||
image = scale(image, 64, 32);
|
||||
}
|
||||
} else {
|
||||
// Very rarely, skins can be larger than Minecraft's default.
|
||||
// Bedrock will not render anything above a width of 128.
|
||||
if (image.getWidth() > 128) {
|
||||
image = scale(image, 128, image.getHeight() / (image.getWidth() / 128));
|
||||
}
|
||||
BufferedImage newImage = new BufferedImage(64, 32, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics g = newImage.createGraphics();
|
||||
g.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null);
|
||||
g.dispose();
|
||||
image = newImage;
|
||||
}
|
||||
|
||||
byte[] data = bufferedImageToImageData(image);
|
||||
|
@ -506,12 +504,13 @@ public class SkinProvider {
|
|||
return null;
|
||||
}
|
||||
|
||||
public static BufferedImage scale(BufferedImage bufferedImage) {
|
||||
BufferedImage resized = new BufferedImage(bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2, BufferedImage.TYPE_INT_ARGB);
|
||||
public static BufferedImage scale(BufferedImage bufferedImage, int newWidth, int newHeight) {
|
||||
BufferedImage resized = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2 = resized.createGraphics();
|
||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g2.drawImage(bufferedImage, 0, 0, bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2, null);
|
||||
g2.drawImage(bufferedImage, 0, 0, newWidth, newHeight, null);
|
||||
g2.dispose();
|
||||
bufferedImage.flush();
|
||||
return resized;
|
||||
}
|
||||
|
||||
|
@ -578,17 +577,17 @@ public class SkinProvider {
|
|||
@AllArgsConstructor
|
||||
@Getter
|
||||
public static class SkinAndCape {
|
||||
private Skin skin;
|
||||
private Cape cape;
|
||||
private final Skin skin;
|
||||
private final Cape cape;
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public static class Skin {
|
||||
private UUID skinOwner;
|
||||
private String textureUrl;
|
||||
private byte[] skinData;
|
||||
private long requestedOn;
|
||||
private final String textureUrl;
|
||||
private final byte[] skinData;
|
||||
private final long requestedOn;
|
||||
private boolean updated;
|
||||
private boolean ears;
|
||||
|
||||
|
@ -602,19 +601,19 @@ public class SkinProvider {
|
|||
@AllArgsConstructor
|
||||
@Getter
|
||||
public static class Cape {
|
||||
private String textureUrl;
|
||||
private String capeId;
|
||||
private byte[] capeData;
|
||||
private long requestedOn;
|
||||
private boolean failed;
|
||||
private final String textureUrl;
|
||||
private final String capeId;
|
||||
private final byte[] capeData;
|
||||
private final long requestedOn;
|
||||
private final boolean failed;
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public static class SkinGeometry {
|
||||
private String geometryName;
|
||||
private String geometryData;
|
||||
private boolean failed;
|
||||
private final String geometryName;
|
||||
private final String geometryData;
|
||||
private final boolean failed;
|
||||
|
||||
/**
|
||||
* Generate generic geometry
|
||||
|
|
|
@ -27,65 +27,42 @@ package org.geysermc.connector.skin;
|
|||
|
||||
import com.nukkitx.protocol.bedrock.data.skin.ImageData;
|
||||
import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin;
|
||||
import com.nukkitx.protocol.bedrock.packet.PlayerListPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.PlayerSkinPacket;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.entity.player.PlayerEntity;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class SkullSkinManager extends SkinManager {
|
||||
|
||||
public static PlayerListPacket.Entry buildSkullEntryManually(UUID uuid, String username, long geyserId,
|
||||
String skinId, byte[] skinData) {
|
||||
public static SerializedSkin buildSkullEntryManually(String skinId, byte[] skinData) {
|
||||
// Prevents https://cdn.discordapp.com/attachments/613194828359925800/779458146191147008/unknown.png
|
||||
skinId = skinId + "_skull";
|
||||
SerializedSkin serializedSkin = SerializedSkin.of(
|
||||
skinId, SkinProvider.SKULL_GEOMETRY.getGeometryName(), ImageData.of(skinData), Collections.emptyList(),
|
||||
return SerializedSkin.of(
|
||||
skinId, "", SkinProvider.SKULL_GEOMETRY.getGeometryName(), ImageData.of(skinData), Collections.emptyList(),
|
||||
ImageData.of(SkinProvider.EMPTY_CAPE.getCapeData()), SkinProvider.SKULL_GEOMETRY.getGeometryData(),
|
||||
"", true, false, false, SkinProvider.EMPTY_CAPE.getCapeId(), skinId
|
||||
);
|
||||
|
||||
PlayerListPacket.Entry entry = new PlayerListPacket.Entry(uuid);
|
||||
entry.setName(username);
|
||||
entry.setEntityId(geyserId);
|
||||
entry.setSkin(serializedSkin);
|
||||
entry.setXuid("");
|
||||
entry.setPlatformChatId("");
|
||||
entry.setTeacher(false);
|
||||
entry.setTrustedSkin(true);
|
||||
return entry;
|
||||
}
|
||||
|
||||
public static void requestAndHandleSkin(PlayerEntity entity, GeyserSession session,
|
||||
Consumer<SkinProvider.Skin> skinConsumer) {
|
||||
GameProfileData data = GameProfileData.from(entity.getProfile());
|
||||
|
||||
SkinProvider.requestSkin(entity.getUuid(), data.getSkinUrl(), false)
|
||||
SkinProvider.requestSkin(entity.getUuid(), data.getSkinUrl(), true)
|
||||
.whenCompleteAsync((skin, throwable) -> {
|
||||
try {
|
||||
if (session.getUpstream().isInitialized()) {
|
||||
PlayerListPacket.Entry updatedEntry = buildSkullEntryManually(
|
||||
entity.getUuid(),
|
||||
entity.getUsername(),
|
||||
entity.getGeyserId(),
|
||||
skin.getTextureUrl(),
|
||||
skin.getSkinData()
|
||||
);
|
||||
|
||||
PlayerListPacket playerAddPacket = new PlayerListPacket();
|
||||
playerAddPacket.setAction(PlayerListPacket.Action.ADD);
|
||||
playerAddPacket.getEntries().add(updatedEntry);
|
||||
session.sendUpstreamPacket(playerAddPacket);
|
||||
|
||||
// It's a skull. We don't want them in the player list.
|
||||
PlayerListPacket playerRemovePacket = new PlayerListPacket();
|
||||
playerRemovePacket.setAction(PlayerListPacket.Action.REMOVE);
|
||||
playerRemovePacket.getEntries().add(updatedEntry);
|
||||
session.sendUpstreamPacket(playerRemovePacket);
|
||||
PlayerSkinPacket packet = new PlayerSkinPacket();
|
||||
packet.setUuid(entity.getUuid());
|
||||
packet.setOldSkinName("");
|
||||
packet.setNewSkinName(skin.getTextureUrl());
|
||||
packet.setSkin(buildSkullEntryManually(skin.getTextureUrl(), skin.getSkinData()));
|
||||
packet.setTrustedSkin(true);
|
||||
session.sendUpstreamPacket(packet);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e);
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
package org.geysermc.connector.utils;
|
||||
|
||||
import com.nukkitx.protocol.bedrock.packet.SetTitlePacket;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -36,11 +35,10 @@ import java.util.concurrent.TimeUnit;
|
|||
* Much of the work here is from the wonderful folks from ViaRewind: https://github.com/ViaVersion/ViaRewind
|
||||
*/
|
||||
public class CooldownUtils {
|
||||
private static boolean SHOW_COOLDOWN;
|
||||
|
||||
private final static boolean SHOW_COOLDOWN;
|
||||
|
||||
static {
|
||||
SHOW_COOLDOWN = GeyserConnector.getInstance().getConfig().isShowCooldown();
|
||||
public static void setShowCooldown(boolean showCooldown) {
|
||||
SHOW_COOLDOWN = showCooldown;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -116,5 +114,4 @@ public class CooldownUtils {
|
|||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -90,21 +90,22 @@ public class FileUtils {
|
|||
*/
|
||||
public static File fileOrCopiedFromResource(File file, String name, Function<String, String> format) throws IOException {
|
||||
if (!file.exists()) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
file.createNewFile();
|
||||
FileOutputStream fos = new FileOutputStream(file);
|
||||
InputStream input = GeyserConnector.class.getResourceAsStream("/" + name); // resources need leading "/" prefix
|
||||
try (FileOutputStream fos = new FileOutputStream(file)) {
|
||||
try (InputStream input = GeyserConnector.class.getResourceAsStream("/" + name)) { // resources need leading "/" prefix
|
||||
byte[] bytes = new byte[input.available()];
|
||||
|
||||
byte[] bytes = new byte[input.available()];
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
input.read(bytes);
|
||||
|
||||
input.read(bytes);
|
||||
for(char c : format.apply(new String(bytes)).toCharArray()) {
|
||||
fos.write(c);
|
||||
}
|
||||
|
||||
for(char c : format.apply(new String(bytes)).toCharArray()) {
|
||||
fos.write(c);
|
||||
fos.flush();
|
||||
}
|
||||
}
|
||||
|
||||
fos.flush();
|
||||
input.close();
|
||||
fos.close();
|
||||
}
|
||||
|
||||
return file;
|
||||
|
@ -122,14 +123,13 @@ public class FileUtils {
|
|||
file.createNewFile();
|
||||
}
|
||||
|
||||
FileOutputStream fos = new FileOutputStream(file);
|
||||
try (FileOutputStream fos = new FileOutputStream(file)) {
|
||||
for (char c : data) {
|
||||
fos.write(c);
|
||||
}
|
||||
|
||||
for (char c : data) {
|
||||
fos.write(c);
|
||||
fos.flush();
|
||||
}
|
||||
|
||||
fos.flush();
|
||||
fos.close();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -232,9 +232,10 @@ public class FileUtils {
|
|||
try {
|
||||
int size = stream.available();
|
||||
byte[] bytes = new byte[size];
|
||||
BufferedInputStream buf = new BufferedInputStream(stream);
|
||||
buf.read(bytes, 0, bytes.length);
|
||||
buf.close();
|
||||
try (BufferedInputStream buf = new BufferedInputStream(stream)) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
buf.read(bytes, 0, bytes.length);
|
||||
}
|
||||
return bytes;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Error while trying to read input stream!");
|
||||
|
|
|
@ -69,8 +69,8 @@ public class LanguageUtils {
|
|||
// Load the locale
|
||||
if (localeStream != null) {
|
||||
Properties localeProp = new Properties();
|
||||
try {
|
||||
localeProp.load(new InputStreamReader(localeStream, StandardCharsets.UTF_8));
|
||||
try (InputStreamReader reader = new InputStreamReader(localeStream, StandardCharsets.UTF_8)) {
|
||||
localeProp.load(reader);
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError(getLocaleStringLog("geyser.language.load_failed", locale), e);
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ public class LocaleUtils {
|
|||
ASSET_MAP.put(entry.getKey(), asset);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.locale.fail.asset_cache", (!e.getMessage().isEmpty() ? e.getMessage() : e.getStackTrace())));
|
||||
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.locale.fail.asset_cache", (!e.getMessage().isEmpty() ? e.getMessage() : e.getStackTrace())));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,6 +147,12 @@ public class LocaleUtils {
|
|||
}
|
||||
}
|
||||
} catch (IOException ignored) { }
|
||||
|
||||
if (clientJarInfo == null) {
|
||||
// Likely failed to download
|
||||
GeyserConnector.getInstance().getLogger().debug("Skipping en_US hash check as client jar is null.");
|
||||
return;
|
||||
}
|
||||
targetHash = clientJarInfo.getSha1();
|
||||
} else {
|
||||
curHash = byteArrayToHexString(FileUtils.calculateSHA1(localeFile));
|
||||
|
@ -168,9 +174,13 @@ public class LocaleUtils {
|
|||
return;
|
||||
}
|
||||
|
||||
// Get the hash and download the locale
|
||||
String hash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash();
|
||||
WebUtils.downloadFile("https://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, localeFile.toString());
|
||||
try {
|
||||
// Get the hash and download the locale
|
||||
String hash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash();
|
||||
WebUtils.downloadFile("https://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, localeFile.toString());
|
||||
} catch (Exception e) {
|
||||
GeyserConnector.getInstance().getLogger().error("Unable to download locale file hash", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -236,31 +246,30 @@ public class LocaleUtils {
|
|||
WebUtils.downloadFile(clientJarInfo.getUrl(), tmpFilePath.toString());
|
||||
|
||||
// Load in the JAR as a zip and extract the file
|
||||
ZipFile localeJar = new ZipFile(tmpFilePath.toString());
|
||||
InputStream fileStream = localeJar.getInputStream(localeJar.getEntry("assets/minecraft/lang/en_us.json"));
|
||||
FileOutputStream outStream = new FileOutputStream(localeFile);
|
||||
try (ZipFile localeJar = new ZipFile(tmpFilePath.toString())) {
|
||||
try (InputStream fileStream = localeJar.getInputStream(localeJar.getEntry("assets/minecraft/lang/en_us.json"))) {
|
||||
try (FileOutputStream outStream = new FileOutputStream(localeFile)) {
|
||||
|
||||
// Write the file to the locale dir
|
||||
byte[] buf = new byte[fileStream.available()];
|
||||
int length;
|
||||
while ((length = fileStream.read(buf)) != -1) {
|
||||
outStream.write(buf, 0, length);
|
||||
// Write the file to the locale dir
|
||||
byte[] buf = new byte[fileStream.available()];
|
||||
int length;
|
||||
while ((length = fileStream.read(buf)) != -1) {
|
||||
outStream.write(buf, 0, length);
|
||||
}
|
||||
|
||||
// Flush all changes to disk and cleanup
|
||||
outStream.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Flush all changes to disk and cleanup
|
||||
outStream.flush();
|
||||
outStream.close();
|
||||
|
||||
fileStream.close();
|
||||
localeJar.close();
|
||||
|
||||
// Store the latest jar hash
|
||||
FileUtils.writeFile(GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("locales/en_us.hash").toString(), clientJarInfo.getSha1().toCharArray());
|
||||
|
||||
// Delete the nolonger needed client/server jar
|
||||
Files.delete(tmpFilePath);
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.locale.fail.en_us"), e);
|
||||
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.locale.fail.en_us"), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,8 @@ import org.geysermc.connector.GeyserConnector;
|
|||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
/**
|
||||
|
@ -70,10 +72,12 @@ public class ResourcePack {
|
|||
|
||||
pack.sha256 = FileUtils.calculateSHA256(file);
|
||||
|
||||
Stream<? extends ZipEntry> stream = null;
|
||||
try {
|
||||
ZipFile zip = new ZipFile(file);
|
||||
|
||||
zip.stream().forEach((x) -> {
|
||||
stream = zip.stream();
|
||||
stream.forEach((x) -> {
|
||||
if (x.getName().contains("manifest.json")) {
|
||||
try {
|
||||
ResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), ResourcePackManifest.class);
|
||||
|
@ -94,6 +98,10 @@ public class ResourcePack {
|
|||
} catch (Exception e) {
|
||||
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.resource_pack.broken", file.getName()));
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (stream != null) {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,8 +88,7 @@ public class WebUtils {
|
|||
}
|
||||
|
||||
public static String post(String reqURL, String postContent) throws IOException {
|
||||
URL url = null;
|
||||
url = new URL(reqURL);
|
||||
URL url = new URL(reqURL);
|
||||
HttpURLConnection con = (HttpURLConnection) url.openConnection();
|
||||
con.setRequestMethod("POST");
|
||||
con.setRequestProperty("Content-Type", "text/plain");
|
||||
|
@ -112,7 +111,7 @@ public class WebUtils {
|
|||
*/
|
||||
private static String connectionToString(HttpURLConnection con) throws IOException {
|
||||
// Send the request (we dont use this but its required for getErrorStream() to work)
|
||||
int code = con.getResponseCode();
|
||||
con.getResponseCode();
|
||||
|
||||
// Read the error message if there is one if not just read normally
|
||||
InputStream inputStream = con.getErrorStream();
|
||||
|
@ -120,18 +119,18 @@ public class WebUtils {
|
|||
inputStream = con.getInputStream();
|
||||
}
|
||||
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
|
||||
String inputLine;
|
||||
StringBuffer content = new StringBuffer();
|
||||
StringBuilder content = new StringBuilder();
|
||||
try (BufferedReader in = new BufferedReader(new InputStreamReader(inputStream))) {
|
||||
String inputLine;
|
||||
|
||||
while ((inputLine = in.readLine()) != null) {
|
||||
content.append(inputLine);
|
||||
content.append("\n");
|
||||
while ((inputLine = in.readLine()) != null) {
|
||||
content.append(inputLine);
|
||||
content.append("\n");
|
||||
}
|
||||
|
||||
con.disconnect();
|
||||
}
|
||||
|
||||
in.close();
|
||||
con.disconnect();
|
||||
|
||||
return content.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,19 @@ bedrock:
|
|||
# This option is for the plugin version only.
|
||||
clone-remote-port: false
|
||||
# The MOTD that will be broadcasted to Minecraft: Bedrock Edition clients. This is irrelevant if "passthrough-motd" is set to true
|
||||
motd1: "GeyserMC"
|
||||
motd2: "Another GeyserMC forced host."
|
||||
# If either of these are empty, the respective string will default to "Geyser"
|
||||
motd1: "Geyser"
|
||||
motd2: "Another Geyser server."
|
||||
# The Server Name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu.
|
||||
server-name: "Geyser"
|
||||
# Whether to enable PROXY protocol or not for clients. You DO NOT WANT this feature unless you run UDP reverse proxy
|
||||
# in front of your Geyser instance.
|
||||
enable-proxy-protocol: false
|
||||
# A list of allowed PROXY protocol speaking proxy IP addresses/subnets. Only effective when "enable-proxy-protocol" is enabled, and
|
||||
# should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.).
|
||||
# Keeping this list empty means there is no IP address whitelist.
|
||||
# Both IP addresses and subnets are supported.
|
||||
#proxy-protocol-whitelisted-ips: [ "127.0.0.1", "172.18.0.0/16" ]
|
||||
remote:
|
||||
# The IP address of the remote (Java Edition) server
|
||||
# If it is "auto", for standalone version the remote address will be set to 127.0.0.1,
|
||||
|
@ -81,7 +90,11 @@ legacy-ping-passthrough: false
|
|||
# Increase if you are getting BrokenPipe errors.
|
||||
ping-passthrough-interval: 3
|
||||
|
||||
# Maximum amount of players that can connect
|
||||
# Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate
|
||||
# ping, it may also cause players to time out more easily.
|
||||
forward-player-ping: false
|
||||
|
||||
# Maximum amount of players that can connect. This is only visual at this time and does not actually limit player count.
|
||||
max-players: 100
|
||||
|
||||
# If debug messages should be sent through console
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 2e52b01cc541c8925346f93be8940087d9af1661
|
||||
Subproject commit bf0610450ce94507a18286e94af2965550ff9eaa
|
Loading…
Reference in a new issue