Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-09-08 20:43:04 +02: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
Dieser Commit ist enthalten in:
Commit
c16c66b860
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
Normale Datei
64
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normale Datei
@ -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
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: GeyserMC Discord
|
- name: Common Issues
|
||||||
url: http://discord.geysermc.org/
|
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.
|
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
Normale Datei
21
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normale Datei
@ -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>
|
||||||
<repository>
|
<repository>
|
||||||
<id>sponge-repo</id>
|
<id>sponge-repo</id>
|
||||||
<url>https://repo.spongepowered.org/maven</url>
|
<url>https://repo.spongepowered.org/repository/maven-public/</url>
|
||||||
</repository>
|
</repository>
|
||||||
<repository>
|
<repository>
|
||||||
<id>bungeecord-repo</id>
|
<id>bungeecord-repo</id>
|
||||||
|
@ -30,9 +30,9 @@
|
|||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.geysermc.adapters</groupId>
|
<groupId>org.geysermc.geyser.adapters</groupId>
|
||||||
<artifactId>spigot-all</artifactId>
|
<artifactId>spigot-all</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.1-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<build>
|
<build>
|
||||||
|
@ -28,7 +28,6 @@ package org.geysermc.platform.spigot;
|
|||||||
import com.github.steveice10.mc.protocol.MinecraftConstants;
|
import com.github.steveice10.mc.protocol.MinecraftConstants;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
import org.geysermc.adapters.spigot.SpigotAdapters;
|
|
||||||
import org.geysermc.common.PlatformType;
|
import org.geysermc.common.PlatformType;
|
||||||
import org.geysermc.connector.GeyserConnector;
|
import org.geysermc.connector.GeyserConnector;
|
||||||
import org.geysermc.connector.bootstrap.GeyserBootstrap;
|
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.ping.IGeyserPingPassthrough;
|
||||||
import org.geysermc.connector.utils.FileUtils;
|
import org.geysermc.connector.utils.FileUtils;
|
||||||
import org.geysermc.connector.utils.LanguageUtils;
|
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.GeyserSpigotCommandExecutor;
|
||||||
import org.geysermc.platform.spigot.command.GeyserSpigotCommandManager;
|
import org.geysermc.platform.spigot.command.GeyserSpigotCommandManager;
|
||||||
import org.geysermc.platform.spigot.command.SpigotCommandSender;
|
import org.geysermc.platform.spigot.command.SpigotCommandSender;
|
||||||
@ -69,6 +69,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||||||
|
|
||||||
private GeyserConnector connector;
|
private GeyserConnector connector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Minecraft server version, formatted as <code>1.#.#</code>
|
||||||
|
*/
|
||||||
|
private String minecraftVersion;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
// This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed
|
// 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);
|
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);
|
this.connector = GeyserConnector.start(PlatformType.SPIGOT, this);
|
||||||
|
|
||||||
if (geyserConfig.isLegacyPingPassthrough()) {
|
if (geyserConfig.isLegacyPingPassthrough()) {
|
||||||
@ -239,6 +247,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||||||
return new GeyserSpigotDumpInfo();
|
return new GeyserSpigotDumpInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMinecraftServerVersion() {
|
||||||
|
return this.minecraftVersion;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isCompatible(String version, String whichVersion) {
|
public boolean isCompatible(String version, String whichVersion) {
|
||||||
int[] currentVersion = parseVersion(version);
|
int[] currentVersion = parseVersion(version);
|
||||||
int[] otherVersion = parseVersion(whichVersion);
|
int[] otherVersion = parseVersion(whichVersion);
|
||||||
@ -277,10 +290,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||||||
* @return the server version before ViaVersion finishes initializing
|
* @return the server version before ViaVersion finishes initializing
|
||||||
*/
|
*/
|
||||||
public ProtocolVersion getServerProtocolVersion() {
|
public ProtocolVersion getServerProtocolVersion() {
|
||||||
String bukkitVersion = Bukkit.getServer().getVersion();
|
return ProtocolVersion.getClosest(this.minecraftVersion);
|
||||||
// Turn "(MC: 1.16.4)" into 1.16.4.
|
|
||||||
String version = bukkitVersion.split("\\(MC: ")[1].split("\\)")[0];
|
|
||||||
return ProtocolVersion.getClosest(version);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,10 +27,10 @@ package org.geysermc.platform.spigot.world.manager;
|
|||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.entity.Player;
|
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.session.GeyserSession;
|
||||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
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.api.Via;
|
||||||
import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage;
|
import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage;
|
||||||
|
|
||||||
|
@ -75,6 +75,10 @@ public class GeyserSpigot1_12WorldManager extends GeyserSpigotWorldManager {
|
|||||||
if (player == null) {
|
if (player == null) {
|
||||||
return BlockTranslator.JAVA_AIR_ID;
|
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
|
// Get block entity storage
|
||||||
BlockStorage storage = Via.getManager().getConnection(player.getUniqueId()).get(BlockStorage.class);
|
BlockStorage storage = Via.getManager().getConnection(player.getUniqueId()).get(BlockStorage.class);
|
||||||
Block block = player.getWorld().getBlockAt(x, y, z);
|
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.Bukkit;
|
||||||
import org.bukkit.entity.Player;
|
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.session.GeyserSession;
|
||||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
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 {
|
public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager {
|
||||||
protected final SpigotWorldAdapter adapter;
|
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());
|
geyserConfig.getBedrock().setPort(geyserConfig.getRemote().getPort());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,4 +163,9 @@ public class GeyserSpongePlugin implements GeyserBootstrap {
|
|||||||
public BootstrapDumpInfo getDumpInfo() {
|
public BootstrapDumpInfo getDumpInfo() {
|
||||||
return new GeyserSpongeDumpInfo();
|
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 com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import net.minecrell.terminalconsole.TerminalConsoleAppender;
|
import net.minecrell.terminalconsole.TerminalConsoleAppender;
|
||||||
|
import org.apache.logging.log4j.Level;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.core.Appender;
|
import org.apache.logging.log4j.core.Appender;
|
||||||
import org.apache.logging.log4j.core.Logger;
|
import org.apache.logging.log4j.core.Logger;
|
||||||
@ -167,11 +168,6 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
|||||||
this.onEnable();
|
this.onEnable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onEnable(boolean useGui) {
|
|
||||||
this.useGui = useGui;
|
|
||||||
this.onEnable();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
Logger logger = (Logger) LogManager.getRootLogger();
|
Logger logger = (Logger) LogManager.getRootLogger();
|
||||||
@ -213,6 +209,9 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
|||||||
}
|
}
|
||||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
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);
|
connector = GeyserConnector.start(PlatformType.STANDALONE, this);
|
||||||
geyserCommandManager = new GeyserCommandManager(connector);
|
geyserCommandManager = new GeyserCommandManager(connector);
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ public class LoopbackUtil {
|
|||||||
|
|
||||||
if (!result.contains("minecraftuwp")) {
|
if (!result.contains("minecraftuwp")) {
|
||||||
Files.write(Paths.get(System.getenv("temp") + "/loopback_minecraft.bat"), loopbackCommand.getBytes(), new OpenOption[0]);
|
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"));
|
geyserLogger.info(ChatColor.AQUA + LanguageUtils.getLocaleStringLog("geyser.bootstrap.loopback.added"));
|
||||||
}
|
}
|
||||||
|
@ -10,16 +10,21 @@
|
|||||||
</parent>
|
</parent>
|
||||||
<artifactId>connector</artifactId>
|
<artifactId>connector</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<netty.version>4.1.59.Final</netty.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.geysermc</groupId>
|
<groupId>org.geysermc</groupId>
|
||||||
<artifactId>common</artifactId>
|
<artifactId>common</artifactId>
|
||||||
<version>1.2.0-SNAPSHOT</version>
|
<version>1.2.0-SNAPSHOT</version>
|
||||||
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||||
<artifactId>jackson-dataformat-yaml</artifactId>
|
<artifactId>jackson-dataformat-yaml</artifactId>
|
||||||
<version>2.9.8</version>
|
<version>2.10.2</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -27,23 +32,16 @@
|
|||||||
<artifactId>Java-Websocket</artifactId>
|
<artifactId>Java-Websocket</artifactId>
|
||||||
<version>1.5.1</version>
|
<version>1.5.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
|
||||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
|
||||||
<version>2.9.8</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.CloudburstMC.Protocol</groupId>
|
<groupId>com.github.CloudburstMC.Protocol</groupId>
|
||||||
<artifactId>bedrock-v422</artifactId>
|
<artifactId>bedrock-v422</artifactId>
|
||||||
<version>d41b84e86c</version>
|
<version>294e7e5</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<groupId>net.sf.trove4j</groupId>
|
<groupId>net.sf.trove4j</groupId>
|
||||||
<artifactId>trove</artifactId>
|
<artifactId>trove</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
<!-- Stay on the older version of Network while it's rewritten -->
|
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<groupId>com.nukkitx.network</groupId>
|
<groupId>com.nukkitx.network</groupId>
|
||||||
<artifactId>raknet</artifactId>
|
<artifactId>raknet</artifactId>
|
||||||
@ -51,10 +49,16 @@
|
|||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.nukkitx.network</groupId>
|
<groupId>com.github.CloudburstMC.Network</groupId>
|
||||||
<artifactId>raknet</artifactId>
|
<artifactId>raknet</artifactId>
|
||||||
<version>1.6.20</version>
|
<version>a94d2dd</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>*</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.nukkitx.fastutil</groupId>
|
<groupId>com.nukkitx.fastutil</groupId>
|
||||||
@ -157,15 +161,51 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.netty</groupId>
|
<groupId>io.netty</groupId>
|
||||||
<artifactId>netty-resolver-dns</artifactId>
|
<artifactId>netty-resolver-dns</artifactId>
|
||||||
<version>4.1.43.Final</version>
|
<version>${netty.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</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>
|
<dependency>
|
||||||
<groupId>io.netty</groupId>
|
<groupId>io.netty</groupId>
|
||||||
<artifactId>netty-codec-haproxy</artifactId>
|
<artifactId>netty-codec-haproxy</artifactId>
|
||||||
<version>4.1.56.Final</version>
|
<version>${netty.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</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>
|
<dependency>
|
||||||
<groupId>org.reflections</groupId>
|
<groupId>org.reflections</groupId>
|
||||||
<artifactId>reflections</artifactId>
|
<artifactId>reflections</artifactId>
|
||||||
@ -179,25 +219,25 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.kyori</groupId>
|
<groupId>net.kyori</groupId>
|
||||||
<artifactId>adventure-api</artifactId>
|
<artifactId>adventure-api</artifactId>
|
||||||
<version>4.3.0</version>
|
<version>4.5.0</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.kyori</groupId>
|
<groupId>net.kyori</groupId>
|
||||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||||
<version>4.3.0</version>
|
<version>4.5.0</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.kyori</groupId>
|
<groupId>net.kyori</groupId>
|
||||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||||
<version>4.3.0</version>
|
<version>4.5.0</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.kyori</groupId>
|
<groupId>net.kyori</groupId>
|
||||||
<artifactId>adventure-text-serializer-gson-legacy-impl</artifactId>
|
<artifactId>adventure-text-serializer-gson-legacy-impl</artifactId>
|
||||||
<version>4.3.0</version>
|
<version>4.5.0</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -29,6 +29,7 @@ import com.fasterxml.jackson.core.JsonParser;
|
|||||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.nukkitx.network.raknet.RakNetConstants;
|
import com.nukkitx.network.raknet.RakNetConstants;
|
||||||
|
import com.nukkitx.network.util.EventLoops;
|
||||||
import com.nukkitx.protocol.bedrock.BedrockServer;
|
import com.nukkitx.protocol.bedrock.BedrockServer;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
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.BlockEntityTranslator;
|
||||||
import org.geysermc.connector.network.translators.world.block.entity.SkullBlockEntityTranslator;
|
import org.geysermc.connector.network.translators.world.block.entity.SkullBlockEntityTranslator;
|
||||||
import org.geysermc.connector.skin.FloodgateSkinUploader;
|
import org.geysermc.connector.skin.FloodgateSkinUploader;
|
||||||
import org.geysermc.connector.utils.DimensionUtils;
|
import org.geysermc.connector.utils.*;
|
||||||
import org.geysermc.connector.utils.LanguageUtils;
|
|
||||||
import org.geysermc.connector.utils.LocaleUtils;
|
|
||||||
import org.geysermc.connector.utils.ResourcePack;
|
|
||||||
import org.geysermc.floodgate.crypto.AesCipher;
|
import org.geysermc.floodgate.crypto.AesCipher;
|
||||||
import org.geysermc.floodgate.crypto.AesKeyProducer;
|
import org.geysermc.floodgate.crypto.AesKeyProducer;
|
||||||
import org.geysermc.floodgate.crypto.Base64Topping;
|
import org.geysermc.floodgate.crypto.Base64Topping;
|
||||||
@ -86,7 +84,8 @@ public class GeyserConnector {
|
|||||||
.enable(JsonParser.Feature.IGNORE_UNDEFINED)
|
.enable(JsonParser.Feature.IGNORE_UNDEFINED)
|
||||||
.enable(JsonParser.Feature.ALLOW_COMMENTS)
|
.enable(JsonParser.Feature.ALLOW_COMMENTS)
|
||||||
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
|
.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 NAME = "Geyser";
|
||||||
public static final String GIT_VERSION = "DEV"; // A fallback for running in IDEs
|
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
|
DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether
|
||||||
SkullBlockEntityTranslator.ALLOW_CUSTOM_SKULLS = config.isAllowCustomSkulls();
|
SkullBlockEntityTranslator.ALLOW_CUSTOM_SKULLS = config.isAllowCustomSkulls();
|
||||||
|
|
||||||
@ -218,7 +218,13 @@ public class GeyserConnector {
|
|||||||
RakNetConstants.MAXIMUM_MTU_SIZE = (short) config.getMtu();
|
RakNetConstants.MAXIMUM_MTU_SIZE = (short) config.getMtu();
|
||||||
logger.debug("Setting MTU to " + 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.setHandler(new ConnectorServerEventHandler(this));
|
||||||
bedrockServer.bind().whenComplete((avoid, throwable) -> {
|
bedrockServer.bind().whenComplete((avoid, throwable) -> {
|
||||||
if (throwable == null) {
|
if (throwable == null) {
|
||||||
@ -265,6 +271,20 @@ public class GeyserConnector {
|
|||||||
}
|
}
|
||||||
return valueMap;
|
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;
|
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.GeyserWorldManager;
|
||||||
import org.geysermc.connector.network.translators.world.WorldManager;
|
import org.geysermc.connector.network.translators.world.WorldManager;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
public interface GeyserBootstrap {
|
public interface GeyserBootstrap {
|
||||||
@ -99,4 +100,18 @@ public interface GeyserBootstrap {
|
|||||||
* @return The info about the bootstrap
|
* @return The info about the bootstrap
|
||||||
*/
|
*/
|
||||||
BootstrapDumpInfo getDumpInfo();
|
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
|
* @return The formatted message
|
||||||
*/
|
*/
|
||||||
private String createMessage() {
|
private String createMessage() {
|
||||||
String message = "";
|
StringBuilder message = new StringBuilder();
|
||||||
|
|
||||||
InputStream helpStream = IGeyserMain.class.getClassLoader().getResourceAsStream("languages/run-help/" + Locale.getDefault().toString() + ".txt");
|
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_type}", this.getPluginType());
|
||||||
line = line.replace("${plugin_folder}", this.getPluginFolder());
|
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 com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import org.geysermc.connector.GeyserLogger;
|
import org.geysermc.connector.GeyserLogger;
|
||||||
|
import org.geysermc.connector.network.CIDRMatcher;
|
||||||
import org.geysermc.connector.utils.LanguageUtils;
|
import org.geysermc.connector.utils.LanguageUtils;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public interface GeyserConfiguration {
|
public interface GeyserConfiguration {
|
||||||
@ -59,6 +61,8 @@ public interface GeyserConfiguration {
|
|||||||
|
|
||||||
int getPingPassthroughInterval();
|
int getPingPassthroughInterval();
|
||||||
|
|
||||||
|
boolean isForwardPlayerPing();
|
||||||
|
|
||||||
int getMaxPlayers();
|
int getMaxPlayers();
|
||||||
|
|
||||||
boolean isDebugMode();
|
boolean isDebugMode();
|
||||||
@ -104,6 +108,15 @@ public interface GeyserConfiguration {
|
|||||||
String getMotd2();
|
String getMotd2();
|
||||||
|
|
||||||
String getServerName();
|
String getServerName();
|
||||||
|
|
||||||
|
boolean isEnableProxyProtocol();
|
||||||
|
|
||||||
|
List<String> getProxyProtocolWhitelistedIPs();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Unmodifiable list of {@link CIDRMatcher}s from {@link #getProxyProtocolWhitelistedIPs()}
|
||||||
|
*/
|
||||||
|
List<CIDRMatcher> getWhitelistedIPsMatchers();
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IRemoteConfiguration {
|
interface IRemoteConfiguration {
|
||||||
|
@ -25,16 +25,21 @@
|
|||||||
|
|
||||||
package org.geysermc.connector.configuration;
|
package org.geysermc.connector.configuration;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import org.geysermc.connector.GeyserConnector;
|
import org.geysermc.connector.GeyserConnector;
|
||||||
import org.geysermc.connector.common.serializer.AsteriskSerializer;
|
import org.geysermc.connector.common.serializer.AsteriskSerializer;
|
||||||
|
import org.geysermc.connector.network.CIDRMatcher;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
@ -74,6 +79,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
|||||||
@JsonProperty("ping-passthrough-interval")
|
@JsonProperty("ping-passthrough-interval")
|
||||||
private int pingPassthroughInterval = 3;
|
private int pingPassthroughInterval = 3;
|
||||||
|
|
||||||
|
@JsonProperty("forward-player-ping")
|
||||||
|
private boolean forwardPlayerPing = false;
|
||||||
|
|
||||||
@JsonProperty("max-players")
|
@JsonProperty("max-players")
|
||||||
private int maxPlayers = 100;
|
private int maxPlayers = 100;
|
||||||
|
|
||||||
@ -119,6 +127,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
|||||||
private MetricsInfo metrics = new MetricsInfo();
|
private MetricsInfo metrics = new MetricsInfo();
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public static class BedrockConfiguration implements IBedrockConfiguration {
|
public static class BedrockConfiguration implements IBedrockConfiguration {
|
||||||
@AsteriskSerializer.Asterisk(sensitive = true)
|
@AsteriskSerializer.Asterisk(sensitive = true)
|
||||||
private String address = "0.0.0.0";
|
private String address = "0.0.0.0";
|
||||||
@ -134,9 +143,33 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
|||||||
|
|
||||||
@JsonProperty("server-name")
|
@JsonProperty("server-name")
|
||||||
private String serverName = GeyserConnector.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
|
@Getter
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public static class RemoteConfiguration implements IRemoteConfiguration {
|
public static class RemoteConfiguration implements IRemoteConfiguration {
|
||||||
@Setter
|
@Setter
|
||||||
@AsteriskSerializer.Asterisk(sensitive = true)
|
@AsteriskSerializer.Asterisk(sensitive = true)
|
||||||
@ -170,6 +203,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public static class MetricsInfo implements IMetricsInfo {
|
public static class MetricsInfo implements IMetricsInfo {
|
||||||
private boolean enabled = true;
|
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) {
|
public AbstractArrowEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
|
||||||
super(entityId, geyserId, entityType, position, motion, rotation);
|
super(entityId, geyserId, entityType, position, motion, rotation);
|
||||||
|
|
||||||
|
setMotion(motion);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -47,4 +49,20 @@ public class AbstractArrowEntity extends Entity {
|
|||||||
|
|
||||||
super.updateBedrockMetadata(entityMetadata, session);
|
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," +
|
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\"," +
|
"\"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 boolean isPaddlingLeft;
|
||||||
private float paddleTimeLeft;
|
private float paddleTimeLeft;
|
||||||
|
@ -26,43 +26,167 @@
|
|||||||
package org.geysermc.connector.entity;
|
package org.geysermc.connector.entity;
|
||||||
|
|
||||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
|
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
|
||||||
import com.github.steveice10.mc.protocol.data.game.entity.object.ProjectileData;
|
|
||||||
import com.nukkitx.math.vector.Vector3f;
|
import com.nukkitx.math.vector.Vector3f;
|
||||||
|
import com.nukkitx.math.vector.Vector3i;
|
||||||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
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.entity.type.EntityType;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
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 {
|
import java.util.List;
|
||||||
public FishingHookEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, ProjectileData data) {
|
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);
|
super(entityId, geyserId, entityType, position, motion, rotation);
|
||||||
|
|
||||||
for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) {
|
this.boundingBox = new BoundingBox(0.125, 0.125, 0.125, 0.25, 0.25, 0.25);
|
||||||
Entity entity = session.getEntityCache().getEntityByJavaId(data.getOwnerId());
|
|
||||||
if (entity == null && session.getPlayerEntity().getEntityId() == data.getOwnerId()) {
|
|
||||||
entity = session.getPlayerEntity();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entity != null) {
|
// In Java, the splash sound depends on the entity's velocity, but in Bedrock the volume doesn't change.
|
||||||
this.metadata.put(EntityData.OWNER_EID, entity.getGeyserId());
|
// This splash can be confused with the sound from catching a fish. This silences the splash from Bedrock,
|
||||||
return;
|
// 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
|
@Override
|
||||||
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
|
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
|
||||||
if (entityMetadata.getId() == 7) {
|
if (entityMetadata.getId() == 7) { // Hooked entity
|
||||||
Entity entity = session.getEntityCache().getEntityByJavaId((Integer) entityMetadata.getValue() - 1);
|
int hookedEntityId = (int) entityMetadata.getValue() - 1;
|
||||||
if (entity == null && session.getPlayerEntity().getEntityId() == (Integer) entityMetadata.getValue() - 1) {
|
Entity entity = session.getEntityCache().getEntityByJavaId(hookedEntityId);
|
||||||
|
if (entity == null && session.getPlayerEntity().getEntityId() == hookedEntityId) {
|
||||||
entity = session.getPlayerEntity();
|
entity = session.getPlayerEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity != null) {
|
if (entity != null) {
|
||||||
metadata.put(EntityData.TARGET_EID, entity.getGeyserId());
|
metadata.put(EntityData.TARGET_EID, entity.getGeyserId());
|
||||||
|
hooked = true;
|
||||||
|
} else {
|
||||||
|
hooked = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
super.updateBedrockMetadata(entityMetadata, session);
|
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 {
|
public class ItemedFireballEntity extends ThrowableEntity {
|
||||||
private final Vector3f acceleration;
|
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) {
|
public ItemedFireballEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
|
||||||
super(entityId, geyserId, entityType, position, Vector3f.ZERO, 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
|
@Override
|
||||||
public void tick(GeyserSession session) {
|
public void tick(GeyserSession session) {
|
||||||
position = position.add(motion);
|
moveAbsoluteImmediate(session, tickMovement(session, position), rotation, false, false);
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,20 +29,21 @@ import com.nukkitx.math.vector.Vector3f;
|
|||||||
import com.nukkitx.protocol.bedrock.data.LevelEventType;
|
import com.nukkitx.protocol.bedrock.data.LevelEventType;
|
||||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||||
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
|
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.entity.type.EntityType;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
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
|
* Used as a class for any object-like entity that moves as a projectile
|
||||||
*/
|
*/
|
||||||
public class ThrowableEntity extends Entity implements Tickable {
|
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) {
|
public ThrowableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
|
||||||
super(entityId, geyserId, entityType, position, motion, 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
|
@Override
|
||||||
public void tick(GeyserSession session) {
|
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 drag = getDrag(session);
|
||||||
float gravity = getGravity();
|
float gravity = getGravity(session);
|
||||||
motion = motion.mul(drag).down(gravity);
|
motion = motion.mul(drag).down(gravity);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
|
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.
|
* 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.
|
* @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)) {
|
if (metadata.getFlags().getFlag(EntityFlag.HAS_GRAVITY)) {
|
||||||
switch (entityType) {
|
switch (entityType) {
|
||||||
case THROWN_POTION:
|
case THROWN_POTION:
|
||||||
@ -76,11 +120,14 @@ public class ThrowableEntity extends Entity implements Tickable {
|
|||||||
case THROWN_EXP_BOTTLE:
|
case THROWN_EXP_BOTTLE:
|
||||||
return 0.07f;
|
return 0.07f;
|
||||||
case FIREBALL:
|
case FIREBALL:
|
||||||
|
case SHULKER_BULLET:
|
||||||
return 0;
|
return 0;
|
||||||
case SNOWBALL:
|
case SNOWBALL:
|
||||||
case THROWN_EGG:
|
case THROWN_EGG:
|
||||||
case THROWN_ENDERPEARL:
|
case THROWN_ENDERPEARL:
|
||||||
return 0.03f;
|
return 0.03f;
|
||||||
|
case LLAMA_SPIT:
|
||||||
|
return 0.06f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@ -101,11 +148,14 @@ public class ThrowableEntity extends Entity implements Tickable {
|
|||||||
case SNOWBALL:
|
case SNOWBALL:
|
||||||
case THROWN_EGG:
|
case THROWN_EGG:
|
||||||
case THROWN_ENDERPEARL:
|
case THROWN_ENDERPEARL:
|
||||||
|
case LLAMA_SPIT:
|
||||||
return 0.99f;
|
return 0.99f;
|
||||||
case FIREBALL:
|
case FIREBALL:
|
||||||
case SMALL_FIREBALL:
|
case SMALL_FIREBALL:
|
||||||
case DRAGON_FIREBALL:
|
case DRAGON_FIREBALL:
|
||||||
return 0.95f;
|
return 0.95f;
|
||||||
|
case SHULKER_BULLET:
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
@ -117,8 +167,10 @@ public class ThrowableEntity extends Entity implements Tickable {
|
|||||||
*/
|
*/
|
||||||
protected boolean isInWater(GeyserSession session) {
|
protected boolean isInWater(GeyserSession session) {
|
||||||
if (session.getConnector().getConfig().isCacheChunks()) {
|
if (session.getConnector().getConfig().isCacheChunks()) {
|
||||||
int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt());
|
if (0 <= position.getFloorY() && position.getFloorY() <= 255) {
|
||||||
return block == BlockTranslator.BEDROCK_WATER_ID;
|
int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt());
|
||||||
|
return BlockStateValues.getWaterLevel(block) != -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -136,14 +188,13 @@ public class ThrowableEntity extends Entity implements Tickable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) {
|
public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) {
|
||||||
position = lastPosition;
|
moveAbsoluteImmediate(session, lastJavaPosition.add(relX, relY, relZ), rotation, isOnGround, false);
|
||||||
super.moveRelative(session, relX, relY, relZ, rotation, isOnGround);
|
lastJavaPosition = position;
|
||||||
lastPosition = position;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
|
public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
|
||||||
super.moveAbsolute(session, position, rotation, isOnGround, teleported);
|
moveAbsoluteImmediate(session, position, rotation, isOnGround, teleported);
|
||||||
lastPosition = position;
|
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) {
|
public WitherSkullEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
|
||||||
super(entityId, geyserId, entityType, position, motion, rotation);
|
super(entityId, geyserId, entityType, position, motion, rotation);
|
||||||
|
|
||||||
|
this.futureTicks = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -44,7 +44,7 @@ public class RabbitEntity extends AnimalEntity {
|
|||||||
if (entityMetadata.getId() == 15) {
|
if (entityMetadata.getId() == 15) {
|
||||||
metadata.put(EntityData.SCALE, .55f);
|
metadata.put(EntityData.SCALE, .55f);
|
||||||
boolean isBaby = (boolean) entityMetadata.getValue();
|
boolean isBaby = (boolean) entityMetadata.getValue();
|
||||||
if(isBaby) {
|
if (isBaby) {
|
||||||
metadata.put(EntityData.SCALE, .35f);
|
metadata.put(EntityData.SCALE, .35f);
|
||||||
metadata.getFlags().setFlag(EntityFlag.BABY, true);
|
metadata.getFlags().setFlag(EntityFlag.BABY, true);
|
||||||
}
|
}
|
||||||
|
@ -125,7 +125,7 @@ public class VillagerEntity extends AbstractMerchantEntity {
|
|||||||
Pattern r = Pattern.compile("facing=([a-z]+)");
|
Pattern r = Pattern.compile("facing=([a-z]+)");
|
||||||
Matcher m = r.matcher(bedRotationZ);
|
Matcher m = r.matcher(bedRotationZ);
|
||||||
if (m.find()) {
|
if (m.find()) {
|
||||||
switch (m.group(0)){
|
switch (m.group(0)) {
|
||||||
case "facing=south":
|
case "facing=south":
|
||||||
//bed is facing south
|
//bed is facing south
|
||||||
z = 180;
|
z = 180;
|
||||||
|
@ -46,7 +46,7 @@ public class WitherEntity extends MonsterEntity {
|
|||||||
|
|
||||||
if (entityMetadata.getId() >= 15 && entityMetadata.getId() <= 17) {
|
if (entityMetadata.getId() >= 15 && entityMetadata.getId() <= 17) {
|
||||||
Entity entity = session.getEntityCache().getEntityByJavaId((int) entityMetadata.getValue());
|
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();
|
entity = session.getPlayerEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ public class WitherEntity extends MonsterEntity {
|
|||||||
} else if (entityMetadata.getId() == 17) {
|
} else if (entityMetadata.getId() == 17) {
|
||||||
metadata.put(EntityData.WITHER_TARGET_3, targetID);
|
metadata.put(EntityData.WITHER_TARGET_3, targetID);
|
||||||
} else if (entityMetadata.getId() == 18) {
|
} 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)
|
// Show the shield for the first few seconds of spawning (like Java)
|
||||||
if ((int) entityMetadata.getValue() >= 165) {
|
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.*;
|
||||||
import org.geysermc.connector.entity.living.animal.*;
|
import org.geysermc.connector.entity.living.animal.*;
|
||||||
import org.geysermc.connector.entity.living.animal.horse.*;
|
import org.geysermc.connector.entity.living.animal.horse.*;
|
||||||
import org.geysermc.connector.entity.living.animal.tameable.*;
|
import org.geysermc.connector.entity.living.animal.tameable.CatEntity;
|
||||||
import org.geysermc.connector.entity.living.merchant.*;
|
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.*;
|
||||||
import org.geysermc.connector.entity.living.monster.raid.AbstractIllagerEntity;
|
import org.geysermc.connector.entity.living.monster.raid.AbstractIllagerEntity;
|
||||||
import org.geysermc.connector.entity.living.monster.raid.PillagerEntity;
|
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.living.monster.raid.SpellcasterIllagerEntity;
|
||||||
import org.geysermc.connector.entity.player.PlayerEntity;
|
import org.geysermc.connector.entity.player.PlayerEntity;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public enum EntityType {
|
public enum EntityType {
|
||||||
|
|
||||||
@ -112,7 +118,7 @@ public enum EntityType {
|
|||||||
TRIDENT(TridentEntity.class, 73, 0f, 0f, 0f, 0f, "minecraft:thrown_trident"),
|
TRIDENT(TridentEntity.class, 73, 0f, 0f, 0f, 0f, "minecraft:thrown_trident"),
|
||||||
TURTLE(TurtleEntity.class, 74, 0.4f, 1.2f),
|
TURTLE(TurtleEntity.class, 74, 0.4f, 1.2f),
|
||||||
CAT(CatEntity.class, 75, 0.35f, 0.3f),
|
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"),
|
FISHING_BOBBER(FishingHookEntity.class, 77, 0f, 0f, 0f, 0f, "minecraft:fishing_hook"),
|
||||||
CHALKBOARD(Entity.class, 78, 0f),
|
CHALKBOARD(Entity.class, 78, 0f),
|
||||||
DRAGON_FIREBALL(ItemedFireballEntity.class, 79, 1.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_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"),
|
MINECART_COMMAND_BLOCK(CommandBlockMinecartEntity.class, 100, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:command_block_minecart"),
|
||||||
LINGERING_POTION(ThrowableEntity.class, 101, 0f),
|
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_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"),
|
EVOKER(SpellcasterIllagerEntity.class, 104, 1.95f, 0.6f, 0.6f, 0f, "minecraft:evocation_illager"),
|
||||||
VEX(VexEntity.class, 105, 0.8f, 0.4f),
|
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");
|
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 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 int type;
|
||||||
private final float height;
|
private final float height;
|
||||||
private final float width;
|
private final float width;
|
||||||
private final float length;
|
private final float length;
|
||||||
private final float offset;
|
private final float offset;
|
||||||
private String identifier;
|
private final String identifier;
|
||||||
|
|
||||||
EntityType(Class<? extends Entity> entityClass, int type, float height) {
|
EntityType(Class<? extends Entity> entityClass, int type, float height) {
|
||||||
|
//noinspection SuspiciousNameCombination
|
||||||
this(entityClass, type, height, height);
|
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) {
|
EntityType(Class<? extends Entity> entityClass, int type, float height, float width, float length, float offset) {
|
||||||
this(entityClass, type, height, width, length, offset, null);
|
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) {
|
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.width = width;
|
||||||
this.length = length;
|
this.length = length;
|
||||||
this.offset = offset + 0.00001f;
|
this.offset = offset + 0.00001f;
|
||||||
this.identifier = identifier;
|
this.identifier = identifier == null ? "minecraft:" + name().toLowerCase() : identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static EntityType getFromIdentifier(String identifier) {
|
public static EntityType getFromIdentifier(String identifier) {
|
||||||
|
@ -36,6 +36,7 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -75,7 +76,7 @@ public class Metrics {
|
|||||||
|
|
||||||
private final static ObjectMapper mapper = new ObjectMapper();
|
private final static ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
private GeyserConnector connector;
|
private final GeyserConnector connector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class constructor.
|
* Class constructor.
|
||||||
@ -156,6 +157,7 @@ public class Metrics {
|
|||||||
String osName = System.getProperty("os.name");
|
String osName = System.getProperty("os.name");
|
||||||
String osArch = System.getProperty("os.arch");
|
String osArch = System.getProperty("os.arch");
|
||||||
String osVersion = System.getProperty("os.version");
|
String osVersion = System.getProperty("os.version");
|
||||||
|
String javaVersion = System.getProperty("java.version");
|
||||||
int coreCount = Runtime.getRuntime().availableProcessors();
|
int coreCount = Runtime.getRuntime().availableProcessors();
|
||||||
|
|
||||||
ObjectNode data = mapper.createObjectNode();
|
ObjectNode data = mapper.createObjectNode();
|
||||||
@ -163,6 +165,7 @@ public class Metrics {
|
|||||||
data.put("serverUUID", serverUUID);
|
data.put("serverUUID", serverUUID);
|
||||||
|
|
||||||
data.put("playerAmount", playerAmount);
|
data.put("playerAmount", playerAmount);
|
||||||
|
data.put("javaVersion", javaVersion);
|
||||||
data.put("osName", osName);
|
data.put("osName", osName);
|
||||||
data.put("osArch", osArch);
|
data.put("osArch", osArch);
|
||||||
data.put("osVersion", osVersion);
|
data.put("osVersion", osVersion);
|
||||||
@ -241,7 +244,7 @@ public class Metrics {
|
|||||||
}
|
}
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
GZIPOutputStream gzip = new GZIPOutputStream(outputStream);
|
GZIPOutputStream gzip = new GZIPOutputStream(outputStream);
|
||||||
gzip.write(str.getBytes("UTF-8"));
|
gzip.write(str.getBytes(StandardCharsets.UTF_8));
|
||||||
gzip.close();
|
gzip.close();
|
||||||
return outputStream.toByteArray();
|
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.net.InetSocketAddress;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class ConnectorServerEventHandler implements BedrockServerEventHandler {
|
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;
|
private final GeyserConnector connector;
|
||||||
|
|
||||||
@ -51,6 +61,21 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onConnectionRequest(InetSocketAddress inetSocketAddress) {
|
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));
|
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.attempt_connect", inetSocketAddress));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -69,16 +94,16 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
|
|||||||
|
|
||||||
BedrockPong pong = new BedrockPong();
|
BedrockPong pong = new BedrockPong();
|
||||||
pong.setEdition("MCPE");
|
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.setNintendoLimited(false);
|
||||||
pong.setProtocolVersion(BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion());
|
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());
|
pong.setIpv4Port(config.getBedrock().getPort());
|
||||||
|
|
||||||
if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) {
|
if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) {
|
||||||
String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n");
|
String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n");
|
||||||
String mainMotd = motd[0]; // First line of the motd.
|
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.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.
|
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());
|
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.
|
// The ping will not appear if the MOTD + sub-MOTD is of a certain length.
|
||||||
// We don't know why, though
|
// We don't know why, though
|
||||||
byte[] motdArray = pong.getMotd().getBytes(StandardCharsets.UTF_8);
|
byte[] motdArray = pong.getMotd().getBytes(StandardCharsets.UTF_8);
|
||||||
if (motdArray.length + pong.getSubMotd().getBytes(StandardCharsets.UTF_8).length > 338) {
|
int subMotdLength = pong.getSubMotd().getBytes(StandardCharsets.UTF_8).length;
|
||||||
// Remove the sub-MOTD first since that only appears locally
|
if (motdArray.length + subMotdLength > (MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH)) {
|
||||||
pong.setSubMotd("");
|
// Shorten the sub-MOTD first since that only appears locally
|
||||||
if (motdArray.length > 338) {
|
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
|
// 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);
|
System.arraycopy(motdArray, 0, newMotdArray, 0, newMotdArray.length);
|
||||||
pong.setMotd(new String(newMotdArray, StandardCharsets.UTF_8));
|
pong.setMotd(new String(newMotdArray, StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ public class QueryPacketHandler {
|
|||||||
* @param buffer The Query data
|
* @param buffer The Query data
|
||||||
*/
|
*/
|
||||||
public QueryPacketHandler(GeyserConnector connector, InetSocketAddress sender, ByteBuf buffer) {
|
public QueryPacketHandler(GeyserConnector connector, InetSocketAddress sender, ByteBuf buffer) {
|
||||||
if(!isQueryPacket(buffer))
|
if (!isQueryPacket(buffer))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.connector = connector;
|
this.connector = connector;
|
||||||
@ -225,7 +225,7 @@ public class QueryPacketHandler {
|
|||||||
query.write(new byte[] { 0x00, 0x00 });
|
query.write(new byte[] { 0x00, 0x00 });
|
||||||
|
|
||||||
// Fill player names
|
// Fill player names
|
||||||
if(pingInfo != null) {
|
if (pingInfo != null) {
|
||||||
for (String username : pingInfo.getPlayerList()) {
|
for (String username : pingInfo.getPlayerList()) {
|
||||||
query.write(username.getBytes());
|
query.write(username.getBytes());
|
||||||
query.write((byte) 0x00);
|
query.write((byte) 0x00);
|
||||||
|
@ -93,6 +93,8 @@ import org.geysermc.cumulus.util.FormBuilder;
|
|||||||
import org.geysermc.floodgate.crypto.FloodgateCipher;
|
import org.geysermc.floodgate.crypto.FloodgateCipher;
|
||||||
import org.geysermc.floodgate.util.BedrockData;
|
import org.geysermc.floodgate.util.BedrockData;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@ -168,6 +170,9 @@ public class GeyserSession implements CommandSender {
|
|||||||
@Setter
|
@Setter
|
||||||
private boolean sprinting;
|
private boolean sprinting;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Not updated if cache chunks is enabled.
|
||||||
|
*/
|
||||||
@Setter
|
@Setter
|
||||||
private boolean jumping;
|
private boolean jumping;
|
||||||
|
|
||||||
@ -370,7 +375,8 @@ public class GeyserSession implements CommandSender {
|
|||||||
tmpPlayers.forEach(player -> this.emotes.addAll(player.getEmotes()));
|
tmpPlayers.forEach(player -> this.emotes.addAll(player.getEmotes()));
|
||||||
|
|
||||||
bedrockServerSession.addDisconnectHandler(disconnectReason -> {
|
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());
|
disconnect(disconnectReason.name());
|
||||||
connector.removePlayer(this);
|
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.ENABLE_CLIENT_PROXY_PROTOCOL, true);
|
||||||
downstream.getSession().setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress());
|
downstream.getSession().setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress());
|
||||||
}
|
}
|
||||||
// Let Geyser handle sending the keep alive
|
if (connector.getConfig().isForwardPlayerPing()) {
|
||||||
downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false);
|
// Let Geyser handle sending the keep alive
|
||||||
|
downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false);
|
||||||
|
}
|
||||||
downstream.getSession().addListener(new SessionAdapter() {
|
downstream.getSession().addListener(new SessionAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void packetSending(PacketSendingEvent event) {
|
public void packetSending(PacketSendingEvent event) {
|
||||||
@ -565,7 +573,7 @@ public class GeyserSession implements CommandSender {
|
|||||||
clientData.getLanguageCode(),
|
clientData.getLanguageCode(),
|
||||||
clientData.getUiProfile().ordinal(),
|
clientData.getUiProfile().ordinal(),
|
||||||
clientData.getCurrentInputMode().ordinal(),
|
clientData.getCurrentInputMode().ordinal(),
|
||||||
upstream.getSession().getAddress().getAddress().getHostAddress(),
|
upstream.getAddress().getAddress().getHostAddress(),
|
||||||
skinUploader.getId(),
|
skinUploader.getId(),
|
||||||
skinUploader.getVerifyCode()
|
skinUploader.getVerifyCode()
|
||||||
).toString());
|
).toString());
|
||||||
@ -826,7 +834,14 @@ public class GeyserSession implements CommandSender {
|
|||||||
startGamePacket.setMultiplayerCorrelationId("");
|
startGamePacket.setMultiplayerCorrelationId("");
|
||||||
startGamePacket.setItemEntries(ItemRegistry.ITEMS);
|
startGamePacket.setItemEntries(ItemRegistry.ITEMS);
|
||||||
startGamePacket.setVanillaVersion("*");
|
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);
|
upstream.sendPacket(startGamePacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +61,6 @@ public class UpstreamSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public InetSocketAddress getAddress() {
|
public InetSocketAddress getAddress() {
|
||||||
return session.getAddress();
|
return session.getRealAddress();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,6 +112,8 @@ public final class BedrockClientData {
|
|||||||
private String skinColor;
|
private String skinColor;
|
||||||
@JsonProperty(value = "ThirdPartyNameOnly")
|
@JsonProperty(value = "ThirdPartyNameOnly")
|
||||||
private boolean thirdPartyNameOnly;
|
private boolean thirdPartyNameOnly;
|
||||||
|
@JsonProperty(value = "PlayFabId")
|
||||||
|
private String playFabId;
|
||||||
|
|
||||||
public void setJsonData(JsonNode data) {
|
public void setJsonData(JsonNode data) {
|
||||||
if (this.jsonData == null && data != null) {
|
if (this.jsonData == null && data != null) {
|
||||||
|
@ -38,7 +38,7 @@ import java.io.InputStream;
|
|||||||
*/
|
*/
|
||||||
public class EntityIdentifierRegistry {
|
public class EntityIdentifierRegistry {
|
||||||
|
|
||||||
public static NbtMap ENTITY_IDENTIFIERS;
|
public static final NbtMap ENTITY_IDENTIFIERS;
|
||||||
|
|
||||||
private EntityIdentifierRegistry() {
|
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.github.steveice10.packetlib.packet.Packet;
|
||||||
import com.nukkitx.protocol.bedrock.BedrockPacket;
|
import com.nukkitx.protocol.bedrock.BedrockPacket;
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||||
|
import org.geysermc.common.PlatformType;
|
||||||
import org.geysermc.connector.GeyserConnector;
|
import org.geysermc.connector.GeyserConnector;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.utils.FileUtils;
|
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) {
|
public <P extends T> boolean translate(Class<? extends P> clazz, P packet, GeyserSession session) {
|
||||||
if (!session.getUpstream().isClosed() && !session.isClosed()) {
|
if (!session.getUpstream().isClosed() && !session.isClosed()) {
|
||||||
try {
|
try {
|
||||||
if (translators.containsKey(clazz)) {
|
PacketTranslator<P> translator = (PacketTranslator<P>) translators.get(clazz);
|
||||||
((PacketTranslator<P>) translators.get(clazz)).translate(packet, session);
|
if (translator != null) {
|
||||||
|
translator.translate(packet, session);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} 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));
|
GeyserConnector.getInstance().getLogger().debug("Could not find packet for " + (packet.toString().length() > 25 ? packet.getClass().getSimpleName() : packet));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable ex) {
|
} catch (Throwable ex) {
|
||||||
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.packet.failed", packet.getClass().getSimpleName()), 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;
|
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)
|
@Translator(packet = NetworkStackLatencyPacket.class)
|
||||||
public class BedrockNetworkStackLatencyTranslator extends PacketTranslator<NetworkStackLatencyPacket> {
|
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
|
// negative timestamps are used as hack to fix the url image loading bug
|
||||||
if (packet.getTimestamp() > 0) {
|
if (packet.getTimestamp() > 0) {
|
||||||
ClientKeepAlivePacket keepAlivePacket = new ClientKeepAlivePacket(pingId);
|
if (session.getConnector().getConfig().isForwardPlayerPing()) {
|
||||||
session.sendDownstreamPacket(keepAlivePacket);
|
ClientKeepAlivePacket keepAlivePacket = new ClientKeepAlivePacket(pingId);
|
||||||
|
session.sendDownstreamPacket(keepAlivePacket);
|
||||||
|
}
|
||||||
return;
|
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.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||||
import com.nukkitx.math.vector.Vector3i;
|
import com.nukkitx.math.vector.Vector3i;
|
||||||
import com.nukkitx.protocol.bedrock.data.LevelEventType;
|
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.data.entity.EntityEventType;
|
||||||
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
|
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
|
||||||
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
|
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
|
||||||
@ -64,7 +65,7 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Send book update before any player action
|
// Send book update before any player action
|
||||||
if (packet.getAction() != PlayerActionPacket.Action.RESPAWN) {
|
if (packet.getAction() != PlayerActionType.RESPAWN) {
|
||||||
session.getBookEditCache().checkForSend();
|
session.getBookEditCache().checkForSend();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,10 +206,11 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
|||||||
session.getEntityCache().updateBossBars();
|
session.getEntityCache().updateBossBars();
|
||||||
break;
|
break;
|
||||||
case JUMP:
|
case JUMP:
|
||||||
session.setJumping(true);
|
if (!session.getConnector().getConfig().isCacheChunks()) {
|
||||||
session.getConnector().getGeneralThreadPool().schedule(() -> {
|
// Save the jumping status for determining teleport status
|
||||||
session.setJumping(false);
|
session.setJumping(true);
|
||||||
}, 1, TimeUnit.SECONDS);
|
session.getConnector().getGeneralThreadPool().schedule(() -> session.setJumping(false), 1, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,24 +177,24 @@ public class CollisionManager {
|
|||||||
session.sendUpstreamPacket(movePlayerPacket);
|
session.sendUpstreamPacket(movePlayerPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Vector3i> getPlayerCollidableBlocks() {
|
public List<Vector3i> getCollidableBlocks(BoundingBox box) {
|
||||||
List<Vector3i> blocks = new ArrayList<>();
|
List<Vector3i> blocks = new ArrayList<>();
|
||||||
|
|
||||||
Vector3d position = Vector3d.from(playerBoundingBox.getMiddleX(),
|
Vector3d position = Vector3d.from(box.getMiddleX(),
|
||||||
playerBoundingBox.getMiddleY() - (playerBoundingBox.getSizeY() / 2),
|
box.getMiddleY() - (box.getSizeY() / 2),
|
||||||
playerBoundingBox.getMiddleZ());
|
box.getMiddleZ());
|
||||||
|
|
||||||
// Loop through all blocks that could collide with the player
|
// Loop through all blocks that could collide
|
||||||
int minCollisionX = (int) Math.floor(position.getX() - ((playerBoundingBox.getSizeX() / 2) + COLLISION_TOLERANCE));
|
int minCollisionX = (int) Math.floor(position.getX() - ((box.getSizeX() / 2) + COLLISION_TOLERANCE));
|
||||||
int maxCollisionX = (int) Math.floor(position.getX() + (playerBoundingBox.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
|
// Y extends 0.5 blocks down because of fence hitboxes
|
||||||
int minCollisionY = (int) Math.floor(position.getY() - 0.5);
|
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 minCollisionZ = (int) Math.floor(position.getZ() - ((box.getSizeZ() / 2) + COLLISION_TOLERANCE));
|
||||||
int maxCollisionZ = (int) Math.floor(position.getZ() + (playerBoundingBox.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 y = minCollisionY; y < maxCollisionY + 1; y++) {
|
||||||
for (int x = minCollisionX; x < maxCollisionX + 1; x++) {
|
for (int x = minCollisionX; x < maxCollisionX + 1; x++) {
|
||||||
@ -207,6 +207,10 @@ public class CollisionManager {
|
|||||||
return blocks;
|
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
|
* Returns false if the movement is invalid, and in this case it shouldn't be sent to the server and should be
|
||||||
* cancelled
|
* cancelled
|
||||||
|
@ -69,6 +69,18 @@ public enum Enchantment {
|
|||||||
QUICK_CHARGE,
|
QUICK_CHARGE,
|
||||||
SOUL_SPEED;
|
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;
|
private final String javaIdentifier;
|
||||||
|
|
||||||
Enchantment() {
|
Enchantment() {
|
||||||
|
@ -63,6 +63,11 @@ public class ItemRegistry {
|
|||||||
public static final List<StartGamePacket.ItemEntry> ITEMS = new ArrayList<>();
|
public static final List<StartGamePacket.ItemEntry> ITEMS = new ArrayList<>();
|
||||||
public static final Int2ObjectMap<ItemEntry> ITEM_ENTRIES = new Int2ObjectOpenHashMap<>();
|
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
|
* 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)
|
// Used to get the Bedrock namespaced ID (in instances where there are small differences)
|
||||||
Int2ObjectMap<String> bedrockIdToIdentifier = new Int2ObjectOpenHashMap<>();
|
Int2ObjectMap<String> bedrockIdToIdentifier = new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
|
List<String> itemNames = new ArrayList<>();
|
||||||
|
|
||||||
List<JsonNode> itemEntries;
|
List<JsonNode> itemEntries;
|
||||||
try {
|
try {
|
||||||
itemEntries = GeyserConnector.JSON_MAPPER.readValue(stream, itemEntriesType);
|
itemEntries = GeyserConnector.JSON_MAPPER.readValue(stream, itemEntriesType);
|
||||||
@ -207,6 +214,8 @@ public class ItemRegistry {
|
|||||||
BUCKETS.add(entry.getValue().get("bedrock_id").intValue());
|
BUCKETS.add(entry.getValue().get("bedrock_id").intValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
itemNames.add(entry.getKey());
|
||||||
|
|
||||||
itemIndex++;
|
itemIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,6 +244,8 @@ public class ItemRegistry {
|
|||||||
creativeItems.add(ItemData.fromNet(netId++, item.getId(), item.getDamage(), item.getCount(), item.getTag()));
|
creativeItems.add(ItemData.fromNet(netId++, item.getId(), item.getDamage(), item.getCount(), item.getTag()));
|
||||||
}
|
}
|
||||||
CREATIVE_ITEMS = creativeItems.toArray(new ItemData[0]);
|
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);
|
CompoundTag javaTag = new CompoundTag(name);
|
||||||
Map<String, Tag> javaValue = javaTag.getValue();
|
Map<String, Tag> javaValue = javaTag.getValue();
|
||||||
if (tag != null && !tag.isEmpty()) {
|
if (tag != null && !tag.isEmpty()) {
|
||||||
for (String str : tag.keySet()) {
|
for (Map.Entry<String, Object> entry : tag.entrySet()) {
|
||||||
Object bedrockTag = tag.get(str);
|
Tag translatedTag = translateToJavaNBT(entry.getKey(), entry.getValue());
|
||||||
Tag translatedTag = translateToJavaNBT(str, bedrockTag);
|
|
||||||
if (translatedTag == null)
|
if (translatedTag == null)
|
||||||
continue;
|
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.CommandParamData;
|
||||||
import com.nukkitx.protocol.bedrock.data.command.CommandParamType;
|
import com.nukkitx.protocol.bedrock.data.command.CommandParamType;
|
||||||
import com.nukkitx.protocol.bedrock.packet.AvailableCommandsPacket;
|
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.Int2ObjectMap;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
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.Getter;
|
||||||
|
import lombok.ToString;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import org.geysermc.connector.GeyserConnector;
|
import org.geysermc.connector.GeyserConnector;
|
||||||
|
import org.geysermc.connector.entity.type.EntityType;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||||
import org.geysermc.connector.network.translators.Translator;
|
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.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Translator(packet = ServerDeclareCommandsPacket.class)
|
@Translator(packet = ServerDeclareCommandsPacket.class)
|
||||||
public class JavaDeclareCommandsTranslator extends PacketTranslator<ServerDeclareCommandsPacket> {
|
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
|
@Override
|
||||||
public void translate(ServerDeclareCommandsPacket packet, GeyserSession session) {
|
public void translate(ServerDeclareCommandsPacket packet, GeyserSession session) {
|
||||||
// Don't send command suggestions if they are disabled
|
// Don't send command suggestions if they are disabled
|
||||||
@ -60,48 +108,50 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator<ServerDeclar
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CommandNode[] nodes = packet.getNodes();
|
||||||
List<CommandData> commandData = new ArrayList<>();
|
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<>();
|
Int2ObjectMap<List<CommandNode>> commandArgs = new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
// Get the first node, it should be a root node
|
// 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
|
// Loop through the root nodes to get all commands
|
||||||
for (int nodeIndex : rootNode.getChildIndices()) {
|
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)
|
// Make sure we don't have duplicated commands (happens if there is more than 1 root node)
|
||||||
if (commands.containsKey(nodeIndex)) { continue; }
|
if (!commandNodes.add(nodeIndex) || !knownAliases.add(node.getName().toLowerCase())) continue;
|
||||||
if (commands.containsValue(node.getName())) { continue; }
|
|
||||||
|
|
||||||
// Get and update the commandArgs list with the found arguments
|
// Get and update the commandArgs list with the found arguments
|
||||||
if (node.getChildIndices().length >= 1) {
|
if (node.getChildIndices().length >= 1) {
|
||||||
for (int childIndex : node.getChildIndices()) {
|
for (int childIndex : node.getChildIndices()) {
|
||||||
commandArgs.putIfAbsent(nodeIndex, new ArrayList<>());
|
commandArgs.computeIfAbsent(nodeIndex, ArrayList::new).add(nodes[childIndex]);
|
||||||
commandArgs.get(nodeIndex).add(packet.getNodes()[childIndex]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert the command name into the list
|
// Get and parse all params
|
||||||
commands.put(nodeIndex, node.getName());
|
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
|
// The command flags, not sure what these do apart from break things
|
||||||
List<CommandData.Flag> flags = Collections.emptyList();
|
List<CommandData.Flag> flags = Collections.emptyList();
|
||||||
|
|
||||||
// Loop through all the found commands
|
// 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
|
// Create a basic alias
|
||||||
CommandEnumData aliases = new CommandEnumData(commandName + "Aliases", new String[] { commandName.toLowerCase() }, false);
|
CommandEnumData aliases = new CommandEnumData(commandName + "Aliases", entry.getValue().toArray(new String[0]), false);
|
||||||
|
|
||||||
// Get and parse all params
|
|
||||||
CommandParamData[][] params = getParams(packet.getNodes()[commandID], packet.getNodes());
|
|
||||||
|
|
||||||
// Build the completed command and add it to the final list
|
// 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);
|
commandData.add(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +159,7 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator<ServerDeclar
|
|||||||
AvailableCommandsPacket availableCommandsPacket = new AvailableCommandsPacket();
|
AvailableCommandsPacket availableCommandsPacket = new AvailableCommandsPacket();
|
||||||
availableCommandsPacket.getCommands().addAll(commandData);
|
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
|
// Finally, send the commands to the client
|
||||||
session.sendUpstreamPacket(availableCommandsPacket);
|
session.sendUpstreamPacket(availableCommandsPacket);
|
||||||
@ -119,11 +169,10 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator<ServerDeclar
|
|||||||
* Build the command parameter array for the given command
|
* Build the command parameter array for the given command
|
||||||
*
|
*
|
||||||
* @param commandNode The command to build the parameters for
|
* @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
|
* @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
|
// Check if the command is an alias and redirect it
|
||||||
if (commandNode.getRedirectIndex() != -1) {
|
if (commandNode.getRedirectIndex() != -1) {
|
||||||
GeyserConnector.getInstance().getLogger().debug("Redirecting command " + commandNode.getName() + " to " + allNodes[commandNode.getRedirectIndex()].getName());
|
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);
|
rootParam.buildChildren(allNodes);
|
||||||
|
|
||||||
List<CommandParamData[]> treeData = rootParam.getTree();
|
List<CommandParamData[]> treeData = rootParam.getTree();
|
||||||
CommandParamData[][] params = new CommandParamData[treeData.size()][];
|
|
||||||
|
|
||||||
// Fill the nested params array
|
return treeData.toArray(new CommandParamData[0][]);
|
||||||
int i = 0;
|
|
||||||
for (CommandParamData[] tree : treeData) {
|
|
||||||
params[i] = tree;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return params;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CommandParamData[0][0];
|
return new CommandParamData[0][0];
|
||||||
@ -155,14 +196,17 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator<ServerDeclar
|
|||||||
* Convert Java edition command types to Bedrock edition
|
* Convert Java edition command types to Bedrock edition
|
||||||
*
|
*
|
||||||
* @param parser Command type to convert
|
* @param parser Command type to convert
|
||||||
*
|
|
||||||
* @return Bedrock parameter data type
|
* @return Bedrock parameter data type
|
||||||
*/
|
*/
|
||||||
private CommandParamType mapCommandType(CommandParser parser) {
|
private static Object mapCommandType(CommandParser parser) {
|
||||||
if (parser == null) { return CommandParamType.STRING; }
|
if (parser == null) {
|
||||||
|
return CommandParamType.STRING;
|
||||||
|
}
|
||||||
|
|
||||||
switch (parser) {
|
switch (parser) {
|
||||||
case FLOAT:
|
case FLOAT:
|
||||||
|
case ROTATION:
|
||||||
|
case DOUBLE:
|
||||||
return CommandParamType.FLOAT;
|
return CommandParamType.FLOAT;
|
||||||
|
|
||||||
case INTEGER:
|
case INTEGER:
|
||||||
@ -189,50 +233,44 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator<ServerDeclar
|
|||||||
return CommandParamType.JSON;
|
return CommandParamType.JSON;
|
||||||
|
|
||||||
case RESOURCE_LOCATION:
|
case RESOURCE_LOCATION:
|
||||||
|
case FUNCTION:
|
||||||
return CommandParamType.FILE_PATH;
|
return CommandParamType.FILE_PATH;
|
||||||
|
|
||||||
case INT_RANGE:
|
|
||||||
return CommandParamType.INT_RANGE;
|
|
||||||
|
|
||||||
case BOOL:
|
case BOOL:
|
||||||
case DOUBLE:
|
return ENUM_BOOLEAN;
|
||||||
case STRING:
|
|
||||||
case VEC2:
|
case OPERATION: // ">=", "==", etc
|
||||||
|
return CommandParamType.OPERATOR;
|
||||||
|
|
||||||
case BLOCK_STATE:
|
case BLOCK_STATE:
|
||||||
case BLOCK_PREDICATE:
|
return BlockTranslator.getAllBlockIdentifiers();
|
||||||
|
|
||||||
case ITEM_STACK:
|
case ITEM_STACK:
|
||||||
case ITEM_PREDICATE:
|
return ItemRegistry.ITEM_NAMES;
|
||||||
case COLOR:
|
|
||||||
case COMPONENT:
|
|
||||||
case OBJECTIVE:
|
|
||||||
case OBJECTIVE_CRITERIA:
|
|
||||||
case OPERATION: // Possibly OPERATOR
|
|
||||||
case PARTICLE:
|
|
||||||
case ROTATION:
|
|
||||||
case SCOREBOARD_SLOT:
|
|
||||||
case SCORE_HOLDER:
|
|
||||||
case SWIZZLE:
|
|
||||||
case TEAM:
|
|
||||||
case ITEM_SLOT:
|
|
||||||
case MOB_EFFECT:
|
|
||||||
case FUNCTION:
|
|
||||||
case ENTITY_ANCHOR:
|
|
||||||
case RANGE:
|
|
||||||
case FLOAT_RANGE:
|
|
||||||
case ITEM_ENCHANTMENT:
|
case ITEM_ENCHANTMENT:
|
||||||
|
return Enchantment.ALL_JAVA_IDENTIFIERS; //TODO: inventory branch use Java enums
|
||||||
|
|
||||||
case ENTITY_SUMMON:
|
case ENTITY_SUMMON:
|
||||||
case DIMENSION:
|
return EntityType.ALL_JAVA_IDENTIFIERS;
|
||||||
case TIME:
|
|
||||||
|
case COLOR:
|
||||||
|
return VALID_COLORS;
|
||||||
|
|
||||||
|
case SCOREBOARD_SLOT:
|
||||||
|
return VALID_SCOREBOARD_SLOTS;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return CommandParamType.STRING;
|
return CommandParamType.STRING;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private class ParamInfo {
|
@ToString
|
||||||
private CommandNode paramNode;
|
private static class ParamInfo {
|
||||||
private CommandParamData paramData;
|
private final CommandNode paramNode;
|
||||||
private List<ParamInfo> children;
|
private final CommandParamData paramData;
|
||||||
|
private final List<ParamInfo> children;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new parameter info object
|
* Create a new parameter info object
|
||||||
@ -252,33 +290,50 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator<ServerDeclar
|
|||||||
* @param allNodes Every command node
|
* @param allNodes Every command node
|
||||||
*/
|
*/
|
||||||
public void buildChildren(CommandNode[] allNodes) {
|
public void buildChildren(CommandNode[] allNodes) {
|
||||||
int enumIndex = -1;
|
|
||||||
|
|
||||||
for (int paramID : paramNode.getChildIndices()) {
|
for (int paramID : paramNode.getChildIndices()) {
|
||||||
CommandNode paramNode = allNodes[paramID];
|
CommandNode paramNode = allNodes[paramID];
|
||||||
|
|
||||||
if (paramNode.getParser() == null) {
|
if (paramNode.getParser() == null) {
|
||||||
if (enumIndex == -1) {
|
boolean foundCompatible = false;
|
||||||
enumIndex = children.size();
|
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
|
// Re-create the command using the updated values
|
||||||
CommandEnumData enumData = new CommandEnumData(paramNode.getName(), new String[] { paramNode.getName() }, false);
|
CommandEnumData enumData = new CommandEnumData(enumParamInfo.getParamData().getEnumData().getName(), enumOptions, false);
|
||||||
children.add(new ParamInfo(paramNode, new CommandParamData(paramNode.getName(), false, enumData, mapCommandType(paramNode.getParser()), null, Collections.emptyList())));
|
children.set(i, new ParamInfo(enumParamInfo.getParamNode(), new CommandParamData(enumParamInfo.getParamData().getName(), this.paramNode.isExecutable(), enumData, null, null, Collections.emptyList())));
|
||||||
} else {
|
break;
|
||||||
// Get the existing enum
|
}
|
||||||
ParamInfo enumParamInfo = children.get(enumIndex);
|
|
||||||
|
|
||||||
// Extend the current list of enum values
|
|
||||||
String[] enumOptions = Arrays.copyOf(enumParamInfo.getParamData().getEnumData().getValues(), enumParamInfo.getParamData().getEnumData().getValues().length + 1);
|
|
||||||
enumOptions[enumOptions.length - 1] = paramNode.getName();
|
|
||||||
|
|
||||||
// Re-create the command using the updated values
|
|
||||||
CommandEnumData enumData = new CommandEnumData(enumParamInfo.getParamData().getEnumData().getName(), enumOptions, false);
|
|
||||||
children.set(enumIndex, new ParamInfo(enumParamInfo.getParamNode(), new CommandParamData(enumParamInfo.getParamData().getName(), false, enumData, enumParamInfo.getParamData().getType(), null, Collections.emptyList())));
|
|
||||||
}
|
}
|
||||||
}else{
|
|
||||||
|
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
|
// 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)
|
* Get the tree of every parameter node (recursive)
|
||||||
*
|
*
|
||||||
@ -301,13 +414,10 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator<ServerDeclar
|
|||||||
List<CommandParamData[]> childTree = child.getTree();
|
List<CommandParamData[]> childTree = child.getTree();
|
||||||
|
|
||||||
// Un-pack the tree append the child node to it and push into the list
|
// Un-pack the tree append the child node to it and push into the list
|
||||||
for (CommandParamData[] subchild : childTree) {
|
for (CommandParamData[] subChild : childTree) {
|
||||||
CommandParamData[] tmpTree = new ArrayList<CommandParamData>() {
|
CommandParamData[] tmpTree = new CommandParamData[subChild.length + 1];
|
||||||
{
|
tmpTree[0] = child.getParamData();
|
||||||
add(child.getParamData());
|
System.arraycopy(subChild, 0, tmpTree, 1, subChild.length);
|
||||||
addAll(Arrays.asList(subchild));
|
|
||||||
}
|
|
||||||
}.toArray(new CommandParamData[0]);
|
|
||||||
|
|
||||||
treeParamData.add(tmpTree);
|
treeParamData.add(tmpTree);
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,9 @@ public class JavaKeepAliveTranslator extends PacketTranslator<ServerKeepAlivePac
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translate(ServerKeepAlivePacket packet, GeyserSession session) {
|
public void translate(ServerKeepAlivePacket packet, GeyserSession session) {
|
||||||
|
if (!session.getConnector().getConfig().isForwardPlayerPing()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket();
|
NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket();
|
||||||
latencyPacket.setFromServer(true);
|
latencyPacket.setFromServer(true);
|
||||||
latencyPacket.setTimestamp(packet.getPingId() * 1000);
|
latencyPacket.setTimestamp(packet.getPingId() * 1000);
|
||||||
|
@ -71,6 +71,7 @@ public class JavaTitleTranslator extends PacketTranslator<ServerTitlePacket> {
|
|||||||
titlePacket.setFadeInTime(packet.getFadeIn());
|
titlePacket.setFadeInTime(packet.getFadeIn());
|
||||||
titlePacket.setFadeOutTime(packet.getFadeOut());
|
titlePacket.setFadeOutTime(packet.getFadeOut());
|
||||||
titlePacket.setStayTime(packet.getStay());
|
titlePacket.setStayTime(packet.getStay());
|
||||||
|
titlePacket.setText("");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +60,9 @@ public class JavaEntityAnimationTranslator extends PacketTranslator<ServerEntity
|
|||||||
case LEAVE_BED:
|
case LEAVE_BED:
|
||||||
animatePacket.setAction(AnimatePacket.Action.WAKE_UP);
|
animatePacket.setAction(AnimatePacket.Action.WAKE_UP);
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
// Unknown Animation
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
session.sendUpstreamPacket(animatePacket);
|
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.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnEntityPacket;
|
||||||
import com.nukkitx.math.vector.Vector3f;
|
import com.nukkitx.math.vector.Vector3f;
|
||||||
import org.geysermc.connector.entity.*;
|
import org.geysermc.connector.entity.*;
|
||||||
|
import org.geysermc.connector.entity.player.PlayerEntity;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||||
import org.geysermc.connector.network.translators.Translator;
|
import org.geysermc.connector.network.translators.Translator;
|
||||||
@ -69,8 +70,18 @@ public class JavaSpawnEntityTranslator extends PacketTranslator<ServerSpawnEntit
|
|||||||
type, position, motion, rotation, (HangingDirection) packet.getData());
|
type, position, motion, rotation, (HangingDirection) packet.getData());
|
||||||
} else if (packet.getType() == EntityType.FISHING_BOBBER) {
|
} else if (packet.getType() == EntityType.FISHING_BOBBER) {
|
||||||
// Fishing bobbers need the owner for the line
|
// Fishing bobbers need the owner for the line
|
||||||
entity = new FishingHookEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(),
|
int ownerEntityId = ((ProjectileData) packet.getData()).getOwnerId();
|
||||||
type, position, motion, rotation, (ProjectileData) packet.getData());
|
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) {
|
} else if (packet.getType() == EntityType.BOAT) {
|
||||||
// Initial rotation is incorrect
|
// Initial rotation is incorrect
|
||||||
entity = new BoatEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(),
|
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:", ""));
|
SoundRegistry.SoundMapping soundMapping = SoundRegistry.fromJava(packetSound.replace("minecraft:", ""));
|
||||||
String playsound;
|
String playsound;
|
||||||
if(soundMapping == null || soundMapping.getPlaysound() == null) {
|
if (soundMapping == null || soundMapping.getPlaysound() == null) {
|
||||||
// no mapping
|
// no mapping
|
||||||
session.getConnector().getLogger()
|
session.getConnector().getLogger()
|
||||||
.debug("[PlaySound] Defaulting to sound server gave us for " + packet.toString());
|
.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();
|
Iterator<Vector3i> iterator = session.getSkullCache().keySet().iterator();
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
Vector3i position = iterator.next();
|
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);
|
session.getSkullCache().get(position).despawnEntity(session);
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
}
|
}
|
||||||
|
@ -49,70 +49,72 @@ public class BlockStateValues {
|
|||||||
private static final Int2ByteMap SKULL_ROTATIONS = new Int2ByteOpenHashMap();
|
private static final Int2ByteMap SKULL_ROTATIONS = new Int2ByteOpenHashMap();
|
||||||
private static final Int2IntMap SKULL_WALL_DIRECTIONS = new Int2IntOpenHashMap();
|
private static final Int2IntMap SKULL_WALL_DIRECTIONS = new Int2IntOpenHashMap();
|
||||||
private static final Int2ByteMap SHULKERBOX_DIRECTIONS = new Int2ByteOpenHashMap();
|
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
|
* 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 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) {
|
public static void storeBlockStateValues(String javaId, int javaBlockState, JsonNode blockData) {
|
||||||
JsonNode bannerColor = entry.getValue().get("banner_color");
|
JsonNode bannerColor = blockData.get("banner_color");
|
||||||
if (bannerColor != null) {
|
if (bannerColor != null) {
|
||||||
BANNER_COLORS.put(javaBlockState, (byte) bannerColor.intValue());
|
BANNER_COLORS.put(javaBlockState, (byte) bannerColor.intValue());
|
||||||
return; // There will never be a banner color and a skull variant
|
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) {
|
if (bedColor != null) {
|
||||||
BED_COLORS.put(javaBlockState, (byte) bedColor.intValue());
|
BED_COLORS.put(javaBlockState, (byte) bedColor.intValue());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry.getKey().contains("command_block")) {
|
if (javaId.contains("command_block")) {
|
||||||
COMMAND_BLOCK_VALUES.put(javaBlockState, entry.getKey().contains("conditional=true") ? (byte) 1 : (byte) 0);
|
COMMAND_BLOCK_VALUES.put(javaBlockState, javaId.contains("conditional=true") ? (byte) 1 : (byte) 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry.getValue().get("double_chest_position") != null) {
|
if (blockData.get("double_chest_position") != null) {
|
||||||
boolean isX = (entry.getValue().get("x") != null);
|
boolean isX = (blockData.get("x") != null);
|
||||||
boolean isDirectionPositive = ((entry.getValue().get("x") != null && entry.getValue().get("x").asBoolean()) ||
|
boolean isDirectionPositive = ((blockData.get("x") != null && blockData.get("x").asBoolean()) ||
|
||||||
(entry.getValue().get("z") != null && entry.getValue().get("z").asBoolean()));
|
(blockData.get("z") != null && blockData.get("z").asBoolean()));
|
||||||
boolean isLeft = (entry.getValue().get("double_chest_position").asText().contains("left"));
|
boolean isLeft = (blockData.get("double_chest_position").asText().contains("left"));
|
||||||
DOUBLE_CHEST_VALUES.put(javaBlockState, new DoubleChestValue(isX, isDirectionPositive, isLeft));
|
DOUBLE_CHEST_VALUES.put(javaBlockState, new DoubleChestValue(isX, isDirectionPositive, isLeft));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry.getKey().contains("potted_") || entry.getKey().contains("flower_pot")) {
|
if (javaId.contains("potted_") || javaId.contains("flower_pot")) {
|
||||||
FLOWER_POT_VALUES.put(javaBlockState, entry.getKey().replace("potted_", ""));
|
FLOWER_POT_VALUES.put(javaBlockState, javaId.replace("potted_", ""));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonNode notePitch = entry.getValue().get("note_pitch");
|
JsonNode notePitch = blockData.get("note_pitch");
|
||||||
if (notePitch != null) {
|
if (notePitch != null) {
|
||||||
NOTEBLOCK_PITCHES.put(javaBlockState, entry.getValue().get("note_pitch").intValue());
|
NOTEBLOCK_PITCHES.put(javaBlockState, blockData.get("note_pitch").intValue());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry.getKey().contains("piston")) {
|
if (javaId.contains("piston")) {
|
||||||
// True if extended, false if not
|
// True if extended, false if not
|
||||||
PISTON_VALUES.put(javaBlockState, entry.getKey().contains("extended=true"));
|
PISTON_VALUES.put(javaBlockState, javaId.contains("extended=true"));
|
||||||
IS_STICKY_PISTON.put(javaBlockState, entry.getKey().contains("sticky"));
|
IS_STICKY_PISTON.put(javaBlockState, javaId.contains("sticky"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonNode skullVariation = entry.getValue().get("variation");
|
JsonNode skullVariation = blockData.get("variation");
|
||||||
if (skullVariation != null) {
|
if (skullVariation != null) {
|
||||||
SKULL_VARIANTS.put(javaBlockState, (byte) skullVariation.intValue());
|
SKULL_VARIANTS.put(javaBlockState, (byte) skullVariation.intValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonNode skullRotation = entry.getValue().get("skull_rotation");
|
JsonNode skullRotation = blockData.get("skull_rotation");
|
||||||
if (skullRotation != null) {
|
if (skullRotation != null) {
|
||||||
SKULL_ROTATIONS.put(javaBlockState, (byte) skullRotation.intValue());
|
SKULL_ROTATIONS.put(javaBlockState, (byte) skullRotation.intValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry.getKey().contains("wall_skull") || entry.getKey().contains("wall_head")) {
|
if (javaId.contains("wall_skull") || javaId.contains("wall_head")) {
|
||||||
String direction = entry.getKey().substring(entry.getKey().lastIndexOf("facing=") + 7);
|
String direction = javaId.substring(javaId.lastIndexOf("facing=") + 7);
|
||||||
int rotation = 0;
|
int rotation = 0;
|
||||||
switch (direction.substring(0, direction.length() - 1)) {
|
switch (direction.substring(0, direction.length() - 1)) {
|
||||||
case "north":
|
case "north":
|
||||||
@ -131,10 +133,16 @@ public class BlockStateValues {
|
|||||||
SKULL_WALL_DIRECTIONS.put(javaBlockState, rotation);
|
SKULL_WALL_DIRECTIONS.put(javaBlockState, rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonNode shulkerDirection = entry.getValue().get("shulker_direction");
|
JsonNode shulkerDirection = blockData.get("shulker_direction");
|
||||||
if (shulkerDirection != null) {
|
if (shulkerDirection != null) {
|
||||||
BlockStateValues.SHULKERBOX_DIRECTIONS.put(javaBlockState, (byte) shulkerDirection.intValue());
|
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) {
|
public static byte getShulkerBoxDirection(int state) {
|
||||||
return SHULKERBOX_DIRECTIONS.getOrDefault(state, (byte) -1);
|
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);
|
JAVA_ID_BLOCK_MAP.put(javaId, javaRuntimeId);
|
||||||
|
|
||||||
BlockStateValues.storeBlockStateValues(entry, javaRuntimeId);
|
BlockStateValues.storeBlockStateValues(entry.getKey(), javaRuntimeId, entry.getValue());
|
||||||
|
|
||||||
String cleanJavaIdentifier = entry.getKey().split("\\[")[0];
|
String cleanJavaIdentifier = entry.getKey().split("\\[")[0];
|
||||||
|
|
||||||
@ -386,4 +386,11 @@ public class BlockTranslator {
|
|||||||
}
|
}
|
||||||
return itemIdentifier;
|
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.CompoundTag;
|
||||||
import com.github.steveice10.opennbt.tag.builtin.IntTag;
|
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.NbtList;
|
||||||
import com.nukkitx.nbt.NbtMapBuilder;
|
import com.nukkitx.nbt.NbtMapBuilder;
|
||||||
import com.nukkitx.nbt.NbtType;
|
import com.nukkitx.nbt.NbtType;
|
||||||
@ -39,7 +41,10 @@ import java.util.LinkedHashMap;
|
|||||||
public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator {
|
public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator {
|
||||||
@Override
|
@Override
|
||||||
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
|
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
|
// Java sometimes does not provide this tag, but Bedrock crashes if it doesn't exist
|
||||||
// Linked coordinates
|
// Linked coordinates
|
||||||
IntList tagsList = new IntArrayList();
|
IntList tagsList = new IntArrayList();
|
||||||
|
@ -33,6 +33,7 @@ import com.nukkitx.protocol.bedrock.packet.PlayerListPacket;
|
|||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.geysermc.connector.GeyserConnector;
|
import org.geysermc.connector.GeyserConnector;
|
||||||
|
import org.geysermc.connector.common.AuthType;
|
||||||
import org.geysermc.connector.entity.player.PlayerEntity;
|
import org.geysermc.connector.entity.player.PlayerEntity;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.network.session.auth.BedrockClientData;
|
import org.geysermc.connector.network.session.auth.BedrockClientData;
|
||||||
@ -80,7 +81,7 @@ public class SkinManager {
|
|||||||
String capeId, byte[] capeData,
|
String capeId, byte[] capeData,
|
||||||
SkinProvider.SkinGeometry geometry) {
|
SkinProvider.SkinGeometry geometry) {
|
||||||
SerializedSkin serializedSkin = SerializedSkin.of(
|
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,
|
ImageData.of(capeData), geometry.getGeometryData(), "", true, false,
|
||||||
!capeId.equals(SkinProvider.EMPTY_CAPE.getCapeId()), capeId, skinId
|
!capeId.equals(SkinProvider.EMPTY_CAPE.getCapeId()), capeId, skinId
|
||||||
);
|
);
|
||||||
@ -163,7 +164,7 @@ public class SkinManager {
|
|||||||
geometry = SkinProvider.SkinGeometry.getEars(data.isAlex());
|
geometry = SkinProvider.SkinGeometry.getEars(data.isAlex());
|
||||||
|
|
||||||
// Store the skin and geometry for the ears
|
// Store the skin and geometry for the ears
|
||||||
SkinProvider.storeEarSkin(entity.getUuid(), skin);
|
SkinProvider.storeEarSkin(skin);
|
||||||
SkinProvider.storeEarGeometry(entity.getUuid(), data.isAlex());
|
SkinProvider.storeEarGeometry(entity.getUuid(), data.isAlex());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -267,7 +268,10 @@ public class SkinManager {
|
|||||||
|
|
||||||
return new GameProfileData(skinUrl, capeUrl, isAlex);
|
return new GameProfileData(skinUrl, capeUrl, isAlex);
|
||||||
} catch (Exception exception) {
|
} 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);
|
return loadBedrockOrOfflineSkin(profile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -282,7 +286,7 @@ public class SkinManager {
|
|||||||
|
|
||||||
String skinUrl = isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl();
|
String skinUrl = isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl();
|
||||||
String capeUrl = SkinProvider.EMPTY_CAPE.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());
|
GeyserSession session = GeyserConnector.getInstance().getPlayerByUuid(profile.getId());
|
||||||
|
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
|
@ -79,13 +79,12 @@ public class SkinProvider {
|
|||||||
.build();
|
.build();
|
||||||
private static final Map<String, CompletableFuture<Cape>> requestedCapes = new ConcurrentHashMap<>();
|
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<>();
|
private static final Map<UUID, SkinGeometry> cachedGeometry = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public static final boolean ALLOW_THIRD_PARTY_EARS = GeyserConnector.getInstance().getConfig().isAllowThirdPartyEars();
|
public static final boolean ALLOW_THIRD_PARTY_EARS = GeyserConnector.getInstance().getConfig().isAllowThirdPartyEars();
|
||||||
public static String EARS_GEOMETRY;
|
public static final String EARS_GEOMETRY;
|
||||||
public static String EARS_GEOMETRY_SLIM;
|
public static final String EARS_GEOMETRY_SLIM;
|
||||||
public static SkinGeometry SKULL_GEOMETRY;
|
public static final SkinGeometry SKULL_GEOMETRY;
|
||||||
|
|
||||||
public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||||
|
|
||||||
@ -229,15 +228,15 @@ public class SkinProvider {
|
|||||||
return CompletableFuture.completedFuture(officialCape);
|
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);
|
if (earsUrl == null || earsUrl.isEmpty()) return CompletableFuture.completedFuture(skin);
|
||||||
|
|
||||||
CompletableFuture<Skin> future;
|
CompletableFuture<Skin> future;
|
||||||
if (newThread) {
|
if (newThread) {
|
||||||
future = CompletableFuture.supplyAsync(() -> supplyEars(skin, earsUrl, provider), EXECUTOR_SERVICE)
|
future = CompletableFuture.supplyAsync(() -> supplyEars(skin, earsUrl), EXECUTOR_SERVICE)
|
||||||
.whenCompleteAsync((outSkin, throwable) -> { });
|
.whenCompleteAsync((outSkin, throwable) -> { });
|
||||||
} else {
|
} else {
|
||||||
Skin ears = supplyEars(skin, earsUrl, provider); // blocking
|
Skin ears = supplyEars(skin, earsUrl); // blocking
|
||||||
future = CompletableFuture.completedFuture(ears);
|
future = CompletableFuture.completedFuture(ears);
|
||||||
}
|
}
|
||||||
return future;
|
return future;
|
||||||
@ -255,7 +254,7 @@ public class SkinProvider {
|
|||||||
public static CompletableFuture<Skin> requestUnofficialEars(Skin officialSkin, UUID playerId, String username, boolean newThread) {
|
public static CompletableFuture<Skin> requestUnofficialEars(Skin officialSkin, UUID playerId, String username, boolean newThread) {
|
||||||
for (EarsProvider provider : EarsProvider.VALUES) {
|
for (EarsProvider provider : EarsProvider.VALUES) {
|
||||||
Skin skin1 = getOrDefault(
|
Skin skin1 = getOrDefault(
|
||||||
requestEars(provider.getUrlFor(playerId, username), provider, newThread, officialSkin),
|
requestEars(provider.getUrlFor(playerId, username), newThread, officialSkin),
|
||||||
officialSkin, 4
|
officialSkin, 4
|
||||||
);
|
);
|
||||||
if (skin1.isEars()) {
|
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
|
* @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);
|
cachedSkins.put(skin.getTextureUrl(), skin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,7 +322,7 @@ public class SkinProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Cape supplyCape(String capeUrl, CapeProvider provider) {
|
private static Cape supplyCape(String capeUrl, CapeProvider provider) {
|
||||||
byte[] cape = new byte[0];
|
byte[] cape = EMPTY_CAPE.getCapeData();
|
||||||
try {
|
try {
|
||||||
cape = requestImage(capeUrl, provider);
|
cape = requestImage(capeUrl, provider);
|
||||||
} catch (Exception ignored) {} // just ignore I guess
|
} catch (Exception ignored) {} // just ignore I guess
|
||||||
@ -334,7 +332,7 @@ public class SkinProvider {
|
|||||||
return new Cape(
|
return new Cape(
|
||||||
capeUrl,
|
capeUrl,
|
||||||
urlSection[urlSection.length - 1], // get the texture id and use it as cape id
|
urlSection[urlSection.length - 1], // get the texture id and use it as cape id
|
||||||
cape.length > 0 ? cape : EMPTY_CAPE.getCapeData(),
|
cape,
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
cape.length == 0
|
cape.length == 0
|
||||||
);
|
);
|
||||||
@ -345,10 +343,9 @@ public class SkinProvider {
|
|||||||
*
|
*
|
||||||
* @param existingSkin The players current skin
|
* @param existingSkin The players current skin
|
||||||
* @param earsUrl The URL to get the ears texture from
|
* @param earsUrl The URL to get the ears texture from
|
||||||
* @param provider The ears texture provider
|
|
||||||
* @return The updated skin with ears
|
* @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 {
|
try {
|
||||||
// Get the ears texture
|
// Get the ears texture
|
||||||
BufferedImage ears = ImageIO.read(new URL(earsUrl));
|
BufferedImage ears = ImageIO.read(new URL(earsUrl));
|
||||||
@ -415,14 +412,15 @@ public class SkinProvider {
|
|||||||
|
|
||||||
// if the requested image is a cape
|
// if the requested image is a cape
|
||||||
if (provider != null) {
|
if (provider != null) {
|
||||||
while(image.getWidth() > 64) {
|
if (image.getWidth() > 64) {
|
||||||
image = scale(image);
|
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);
|
byte[] data = bufferedImageToImageData(image);
|
||||||
@ -506,12 +504,13 @@ public class SkinProvider {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BufferedImage scale(BufferedImage bufferedImage) {
|
public static BufferedImage scale(BufferedImage bufferedImage, int newWidth, int newHeight) {
|
||||||
BufferedImage resized = new BufferedImage(bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2, BufferedImage.TYPE_INT_ARGB);
|
BufferedImage resized = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
|
||||||
Graphics2D g2 = resized.createGraphics();
|
Graphics2D g2 = resized.createGraphics();
|
||||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
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();
|
g2.dispose();
|
||||||
|
bufferedImage.flush();
|
||||||
return resized;
|
return resized;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -578,17 +577,17 @@ public class SkinProvider {
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
public static class SkinAndCape {
|
public static class SkinAndCape {
|
||||||
private Skin skin;
|
private final Skin skin;
|
||||||
private Cape cape;
|
private final Cape cape;
|
||||||
}
|
}
|
||||||
|
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
public static class Skin {
|
public static class Skin {
|
||||||
private UUID skinOwner;
|
private UUID skinOwner;
|
||||||
private String textureUrl;
|
private final String textureUrl;
|
||||||
private byte[] skinData;
|
private final byte[] skinData;
|
||||||
private long requestedOn;
|
private final long requestedOn;
|
||||||
private boolean updated;
|
private boolean updated;
|
||||||
private boolean ears;
|
private boolean ears;
|
||||||
|
|
||||||
@ -602,19 +601,19 @@ public class SkinProvider {
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
public static class Cape {
|
public static class Cape {
|
||||||
private String textureUrl;
|
private final String textureUrl;
|
||||||
private String capeId;
|
private final String capeId;
|
||||||
private byte[] capeData;
|
private final byte[] capeData;
|
||||||
private long requestedOn;
|
private final long requestedOn;
|
||||||
private boolean failed;
|
private final boolean failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
public static class SkinGeometry {
|
public static class SkinGeometry {
|
||||||
private String geometryName;
|
private final String geometryName;
|
||||||
private String geometryData;
|
private final String geometryData;
|
||||||
private boolean failed;
|
private final boolean failed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate generic geometry
|
* 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.ImageData;
|
||||||
import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin;
|
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.GeyserConnector;
|
||||||
import org.geysermc.connector.entity.player.PlayerEntity;
|
import org.geysermc.connector.entity.player.PlayerEntity;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.utils.LanguageUtils;
|
import org.geysermc.connector.utils.LanguageUtils;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class SkullSkinManager extends SkinManager {
|
public class SkullSkinManager extends SkinManager {
|
||||||
|
|
||||||
public static PlayerListPacket.Entry buildSkullEntryManually(UUID uuid, String username, long geyserId,
|
public static SerializedSkin buildSkullEntryManually(String skinId, byte[] skinData) {
|
||||||
String skinId, byte[] skinData) {
|
|
||||||
// Prevents https://cdn.discordapp.com/attachments/613194828359925800/779458146191147008/unknown.png
|
// Prevents https://cdn.discordapp.com/attachments/613194828359925800/779458146191147008/unknown.png
|
||||||
skinId = skinId + "_skull";
|
skinId = skinId + "_skull";
|
||||||
SerializedSkin serializedSkin = SerializedSkin.of(
|
return SerializedSkin.of(
|
||||||
skinId, SkinProvider.SKULL_GEOMETRY.getGeometryName(), ImageData.of(skinData), Collections.emptyList(),
|
skinId, "", SkinProvider.SKULL_GEOMETRY.getGeometryName(), ImageData.of(skinData), Collections.emptyList(),
|
||||||
ImageData.of(SkinProvider.EMPTY_CAPE.getCapeData()), SkinProvider.SKULL_GEOMETRY.getGeometryData(),
|
ImageData.of(SkinProvider.EMPTY_CAPE.getCapeData()), SkinProvider.SKULL_GEOMETRY.getGeometryData(),
|
||||||
"", true, false, false, SkinProvider.EMPTY_CAPE.getCapeId(), skinId
|
"", 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,
|
public static void requestAndHandleSkin(PlayerEntity entity, GeyserSession session,
|
||||||
Consumer<SkinProvider.Skin> skinConsumer) {
|
Consumer<SkinProvider.Skin> skinConsumer) {
|
||||||
GameProfileData data = GameProfileData.from(entity.getProfile());
|
GameProfileData data = GameProfileData.from(entity.getProfile());
|
||||||
|
|
||||||
SkinProvider.requestSkin(entity.getUuid(), data.getSkinUrl(), false)
|
SkinProvider.requestSkin(entity.getUuid(), data.getSkinUrl(), true)
|
||||||
.whenCompleteAsync((skin, throwable) -> {
|
.whenCompleteAsync((skin, throwable) -> {
|
||||||
try {
|
try {
|
||||||
if (session.getUpstream().isInitialized()) {
|
if (session.getUpstream().isInitialized()) {
|
||||||
PlayerListPacket.Entry updatedEntry = buildSkullEntryManually(
|
PlayerSkinPacket packet = new PlayerSkinPacket();
|
||||||
entity.getUuid(),
|
packet.setUuid(entity.getUuid());
|
||||||
entity.getUsername(),
|
packet.setOldSkinName("");
|
||||||
entity.getGeyserId(),
|
packet.setNewSkinName(skin.getTextureUrl());
|
||||||
skin.getTextureUrl(),
|
packet.setSkin(buildSkullEntryManually(skin.getTextureUrl(), skin.getSkinData()));
|
||||||
skin.getSkinData()
|
packet.setTrustedSkin(true);
|
||||||
);
|
session.sendUpstreamPacket(packet);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e);
|
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e);
|
||||||
|
@ -26,7 +26,6 @@
|
|||||||
package org.geysermc.connector.utils;
|
package org.geysermc.connector.utils;
|
||||||
|
|
||||||
import com.nukkitx.protocol.bedrock.packet.SetTitlePacket;
|
import com.nukkitx.protocol.bedrock.packet.SetTitlePacket;
|
||||||
import org.geysermc.connector.GeyserConnector;
|
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
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
|
* Much of the work here is from the wonderful folks from ViaRewind: https://github.com/ViaVersion/ViaRewind
|
||||||
*/
|
*/
|
||||||
public class CooldownUtils {
|
public class CooldownUtils {
|
||||||
|
private static boolean SHOW_COOLDOWN;
|
||||||
|
|
||||||
private final static boolean SHOW_COOLDOWN;
|
public static void setShowCooldown(boolean showCooldown) {
|
||||||
|
SHOW_COOLDOWN = showCooldown;
|
||||||
static {
|
|
||||||
SHOW_COOLDOWN = GeyserConnector.getInstance().getConfig().isShowCooldown();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -116,5 +114,4 @@ public class CooldownUtils {
|
|||||||
}
|
}
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -90,21 +90,22 @@ public class FileUtils {
|
|||||||
*/
|
*/
|
||||||
public static File fileOrCopiedFromResource(File file, String name, Function<String, String> format) throws IOException {
|
public static File fileOrCopiedFromResource(File file, String name, Function<String, String> format) throws IOException {
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
file.createNewFile();
|
file.createNewFile();
|
||||||
FileOutputStream fos = new FileOutputStream(file);
|
try (FileOutputStream fos = new FileOutputStream(file)) {
|
||||||
InputStream input = GeyserConnector.class.getResourceAsStream("/" + name); // resources need leading "/" prefix
|
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.flush();
|
||||||
fos.write(c);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fos.flush();
|
|
||||||
input.close();
|
|
||||||
fos.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return file;
|
return file;
|
||||||
@ -122,14 +123,13 @@ public class FileUtils {
|
|||||||
file.createNewFile();
|
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.flush();
|
||||||
fos.write(c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fos.flush();
|
|
||||||
fos.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -232,9 +232,10 @@ public class FileUtils {
|
|||||||
try {
|
try {
|
||||||
int size = stream.available();
|
int size = stream.available();
|
||||||
byte[] bytes = new byte[size];
|
byte[] bytes = new byte[size];
|
||||||
BufferedInputStream buf = new BufferedInputStream(stream);
|
try (BufferedInputStream buf = new BufferedInputStream(stream)) {
|
||||||
buf.read(bytes, 0, bytes.length);
|
//noinspection ResultOfMethodCallIgnored
|
||||||
buf.close();
|
buf.read(bytes, 0, bytes.length);
|
||||||
|
}
|
||||||
return bytes;
|
return bytes;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException("Error while trying to read input stream!");
|
throw new RuntimeException("Error while trying to read input stream!");
|
||||||
|
@ -69,8 +69,8 @@ public class LanguageUtils {
|
|||||||
// Load the locale
|
// Load the locale
|
||||||
if (localeStream != null) {
|
if (localeStream != null) {
|
||||||
Properties localeProp = new Properties();
|
Properties localeProp = new Properties();
|
||||||
try {
|
try (InputStreamReader reader = new InputStreamReader(localeStream, StandardCharsets.UTF_8)) {
|
||||||
localeProp.load(new InputStreamReader(localeStream, StandardCharsets.UTF_8));
|
localeProp.load(reader);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new AssertionError(getLocaleStringLog("geyser.language.load_failed", locale), e);
|
throw new AssertionError(getLocaleStringLog("geyser.language.load_failed", locale), e);
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,7 @@ public class LocaleUtils {
|
|||||||
ASSET_MAP.put(entry.getKey(), asset);
|
ASSET_MAP.put(entry.getKey(), asset);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} 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) { }
|
} 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();
|
targetHash = clientJarInfo.getSha1();
|
||||||
} else {
|
} else {
|
||||||
curHash = byteArrayToHexString(FileUtils.calculateSHA1(localeFile));
|
curHash = byteArrayToHexString(FileUtils.calculateSHA1(localeFile));
|
||||||
@ -168,9 +174,13 @@ public class LocaleUtils {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the hash and download the locale
|
try {
|
||||||
String hash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash();
|
// Get the hash and download the locale
|
||||||
WebUtils.downloadFile("https://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, localeFile.toString());
|
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());
|
WebUtils.downloadFile(clientJarInfo.getUrl(), tmpFilePath.toString());
|
||||||
|
|
||||||
// Load in the JAR as a zip and extract the file
|
// Load in the JAR as a zip and extract the file
|
||||||
ZipFile localeJar = new ZipFile(tmpFilePath.toString());
|
try (ZipFile localeJar = new ZipFile(tmpFilePath.toString())) {
|
||||||
InputStream fileStream = localeJar.getInputStream(localeJar.getEntry("assets/minecraft/lang/en_us.json"));
|
try (InputStream fileStream = localeJar.getInputStream(localeJar.getEntry("assets/minecraft/lang/en_us.json"))) {
|
||||||
FileOutputStream outStream = new FileOutputStream(localeFile);
|
try (FileOutputStream outStream = new FileOutputStream(localeFile)) {
|
||||||
|
|
||||||
// Write the file to the locale dir
|
// Write the file to the locale dir
|
||||||
byte[] buf = new byte[fileStream.available()];
|
byte[] buf = new byte[fileStream.available()];
|
||||||
int length;
|
int length;
|
||||||
while ((length = fileStream.read(buf)) != -1) {
|
while ((length = fileStream.read(buf)) != -1) {
|
||||||
outStream.write(buf, 0, length);
|
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
|
// Store the latest jar hash
|
||||||
FileUtils.writeFile(GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("locales/en_us.hash").toString(), clientJarInfo.getSha1().toCharArray());
|
FileUtils.writeFile(GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("locales/en_us.hash").toString(), clientJarInfo.getSha1().toCharArray());
|
||||||
|
|
||||||
// Delete the nolonger needed client/server jar
|
// Delete the nolonger needed client/server jar
|
||||||
Files.delete(tmpFilePath);
|
Files.delete(tmpFilePath);
|
||||||
} catch (Exception e) {
|
} 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.io.File;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipFile;
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -70,10 +72,12 @@ public class ResourcePack {
|
|||||||
|
|
||||||
pack.sha256 = FileUtils.calculateSHA256(file);
|
pack.sha256 = FileUtils.calculateSHA256(file);
|
||||||
|
|
||||||
|
Stream<? extends ZipEntry> stream = null;
|
||||||
try {
|
try {
|
||||||
ZipFile zip = new ZipFile(file);
|
ZipFile zip = new ZipFile(file);
|
||||||
|
|
||||||
zip.stream().forEach((x) -> {
|
stream = zip.stream();
|
||||||
|
stream.forEach((x) -> {
|
||||||
if (x.getName().contains("manifest.json")) {
|
if (x.getName().contains("manifest.json")) {
|
||||||
try {
|
try {
|
||||||
ResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), ResourcePackManifest.class);
|
ResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), ResourcePackManifest.class);
|
||||||
@ -94,6 +98,10 @@ public class ResourcePack {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.resource_pack.broken", file.getName()));
|
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.resource_pack.broken", file.getName()));
|
||||||
e.printStackTrace();
|
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 {
|
public static String post(String reqURL, String postContent) throws IOException {
|
||||||
URL url = null;
|
URL url = new URL(reqURL);
|
||||||
url = new URL(reqURL);
|
|
||||||
HttpURLConnection con = (HttpURLConnection) url.openConnection();
|
HttpURLConnection con = (HttpURLConnection) url.openConnection();
|
||||||
con.setRequestMethod("POST");
|
con.setRequestMethod("POST");
|
||||||
con.setRequestProperty("Content-Type", "text/plain");
|
con.setRequestProperty("Content-Type", "text/plain");
|
||||||
@ -112,7 +111,7 @@ public class WebUtils {
|
|||||||
*/
|
*/
|
||||||
private static String connectionToString(HttpURLConnection con) throws IOException {
|
private static String connectionToString(HttpURLConnection con) throws IOException {
|
||||||
// Send the request (we dont use this but its required for getErrorStream() to work)
|
// 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
|
// Read the error message if there is one if not just read normally
|
||||||
InputStream inputStream = con.getErrorStream();
|
InputStream inputStream = con.getErrorStream();
|
||||||
@ -120,18 +119,18 @@ public class WebUtils {
|
|||||||
inputStream = con.getInputStream();
|
inputStream = con.getInputStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
|
StringBuilder content = new StringBuilder();
|
||||||
String inputLine;
|
try (BufferedReader in = new BufferedReader(new InputStreamReader(inputStream))) {
|
||||||
StringBuffer content = new StringBuffer();
|
String inputLine;
|
||||||
|
|
||||||
while ((inputLine = in.readLine()) != null) {
|
while ((inputLine = in.readLine()) != null) {
|
||||||
content.append(inputLine);
|
content.append(inputLine);
|
||||||
content.append("\n");
|
content.append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
con.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
in.close();
|
|
||||||
con.disconnect();
|
|
||||||
|
|
||||||
return content.toString();
|
return content.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,10 +18,19 @@ bedrock:
|
|||||||
# This option is for the plugin version only.
|
# This option is for the plugin version only.
|
||||||
clone-remote-port: false
|
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
|
# The MOTD that will be broadcasted to Minecraft: Bedrock Edition clients. This is irrelevant if "passthrough-motd" is set to true
|
||||||
motd1: "GeyserMC"
|
# If either of these are empty, the respective string will default to "Geyser"
|
||||||
motd2: "Another GeyserMC forced host."
|
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.
|
# 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"
|
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:
|
remote:
|
||||||
# The IP address of the remote (Java Edition) server
|
# 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,
|
# 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.
|
# Increase if you are getting BrokenPipe errors.
|
||||||
ping-passthrough-interval: 3
|
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
|
max-players: 100
|
||||||
|
|
||||||
# If debug messages should be sent through console
|
# If debug messages should be sent through console
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 2e52b01cc541c8925346f93be8940087d9af1661
|
Subproject commit bf0610450ce94507a18286e94af2965550ff9eaa
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren