Merge remote-tracking branch 'origin/master' into floodgate-2.0

# Conflicts:
#	bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java
#	bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java
#	connector/src/main/java/org/geysermc/connector/GeyserConnector.java
#	connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java
This commit is contained in:
Tim203 2020-09-19 14:40:12 +02:00
commit 5b317ec595
No known key found for this signature in database
GPG Key ID: 064EE9F5BF7C3EE8
122 changed files with 3221 additions and 1121 deletions

View File

@ -11,4 +11,4 @@ assignees: ''
Add a description
**Alternatives?**
Any alternatives you have tryed
List any alternatives you might have tried

View File

@ -22,7 +22,7 @@ jobs:
- name: submodules-init
uses: snickerbockers/submodules-init@v4
- name: Build with Maven
run: mvn -B package
run: mvn -B package -T 2C
- name: Archive artifacts (Geyser Standalone)
uses: actions/upload-artifact@v2
if: success()

3
.gitignore vendored
View File

@ -241,4 +241,5 @@ config.yml
logs/
public-key.pem
locales/
cache/
/cache/
/packs/

View File

@ -18,7 +18,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have now joined us here!
### Currently supporting Minecraft Bedrock v1.16.x and Minecraft Java v1.16.2.
### Currently supporting Minecraft Bedrock v1.16.x and Minecraft Java v1.16.3.
## Setting Up
Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser.
@ -35,13 +35,14 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set
## What's Left to be Added/Fixed
- The Following Inventories
- [ ] Enchantment Table
- [ ] Enchantment Table (as a proper GUI)
- [ ] Beacon
- [ ] Cartography Table
- [ ] Stonecutter
- [ ] Command Block
- [ ] Structure Block
- [ ] Horse Inventory
- [ ] Loom
- [ ] Smithing Table
- Some Entity Flags
## Compiling

View File

@ -61,14 +61,34 @@
<pattern>net.md_5.bungee.jni</pattern>
<shadedPattern>org.geysermc.platform.bungeecord.shaded.jni</shadedPattern>
</relocation>
<relocation>
<pattern>com.fasterxml.jackson</pattern>
<shadedPattern>org.geysermc.platform.bungeecord.shaded.jackson</shadedPattern>
</relocation>
<relocation>
<pattern>io.netty</pattern>
<shadedPattern>org.geysermc.platform.bungeecord.shaded.netty</shadedPattern>
</relocation>
<relocation>
<pattern>org.reflections.reflections</pattern>
<pattern>org.reflections</pattern>
<shadedPattern>org.geysermc.platform.bungeecord.shaded.reflections</shadedPattern>
</relocation>
<relocation>
<pattern>com.google.common</pattern>
<shadedPattern>org.geysermc.platform.bungeecord.shaded.google.common</shadedPattern>
</relocation>
<relocation>
<pattern>com.google.guava</pattern>
<shadedPattern>org.geysermc.platform.bungeecord.shaded.google.guava</shadedPattern>
</relocation>
<relocation>
<pattern>org.dom4j</pattern>
<shadedPattern>org.geysermc.platform.bungeecord.shaded.dom4j</shadedPattern>
</relocation>
<relocation>
<pattern>net.kyori.adventure</pattern>
<shadedPattern>org.geysermc.platform.bungeecord.shaded.adventure</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>

View File

@ -29,27 +29,22 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Getter;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.config.Configuration;
import org.geysermc.connector.FloodgateKeyLoader;
import org.geysermc.connector.configuration.GeyserJacksonConfiguration;
import java.nio.file.Path;
import java.nio.file.Paths;
@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
public class GeyserBungeeConfiguration extends GeyserJacksonConfiguration {
public final class GeyserBungeeConfiguration extends GeyserJacksonConfiguration {
@JsonIgnore
private Path floodgateKey;
private Path floodgateKeyPath;
public void loadFloodgate(GeyserBungeePlugin plugin, Configuration configuration) {
public void loadFloodgate(GeyserBungeePlugin plugin) {
Plugin floodgate = plugin.getProxy().getPluginManager().getPlugin("floodgate");
floodgateKey = FloodgateKeyLoader.getKey(plugin.getGeyserLogger(), this, Paths.get(plugin.getDataFolder().toString(), configuration.getString("floodgate-key-file"), "public-key.pem"), floodgate, floodgate != null ? floodgate.getDataFolder().toPath() : null);
}
Path geyserDataFolder = plugin.getDataFolder().toPath();
Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null;
@Override
public Path getFloodgateKeyFile() {
return floodgateKey;
floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgate, floodgateDataFolder, geyserDataFolder, plugin.getGeyserLogger());
}
}

View File

@ -25,19 +25,21 @@
package org.geysermc.platform.bungeecord;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.connector.GeyserLogger;
import java.util.logging.Level;
import java.util.logging.Logger;
public class GeyserBungeeLogger implements GeyserLogger {
private final Logger logger;
@Getter @Setter
private boolean debug;
private Logger logger;
private boolean debugMode;
public GeyserBungeeLogger(Logger logger, boolean debugMode) {
public GeyserBungeeLogger(Logger logger, boolean debug) {
this.logger = logger;
this.debugMode = debugMode;
this.debug = debug;
}
@Override
@ -72,12 +74,8 @@ public class GeyserBungeeLogger implements GeyserLogger {
@Override
public void debug(String message) {
if (debugMode)
if (debug) {
info(message);
}
@Override
public void setDebug(boolean debug) {
debugMode = debug;
}
}
}

View File

@ -27,13 +27,10 @@ package org.geysermc.platform.bungeecord;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;
import net.md_5.bungee.config.YamlConfiguration;
import org.geysermc.connector.common.PlatformType;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.bootstrap.GeyserBootstrap;
import org.geysermc.connector.command.CommandManager;
import org.geysermc.connector.common.PlatformType;
import org.geysermc.connector.configuration.GeyserConfiguration;
import org.geysermc.connector.dump.BootstrapDumpInfo;
import org.geysermc.connector.ping.GeyserLegacyPingPassthrough;
@ -64,13 +61,11 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
if (!getDataFolder().exists())
getDataFolder().mkdir();
Configuration configuration = null;
try {
if (!getDataFolder().exists())
getDataFolder().mkdir();
File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()));
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserBungeeConfiguration.class);
configuration = ConfigurationProvider.getProvider(YamlConfiguration.class).load(new File(getDataFolder(), "config.yml"));
} catch (IOException ex) {
getLogger().log(Level.WARNING, LanguageUtils.getLocaleStringLog("geyser.config.failed"), ex);
ex.printStackTrace();
@ -83,6 +78,11 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
// By default this should be localhost but may need to be changed in some circumstances
if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) {
this.geyserConfig.setAutoconfiguredRemote(true);
// Don't use localhost if not listening on all interfaces
if (!javaAddr.getHostString().equals("0.0.0.0") && !javaAddr.getHostString().equals("")) {
this.geyserConfig.getRemote().setAddress(javaAddr.getHostString());
}
this.geyserConfig.getRemote().setPort(javaAddr.getPort());
}
@ -97,9 +97,13 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
if (geyserConfig.getRemote().getAuthType().equals("floodgate") && getProxy().getPluginManager().getPlugin("floodgate") == null) {
geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling"));
return;
} else if (geyserConfig.isAutoconfiguredRemote() && getProxy().getPluginManager().getPlugin("floodgate-bungee") != null) {
// Floodgate installed means that the user wants Floodgate authentication
geyserLogger.debug("Auto-setting to Floodgate authentication.");
geyserConfig.getRemote().setAuthType("floodgate");
}
geyserConfig.loadFloodgate(this, configuration);
geyserConfig.loadFloodgate(this);
this.connector = GeyserConnector.start(PlatformType.BUNGEECORD, this);

View File

@ -20,13 +20,13 @@
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.14-R0.1-SNAPSHOT</version>
<version>1.15.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>us.myles</groupId>
<artifactId>viaversion</artifactId>
<version>3.1.0</version>
<version>3.1.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
@ -76,9 +76,25 @@
<shadedPattern>org.geysermc.platform.spigot.shaded.jackson</shadedPattern>
</relocation>
<relocation>
<pattern>org.reflections.reflections</pattern>
<pattern>org.reflections</pattern>
<shadedPattern>org.geysermc.platform.spigot.shaded.reflections</shadedPattern>
</relocation>
<relocation>
<pattern>com.google.common</pattern>
<shadedPattern>org.geysermc.platform.spigot.shaded.google.common</shadedPattern>
</relocation>
<relocation>
<pattern>com.google.guava</pattern>
<shadedPattern>org.geysermc.platform.spigot.shaded.google.guava</shadedPattern>
</relocation>
<relocation>
<pattern>org.dom4j</pattern>
<shadedPattern>org.geysermc.platform.spigot.shaded.dom4j</shadedPattern>
</relocation>
<relocation>
<pattern>net.kyori.adventure</pattern>
<shadedPattern>org.geysermc.platform.spigot.shaded.adventure</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>

View File

@ -27,7 +27,6 @@ package org.geysermc.platform.spigot;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
@ -35,26 +34,19 @@ import org.geysermc.connector.FloodgateKeyLoader;
import org.geysermc.connector.configuration.GeyserJacksonConfiguration;
import java.nio.file.Path;
import java.nio.file.Paths;
@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
public class GeyserSpigotConfiguration extends GeyserJacksonConfiguration {
@JsonProperty("floodgate-key-file")
private String floodgateKeyFile;
public final class GeyserSpigotConfiguration extends GeyserJacksonConfiguration {
@JsonIgnore
private Path floodgateKey;
private Path floodgateKeyPath;
public void loadFloodgate(GeyserSpigotPlugin plugin) {
Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate");
floodgateKey = FloodgateKeyLoader.getKey(plugin.getGeyserLogger(), this, Paths.get(plugin.getDataFolder().toString(), plugin.getConfig().getString("floodgate-key-file", "public-key.pem")), floodgate, floodgate != null ? floodgate.getDataFolder().toPath() : null);
}
Path geyserDataFolder = plugin.getDataFolder().toPath();
Path floodgateDataFolder = floodgate != null ? floodgate.getDataFolder().toPath() : null;
@Override
public Path getFloodgateKeyFile() {
return floodgateKey;
floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgate, floodgateDataFolder, geyserDataFolder, plugin.getGeyserLogger());
}
@Override

View File

@ -26,7 +26,8 @@
package org.geysermc.platform.spigot;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.connector.GeyserLogger;
import java.util.logging.Level;
@ -34,9 +35,9 @@ import java.util.logging.Logger;
@AllArgsConstructor
public class GeyserSpigotLogger implements GeyserLogger {
private Logger logger;
private boolean debugMode;
private final Logger logger;
@Getter @Setter
private boolean debug;
@Override
public void severe(String message) {
@ -70,12 +71,8 @@ public class GeyserSpigotLogger implements GeyserLogger {
@Override
public void debug(String message) {
if (debugMode)
if (debug) {
info(message);
}
@Override
public void setDebug(boolean debug) {
debugMode = debug;
}
}
}

View File

@ -27,10 +27,10 @@ package org.geysermc.platform.spigot;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import org.geysermc.connector.common.PlatformType;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.bootstrap.GeyserBootstrap;
import org.geysermc.connector.command.CommandManager;
import org.geysermc.connector.common.PlatformType;
import org.geysermc.connector.configuration.GeyserConfiguration;
import org.geysermc.connector.dump.BootstrapDumpInfo;
import org.geysermc.connector.network.translators.world.WorldManager;
@ -56,7 +56,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
private GeyserSpigotConfiguration geyserConfig;
private GeyserSpigotLogger geyserLogger;
private IGeyserPingPassthrough geyserSpigotPingPassthrough;
private GeyserSpigotBlockPlaceListener blockPlaceListener;
private GeyserSpigotWorldManager geyserWorldManager;
private GeyserConnector connector;
@ -83,6 +82,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
// By default this should be localhost but may need to be changed in some circumstances
if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) {
geyserConfig.setAutoconfiguredRemote(true);
// Don't use localhost if not listening on all interfaces
if (!Bukkit.getIp().equals("0.0.0.0") && !Bukkit.getIp().equals("")) {
geyserConfig.getRemote().setAddress(Bukkit.getIp());
}
geyserConfig.getRemote().setPort(Bukkit.getPort());
}
@ -97,6 +101,10 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling"));
this.getPluginLoader().disablePlugin(this);
return;
} else if (geyserConfig.isAutoconfiguredRemote() && Bukkit.getPluginManager().getPlugin("floodgate-bukkit") != null) {
// Floodgate installed means that the user wants Floodgate authentication
geyserLogger.debug("Auto-setting to Floodgate authentication.");
geyserConfig.getRemote().setAuthType("floodgate");
}
geyserConfig.loadFloodgate(this);
@ -115,10 +123,15 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
// Used to determine if Block.getBlockData() is present.
boolean isLegacy = !isCompatible(Bukkit.getServer().getVersion(), "1.13.0");
if (isLegacy)
geyserLogger.debug("Legacy version of Minecraft (1.12.2 or older) detected.");
geyserLogger.debug("Legacy version of Minecraft (1.12.2 or older) detected; falling back to ViaVersion for block state retrieval.");
this.geyserWorldManager = new GeyserSpigotWorldManager(isLegacy, isViaVersion);
this.blockPlaceListener = new GeyserSpigotBlockPlaceListener(connector, isLegacy, isViaVersion);
boolean use3dBiomes = isCompatible(Bukkit.getServer().getVersion(), "1.16.0");
if (!use3dBiomes) {
geyserLogger.debug("Legacy version of Minecraft (1.15.2 or older) detected; not using 3D biomes.");
}
this.geyserWorldManager = new GeyserSpigotWorldManager(isLegacy, use3dBiomes, isViaVersion);
GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(connector, isLegacy, isViaVersion);
Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this);

View File

@ -59,7 +59,7 @@ public class GeyserSpigotBlockPlaceListener implements Listener {
} else {
javaBlockId = event.getBlockPlaced().getBlockData().getAsString();
}
placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaIdBlockMap().get(javaBlockId)));
placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaIdBlockMap().getOrDefault(javaBlockId, 0)));
placeBlockSoundPacket.setIdentifier(":");
session.sendUpstreamPacket(placeBlockSoundPacket);
session.setLastBlockPlacePosition(null);

View File

@ -25,34 +25,85 @@
package org.geysermc.platform.spigot.world;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import lombok.AllArgsConstructor;
import com.fasterxml.jackson.databind.JsonNode;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.block.Biome;
import org.bukkit.block.Block;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.GeyserWorldManager;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.utils.FileUtils;
import org.geysermc.connector.utils.GameRule;
import org.geysermc.connector.utils.LanguageUtils;
import us.myles.ViaVersion.protocols.protocol1_13_1to1_13.Protocol1_13_1To1_13;
import us.myles.ViaVersion.protocols.protocol1_16_2to1_16_1.data.MappingData;
@AllArgsConstructor
import java.io.InputStream;
public class GeyserSpigotWorldManager extends GeyserWorldManager {
private final boolean isLegacy;
// You need ViaVersion to connect to an older server with Geyser.
// However, we still check for ViaVersion in case there's some other way that gets Geyser on a pre-1.13 Bukkit server
private final boolean use3dBiomes;
/**
* You need ViaVersion to connect to an older server with Geyser.
* However, we still check for ViaVersion in case there's some other way that gets Geyser on a pre-1.13 Bukkit server
*/
private final boolean isViaVersion;
/**
* Stores a list of {@link Biome} ordinal numbers to Minecraft biome numeric IDs.
*
* Working with the Biome enum in Spigot poses two problems:
* 1: The Biome enum values change in both order and names over the years.
* 2: There is no way to get the Minecraft biome ID from the name itself with Spigot.
* To solve both of these problems, we store a JSON file of every Biome enum that has existed,
* along with its 1.16 biome number.
*
* The key is the Spigot Biome ordinal; the value is the Minecraft Java biome numerical ID
*/
private final Int2IntMap biomeToIdMap = new Int2IntOpenHashMap(Biome.values().length);
public GeyserSpigotWorldManager(boolean isLegacy, boolean use3dBiomes, boolean isViaVersion) {
this.isLegacy = isLegacy;
this.use3dBiomes = use3dBiomes;
this.isViaVersion = isViaVersion;
// Load the values into the biome-to-ID map
InputStream biomeStream = FileUtils.getResource("biomes.json");
JsonNode biomes;
try {
biomes = GeyserConnector.JSON_MAPPER.readTree(biomeStream);
} catch (Exception e) {
throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e);
}
// Only load in the biomes that are present in this version of Minecraft
for (Biome enumBiome : Biome.values()) {
if (biomes.has(enumBiome.toString())) {
biomeToIdMap.put(enumBiome.ordinal(), biomes.get(enumBiome.toString()).intValue());
} else {
GeyserConnector.getInstance().getLogger().debug("No biome mapping found for " + enumBiome.toString() +
", defaulting to 0");
biomeToIdMap.put(enumBiome.ordinal(), 0);
}
}
}
@Override
public int getBlockAt(GeyserSession session, int x, int y, int z) {
if (session.getPlayerEntity() == null) {
return BlockTranslator.AIR;
}
if (Bukkit.getPlayer(session.getPlayerEntity().getUsername()) == null) {
return BlockTranslator.AIR;
}
if (isLegacy) {
return getLegacyBlock(session, x, y, z, isViaVersion);
}
return BlockTranslator.getJavaIdBlockMap().get(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getBlockAt(x, y, z).getBlockData().getAsString());
//TODO possibly: detect server version for all versions and use ViaVersion for block state mappings like below
return BlockTranslator.getJavaIdBlockMap().getOrDefault(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getBlockAt(x, y, z).getBlockData().getAsString(), 0);
}
@SuppressWarnings("deprecation")
@ -74,6 +125,43 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager {
}
@Override
@SuppressWarnings("deprecation")
public int[] getBiomeDataAt(GeyserSession session, int x, int z) {
if (session.getPlayerEntity() == null) {
return new int[1024];
}
int[] biomeData = new int[1024];
World world = Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld();
int chunkX = x << 4;
int chunkZ = z << 4;
int chunkXmax = chunkX + 16;
int chunkZmax = chunkZ + 16;
// 3D biomes didn't exist until 1.15
if (use3dBiomes) {
for (int localX = chunkX; localX < chunkXmax; localX += 4) {
for (int localY = 0; localY < 255; localY += + 4) {
for (int localZ = chunkZ; localZ < chunkZmax; localZ += 4) {
// Index is based on wiki.vg's index requirements
final int i = ((localY >> 2) & 63) << 4 | ((localZ >> 2) & 3) << 2 | ((localX >> 2) & 3);
biomeData[i] = biomeToIdMap.getOrDefault(world.getBiome(localX, localY, localZ).ordinal(), 0);
}
}
}
} else {
// Looks like the same code, but we're not checking the Y coordinate here
for (int localX = chunkX; localX < chunkXmax; localX += 4) {
for (int localY = 0; localY < 255; localY += + 4) {
for (int localZ = chunkZ; localZ < chunkZmax; localZ += 4) {
// Index is based on wiki.vg's index requirements
final int i = ((localY >> 2) & 63) << 4 | ((localZ >> 2) & 3) << 2 | ((localX >> 2) & 3);
biomeData[i] = biomeToIdMap.getOrDefault(world.getBiome(localX, localZ).ordinal(), 0);
}
}
}
}
return biomeData;
}
public Boolean getGameRuleBool(GeyserSession session, GameRule gameRule) {
return Boolean.parseBoolean(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getGameRuleValue(gameRule.getJavaID()));
}

View File

@ -0,0 +1,155 @@
{
"MUTATED_ICE_FLATS" : 140,
"MUTATED_TAIGA" : 133,
"SAVANNA_PLATEAU_MOUNTAINS" : 164,
"DEEP_WARM_OCEAN" : 47,
"REDWOOD_TAIGA_HILLS" : 33,
"THE_VOID" : 127,
"COLD_TAIGA_MOUNTAINS" : 158,
"BAMBOO_JUNGLE_HILLS" : 169,
"MOUNTAINS" : 3,
"MESA_PLATEAU" : 39,
"SNOWY_TAIGA_HILLS" : 31,
"DEEP_FROZEN_OCEAN" : 50,
"EXTREME_HILLS" : 3,
"BIRCH_FOREST_MOUNTAINS" : 155,
"FOREST" : 4,
"BIRCH_FOREST" : 27,
"SNOWY_TUNDRA" : 12,
"ICE_SPIKES" : 140,
"FROZEN_OCEAN" : 10,
"WARPED_FOREST" : 172,
"WOODED_BADLANDS_PLATEAU" : 38,
"BADLANDS_PLATEAU" : 39,
"ICE_PLAINS_SPIKES" : 140,
"MEGA_TAIGA" : 32,
"MUTATED_SAVANNA_ROCK" : 164,
"SAVANNA_PLATEAU" : 36,
"DARK_FOREST_HILLS" : 157,
"END_MIDLANDS" : 41,
"SHATTERED_SAVANNA_PLATEAU" : 164,
"SAVANNA" : 35,
"MUSHROOM_ISLAND_SHORE" : 15,
"SWAMP" : 6,
"ICE_MOUNTAINS" : 13,
"BEACH" : 16,
"MUTATED_MESA_CLEAR_ROCK" : 167,
"END_HIGHLANDS" : 42,
"COLD_BEACH" : 26,
"JUNGLE" : 21,
"MUTATED_TAIGA_COLD" : 158,
"TALL_BIRCH_HILLS" : 156,
"DARK_FOREST" : 29,
"WOODED_HILLS" : 18,
"HELL" : 8,
"MUTATED_REDWOOD_TAIGA" : 160,
"MESA_PLATEAU_FOREST" : 38,
"MUSHROOM_ISLAND" : 14,
"BADLANDS" : 37,
"END_BARRENS" : 43,
"MUTATED_EXTREME_HILLS_WITH_TREES" : 162,
"MUTATED_JUNGLE_EDGE" : 151,
"MODIFIED_BADLANDS_PLATEAU" : 167,
"ROOFED_FOREST_MOUNTAINS" : 157,
"SOUL_SAND_VALLEY" : 170,
"DESERT" : 2,
"MUTATED_PLAINS" : 129,
"MUTATED_BIRCH_FOREST" : 155,
"WOODED_MOUNTAINS" : 34,
"TAIGA_HILLS" : 19,
"BAMBOO_JUNGLE" : 168,
"SWAMPLAND_MOUNTAINS" : 134,
"DESERT_MOUNTAINS" : 130,
"REDWOOD_TAIGA" : 32,
"MUSHROOM_FIELDS" : 14,
"GIANT_TREE_TAIGA_HILLS" : 33,
"PLAINS" : 1,
"JUNGLE_EDGE" : 23,
"SAVANNA_MOUNTAINS" : 163,
"DEEP_COLD_OCEAN" : 49,
"DESERT_LAKES" : 130,
"MOUNTAIN_EDGE" : 20,
"SNOWY_MOUNTAINS" : 13,
"MESA_PLATEAU_MOUNTAINS" : 167,
"JUNGLE_MOUNTAINS" : 149,
"SMALLER_EXTREME_HILLS" : 20,
"MESA_PLATEAU_FOREST_MOUNTAINS" : 166,
"NETHER_WASTES" : 8,
"BIRCH_FOREST_HILLS_MOUNTAINS" : 156,
"MUTATED_JUNGLE" : 149,
"WARM_OCEAN" : 44,
"DEEP_OCEAN" : 24,
"STONE_BEACH" : 25,
"MODIFIED_JUNGLE" : 149,
"MUTATED_SAVANNA" : 163,
"TAIGA_COLD_HILLS" : 31,
"OCEAN" : 0,
"SMALL_END_ISLANDS" : 40,
"MUSHROOM_FIELD_SHORE" : 15,
"GRAVELLY_MOUNTAINS" : 131,
"FROZEN_RIVER" : 11,
"TAIGA_COLD" : 30,
"BASALT_DELTAS" : 173,
"EXTREME_HILLS_WITH_TREES" : 34,
"MEGA_TAIGA_HILLS" : 33,
"MUTATED_FOREST" : 132,
"MUTATED_BIRCH_FOREST_HILLS" : 156,
"SKY" : 9,
"LUKEWARM_OCEAN" : 45,
"EXTREME_HILLS_MOUNTAINS" : 131,
"COLD_TAIGA_HILLS" : 31,
"THE_END" : 9,
"SUNFLOWER_PLAINS" : 129,
"SAVANNA_ROCK" : 36,
"ERODED_BADLANDS" : 165,
"STONE_SHORE" : 25,
"EXTREME_HILLS_PLUS_MOUNTAINS" : 162,
"CRIMSON_FOREST" : 171,
"VOID" : 127,
"SNOWY_TAIGA" : 30,
"SNOWY_TAIGA_MOUNTAINS" : 158,
"FLOWER_FOREST" : 132,
"COLD_OCEAN" : 46,
"BEACHES" : 16,
"MESA" : 37,
"MUSHROOM_SHORE" : 15,
"MESA_CLEAR_ROCK" : 39,
"NETHER" : 8,
"ICE_PLAINS" : 12,
"SHATTERED_SAVANNA" : 163,
"ROOFED_FOREST" : 29,
"GIANT_SPRUCE_TAIGA_HILLS" : 161,
"SNOWY_BEACH" : 26,
"MESA_BRYCE" : 165,
"JUNGLE_EDGE_MOUNTAINS" : 151,
"MUTATED_DESERT" : 130,
"MODIFIED_GRAVELLY_MOUNTAINS" : 158,
"MEGA_SPRUCE_TAIGA" : 160,
"TAIGA_MOUNTAINS" : 133,
"SMALL_MOUNTAINS" : 20,
"EXTREME_HILLS_PLUS" : 34,
"GIANT_SPRUCE_TAIGA" : 160,
"FOREST_HILLS" : 18,
"DESERT_HILLS" : 17,
"MUTATED_REDWOOD_TAIGA_HILLS" : 161,
"MEGA_SPRUCE_TAIGA_HILLS" : 161,
"RIVER" : 7,
"GIANT_TREE_TAIGA" : 32,
"SWAMPLAND" : 6,
"JUNGLE_HILLS" : 22,
"TALL_BIRCH_FOREST" : 155,
"DEEP_LUKEWARM_OCEAN" : 48,
"MESA_ROCK" : 38,
"SWAMP_HILLS" : 134,
"MODIFIED_WOODED_BADLANDS_PLATEAU" : 166,
"MODIFIED_JUNGLE_EDGE" : 151,
"BIRCH_FOREST_HILLS" : 28,
"COLD_TAIGA" : 30,
"TAIGA" : 5,
"MUTATED_MESA_ROCK" : 166,
"MUTATED_SWAMPLAND" : 134,
"ICE_FLATS" : 12,
"MUTATED_ROOFED_FOREST" : 157,
"MUTATED_MESA" : 165,
"MUTATED_EXTREME_HILLS" : 131
}

View File

@ -70,9 +70,25 @@
<shadedPattern>org.geysermc.platform.sponge.shaded.fastutil</shadedPattern>
</relocation>
<relocation>
<pattern>org.reflections.reflections</pattern>
<pattern>org.reflections</pattern>
<shadedPattern>org.geysermc.platform.sponge.shaded.reflections</shadedPattern>
</relocation>
<relocation>
<pattern>com.google.common</pattern>
<shadedPattern>org.geysermc.platform.sponge.shaded.google.common</shadedPattern>
</relocation>
<relocation>
<pattern>com.google.guava</pattern>
<shadedPattern>org.geysermc.platform.sponge.shaded.google.guava</shadedPattern>
</relocation>
<relocation>
<pattern>org.dom4j</pattern>
<shadedPattern>org.geysermc.platform.sponge.shaded.dom4j</shadedPattern>
</relocation>
<relocation>
<pattern>net.kyori.adventure</pattern>
<shadedPattern>org.geysermc.platform.sponge.shaded.adventure</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>

View File

@ -25,256 +25,13 @@
package org.geysermc.platform.sponge;
import lombok.AllArgsConstructor;
import org.geysermc.connector.configuration.GeyserJacksonConfiguration;
import ninja.leaping.configurate.ConfigurationNode;
import org.geysermc.connector.configuration.GeyserConfiguration;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
public class GeyserSpongeConfiguration implements GeyserConfiguration {
private File dataFolder;
private ConfigurationNode node;
private SpongeBedrockConfiguration bedrockConfig;
private SpongeRemoteConfiguration remoteConfig;
private SpongeMetricsInfo metricsInfo;
private Map<String, SpongeUserAuthenticationInfo> userAuthInfo = new HashMap<>();
public GeyserSpongeConfiguration(File dataFolder, ConfigurationNode node) {
this.dataFolder = dataFolder;
this.node = node;
this.bedrockConfig = new SpongeBedrockConfiguration(node.getNode("bedrock"));
this.remoteConfig = new SpongeRemoteConfiguration(node.getNode("remote"));
this.metricsInfo = new SpongeMetricsInfo();
if (node.getNode("userAuths").getValue() == null)
return;
List<String> userAuths = new ArrayList<String>(((LinkedHashMap)node.getNode("userAuths").getValue()).keySet());
for (String key : userAuths) {
userAuthInfo.put(key, new SpongeUserAuthenticationInfo(key));
}
}
public final class GeyserSpongeConfiguration extends GeyserJacksonConfiguration {
@Override
public SpongeBedrockConfiguration getBedrock() {
return bedrockConfig;
}
@Override
public SpongeRemoteConfiguration getRemote() {
return remoteConfig;
}
@Override
public Map<String, SpongeUserAuthenticationInfo> getUserAuths() {
return userAuthInfo;
}
@Override
public boolean isCommandSuggestions() {
return node.getNode("command-suggestions").getBoolean(true);
}
@Override
public boolean isPassthroughMotd() {
return node.getNode("passthrough-motd").getBoolean(false);
}
@Override
public boolean isPassthroughProtocolName() {
return node.getNode("passthrough-protocol-name").getBoolean(false);
}
@Override
public boolean isPassthroughPlayerCounts() {
return node.getNode("passthrough-player-counts").getBoolean(false);
}
@Override
public boolean isLegacyPingPassthrough() {
return node.getNode("legacy-ping-passthrough").getBoolean(false);
}
@Override
public int getPingPassthroughInterval() {
return node.getNode("ping-passthrough-interval").getInt(3);
}
@Override
public int getMaxPlayers() {
return node.getNode("max-players").getInt(100);
}
@Override
public boolean isDebugMode() {
return node.getNode("debug-mode").getBoolean(false);
}
@Override
public int getGeneralThreadPool() {
return node.getNode("genereal-thread-pool").getInt(32);
}
@Override
public boolean isAllowThirdPartyCapes() {
return node.getNode("allow-third-party-capes").getBoolean(true);
}
@Override
public boolean isAllowThirdPartyEars() {
return node.getNode("allow-third-party-ears").getBoolean(false);
}
@Override
public boolean isShowCooldown() {
return node.getNode("show-cooldown").getBoolean(true);
}
@Override
public String getDefaultLocale() {
return node.getNode("default-locale").getString("en_us");
}
@Override
public Path getFloodgateKeyFile() {
return Paths.get(dataFolder.toString(), node.getNode("floodgate-key-file").getString("public-key.pem"));
}
@Override
public boolean isCacheChunks() {
return node.getNode("cache-chunks").getBoolean(false);
}
@Override
public int getCacheImages() {
return node.getNode("cache-skins").getInt(0);
}
@Override
public boolean isAboveBedrockNetherBuilding() {
return node.getNode("above-bedrock-nether-building").getBoolean(false);
}
@Override
public SpongeMetricsInfo getMetrics() {
return metricsInfo;
}
@AllArgsConstructor
public class SpongeBedrockConfiguration implements IBedrockConfiguration {
private ConfigurationNode node;
@Override
public String getAddress() {
return node.getNode("address").getString("0.0.0.0");
}
@Override
public int getPort() {
return node.getNode("port").getInt(19132);
}
@Override
public boolean isCloneRemotePort() {
return node.getNode("clone-remote-port").getBoolean(false);
}
@Override
public String getMotd1() {
return node.getNode("motd1").getString("GeyserMC");
}
@Override
public String getMotd2() {
return node.getNode("motd2").getString("GeyserMC");
}
}
@AllArgsConstructor
public class SpongeRemoteConfiguration implements IRemoteConfiguration {
private ConfigurationNode node;
@Override
public String getAddress() {
return node.getNode("address").getString("127.0.0.1");
}
@Override
public void setAddress(String address) {
node.getNode("address").setValue(address);
}
@Override
public int getPort() {
return node.getNode("port").getInt(25565);
}
@Override
public void setPort(int port) {
node.getNode("port").setValue(port);
}
@Override
public String getAuthType() {
return node.getNode("auth-type").getString("online");
}
}
public class SpongeUserAuthenticationInfo implements IUserAuthenticationInfo {
private String key;
public SpongeUserAuthenticationInfo(String key) {
this.key = key;
}
@Override
public String getEmail() {
return node.getNode("userAuths").getNode(key).getNode("email").getString();
}
@Override
public String getPassword() {
return node.getNode("userAuths").getNode(key).getNode("password").getString();
}
}
public class SpongeMetricsInfo implements IMetricsInfo {
@Override
public boolean isEnabled() {
return node.getNode("metrics").getNode("enabled").getBoolean(true);
}
@Override
public String getUniqueId() {
return node.getNode("metrics").getNode("uuid").getString("generateduuid");
}
}
@Override
public boolean isEnableProxyConnections() {
return node.getNode("enable-proxy-connections").getBoolean(false);
}
@Override
public int getMtu() {
return node.getNode("mtu").getInt(1400);
}
@Override
public int getConfigVersion() {
return node.getNode("config-version").getInt(0);
public Path getFloodgateKeyPath() {
return null; //floodgate isn't available for Sponge
}
}

View File

@ -26,15 +26,16 @@
package org.geysermc.platform.sponge;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.connector.GeyserLogger;
import org.slf4j.Logger;
@AllArgsConstructor
public class GeyserSpongeLogger implements GeyserLogger {
private Logger logger;
private boolean debugMode;
private final Logger logger;
@Getter @Setter
private boolean debug;
@Override
public void severe(String message) {
@ -68,12 +69,8 @@ public class GeyserSpongeLogger implements GeyserLogger {
@Override
public void debug(String message) {
if (debugMode)
if (debug) {
info(message);
}
@Override
public void setDebug(boolean debugMode) {
this.debugMode = debugMode;
}
}
}

View File

@ -26,14 +26,11 @@
package org.geysermc.platform.sponge;
import com.google.inject.Inject;
import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.loader.ConfigurationLoader;
import ninja.leaping.configurate.yaml.YAMLConfigurationLoader;
import org.geysermc.connector.common.PlatformType;
import org.geysermc.connector.configuration.GeyserConfiguration;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.bootstrap.GeyserBootstrap;
import org.geysermc.connector.command.CommandManager;
import org.geysermc.connector.common.PlatformType;
import org.geysermc.connector.configuration.GeyserConfiguration;
import org.geysermc.connector.dump.BootstrapDumpInfo;
import org.geysermc.connector.ping.GeyserLegacyPingPassthrough;
import org.geysermc.connector.ping.IGeyserPingPassthrough;
@ -85,33 +82,27 @@ public class GeyserSpongePlugin implements GeyserBootstrap {
ex.printStackTrace();
}
ConfigurationLoader loader = YAMLConfigurationLoader.builder().setPath(configFile.toPath()).build();
ConfigurationNode config;
try {
config = loader.load();
this.geyserConfig = new GeyserSpongeConfiguration(configDir, config);
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpongeConfiguration.class);
} catch (IOException ex) {
logger.warn(LanguageUtils.getLocaleStringLog("geyser.config.failed"));
ex.printStackTrace();
return;
}
ConfigurationNode serverIP = config.getNode("remote").getNode("address");
ConfigurationNode serverPort = config.getNode("remote").getNode("port");
if (Sponge.getServer().getBoundAddress().isPresent()) {
InetSocketAddress javaAddr = Sponge.getServer().getBoundAddress().get();
// Don't change the ip if its listening on all interfaces
// By default this should be 127.0.0.1 but may need to be changed in some circumstances
if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) {
serverPort.setValue(javaAddr.getPort());
this.geyserConfig.setAutoconfiguredRemote(true);
geyserConfig.getRemote().setPort(javaAddr.getPort());
}
}
ConfigurationNode bedrockPort = config.getNode("bedrock").getNode("port");
if (geyserConfig.getBedrock().isCloneRemotePort()){
bedrockPort.setValue(serverPort.getValue());
geyserConfig.getBedrock().setPort(geyserConfig.getRemote().getPort());
}
this.geyserLogger = new GeyserSpongeLogger(logger, geyserConfig.isDebugMode());

View File

@ -33,8 +33,8 @@ import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.appender.ConsoleAppender;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.bootstrap.GeyserBootstrap;
import org.geysermc.connector.common.PlatformType;
import org.geysermc.connector.command.CommandManager;
import org.geysermc.connector.common.PlatformType;
import org.geysermc.connector.configuration.GeyserConfiguration;
import org.geysermc.connector.dump.BootstrapDumpInfo;
import org.geysermc.connector.ping.GeyserLegacyPingPassthrough;
@ -49,6 +49,7 @@ import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.UUID;
public class GeyserStandaloneBootstrap implements GeyserBootstrap {
@ -62,22 +63,61 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
@Getter
private boolean useGui = System.console() == null && !isHeadless();
private String configFilename = "config.yml";
private GeyserConnector connector;
public static void main(String[] args) {
for (String arg : args) {
GeyserStandaloneBootstrap bootstrap = new GeyserStandaloneBootstrap();
// Set defaults
boolean useGuiOpts = bootstrap.useGui;
String configFilenameOpt = bootstrap.configFilename;
for (int i = 0; i < args.length; i++) {
// By default, standalone Geyser will check if it should open the GUI based on if the GUI is null
// Optionally, you can force the use of a GUI or no GUI by specifying args
if (arg.equals("gui")) {
new GeyserStandaloneBootstrap().onEnable(true);
return;
} else if (arg.equals("nogui")) {
new GeyserStandaloneBootstrap().onEnable(false);
return;
// Allows gui and nogui without options, for backwards compatibility
String arg = args[i];
switch (arg) {
case "--gui":
case "gui":
useGuiOpts = true;
break;
case "--nogui":
case "nogui":
useGuiOpts = false;
break;
case "--config":
case "-c":
if (i >= args.length - 1) {
System.err.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.confignotspecified"), "-c"));
return;
}
configFilenameOpt = args[i+1]; i++;
System.out.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.configspecified"), configFilenameOpt));
break;
case "--help":
case "-h":
System.out.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.usage"), "[java -jar] Geyser.jar [opts]"));
System.out.println(" " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.options"));
System.out.println(" -c, --config [file] " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.config"));
System.out.println(" -h, --help " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.help"));
System.out.println(" --gui, --nogui " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.gui"));
return;
default:
String badArgMsg = LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.unrecognised");
System.err.println(MessageFormat.format(badArgMsg, arg));
return;
}
}
new GeyserStandaloneBootstrap().onEnable();
bootstrap.onEnable(useGuiOpts, configFilenameOpt);
}
public void onEnable(boolean useGui, String configFilename) {
this.configFilename = configFilename;
this.useGui = useGui;
this.onEnable();
}
public void onEnable(boolean useGui) {
@ -106,9 +146,10 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
LoopbackUtil.checkLoopback(geyserLogger);
try {
File configFile = FileUtils.fileOrCopiedFromResource("config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()));
File configFile = FileUtils.fileOrCopiedFromResource(new File(configFilename), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()));
geyserConfig = FileUtils.loadConfig(configFile, GeyserStandaloneConfiguration.class);
if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) {
geyserConfig.setAutoconfiguredRemote(true); // Doesn't really need to be set but /shrug
geyserConfig.getRemote().setAddress("127.0.0.1");
}
} catch (IOException ex) {

View File

@ -26,7 +26,6 @@
package org.geysermc.platform.standalone;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import org.geysermc.connector.configuration.GeyserJacksonConfiguration;
@ -35,13 +34,9 @@ import java.nio.file.Paths;
@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
public class GeyserStandaloneConfiguration extends GeyserJacksonConfiguration {
@JsonProperty("floodgate-key-file")
private String floodgateKeyFile;
public final class GeyserStandaloneConfiguration extends GeyserJacksonConfiguration {
@Override
public Path getFloodgateKeyFile() {
return Paths.get(floodgateKeyFile);
public Path getFloodgateKeyPath() {
return Paths.get(getFloodgateKeyFile());
}
}

View File

@ -26,18 +26,16 @@
package org.geysermc.platform.standalone;
import lombok.extern.log4j.Log4j2;
import net.minecrell.terminalconsole.SimpleTerminalConsole;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.config.Configurator;
import org.geysermc.connector.common.ChatColor;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.GeyserLogger;
import org.geysermc.connector.command.CommandSender;
import org.geysermc.connector.common.ChatColor;
@Log4j2
public class GeyserStandaloneLogger extends SimpleTerminalConsole implements org.geysermc.connector.GeyserLogger, CommandSender {
public class GeyserStandaloneLogger extends SimpleTerminalConsole implements GeyserLogger, CommandSender {
private boolean colored = true;
@Override
@ -99,10 +97,6 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements org
Configurator.setLevel(log.getName(), debug ? Level.DEBUG : Level.INFO);
}
/**
* Used for setting debug mode in GUI mode
* @return if debug is enabled
*/
public boolean isDebug() {
return log.isDebugEnabled();
}

View File

@ -57,14 +57,34 @@
</goals>
<configuration>
<relocations>
<relocation>
<pattern>com.fasterxml.jackson</pattern>
<shadedPattern>org.geysermc.platform.velocity.shaded.jackson</shadedPattern>
</relocation>
<relocation>
<pattern>it.unimi.dsi.fastutil</pattern>
<shadedPattern>org.geysermc.platform.velocity.shaded.fastutil</shadedPattern>
</relocation>
<relocation>
<pattern>org.reflections.reflections</pattern>
<pattern>org.reflections</pattern>
<shadedPattern>org.geysermc.platform.velocity.shaded.reflections</shadedPattern>
</relocation>
<relocation>
<pattern>com.google.common</pattern>
<shadedPattern>org.geysermc.platform.velocity.shaded.google.common</shadedPattern>
</relocation>
<relocation>
<pattern>com.google.guava</pattern>
<shadedPattern>org.geysermc.platform.velocity.shaded.google.guava</shadedPattern>
</relocation>
<relocation>
<pattern>org.dom4j</pattern>
<shadedPattern>org.geysermc.platform.velocity.shaded.dom4j</shadedPattern>
</relocation>
<relocation>
<pattern>net.kyori.adventure</pattern>
<shadedPattern>org.geysermc.platform.velocity.shaded.adventure</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>

View File

@ -27,7 +27,6 @@ package org.geysermc.platform.velocity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.proxy.ProxyServer;
import lombok.Getter;
@ -37,25 +36,15 @@ import org.geysermc.connector.configuration.GeyserJacksonConfiguration;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
public class GeyserVelocityConfiguration extends GeyserJacksonConfiguration {
@JsonProperty("floodgate-key-file")
private String floodgateKeyFile;
public final class GeyserVelocityConfiguration extends GeyserJacksonConfiguration {
@JsonIgnore
private Path floodgateKey;
@Override
public Path getFloodgateKeyFile() {
return floodgateKey;
}
private Path floodgateKeyPath;
public void loadFloodgate(GeyserVelocityPlugin plugin, ProxyServer proxyServer, File dataFolder) {
Optional<PluginContainer> floodgate = proxyServer.getPluginManager().getPlugin("floodgate");
floodgate.ifPresent(it -> floodgateKey = FloodgateKeyLoader.getKey(plugin.getGeyserLogger(), this, Paths.get(dataFolder.toString(), floodgateKeyFile.isEmpty() ? floodgateKeyFile : "public-key.pem"), it, Paths.get("plugins/floodgate/")));
PluginContainer floodgate = proxyServer.getPluginManager().getPlugin("floodgate").orElse(null);
floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgate, Paths.get("plugins/floodgate/"), dataFolder.toPath(), plugin.getGeyserLogger());
}
}

View File

@ -26,15 +26,16 @@
package org.geysermc.platform.velocity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.connector.GeyserLogger;
import org.slf4j.Logger;
@AllArgsConstructor
public class GeyserVelocityLogger implements GeyserLogger {
private Logger logger;
private boolean debugMode;
private final Logger logger;
@Getter @Setter
private boolean debug;
@Override
public void severe(String message) {
@ -68,12 +69,8 @@ public class GeyserVelocityLogger implements GeyserLogger {
@Override
public void debug(String message) {
if (debugMode)
if (debug) {
info(message);
}
@Override
public void setDebug(boolean debugMode) {
this.debugMode = debugMode;
}
}
}

View File

@ -26,19 +26,17 @@
package org.geysermc.platform.velocity;
import com.google.inject.Inject;
import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.proxy.ProxyServer;
import lombok.Getter;
import org.geysermc.connector.common.PlatformType;
import org.geysermc.connector.configuration.GeyserConfiguration;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.bootstrap.GeyserBootstrap;
import org.geysermc.connector.common.PlatformType;
import org.geysermc.connector.configuration.GeyserConfiguration;
import org.geysermc.connector.dump.BootstrapDumpInfo;
import org.geysermc.connector.ping.GeyserLegacyPingPassthrough;
import org.geysermc.connector.ping.IGeyserPingPassthrough;
@ -94,6 +92,11 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
// By default this should be localhost but may need to be changed in some circumstances
if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) {
this.geyserConfig.setAutoconfiguredRemote(true);
// Don't use localhost if not listening on all interfaces
if (!javaAddr.getHostString().equals("0.0.0.0") && !javaAddr.getHostString().equals("")) {
this.geyserConfig.getRemote().setAddress(javaAddr.getHostString());
}
geyserConfig.getRemote().setPort(javaAddr.getPort());
}
@ -107,6 +110,10 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
if (geyserConfig.getRemote().getAuthType().equals("floodgate") && !proxyServer.getPluginManager().getPlugin("floodgate").isPresent()) {
geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling"));
return;
} else if (geyserConfig.isAutoconfiguredRemote() && proxyServer.getPluginManager().getPlugin("floodgate").isPresent()) {
// Floodgate installed means that the user wants Floodgate authentication
geyserLogger.debug("Auto-setting to Floodgate authentication.");
geyserConfig.getRemote().setAuthType("floodgate");
}
geyserConfig.loadFloodgate(this, proxyServer, configFolder.toFile());

View File

@ -31,9 +31,9 @@
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.nukkitx.protocol</groupId>
<artifactId>bedrock-v409</artifactId>
<version>2.6.0-SNAPSHOT</version>
<groupId>com.github.CloudburstMC.Protocol</groupId>
<artifactId>bedrock-v408</artifactId>
<version>250beb2a94</version>
<scope>compile</scope>
<exclusions>
<exclusion>
@ -110,8 +110,8 @@
</dependency>
<dependency>
<groupId>com.github.GeyserMC</groupId>
<artifactId>MCProtocolLib</artifactId>
<version>f37c98dc70</version>
<artifactId>mcprotocollib</artifactId>
<version>e4a3aa636a</version>
<scope>compile</scope>
<exclusions>
<exclusion>
@ -129,24 +129,29 @@
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.12</version>
<version>0.9.11</version> <!-- This isn't the latest version to get round https://github.com/ronmamo/reflections/issues/273 -->
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>com.github.kyoripowered.adventure</groupId>
<artifactId>adventure-api</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>557865caef</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<groupId>com.github.kyoripowered.adventure</groupId>
<artifactId>adventure-text-serializer-gson</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>557865caef</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<groupId>com.github.kyoripowered.adventure</groupId>
<artifactId>adventure-text-serializer-legacy</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>557865caef</version>
<scope>compile</scope>
</dependency>
</dependencies>
@ -232,6 +237,52 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.9.1</version>
<executions>
<execution>
<phase>process-classes</phase>
<goals>
<goal>execute</goal>
</goals>
<configuration>
<scripts>
<script><![CDATA[
new org.reflections.Reflections("org.geysermc.connector.network.translators")
.save("${project.artifactId}/target/classes/META-INF/reflections/org.geysermc.connector.network.translators-reflections.xml")
new org.reflections.Reflections("org.geysermc.connector.network.translators.item")
.save("${project.artifactId}/target/classes/META-INF/reflections/org.geysermc.connector.network.translators.item-reflections.xml")
new org.reflections.Reflections("org.geysermc.connector.network.translators.sound")
.save("${project.artifactId}/target/classes/META-INF/reflections/org.geysermc.connector.network.translators.sound-reflections.xml")
new org.reflections.Reflections("org.geysermc.connector.network.translators.world.block.entity")
.save("${project.artifactId}/target/classes/META-INF/reflections/org.geysermc.connector.network.translators.world.block.entity-reflections.xml")
]]></script>
</scripts>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.11</version>
</dependency>
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>3.0.5</version>
<scope>runtime</scope>
<type>pom</type>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>

View File

@ -25,17 +25,19 @@
package org.geysermc.connector;
import org.geysermc.connector.configuration.GeyserJacksonConfiguration;
import org.geysermc.connector.utils.LanguageUtils;
import org.geysermc.connector.configuration.GeyserConfiguration;
import java.nio.file.Files;
import java.nio.file.Path;
public class FloodgateKeyLoader {
public static Path getKey(GeyserLogger logger, GeyserConfiguration config, Path floodgateKey, Object floodgate, Path floodgateFolder) {
public static Path getKeyPath(GeyserJacksonConfiguration config, Object floodgate, Path floodgateDataFolder, Path geyserDataFolder, GeyserLogger logger) {
Path floodgateKey = geyserDataFolder.resolve(config.getFloodgateKeyFile());
if (!Files.exists(floodgateKey) && config.getRemote().getAuthType().equals("floodgate")) {
if (floodgate != null) {
Path autoKey = floodgateFolder.resolve("public-key.pem");
Path autoKey = floodgateDataFolder.resolve("public-key.pem");
if (Files.exists(autoKey)) {
logger.info(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.auto_loaded"));
floodgateKey = autoKey;

View File

@ -25,6 +25,7 @@
package org.geysermc.connector;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nukkitx.network.raknet.RakNetConstants;
@ -47,6 +48,7 @@ import org.geysermc.connector.network.translators.effect.EffectRegistry;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.item.ItemTranslator;
import org.geysermc.connector.network.translators.item.PotionMixRegistry;
import org.geysermc.connector.network.translators.item.RecipeRegistry;
import org.geysermc.connector.network.translators.sound.SoundHandlerRegistry;
import org.geysermc.connector.network.translators.sound.SoundRegistry;
import org.geysermc.connector.network.translators.world.WorldManager;
@ -55,6 +57,7 @@ import org.geysermc.connector.network.translators.world.block.entity.BlockEntity
import org.geysermc.connector.utils.DimensionUtils;
import org.geysermc.connector.utils.LanguageUtils;
import org.geysermc.connector.utils.LocaleUtils;
import org.geysermc.connector.utils.ResourcePack;
import org.geysermc.floodgate.crypto.AesCipher;
import org.geysermc.floodgate.crypto.AesKeyProducer;
import org.geysermc.floodgate.crypto.Base64Topping;
@ -77,7 +80,7 @@ import java.util.concurrent.TimeUnit;
@Getter
public class GeyserConnector {
public static final ObjectMapper JSON_MAPPER = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES);
public static final ObjectMapper JSON_MAPPER = new ObjectMapper().enable(JsonParser.Feature.IGNORE_UNDEFINED).enable(JsonParser.Feature.ALLOW_COMMENTS).disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
public static final String NAME = "Geyser";
public static final String VERSION = "DEV"; // A fallback for running in IDEs
@ -138,9 +141,12 @@ public class GeyserConnector {
ItemTranslator.init();
LocaleUtils.init();
PotionMixRegistry.init();
RecipeRegistry.init();
SoundRegistry.init();
SoundHandlerRegistry.init();
ResourcePack.loadPacks();
if (platformType != PlatformType.STANDALONE && config.getRemote().getAddress().equals("auto")) {
// Set the remote address to localhost since that is where we are always connecting
try {
@ -168,7 +174,7 @@ public class GeyserConnector {
config.getRemote().setPort(remotePort = Integer.parseInt(record[2]));
logger.debug("Found SRV record \"" + remoteAddress + ":" + remotePort + "\"");
}
} catch (Exception ex) {
} catch (Exception | NoClassDefFoundError ex) { // Check for a NoClassDefFoundError to prevent Android crashes
logger.debug("Exception while trying to find an SRV record for the remote host.");
if (config.isDebugMode())
ex.printStackTrace(); // Otherwise we can get a stack trace for any domain that doesn't have an SRV record
@ -202,7 +208,7 @@ public class GeyserConnector {
if (throwable == null) {
logger.info(LanguageUtils.getLocaleStringLog("geyser.core.start", config.getBedrock().getAddress(), String.valueOf(config.getBedrock().getPort())));
} else {
logger.severe(LanguageUtils.getLocaleStringLog("geyser.core.fail", config.getBedrock().getAddress(), config.getBedrock().getPort()));
logger.severe(LanguageUtils.getLocaleStringLog("geyser.core.fail", config.getBedrock().getAddress(), String.valueOf(config.getBedrock().getPort())));
throwable.printStackTrace();
}
}).join();
@ -234,6 +240,10 @@ public class GeyserConnector {
message += LanguageUtils.getLocaleStringLog("geyser.core.finish.console");
}
logger.info(message);
if (platformType == PlatformType.STANDALONE) {
logger.warning(LanguageUtils.getLocaleStringLog("geyser.core.movement_warn"));
}
}
public void shutdown() {
@ -319,6 +329,18 @@ public class GeyserConnector {
return bootstrap.getWorldManager();
}
/**
* Get the production status of the current runtime.
* Will return true if the version number is not 'DEV'.
* Should only happen in compiled jars.
*
* @return If we are in a production build/environment
*/
public boolean isProduction() {
//noinspection ConstantConditions
return !"DEV".equals(GeyserConnector.VERSION);
}
public static GeyserConnector getInstance() {
return instance;
}

View File

@ -84,4 +84,9 @@ public interface GeyserLogger {
* @param debug if the logger should print debug messages
*/
void setDebug(boolean debug);
/**
* If debug is enabled for this logger
*/
boolean isDebug();
}

View File

@ -49,10 +49,6 @@ public class StopCommand extends GeyserCommand {
return;
}
connector.shutdown();
if (connector.getPlatformType() == PlatformType.STANDALONE) {
System.exit(0);
}
connector.getBootstrap().onDisable();
}
}

View File

@ -26,6 +26,7 @@
package org.geysermc.connector.command.defaults;
import com.github.steveice10.mc.protocol.MinecraftConstants;
import com.nukkitx.protocol.bedrock.BedrockPacketCodec;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.command.CommandSender;
import org.geysermc.connector.command.GeyserCommand;
@ -38,6 +39,7 @@ import org.geysermc.connector.utils.WebUtils;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Properties;
public class VersionCommand extends GeyserCommand {
@ -51,7 +53,15 @@ public class VersionCommand extends GeyserCommand {
@Override
public void execute(CommandSender sender, String[] args) {
sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.version", GeyserConnector.NAME, GeyserConnector.VERSION, MinecraftConstants.GAME_VERSION, BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()));
String bedrockVersions;
List<BedrockPacketCodec> supportedCodecs = BedrockProtocol.SUPPORTED_BEDROCK_CODECS;
if (supportedCodecs.size() > 1) {
bedrockVersions = supportedCodecs.get(0).getMinecraftVersion() + " - " + supportedCodecs.get(supportedCodecs.size() - 1).getMinecraftVersion();
} else {
bedrockVersions = BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion();
}
sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.version", GeyserConnector.NAME, GeyserConnector.VERSION, MinecraftConstants.GAME_VERSION, bedrockVersions));
// Disable update checking in dev mode
//noinspection ConstantConditions - changes in production

View File

@ -32,6 +32,7 @@ import lombok.Getter;
@AllArgsConstructor
public enum PlatformType {
ANDROID("Android"),
BUNGEECORD("BungeeCord"),
SPIGOT("Spigot"),
SPONGE("Sponge"),

View File

@ -27,7 +27,6 @@ package org.geysermc.connector.configuration;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.geysermc.connector.GeyserLogger;
import org.geysermc.connector.utils.LanguageUtils;
import java.nio.file.Path;
@ -74,12 +73,14 @@ public interface GeyserConfiguration {
String getDefaultLocale();
Path getFloodgateKeyFile();
Path getFloodgateKeyPath();
boolean isAboveBedrockNetherBuilding();
boolean isCacheChunks();
boolean isForceResourcePacks();
int getCacheImages();
IMetricsInfo getMetrics();
@ -95,6 +96,8 @@ public interface GeyserConfiguration {
String getMotd1();
String getMotd2();
String getServerName();
}
interface IRemoteConfiguration {
@ -123,6 +126,8 @@ public interface GeyserConfiguration {
String getUniqueId();
}
int getScoreboardPacketThreshold();
// if u have offline mode enabled pls be safe
boolean isEnableProxyConnections();

View File

@ -29,6 +29,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.common.serializer.AsteriskSerializer;
import java.nio.file.Path;
@ -38,94 +39,105 @@ import java.util.Map;
@JsonIgnoreProperties(ignoreUnknown = true)
public abstract class GeyserJacksonConfiguration implements GeyserConfiguration {
/**
* If the config was originally 'auto' before the values changed
*/
@Setter
private boolean autoconfiguredRemote = false;
private BedrockConfiguration bedrock;
private RemoteConfiguration remote;
@JsonProperty("floodgate-key-file")
private String floodgateKeyFile;
private String floodgateKeyFile = "public-key.pem";
public abstract Path getFloodgateKeyFile();
public abstract Path getFloodgateKeyPath();
private Map<String, UserAuthenticationInfo> userAuths;
@JsonProperty("command-suggestions")
private boolean commandSuggestions;
private boolean commandSuggestions = true;
@JsonProperty("passthrough-motd")
private boolean isPassthroughMotd;
private boolean isPassthroughMotd = false;
@JsonProperty("passthrough-player-counts")
private boolean isPassthroughPlayerCounts;
private boolean isPassthroughPlayerCounts = false;
@JsonProperty("passthrough-protocol-name")
private boolean isPassthroughProtocolName;
private boolean isPassthroughProtocolName = false;
@JsonProperty("legacy-ping-passthrough")
private boolean isLegacyPingPassthrough;
private boolean isLegacyPingPassthrough = false;
@JsonProperty("ping-passthrough-interval")
private int pingPassthroughInterval;
private int pingPassthroughInterval = 3;
@JsonProperty("max-players")
private int maxPlayers;
private int maxPlayers = 100;
@JsonProperty("debug-mode")
private boolean debugMode;
private boolean debugMode = false;
@JsonProperty("general-thread-pool")
private int generalThreadPool;
private int generalThreadPool = 32;
@JsonProperty("allow-third-party-capes")
private boolean allowThirdPartyCapes;
private boolean allowThirdPartyCapes = true;
@JsonProperty("show-cooldown")
private boolean showCooldown = true;
@JsonProperty("allow-third-party-ears")
private boolean allowThirdPartyEars;
private boolean allowThirdPartyEars = false;
@JsonProperty("default-locale")
private String defaultLocale;
private String defaultLocale = null; // is null by default so system language takes priority
@JsonProperty("cache-chunks")
private boolean cacheChunks;
private boolean cacheChunks = false;
@JsonProperty("cache-images")
private int cacheImages = 0;
@JsonProperty("above-bedrock-nether-building")
private boolean aboveBedrockNetherBuilding;
private boolean aboveBedrockNetherBuilding = false;
@JsonProperty("force-resource-packs")
private boolean forceResourcePacks = true;
private MetricsInfo metrics;
@Getter
public static class BedrockConfiguration implements IBedrockConfiguration {
@AsteriskSerializer.Asterisk(sensitive = true)
private String address;
private String address = "0.0.0.0";
@Setter
private int port;
private int port = 19132;
@JsonProperty("clone-remote-port")
private boolean cloneRemotePort;
private boolean cloneRemotePort = false;
private String motd1;
private String motd2;
private String motd1 = "GeyserMC";
private String motd2 = "Geyser";
@JsonProperty("server-name")
private String serverName = GeyserConnector.NAME;
}
@Getter
public static class RemoteConfiguration implements IRemoteConfiguration {
@Setter
@AsteriskSerializer.Asterisk(sensitive = true)
private String address;
private String address = "auto";
@Setter
private int port;
private int port = 25565;
@Setter
@JsonProperty("auth-type")
private String authType;
private String authType = "online";
}
@Getter
@ -139,13 +151,15 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
@Getter
public static class MetricsInfo implements IMetricsInfo {
private boolean enabled;
private boolean enabled = true;
@JsonProperty("uuid")
private String uniqueId;
private String uniqueId = "generateuuid";
}
@JsonProperty("scoreboard-packet-threshold")
private int scoreboardPacketThreshold = 10;
@JsonProperty("enable-proxy-connections")
private boolean enableProxyConnections = false;
@ -153,5 +167,5 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
private int mtu = 1400;
@JsonProperty("config-version")
private int configVersion;
private int configVersion = 0;
}

View File

@ -31,6 +31,7 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import lombok.Getter;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.common.serializer.AsteriskSerializer;
import org.geysermc.connector.configuration.GeyserConfiguration;
import org.geysermc.connector.network.BedrockProtocol;
import org.geysermc.connector.network.session.GeyserSession;
@ -112,16 +113,21 @@ public class DumpInfo {
private final boolean dockerCheck;
NetworkInfo() {
try {
// This is the most reliable for getting the main local IP
Socket socket = new Socket();
socket.connect(new InetSocketAddress("geysermc.org", 80));
this.internalIP = socket.getLocalAddress().getHostAddress();
} catch (IOException e1) {
if (AsteriskSerializer.showSensitive) {
try {
// Fallback to the normal way of getting the local IP
this.internalIP = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException ignored) { }
// This is the most reliable for getting the main local IP
Socket socket = new Socket();
socket.connect(new InetSocketAddress("geysermc.org", 80));
this.internalIP = socket.getLocalAddress().getHostAddress();
} catch (IOException e1) {
try {
// Fallback to the normal way of getting the local IP
this.internalIP = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException ignored) { }
}
} else {
// Sometimes the internal IP is the external IP...
this.internalIP = "***";
}
this.dockerCheck = DockerCheck.checkBasic();

View File

@ -0,0 +1,67 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.entity;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.github.steveice10.mc.protocol.data.message.Message;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.utils.MessageUtils;
public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity {
public CommandBlockMinecartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
// Required, or else the GUI will not open
metadata.put(EntityData.CONTAINER_TYPE, (byte) 16);
metadata.put(EntityData.CONTAINER_BASE_SIZE, 1);
// Required, or else the client does not bother to send a packet back with the new information
metadata.put(EntityData.COMMAND_BLOCK_ENABLED, (byte) 1);
}
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 13) {
metadata.put(EntityData.COMMAND_BLOCK_COMMAND, entityMetadata.getValue());
}
if (entityMetadata.getId() == 14) {
metadata.put(EntityData.COMMAND_BLOCK_LAST_OUTPUT, MessageUtils.getBedrockMessage((Message) entityMetadata.getValue()));
}
super.updateBedrockMetadata(entityMetadata, session);
}
/**
* By default, the command block shown is purple on Bedrock, which does not match Java Edition's orange.
*/
@Override
public void updateDefaultBlockMetadata() {
metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.BEDROCK_RUNTIME_COMMAND_BLOCK_ID);
metadata.put(EntityData.DISPLAY_OFFSET, 6);
}
}

View File

@ -72,6 +72,10 @@ public class FireworkEntity extends Entity {
}
CompoundTag fireworks = tag.get("Fireworks");
if (fireworks == null) {
// Thank you Mineplex very cool
return;
}
NbtMapBuilder fireworksBuilder = NbtMap.builder();
if (fireworks.get("Flight") != null) {

View File

@ -34,7 +34,6 @@ import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtMapBuilder;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket;
import com.nukkitx.protocol.bedrock.packet.StartGamePacket;
import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
@ -102,13 +101,7 @@ public class ItemFrameEntity extends Entity {
ItemEntry itemEntry = ItemRegistry.getItem((ItemStack) entityMetadata.getValue());
NbtMapBuilder builder = NbtMap.builder();
String blockName = "";
for (StartGamePacket.ItemEntry startGamePacketItemEntry : ItemRegistry.ITEMS) {
if (startGamePacketItemEntry.getId() == (short) itemEntry.getBedrockId()) {
blockName = startGamePacketItemEntry.getIdentifier();
break;
}
}
String blockName = ItemRegistry.getBedrockIdentifer(itemEntry);
builder.putByte("Count", (byte) itemData.getCount());
if (itemData.getTag() != null) {

View File

@ -27,20 +27,24 @@ package org.geysermc.connector.entity;
import com.github.steveice10.mc.auth.data.GameProfile;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.github.steveice10.mc.protocol.data.game.scoreboard.NameTagVisibility;
import com.github.steveice10.mc.protocol.data.message.TextMessage;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.AdventureSetting;
import com.nukkitx.protocol.bedrock.data.AttributeData;
import com.nukkitx.protocol.bedrock.data.PlayerPermission;
import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData;
import com.nukkitx.protocol.bedrock.packet.*;
import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket;
import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket;
import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket;
import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.connector.entity.attribute.Attribute;
import org.geysermc.connector.entity.attribute.AttributeType;
import org.geysermc.connector.entity.living.animal.tameable.ParrotEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.session.cache.EntityEffectCache;
@ -48,7 +52,10 @@ import org.geysermc.connector.scoreboard.Team;
import org.geysermc.connector.utils.AttributeUtils;
import org.geysermc.connector.utils.MessageUtils;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Getter @Setter
@ -60,8 +67,14 @@ public class PlayerEntity extends LivingEntity {
private boolean playerList = true; // Player is in the player list
private final EntityEffectCache effectCache;
private Entity leftParrot;
private Entity rightParrot;
/**
* Saves the parrot currently on the player's left shoulder; otherwise null
*/
private ParrotEntity leftParrot;
/**
* Saves the parrot currently on the player's right shoulder; otherwise null
*/
private ParrotEntity rightParrot;
public PlayerEntity(GameProfile gameProfile, long entityId, long geyserId, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, EntityType.PLAYER, position, motion, rotation);
@ -73,12 +86,6 @@ public class PlayerEntity extends LivingEntity {
if (geyserId == 1) valid = true;
}
@Override
public boolean despawnEntity(GeyserSession session) {
super.despawnEntity(session);
return !playerList; // don't remove from cache when still on playerlist
}
@Override
public void spawnEntity(GeyserSession session) {
if (geyserId == 1) return;
@ -111,7 +118,7 @@ public class PlayerEntity extends LivingEntity {
}
public void sendPlayer(GeyserSession session) {
if(session.getEntityCache().getPlayerEntity(uuid) == null)
if (session.getEntityCache().getPlayerEntity(uuid) == null)
return;
if (session.getUpstream().isInitialized() && session.getEntityCache().getEntityByGeyserId(geyserId) == null) {
@ -184,6 +191,12 @@ public class PlayerEntity extends LivingEntity {
@Override
public void updatePositionAndRotation(GeyserSession session, double moveX, double moveY, double moveZ, float yaw, float pitch, boolean isOnGround) {
moveRelative(session, moveX, moveY, moveZ, yaw, pitch, isOnGround);
if (leftParrot != null) {
leftParrot.moveRelative(session, moveX, moveY, moveZ, yaw, pitch, isOnGround);
}
if (rightParrot != null) {
rightParrot.moveRelative(session, moveX, moveY, moveZ, yaw, pitch, isOnGround);
}
}
@Override
@ -197,6 +210,12 @@ public class PlayerEntity extends LivingEntity {
movePlayerPacket.setOnGround(isOnGround);
movePlayerPacket.setMode(MovePlayerPacket.Mode.HEAD_ROTATION);
session.sendUpstreamPacket(movePlayerPacket);
if (leftParrot != null) {
leftParrot.updateRotation(session, yaw, pitch, isOnGround);
}
if (rightParrot != null) {
rightParrot.updateRotation(session, yaw, pitch, isOnGround);
}
}
@Override
@ -209,11 +228,6 @@ public class PlayerEntity extends LivingEntity {
super.updateBedrockMetadata(entityMetadata, session);
if (entityMetadata.getId() == 2) {
// System.out.println(session.getScoreboardCache().getScoreboard().getObjectives().keySet());
for (Team team : session.getWorldCache().getScoreboard().getTeams().values()) {
// session.getConnector().getLogger().info("team name " + team.getName());
// session.getConnector().getLogger().info("team entities " + team.getEntities());
}
String username = this.username;
TextMessage name = (TextMessage) entityMetadata.getValue();
if (name != null) {
@ -221,8 +235,18 @@ public class PlayerEntity extends LivingEntity {
}
Team team = session.getWorldCache().getScoreboard().getTeamFor(username);
if (team != null) {
// session.getConnector().getLogger().info("team name es " + team.getName() + " with prefix " + team.getPrefix() + " and suffix " + team.getSuffix());
metadata.put(EntityData.NAMETAG, team.getPrefix() + MessageUtils.toChatColor(team.getColor()) + username + team.getSuffix());
// Cover different visibility settings
if (team.getNameTagVisibility() == NameTagVisibility.NEVER) {
metadata.put(EntityData.NAMETAG, "");
} else if (team.getNameTagVisibility() == NameTagVisibility.HIDE_FOR_OTHER_TEAMS &&
!team.getEntities().contains(session.getPlayerEntity().getUsername())) {
metadata.put(EntityData.NAMETAG, "");
} else if (team.getNameTagVisibility() == NameTagVisibility.HIDE_FOR_OWN_TEAM &&
team.getEntities().contains(session.getPlayerEntity().getUsername())) {
metadata.put(EntityData.NAMETAG, "");
} else {
metadata.put(EntityData.NAMETAG, team.getPrefix() + MessageUtils.toChatColor(team.getColor()) + username + team.getSuffix());
}
}
}
@ -238,11 +262,15 @@ public class PlayerEntity extends LivingEntity {
}
// Parrot occupying shoulder
if ((entityMetadata.getId() == 18 && leftParrot == null) || (entityMetadata.getId() == 19 && rightParrot == null)) { // null check since this code just creates the parrot
if (entityMetadata.getId() == 18 || entityMetadata.getId() == 19) {
CompoundTag tag = (CompoundTag) entityMetadata.getValue();
if (tag != null && !tag.isEmpty()) {
if ((entityMetadata.getId() == 18 && leftParrot != null) || (entityMetadata.getId() == 19 && rightParrot != null)) {
// No need to update a parrot's data when it already exists
return;
}
// The parrot is a separate entity in Bedrock, but part of the player entity in Java
Entity parrot = new Entity(0, session.getEntityCache().getNextEntityId().incrementAndGet(),
ParrotEntity parrot = new ParrotEntity(0, session.getEntityCache().getNextEntityId().incrementAndGet(),
EntityType.PARROT, position, motion, rotation);
parrot.spawnEntity(session);
parrot.getMetadata().put(EntityData.VARIANT, tag.get("Variant").getValue());

View File

@ -135,7 +135,7 @@ public enum EntityType {
MINECART_CHEST(MinecartEntity.class, 98, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:chest_minecart"),
MINECART_FURNACE(FurnaceMinecartEntity.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(MinecartEntity.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),
LLAMA_SPIT(Entity.class, 102, 0.25f),
EVOKER_FANGS(Entity.class, 103, 0.8f, 0.5f, 0.5f, 0f, "minecraft:evocation_fang"),

View File

@ -31,6 +31,10 @@ import lombok.Setter;
public class PlayerInventory extends Inventory {
/**
* Stores the held item slot, starting at index 0.
* Add 36 in order to get the network item slot.
*/
@Getter
@Setter
private int heldItemSlot;

View File

@ -28,28 +28,27 @@ package org.geysermc.connector.network;
import com.nukkitx.protocol.bedrock.BedrockPacketCodec;
import com.nukkitx.protocol.bedrock.v407.Bedrock_v407;
import com.nukkitx.protocol.bedrock.v408.Bedrock_v408;
import com.nukkitx.protocol.bedrock.v409.Bedrock_v409;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.ArrayList;
import java.util.List;
/**
* Contains information about the supported Bedrock protocols in Geyser.
*/
public class BedrockProtocol {
/**
* Default Bedrock codec that should act as a fallback and as the version shown in /geyser version
* Default Bedrock codec that should act as a fallback. Should represent the latest available
* release of the game that Geyser supports.
*/
public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v408.V408_CODEC;
/**
* A list of all supported Bedrock versions that can join Geyser
*/
public static final Set<BedrockPacketCodec> SUPPORTED_BEDROCK_CODECS = ConcurrentHashMap.newKeySet();
public static final List<BedrockPacketCodec> SUPPORTED_BEDROCK_CODECS = new ArrayList<>();
static {
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC);
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v407.V407_CODEC);
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v409.V409_CODEC);
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC);
}
/**

View File

@ -26,17 +26,24 @@
package org.geysermc.connector.network;
import com.nukkitx.protocol.bedrock.BedrockPacket;
import com.nukkitx.protocol.bedrock.data.ResourcePackType;
import com.nukkitx.protocol.bedrock.BedrockPacketCodec;
import com.nukkitx.protocol.bedrock.packet.*;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.common.AuthType;
import org.geysermc.connector.configuration.GeyserConfiguration;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslatorRegistry;
import org.geysermc.connector.utils.LoginEncryptionUtils;
import org.geysermc.connector.utils.LanguageUtils;
import org.geysermc.connector.utils.LoginEncryptionUtils;
import org.geysermc.connector.utils.MathUtils;
import org.geysermc.connector.utils.ResourcePack;
import org.geysermc.connector.utils.ResourcePackManifest;
import org.geysermc.connector.utils.SettingsUtils;
import java.io.FileInputStream;
import java.io.InputStream;
public class UpstreamPacketHandler extends LoggingPacketHandler {
public UpstreamPacketHandler(GeyserConnector connector, GeyserSession session) {
@ -70,6 +77,11 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
session.sendUpstreamPacket(playStatus);
ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket();
for(ResourcePack resourcePack : ResourcePack.PACKS.values()) {
ResourcePackManifest.Header header = resourcePack.getManifest().getHeader();
resourcePacksInfo.getResourcePackInfos().add(new ResourcePacksInfoPacket.Entry(header.getUuid().toString(), header.getVersionString(), resourcePack.getFile().length(), "", "", "", false));
}
resourcePacksInfo.setForcedToAccept(GeyserConnector.getInstance().getConfig().isForceResourcePacks());
session.sendUpstreamPacket(resourcePacksInfo);
return true;
}
@ -81,13 +93,42 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
session.connect(connector.getRemoteServer());
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.connect", session.getAuthData().getName()));
break;
case HAVE_ALL_PACKS:
ResourcePackStackPacket stack = new ResourcePackStackPacket();
stack.setExperimental(false);
stack.setForcedToAccept(false);
stack.setGameVersion("*");
session.sendUpstreamPacket(stack);
case SEND_PACKS:
for(String id : packet.getPackIds()) {
ResourcePackDataInfoPacket data = new ResourcePackDataInfoPacket();
String[] packID = id.split("_");
ResourcePack pack = ResourcePack.PACKS.get(packID[0]);
ResourcePackManifest.Header header = pack.getManifest().getHeader();
data.setPackId(header.getUuid());
int chunkCount = (int) Math.ceil((int) pack.getFile().length() / (double) ResourcePack.CHUNK_SIZE);
data.setChunkCount(chunkCount);
data.setCompressedPackSize(pack.getFile().length());
data.setMaxChunkSize(ResourcePack.CHUNK_SIZE);
data.setHash(pack.getSha256());
data.setPackVersion(packID[1]);
data.setPremium(false);
data.setType(ResourcePackType.RESOURCE);
session.sendUpstreamPacket(data);
}
break;
case HAVE_ALL_PACKS:
ResourcePackStackPacket stackPacket = new ResourcePackStackPacket();
stackPacket.setExperimental(false);
stackPacket.setForcedToAccept(false); // Leaving this as false allows the player to choose to download or not
stackPacket.setGameVersion(session.getClientData().getGameVersion());
for (ResourcePack pack : ResourcePack.PACKS.values()) {
ResourcePackManifest.Header header = pack.getManifest().getHeader();
stackPacket.getResourcePacks().add(new ResourcePackStackPacket.Entry(header.getUuid().toString(), header.getVersionString(), ""));
}
session.sendUpstreamPacket(stackPacket);
break;
default:
session.disconnect("disconnectionScreen.resourcePack");
break;
@ -149,4 +190,30 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
boolean defaultHandler(BedrockPacket packet) {
return translateAndDefault(packet);
}
@Override
public boolean handle(ResourcePackChunkRequestPacket packet) {
ResourcePackChunkDataPacket data = new ResourcePackChunkDataPacket();
ResourcePack pack = ResourcePack.PACKS.get(packet.getPackId().toString());
data.setChunkIndex(packet.getChunkIndex());
data.setProgress(packet.getChunkIndex() * ResourcePack.CHUNK_SIZE);
data.setPackVersion(packet.getPackVersion());
data.setPackId(packet.getPackId());
int offset = packet.getChunkIndex() * ResourcePack.CHUNK_SIZE;
byte[] packData = new byte[(int) MathUtils.constrain(pack.getFile().length() - offset, 0, ResourcePack.CHUNK_SIZE)];
try (InputStream inputStream = new FileInputStream(pack.getFile())) {
inputStream.skip(offset);
inputStream.read(packData, 0, packData.length);
} catch (Exception e) {
e.printStackTrace();
}
data.setData(packData);
session.sendUpstreamPacket(data);
return true;
}
}

View File

@ -70,6 +70,7 @@ import org.geysermc.connector.network.session.cache.*;
import org.geysermc.connector.network.translators.BiomeTranslator;
import org.geysermc.connector.network.translators.EntityIdentifierRegistry;
import org.geysermc.connector.network.translators.PacketTranslatorRegistry;
import org.geysermc.connector.network.translators.inventory.EnchantmentInventoryTranslator;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.utils.*;
@ -155,8 +156,6 @@ public class GeyserSession implements CommandSender {
@Setter
private Vector3i lastInteractionPosition;
@Setter
private boolean switchingDimension = false;
private boolean manyDimPackets = false;
private ServerRespawnPacket lastDimPacket = null;
@ -174,6 +173,11 @@ public class GeyserSession implements CommandSender {
@Setter
private long lastInteractedVillagerEid;
/**
* Stores the enchantment information the client has received if they are in an enchantment table GUI
*/
private final EnchantmentInventoryTranslator.EnchantmentSlotData[] enchantmentSlotData = new EnchantmentInventoryTranslator.EnchantmentSlotData[3];
/**
* The current attack speed of the player. Used for sending proper cooldown timings.
*/
@ -186,8 +190,6 @@ public class GeyserSession implements CommandSender {
@Setter
private long lastHitTime;
private MinecraftProtocol protocol;
private boolean reducedDebugInfo = false;
@Setter
@ -235,6 +237,17 @@ public class GeyserSession implements CommandSender {
@Setter
private boolean thunder = false;
/**
* Stores the last text inputted into a sign.
*
* Bedrock sends packets every time you update the sign, Java only wants the final packet.
* Until we determine that the user has finished editing, we save the sign's current status.
*/
@Setter
private String lastSignMessage;
private MinecraftProtocol protocol;
public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) {
this.connector = connector;
this.upstream = new UpstreamSession(bedrockServerSession);
@ -294,10 +307,13 @@ public class GeyserSession implements CommandSender {
attributesPacket.setAttributes(attributes);
upstream.sendPacket(attributesPacket);
GameRulesChangedPacket gamerulePacket = new GameRulesChangedPacket();
// Only allow the server to send health information
// Setting this to false allows natural regeneration to work false but doesn't break it being true
GameRulesChangedPacket gamerulePacket = new GameRulesChangedPacket();
gamerulePacket.getGameRules().add(new GameRuleData<>("naturalregeneration", false));
// Don't let the client modify the inventory on death
// Setting this to true allows keep inventory to work if enabled but doesn't break functionality being false
gamerulePacket.getGameRules().add(new GameRuleData<>("keepinventory", true));
upstream.sendPacket(gamerulePacket);
}
@ -578,8 +594,10 @@ public class GeyserSession implements CommandSender {
startGamePacket.setFromWorldTemplate(false);
startGamePacket.setWorldTemplateOptionLocked(false);
startGamePacket.setLevelId("world");
startGamePacket.setLevelName("world");
String serverName = connector.getConfig().getBedrock().getServerName();
startGamePacket.setLevelId(serverName);
startGamePacket.setLevelName(serverName);
startGamePacket.setPremiumWorldTemplateId("00000000-0000-0000-0000-000000000000");
// startGamePacket.setCurrentTick(0);
startGamePacket.setEnchantmentSeed(0);
@ -587,7 +605,7 @@ public class GeyserSession implements CommandSender {
startGamePacket.setBlockPalette(BlockTranslator.BLOCKS);
startGamePacket.setItemEntries(ItemRegistry.ITEMS);
startGamePacket.setVanillaVersion("*");
// startGamePacket.setMovementServerAuthoritative(true);
startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT);
upstream.sendPacket(startGamePacket);
}
@ -612,7 +630,7 @@ public class GeyserSession implements CommandSender {
* @param packet the bedrock packet from the NukkitX protocol lib
*/
public void sendUpstreamPacket(BedrockPacket packet) {
if (upstream != null && !upstream.isClosed()) {
if (upstream != null) {
upstream.sendPacket(packet);
} else {
connector.getLogger().debug("Tried to send upstream packet " + packet.getClass().getSimpleName() + " but the session was null");
@ -625,7 +643,7 @@ public class GeyserSession implements CommandSender {
* @param packet the bedrock packet from the NukkitX protocol lib
*/
public void sendUpstreamPacketImmediately(BedrockPacket packet) {
if (upstream != null && !upstream.isClosed()) {
if (upstream != null) {
upstream.sendPacketImmediately(packet);
} else {
connector.getLogger().debug("Tried to send upstream packet " + packet.getClass().getSimpleName() + " immediately but the session was null");
@ -684,8 +702,12 @@ public class GeyserSession implements CommandSender {
public void sendAdventureSettings() {
AdventureSettingsPacket adventureSettingsPacket = new AdventureSettingsPacket();
adventureSettingsPacket.setUniqueEntityId(playerEntity.getGeyserId());
adventureSettingsPacket.setCommandPermission(CommandPermission.NORMAL);
adventureSettingsPacket.setPlayerPermission(PlayerPermission.MEMBER);
// Set command permission if OP permission level is high enough
// This allows mobile players access to a GUI for doing commands. The commands there do not change above OPERATOR
// and all commands there are accessible with OP permission level 2
adventureSettingsPacket.setCommandPermission(opPermissionLevel >= 2 ? CommandPermission.OPERATOR : CommandPermission.NORMAL);
// Required to make command blocks destroyable
adventureSettingsPacket.setPlayerPermission(opPermissionLevel >= 2 ? PlayerPermission.OPERATOR : PlayerPermission.MEMBER);
Set<AdventureSetting> flags = new HashSet<>();
if (canFly) {

View File

@ -41,17 +41,15 @@ public class UpstreamSession {
private boolean initialized = false;
public void sendPacket(@NonNull BedrockPacket packet) {
if (isClosed())
return;
session.sendPacket(packet);
if (!isClosed()) {
session.sendPacket(packet);
}
}
public void sendPacketImmediately(@NonNull BedrockPacket packet) {
if (isClosed())
return;
session.sendPacketImmediately(packet);
if (!isClosed()) {
session.sendPacketImmediately(packet);
}
}
public void disconnect(String reason) {

View File

@ -42,7 +42,7 @@ public class ChunkCache {
private final boolean cache;
@Getter
private Map<ChunkPosition, Column> chunks = new HashMap<>();
private final Map<ChunkPosition, Column> chunks = new HashMap<>();
public ChunkCache(GeyserSession session) {
if (session.getConnector().getWorldManager().getClass() == GeyserBootstrap.DEFAULT_CHUNK_MANAGER.getClass()) {
@ -57,6 +57,15 @@ public class ChunkCache {
return;
}
ChunkPosition position = new ChunkPosition(chunk.getX(), chunk.getZ());
if (chunk.getBiomeData() == null && chunks.containsKey(position)) {
Column newColumn = chunk;
chunk = chunks.get(position);
for (int i = 0; i < newColumn.getChunks().length; i++) {
if (newColumn.getChunks()[i] != null) {
chunk.getChunks()[i] = newColumn.getChunks()[i];
}
}
}
chunks.put(position, chunk);
}

View File

@ -76,9 +76,6 @@ public class EntityCache {
if (entity != null && entity.isValid() && (force || entity.despawnEntity(session))) {
long geyserId = entityIdTranslations.remove(entity.getEntityId());
entities.remove(geyserId);
if (entity.is(PlayerEntity.class)) {
playerEntities.remove(entity.as(PlayerEntity.class).getUuid());
}
return true;
}
return false;

View File

@ -31,37 +31,40 @@ import lombok.Setter;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.scoreboard.Objective;
import org.geysermc.connector.scoreboard.Scoreboard;
import java.util.Collection;
import org.geysermc.connector.scoreboard.ScoreboardUpdater;
@Getter
public class WorldCache {
private GeyserSession session;
private final GeyserSession session;
@Setter
private Difficulty difficulty = Difficulty.EASY;
private boolean showCoordinates = true;
private Scoreboard scoreboard;
private final ScoreboardUpdater scoreboardUpdater;
public WorldCache(GeyserSession session) {
this.session = session;
this.scoreboard = new Scoreboard(session);
scoreboardUpdater = new ScoreboardUpdater(this);
scoreboardUpdater.start();
}
public void removeScoreboard() {
if (scoreboard != null) {
Collection<Objective> objectives = scoreboard.getObjectives().values();
scoreboard = new Scoreboard(session);
for (Objective objective : objectives) {
for (Objective objective : scoreboard.getObjectives().values()) {
scoreboard.despawnObjective(objective);
}
scoreboard = new Scoreboard(session);
}
}
public int increaseAndGetScoreboardPacketsPerSecond() {
int pendingPps = scoreboardUpdater.incrementAndGetPacketsPerSecond();
int pps = scoreboardUpdater.getPacketsPerSecond();
return Math.max(pps, pendingPps);
}
/**
* Tell the client to hide or show the coordinates
*

View File

@ -33,6 +33,7 @@ import com.nukkitx.protocol.bedrock.BedrockPacket;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.utils.FileUtils;
import org.geysermc.connector.utils.LanguageUtils;
import org.reflections.Reflections;
@ -48,7 +49,7 @@ public class PacketTranslatorRegistry<T> {
private static final ObjectArrayList<Class<?>> IGNORED_PACKETS = new ObjectArrayList<>();
static {
Reflections ref = new Reflections("org.geysermc.connector.network.translators");
Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators") : new Reflections("org.geysermc.connector.network.translators");
for (Class<?> clazz : ref.getTypesAnnotatedWith(Translator.class)) {
Class<?> packet = clazz.getAnnotation(Translator.class).packet();

View File

@ -26,23 +26,18 @@
package org.geysermc.connector.network.translators.bedrock;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientUpdateJigsawBlockPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientUpdateSignPacket;
import com.nukkitx.nbt.NbtMap;
import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import java.util.HashMap;
import java.util.Map;
import org.geysermc.connector.utils.SignUtils;
@Translator(packet = BlockEntityDataPacket.class)
public class BedrockBlockEntityDataTranslator extends PacketTranslator<BlockEntityDataPacket> {
// In case two people are editing signs at the same time this array holds the temporary messages to be sent
// Position -> Message being held
protected static Map<Position, String> lastMessages = new HashMap<>();
@Override
public void translate(BlockEntityDataPacket packet, GeyserSession session) {
NbtMap tag = packet.getData();
@ -50,9 +45,8 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator<BlockEnti
// This is the reason why this all works - Bedrock sends packets every time you update the sign, Java only wants the final packet
// But Bedrock sends one final packet when you're done editing the sign, which should be equal to the last message since there's no edits
// So if the latest update does not match the last cached update then it's still being edited
Position pos = new Position(tag.getInt("x"), tag.getInt("y"), tag.getInt("z"));
if (!tag.getString("Text").equals(lastMessages.get(pos))) {
lastMessages.put(pos, tag.getString("Text"));
if (!tag.getString("Text").equals(session.getLastSignMessage())) {
session.setLastSignMessage(tag.getString("Text"));
return;
}
// Otherwise the two messages are identical and we can get to work deconstructing
@ -61,29 +55,73 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator<BlockEnti
// (Initialized all with empty strings because it complains about null)
String[] lines = new String[] {"", "", "", ""};
int iterator = 0;
// Keep track of the width of each character
// If it goes over the maximum, we need to start a new line to match Java
int widthCount = 0;
// This converts the message into the array'd message Java wants
for (char character : tag.getString("Text").toCharArray()) {
// If we get a return in Bedrock, that signals to use the next line.
if (character == '\n') {
widthCount += SignUtils.getCharacterWidth(character);
// If we get a return in Bedrock, or go over the character width max, that signals to use the next line.
if (character == '\n' || widthCount > SignUtils.JAVA_CHARACTER_WIDTH_MAX) {
// We need to apply some more logic if we went over the character width max
boolean wentOverMax = widthCount > SignUtils.JAVA_CHARACTER_WIDTH_MAX && character != '\n';
widthCount = 0;
// Saves if we're moving a word to the next line
String word = null;
if (wentOverMax && iterator < lines.length - 1) {
// If we went over the max, we want to try to wrap properly like Bedrock does.
// So we look for a space in the Bedrock user's text to imply a word.
int index = newMessage.lastIndexOf(" ");
if (index != -1) {
// There is indeed a space in this line; let's get it
word = newMessage.substring(index + 1);
// 'Delete' that word from the string builder
newMessage.delete(index, newMessage.length());
}
}
lines[iterator] = newMessage.toString();
iterator++;
// Bedrock, for whatever reason, can hold a message out of bounds
// Bedrock, for whatever reason, can hold a message out of the bounds of the four lines
// We don't care about that so we discard that
if (iterator > lines.length - 1) {
break;
}
newMessage = new StringBuilder();
if (wentOverMax) {
// Apply the wrapped word to the new line
if (word != null) {
newMessage.append(word);
// And apply the width count
for (char wordCharacter : word.toCharArray()) {
widthCount += SignUtils.getCharacterWidth(wordCharacter);
}
}
// If we went over the max, we want to append the character to the new line.
newMessage.append(character);
widthCount += SignUtils.getCharacterWidth(character);
}
} else newMessage.append(character);
}
// Put the final line on since it isn't done in the for loop
if (iterator < lines.length) lines[iterator] = newMessage.toString();
Position pos = new Position(tag.getInt("x"), tag.getInt("y"), tag.getInt("z"));
ClientUpdateSignPacket clientUpdateSignPacket = new ClientUpdateSignPacket(pos, lines);
session.sendDownstreamPacket(clientUpdateSignPacket);
//TODO (potentially): originally I was going to update the sign blocks so Bedrock and Java users would match visually
// However Java can still store a lot per-line and visuals are still messed up so that doesn't work
// We remove the sign position from map to indicate there is no work-in-progress sign
lastMessages.remove(pos);
// We set the sign text cached in the session to null to indicate there is no work-in-progress sign
session.setLastSignMessage(null);
} else if (tag.getString("id").equals("JigsawBlock")) {
// Client has just sent a jigsaw block update
Position pos = new Position(tag.getInt("x"), tag.getInt("y"), tag.getInt("z"));
String name = tag.getString("name");
String target = tag.getString("target");
String pool = tag.getString("target_pool");
String finalState = tag.getString("final_state");
String joint = tag.getString("joint");
ClientUpdateJigsawBlockPacket jigsawPacket = new ClientUpdateJigsawBlockPacket(pos, name, target, pool,
finalState, joint);
session.sendDownstreamPacket(jigsawPacket);
}
}

View File

@ -1,99 +0,0 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.bedrock;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientMoveItemToHotbarPacket;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.packet.BlockPickRequestPacket;
import com.nukkitx.protocol.bedrock.packet.PlayerHotbarPacket;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
@Translator(packet = BlockPickRequestPacket.class)
public class BedrockBlockPickRequestPacketTranslator extends PacketTranslator<BlockPickRequestPacket> {
@Override
public void translate(BlockPickRequestPacket packet, GeyserSession session) {
Vector3i vector = packet.getBlockPosition();
int blockToPick = session.getConnector().getWorldManager().getBlockAt(session, vector.getX(), vector.getY(), vector.getZ());
// Block is air - chunk caching is probably off
if (blockToPick == 0) {
return;
}
// Get the inventory to choose a slot to pick
Inventory inventory = session.getInventoryCache().getOpenInventory();
if (inventory == null) {
inventory = session.getInventory();
}
String targetIdentifier = BlockTranslator.getJavaIdBlockMap().inverse().get(blockToPick).split("\\[")[0];
// Check hotbar for item
for (int i = 36; i < 45; i++) {
if (inventory.getItem(i) == null) {
continue;
}
ItemEntry item = ItemRegistry.getItem(inventory.getItem(i));
// If this isn't the item we're looking for
if (!item.getJavaIdentifier().equals(targetIdentifier)) {
continue;
}
PlayerHotbarPacket hotbarPacket = new PlayerHotbarPacket();
hotbarPacket.setContainerId(0);
// Java inventory slot to hotbar slot ID
hotbarPacket.setSelectedHotbarSlot(i - 36);
hotbarPacket.setSelectHotbarSlot(true);
session.sendUpstreamPacket(hotbarPacket);
session.getInventory().setHeldItemSlot(i - 36);
// Don't check inventory if item was in hotbar
return;
}
// Check inventory for item
for (int i = 9; i < 36; i++) {
if (inventory.getItem(i) == null) {
continue;
}
ItemEntry item = ItemRegistry.getItem(inventory.getItem(i));
// If this isn't the item we're looking for
if (!item.getJavaIdentifier().equals(targetIdentifier)) {
continue;
}
ClientMoveItemToHotbarPacket packetToSend = new ClientMoveItemToHotbarPacket(i); // https://wiki.vg/Protocol#Pick_Item
session.sendDownstreamPacket(packetToSend);
return;
}
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.bedrock;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.packet.BlockPickRequestPacket;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.utils.InventoryUtils;
@Translator(packet = BlockPickRequestPacket.class)
public class BedrockBlockPickRequestTranslator extends PacketTranslator<BlockPickRequestPacket> {
@Override
public void translate(BlockPickRequestPacket packet, GeyserSession session) {
Vector3i vector = packet.getBlockPosition();
int blockToPick = session.getConnector().getWorldManager().getBlockAt(session, vector.getX(), vector.getY(), vector.getZ());
// Block is air - chunk caching is probably off
if (blockToPick == 0) {
return;
}
String targetIdentifier = BlockTranslator.getJavaIdBlockMap().inverse().get(blockToPick).split("\\[")[0];
InventoryUtils.findOrCreatePickedBlock(session, targetIdentifier);
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.bedrock;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.world.block.CommandBlockMode;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientUpdateCommandBlockMinecartPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientUpdateCommandBlockPacket;
import com.nukkitx.protocol.bedrock.packet.CommandBlockUpdatePacket;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
@Translator(packet = CommandBlockUpdatePacket.class)
public class BedrockCommandBlockUpdateTranslator extends PacketTranslator<CommandBlockUpdatePacket> {
@Override
public void translate(CommandBlockUpdatePacket packet, GeyserSession session) {
String command = packet.getCommand();
boolean outputTracked = packet.isOutputTracked();
if (packet.isBlock()) {
CommandBlockMode mode;
switch (packet.getMode()) {
case CHAIN: // The green one
mode = CommandBlockMode.SEQUENCE;
break;
case REPEATING: // The purple one
mode = CommandBlockMode.AUTO;
break;
default: // NORMAL, the orange one
mode = CommandBlockMode.REDSTONE;
break;
}
boolean isConditional = packet.isConditional();
boolean automatic = !packet.isRedstoneMode(); // Automatic = Always Active option in Java
ClientUpdateCommandBlockPacket commandBlockPacket = new ClientUpdateCommandBlockPacket(
new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()),
command, mode, outputTracked, isConditional, automatic);
session.sendDownstreamPacket(commandBlockPacket);
} else {
ClientUpdateCommandBlockMinecartPacket commandMinecartPacket = new ClientUpdateCommandBlockMinecartPacket(
(int) session.getEntityCache().getEntityByGeyserId(packet.getMinecartRuntimeEntityId()).getEntityId(),
command, outputTracked
);
session.sendDownstreamPacket(commandMinecartPacket);
}
}
}

View File

@ -0,0 +1,115 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.bedrock;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.packet.EntityPickRequestPacket;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.utils.InventoryUtils;
/**
* Called when the Bedrock user uses the pick block button on an entity
*/
@Translator(packet = EntityPickRequestPacket.class)
public class BedrockEntityPickRequestTranslator extends PacketTranslator<EntityPickRequestPacket> {
@Override
public void translate(EntityPickRequestPacket packet, GeyserSession session) {
if (session.getGameMode() != GameMode.CREATIVE) return; // Apparently Java behavior
Entity entity = session.getEntityCache().getEntityByGeyserId(packet.getRuntimeEntityId());
if (entity == null) return;
// Get the corresponding item
String itemName;
switch (entity.getEntityType()) {
case BOAT:
// Include type of boat in the name
int variant = entity.getMetadata().getInt(EntityData.VARIANT);
String typeOfBoat;
switch (variant) {
case 1:
typeOfBoat = "spruce";
break;
case 2:
typeOfBoat = "birch";
break;
case 3:
typeOfBoat = "jungle";
break;
case 4:
typeOfBoat = "acacia";
break;
case 5:
typeOfBoat = "dark_oak";
break;
default:
typeOfBoat = "oak";
break;
}
itemName = typeOfBoat + "_boat";
break;
case LEASH_KNOT:
itemName = "lead";
break;
case MINECART_CHEST:
case MINECART_COMMAND_BLOCK:
case MINECART_FURNACE:
case MINECART_HOPPER:
case MINECART_TNT:
// Move MINECART to the end of the name
itemName = entity.getEntityType().toString().toLowerCase().replace("minecart_", "") + "_minecart";
break;
case MINECART_SPAWNER:
// Turns into a normal minecart
itemName = "minecart";
break;
case ARMOR_STAND:
case END_CRYSTAL:
case ITEM_FRAME:
case MINECART:
case PAINTING:
// No spawn egg, just an item
itemName = entity.getEntityType().toString().toLowerCase();
break;
default:
itemName = entity.getEntityType().toString().toLowerCase() + "_spawn_egg";
break;
}
String fullItemName = "minecraft:" + itemName;
ItemEntry entry = ItemRegistry.getItemEntry(fullItemName);
// Verify it is, indeed, an item
if (entry == null) return;
InventoryUtils.findOrCreatePickedBlock(session, fullItemName);
}
}

View File

@ -39,8 +39,11 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlaye
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.LevelEventType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket;
import com.nukkitx.protocol.bedrock.packet.InventoryTransactionPacket;
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
import org.geysermc.connector.entity.CommandBlockMinecartEntity;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.ItemFrameEntity;
import org.geysermc.connector.entity.living.merchant.AbstractMerchantEntity;
@ -98,11 +101,31 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
session.sendDownstreamPacket(blockPacket);
// Otherwise boats will not be able to be placed in survival and buckets wont work on mobile
if (packet.getItemInHand() != null && (packet.getItemInHand().getId() == ItemRegistry.BOAT.getBedrockId() || packet.getItemInHand().getId() == ItemRegistry.BUCKET.getBedrockId())) {
// Check actions, otherwise buckets may be activated when block inventories are accessed
if (packet.getItemInHand() != null && (packet.getItemInHand().getId() == ItemRegistry.BOAT.getBedrockId() ||
packet.getItemInHand().getId() == ItemRegistry.BUCKET.getBedrockId()) && !packet.getActions().isEmpty()) {
ClientPlayerUseItemPacket itemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND);
session.sendDownstreamPacket(itemPacket);
}
if (packet.getActions().isEmpty()) {
if (session.getOpPermissionLevel() >= 2 && session.getGameMode() == GameMode.CREATIVE) {
// Otherwise insufficient permissions
int blockState = BlockTranslator.getJavaBlockState(packet.getBlockRuntimeId());
String blockName = BlockTranslator.getJavaIdBlockMap().inverse().getOrDefault(blockState, "");
// In the future this can be used for structure blocks too, however not all elements
// are available in each GUI
if (blockName.contains("jigsaw")) {
ContainerOpenPacket openPacket = new ContainerOpenPacket();
openPacket.setBlockPosition(packet.getBlockPosition());
openPacket.setId((byte) 1);
openPacket.setType(ContainerType.JIGSAW_EDITOR);
openPacket.setUniqueEntityId(-1);
session.sendUpstreamPacket(openPacket);
}
}
}
Vector3i blockPos = packet.getBlockPosition();
// TODO: Find a better way to do this?
switch (packet.getBlockFace()) {
@ -195,6 +218,18 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
//https://wiki.vg/Protocol#Interact_Entity
switch (packet.getActionType()) {
case 0: //Interact
if (entity instanceof CommandBlockMinecartEntity) {
// The UI is handled client-side on Java Edition
// Ensure OP permission level and gamemode is appropriate
if (session.getOpPermissionLevel() < 2 || session.getGameMode() != GameMode.CREATIVE) return;
ContainerOpenPacket openPacket = new ContainerOpenPacket();
openPacket.setBlockPosition(Vector3i.ZERO);
openPacket.setId((byte) 1);
openPacket.setType(ContainerType.COMMAND_BLOCK);
openPacket.setUniqueEntityId(entity.getGeyserId());
session.sendUpstreamPacket(openPacket);
break;
}
Vector3f vector = packet.getClickPosition();
ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(),
InteractAction.INTERACT, Hand.MAIN_HAND, session.isSneaking());

View File

@ -26,7 +26,7 @@
package org.geysermc.connector.network.translators.bedrock.entity.player;
import com.nukkitx.protocol.bedrock.packet.EmotePacket;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
@ -37,9 +37,12 @@ public class BedrockEmoteTranslator extends PacketTranslator<EmotePacket> {
@Override
public void translate(EmotePacket packet, GeyserSession session) {
long javaId = session.getPlayerEntity().getEntityId();
for (GeyserSession otherSession : GeyserConnector.getInstance().getPlayers()) {
for (GeyserSession otherSession : session.getConnector().getPlayers()) {
if (otherSession != session) {
packet.setRuntimeEntityId(otherSession.getEntityCache().getEntityByJavaId(javaId).getGeyserId());
if (otherSession.isClosed()) continue;
Entity otherEntity = otherSession.getEntityCache().getEntityByJavaId(javaId);
if (otherEntity == null) continue;
packet.setRuntimeEntityId(otherEntity.getGeyserId());
otherSession.sendUpstreamPacket(packet);
}
}

View File

@ -86,6 +86,13 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
entity.setPosition(packet.getPosition().sub(0, EntityType.PLAYER.getOffset(), 0));
entity.setRotation(rotation);
entity.setOnGround(packet.isOnGround());
// Move parrots to match if applicable
if (entity.getLeftParrot() != null) {
entity.getLeftParrot().moveAbsolute(session, entity.getPosition(), entity.getRotation(), true, false);
}
if (entity.getRightParrot() != null) {
entity.getRightParrot().moveAbsolute(session, entity.getPosition(), entity.getRotation(), true, false);
}
/*
boolean colliding = false;

View File

@ -25,18 +25,243 @@
package org.geysermc.connector.network.translators.inventory;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket;
import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtMapBuilder;
import com.nukkitx.nbt.NbtType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket;
import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.geysermc.connector.common.ChatColor;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater;
import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater;
import org.geysermc.connector.network.translators.item.ItemTranslator;
import org.geysermc.connector.utils.InventoryUtils;
import org.geysermc.connector.utils.LocaleUtils;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* A temporary reconstruction of the enchantment table UI until our inventory rewrite is complete.
* The enchantment table on Bedrock without server authoritative inventories doesn't tell us which button is pressed
* when selecting an enchantment.
*/
public class EnchantmentInventoryTranslator extends BlockInventoryTranslator {
public EnchantmentInventoryTranslator() {
super(2, "minecraft:enchanting_table", ContainerType.ENCHANTMENT, new ContainerInventoryUpdater());
private static final int DYE_ID = 351;
private static final short LAPIS_DAMAGE = 4;
private static final int ENCHANTED_BOOK_ID = 403;
public EnchantmentInventoryTranslator(InventoryUpdater updater) {
super(2, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER, updater);
}
@Override
public void translateActions(GeyserSession session, Inventory inventory, List<InventoryActionData> actions) {
for (InventoryActionData action : actions) {
if (action.getSource().getContainerId() == inventory.getId()) {
// This is the hopper UI
switch (action.getSlot()) {
case 1:
// Don't allow the slot to be put through if the item isn't lapis
if ((action.getToItem().getId() != DYE_ID
&& action.getToItem().getDamage() != LAPIS_DAMAGE) && action.getToItem() != ItemData.AIR) {
updateInventory(session, inventory);
InventoryUtils.updateCursor(session);
return;
}
break;
case 2:
case 3:
case 4:
// The books here act as buttons
ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), action.getSlot() - 2);
session.sendDownstreamPacket(packet);
updateInventory(session, inventory);
InventoryUtils.updateCursor(session);
return;
default:
break;
}
}
}
super.translateActions(session, inventory, actions);
}
@Override
public void updateInventory(GeyserSession session, Inventory inventory) {
super.updateInventory(session, inventory);
ItemData[] items = new ItemData[5];
items[0] = ItemTranslator.translateToBedrock(session, inventory.getItem(0));
items[1] = ItemTranslator.translateToBedrock(session, inventory.getItem(1));
for (int i = 0; i < 3; i++) {
items[i + 2] = session.getEnchantmentSlotData()[i].getItem() != null ? session.getEnchantmentSlotData()[i].getItem() : createEnchantmentBook();
}
InventoryContentPacket contentPacket = new InventoryContentPacket();
contentPacket.setContainerId(inventory.getId());
contentPacket.setContents(items);
session.sendUpstreamPacket(contentPacket);
}
@Override
public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) {
int bookSlotToUpdate;
switch (key) {
case 0:
case 1:
case 2:
// Experience required
bookSlotToUpdate = key;
session.getEnchantmentSlotData()[bookSlotToUpdate].setExperienceRequired(value);
break;
case 4:
case 5:
case 6:
// Enchantment name
bookSlotToUpdate = key - 4;
if (value != -1) {
session.getEnchantmentSlotData()[bookSlotToUpdate].setEnchantmentType(EnchantmentTableEnchantments.values()[value - 1]);
} else {
// -1 means no enchantment specified
session.getEnchantmentSlotData()[bookSlotToUpdate].setEnchantmentType(null);
}
break;
case 7:
case 8:
case 9:
// Enchantment level
bookSlotToUpdate = key - 7;
session.getEnchantmentSlotData()[bookSlotToUpdate].setEnchantmentLevel(value);
break;
default:
return;
}
updateEnchantmentBook(session, inventory, bookSlotToUpdate);
}
@Override
public void openInventory(GeyserSession session, Inventory inventory) {
super.openInventory(session, inventory);
for (int i = 0; i < session.getEnchantmentSlotData().length; i++) {
session.getEnchantmentSlotData()[i] = new EnchantmentSlotData();
}
}
@Override
public void closeInventory(GeyserSession session, Inventory inventory) {
super.closeInventory(session, inventory);
Arrays.fill(session.getEnchantmentSlotData(), null);
}
private ItemData createEnchantmentBook() {
NbtMapBuilder root = NbtMap.builder();
NbtMapBuilder display = NbtMap.builder();
display.putString("Name", ChatColor.RESET + "No Enchantment");
root.put("display", display.build());
return ItemData.of(ENCHANTED_BOOK_ID, (short) 0, 1, root.build());
}
private void updateEnchantmentBook(GeyserSession session, Inventory inventory, int slot) {
NbtMapBuilder root = NbtMap.builder();
NbtMapBuilder display = NbtMap.builder();
EnchantmentSlotData data = session.getEnchantmentSlotData()[slot];
if (data.getEnchantmentType() != null) {
display.putString("Name", ChatColor.ITALIC + data.getEnchantmentType().toString(session) +
(data.getEnchantmentLevel() != -1 ? " " + toRomanNumeral(session, data.getEnchantmentLevel()) : "") + "?");
} else {
display.putString("Name", ChatColor.RESET + "No Enchantment");
}
display.putList("Lore", NbtType.STRING, Collections.singletonList(ChatColor.DARK_GRAY + data.getExperienceRequired() + "xp"));
root.put("display", display.build());
ItemData book = ItemData.of(ENCHANTED_BOOK_ID, (short) 0, 1, root.build());
InventorySlotPacket slotPacket = new InventorySlotPacket();
slotPacket.setContainerId(inventory.getId());
slotPacket.setSlot(slot + 2);
slotPacket.setItem(book);
session.sendUpstreamPacket(slotPacket);
data.setItem(book);
}
private String toRomanNumeral(GeyserSession session, int level) {
return LocaleUtils.getLocaleString("enchantment.level." + level,
session.getClientData().getLanguageCode());
}
/**
* Stores the data of each slot in an enchantment table
*/
@NoArgsConstructor
@Getter
@Setter
@ToString
public static class EnchantmentSlotData {
private EnchantmentTableEnchantments enchantmentType = null;
private int enchantmentLevel = 0;
private int experienceRequired = 0;
private ItemData item;
}
/**
* Classifies enchantments by Java order
*/
public enum EnchantmentTableEnchantments {
PROTECTION,
FIRE_PROTECTION,
FEATHER_FALLING,
BLAST_PROTECTION,
PROJECTILE_PROTECTION,
RESPIRATION,
AQUA_AFFINITY,
THORNS,
DEPTH_STRIDER,
FROST_WALKER,
BINDING_CURSE,
SHARPNESS,
SMITE,
BANE_OF_ARTHROPODS,
KNOCKBACK,
FIRE_ASPECT,
LOOTING,
SWEEPING,
EFFICIENCY,
SILK_TOUCH,
UNBREAKING,
FORTUNE,
POWER,
PUNCH,
FLAME,
INFINITY,
LUCK_OF_THE_SEA,
LURE,
LOYALTY,
IMPALING,
RIPTIDE,
CHANNELING,
MENDING,
VANISHING_CURSE, // After this is not documented
MULTISHOT,
PIERCING,
QUICK_CHARGE,
SOUL_SPEED;
public String toString(GeyserSession session) {
return LocaleUtils.getLocaleString("enchantment.minecraft." + this.toString().toLowerCase(),
session.getClientData().getLanguageCode());
}
}
}

View File

@ -56,7 +56,6 @@ public abstract class InventoryTranslator {
put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator());
put(WindowType.MERCHANT, new MerchantInventoryTranslator());
put(WindowType.SMITHING, new SmithingInventoryTranslator());
//put(WindowType.ENCHANTMENT, new EnchantmentInventoryTranslator()); //TODO
InventoryTranslator furnace = new FurnaceInventoryTranslator();
put(WindowType.FURNACE, furnace);
@ -64,6 +63,7 @@ public abstract class InventoryTranslator {
put(WindowType.SMOKER, furnace);
InventoryUpdater containerUpdater = new ContainerInventoryUpdater();
put(WindowType.ENCHANTMENT, new EnchantmentInventoryTranslator(containerUpdater)); //TODO
put(WindowType.GENERIC_3X3, new BlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER, containerUpdater));
put(WindowType.HOPPER, new BlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER, containerUpdater));
put(WindowType.SHULKER_BOX, new BlockInventoryTranslator(27, "minecraft:shulker_box[facing=north]", ContainerType.CONTAINER, containerUpdater));

View File

@ -173,21 +173,8 @@ public class ItemRegistry {
int netId = 1;
List<ItemData> creativeItems = new ArrayList<>();
for (JsonNode itemNode : creativeItemEntries) {
try {
short damage = 0;
NbtMap tag = null;
if (itemNode.has("damage")) {
damage = itemNode.get("damage").numberValue().shortValue();
}
if (itemNode.has("nbt_b64")) {
byte[] bytes = Base64.getDecoder().decode(itemNode.get("nbt_b64").asText());
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
tag = (NbtMap) NbtUtils.createReaderLE(bais).readTag();
}
creativeItems.add(ItemData.fromNet(netId++, itemNode.get("id").asInt(), damage, 1, tag));
} catch (IOException e) {
e.printStackTrace();
}
ItemData item = getBedrockItemFromJson(itemNode);
creativeItems.add(ItemData.fromNet(netId++, item.getId(), item.getDamage(), item.getCount(), item.getTag()));
}
CREATIVE_ITEMS = creativeItems.toArray(new ItemData[0]);
}
@ -233,4 +220,48 @@ public class ItemRegistry {
return JAVA_IDENTIFIER_MAP.computeIfAbsent(javaIdentifier, key -> ITEM_ENTRIES.values()
.stream().filter(itemEntry -> itemEntry.getJavaIdentifier().equals(key)).findFirst().orElse(null));
}
/**
* Finds the Bedrock string identifier of an ItemEntry
*
* @param entry the ItemEntry to search for
* @return the Bedrock identifier
*/
public static String getBedrockIdentifer(ItemEntry entry) {
String blockName = "";
for (StartGamePacket.ItemEntry startGamePacketItemEntry : ItemRegistry.ITEMS) {
if (startGamePacketItemEntry.getId() == (short) entry.getBedrockId()) {
blockName = startGamePacketItemEntry.getIdentifier(); // Find the Bedrock string name
break;
}
}
return blockName;
}
/**
* Gets a Bedrock {@link ItemData} from a {@link JsonNode}
* @param itemNode the JSON node that contains ProxyPass-compatible Bedrock item data
* @return
*/
public static ItemData getBedrockItemFromJson(JsonNode itemNode) {
int count = 1;
short damage = 0;
NbtMap tag = null;
if (itemNode.has("damage")) {
damage = itemNode.get("damage").numberValue().shortValue();
}
if (itemNode.has("count")) {
count = itemNode.get("count").asInt();
}
if (itemNode.has("nbt_b64")) {
byte[] bytes = Base64.getDecoder().decode(itemNode.get("nbt_b64").asText());
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
try {
tag = (NbtMap) NbtUtils.createReaderLE(bais).readTag();
} catch (IOException e) {
e.printStackTrace();
}
}
return ItemData.of(itemNode.get("id").asInt(), damage, count, tag);
}
}

View File

@ -35,12 +35,14 @@ import com.nukkitx.nbt.NbtType;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.utils.FileUtils;
import org.geysermc.connector.utils.LanguageUtils;
import org.geysermc.connector.utils.MessageUtils;
import org.reflections.Reflections;
@ -62,7 +64,7 @@ public abstract class ItemTranslator {
static {
/* Load item translators */
Reflections ref = new Reflections("org.geysermc.connector.network.translators.item");
Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.item") : new Reflections("org.geysermc.connector.network.translators.item");
Map<NbtItemStackTranslator, Integer> loadedNbtItemTranslators = new HashMap<>();
for (Class<?> clazz : ref.getTypesAnnotatedWith(ItemRemapper.class)) {
@ -138,11 +140,13 @@ public abstract class ItemTranslator {
if (nbt != null) {
for (NbtItemStackTranslator translator : NBT_TRANSLATORS) {
if (translator.acceptItem(bedrockItem)) {
translator.translateToBedrock(nbt, bedrockItem);
translator.translateToBedrock(session, nbt, bedrockItem);
}
}
}
translateDisplayProperties(session, nbt);
ItemData itemData;
ItemTranslator itemStackTranslator = ITEM_STACK_TRANSLATORS.get(bedrockItem.getJavaId());
if (itemStackTranslator != null) {
@ -151,42 +155,43 @@ public abstract class ItemTranslator {
itemData = DEFAULT_TRANSLATOR.translateToBedrock(itemStack, bedrockItem);
}
// Get the display name of the item
NbtMap tag = itemData.getTag();
if (tag != null) {
NbtMap display = tag.getCompound("display");
if (display != null && !display.isEmpty() && display.containsKey("Name")) {
String name = display.getString("Name");
// If its not a message convert it
if (!MessageUtils.isMessage(name)) {
TextComponent component = LegacyComponentSerializer.legacySection().deserialize(name);
name = GsonComponentSerializer.gson().serialize(component);
}
// Check if its a message to translate
if (MessageUtils.isMessage(name)) {
// Get the translated name
name = MessageUtils.getTranslatedBedrockMessage(MessageSerializer.fromString(name), session.getClientData().getLanguageCode());
// Build the new display tag
NbtMapBuilder displayBuilder = display.toBuilder();
displayBuilder.putString("Name", name);
// Build the new root tag
NbtMapBuilder builder = tag.toBuilder();
builder.put("display", displayBuilder.build());
// Create a new item with the original data + updated name
itemData = ItemData.of(itemData.getId(), itemData.getDamage(), itemData.getCount(), builder.build());
}
}
if (nbt != null) {
// Translate the canDestroy and canPlaceOn Java NBT
ListTag canDestroy = nbt.get("CanDestroy");
String[] canBreak = new String[0];
ListTag canPlaceOn = nbt.get("CanPlaceOn");
String[] canPlace = new String[0];
canBreak = getCanModify(canDestroy, canBreak);
canPlace = getCanModify(canPlaceOn, canPlace);
itemData = ItemData.of(itemData.getId(), itemData.getDamage(), itemData.getCount(), itemData.getTag(), canPlace, canBreak);
}
return itemData;
}
/**
* Translates the Java NBT of canDestroy and canPlaceOn to its Bedrock counterparts.
* In Java, this is treated as normal NBT, but in Bedrock, these arguments are extra parts of the item data itself.
* @param canModifyJava the list of items in Java
* @param canModifyBedrock the empty list of items in Bedrock
* @return the new list of items in Bedrock
*/
private static String[] getCanModify(ListTag canModifyJava, String[] canModifyBedrock) {
if (canModifyJava != null && canModifyJava.size() > 0) {
canModifyBedrock = new String[canModifyJava.size()];
for (int i = 0; i < canModifyBedrock.length; i++) {
// Get the Java identifier of the block that can be placed
String block = ((StringTag) canModifyJava.get(i)).getValue();
// Sometimes this is done but it's still valid
if (!block.startsWith("minecraft:")) block = "minecraft:" + block;
// Get the Bedrock identifier of the item and replace it.
// This will unfortunately be limited - for example, beds and banners will be translated weirdly
canModifyBedrock[i] = BlockTranslator.getBedrockBlockIdentifier(block).replace("minecraft:", "");
}
}
return canModifyBedrock;
}
private static final ItemTranslator DEFAULT_TRANSLATOR = new ItemTranslator() {
@Override
public List<ItemEntry> getAppliedItems() {
@ -375,6 +380,38 @@ public abstract class ItemTranslator {
return null;
}
/**
* Translates the display name of the item
* @param session the Bedrock client's session
* @param tag the tag to translate
*/
public static void translateDisplayProperties(GeyserSession session, CompoundTag tag) {
if (tag != null) {
CompoundTag display = tag.get("display");
if (display != null && !display.isEmpty() && display.contains("Name")) {
String name = ((StringTag) display.get("Name")).getValue();
// If its not a message convert it
if (!MessageUtils.isMessage(name)) {
Component component = LegacyComponentSerializer.legacySection().deserialize(name);
name = GsonComponentSerializer.gson().serialize(component);
}
// Check if its a message to translate
if (MessageUtils.isMessage(name)) {
// Get the translated name
name = MessageUtils.getTranslatedBedrockMessage(MessageSerializer.fromString(name), session.getClientData().getLanguageCode());
// Add the new name tag
display.put(new StringTag("Name", name));
// Add to the new root tag
tag.put(display);
}
}
}
}
/**
* Checks if an {@link ItemStack} is equal to another item stack
*

View File

@ -26,17 +26,33 @@
package org.geysermc.connector.network.translators.item;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import org.geysermc.connector.network.session.GeyserSession;
public class NbtItemStackTranslator {
public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) {
/**
* Translate the item NBT to Bedrock
* @param session the client's current session
* @param itemTag the item's CompoundTag
* @param itemEntry Geyser's item entry
*/
public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) {
}
/**
* Translate the item NBT to Java.
* @param itemTag the item's CompoundTag
* @param itemEntry Geyser's item entry
*/
public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) {
}
/**
* @param itemEntry Geyser's item entry
* @return if the item should be processed under this class
*/
public boolean acceptItem(ItemEntry itemEntry) {
return true;
}

View File

@ -0,0 +1,117 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.item;
import com.fasterxml.jackson.databind.JsonNode;
import com.nukkitx.protocol.bedrock.data.inventory.CraftingData;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.utils.FileUtils;
import org.geysermc.connector.utils.LanguageUtils;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;
/**
* Manages any recipe-related storing
*/
public class RecipeRegistry {
/**
* A list of all possible leather armor dyeing recipes.
* Created manually.
*/
public static List<CraftingData> LEATHER_DYEING_RECIPES = new ObjectArrayList<>();
/**
* A list of all possible firework rocket recipes, including the base rocket.
* Obtained from a ProxyPass dump of protocol v407
*/
public static List<CraftingData> FIREWORK_ROCKET_RECIPES = new ObjectArrayList<>(21);
/**
* A list of all possible firework star recipes.
* Obtained from a ProxyPass dump of protocol v407
*/
public static List<CraftingData> FIREWORK_STAR_RECIPES = new ObjectArrayList<>(40);
/**
* A list of all possible shulker box dyeing options.
* Obtained from a ProxyPass dump of protocol v407
*/
public static List<CraftingData> SHULKER_BOX_DYEING_RECIPES = new ObjectArrayList<>();
static {
// Get all recipes that are not directly sent from a Java server
InputStream stream = FileUtils.getResource("mappings/recipes.json");
JsonNode items;
try {
items = GeyserConnector.JSON_MAPPER.readTree(stream);
} catch (Exception e) {
throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e);
}
for (JsonNode entry: items.get("leather_armor")) {
// This won't be perfect, as we can't possibly send every leather input for every kind of color
// But it does display the correct output from a base leather armor, and besides visuals everything works fine
LEATHER_DYEING_RECIPES.add(getCraftingDataFromJsonNode(entry));
}
for (JsonNode entry : items.get("firework_rockets")) {
FIREWORK_ROCKET_RECIPES.add(getCraftingDataFromJsonNode(entry));
}
for (JsonNode entry : items.get("firework_stars")) {
FIREWORK_STAR_RECIPES.add(getCraftingDataFromJsonNode(entry));
}
for (JsonNode entry : items.get("shulker_boxes")) {
SHULKER_BOX_DYEING_RECIPES.add(getCraftingDataFromJsonNode(entry));
}
}
/**
* Computes a Bedrock crafting recipe from the given JSON data.
* @param node the JSON data to compute
* @return the {@link CraftingData} to send to the Bedrock client.
*/
private static CraftingData getCraftingDataFromJsonNode(JsonNode node) {
ItemData output = ItemRegistry.getBedrockItemFromJson(node.get("output").get(0));
List<ItemData> inputs = new ObjectArrayList<>();
for (JsonNode entry : node.get("input")) {
inputs.add(ItemRegistry.getBedrockItemFromJson(entry));
}
UUID uuid = UUID.randomUUID();
if (node.get("type").asInt() == 5) {
// Shulker box
return CraftingData.fromShulkerBox(uuid.toString(),
inputs.toArray(new ItemData[0]), new ItemData[]{output}, uuid, "crafting_table", 0);
}
return CraftingData.fromShapeless(uuid.toString(),
inputs.toArray(new ItemData[0]), new ItemData[]{output}, uuid, "crafting_table", 0);
}
public static void init() {
// no-op
}
}

View File

@ -33,6 +33,7 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
@ -45,7 +46,7 @@ import java.util.List;
public class BasicItemTranslator extends NbtItemStackTranslator {
@Override
public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) {
public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) {
if (!itemTag.contains("display")) {
return;
}

View File

@ -29,6 +29,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
import org.geysermc.connector.network.translators.item.ItemEntry;
@ -41,7 +42,7 @@ import java.util.List;
public class BookPagesTranslator extends NbtItemStackTranslator {
@Override
public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) {
public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) {
if (!itemTag.contains("pages")) {
return;
}

View File

@ -29,6 +29,7 @@ import com.github.steveice10.opennbt.tag.builtin.ByteTag;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
import org.geysermc.connector.network.translators.item.ItemEntry;
@ -37,7 +38,7 @@ import org.geysermc.connector.network.translators.item.ItemEntry;
public class CrossbowTranslator extends NbtItemStackTranslator {
@Override
public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) {
public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) {
if (itemTag.get("ChargedProjectiles") != null) {
ListTag chargedProjectiles = itemTag.get("ChargedProjectiles");
if (!chargedProjectiles.getValue().isEmpty()) {

View File

@ -28,6 +28,7 @@ package org.geysermc.connector.network.translators.item.translators.nbt;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
import org.geysermc.connector.network.translators.item.ItemEntry;
@ -36,7 +37,7 @@ import org.geysermc.connector.network.translators.item.ItemEntry;
public class EnchantedBookTranslator extends NbtItemStackTranslator {
@Override
public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) {
public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) {
if (!itemTag.contains("StoredEnchantments")) {
return;
}

View File

@ -27,6 +27,7 @@ package org.geysermc.connector.network.translators.item.translators.nbt;
import com.github.steveice10.opennbt.tag.builtin.*;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
import org.geysermc.connector.network.translators.item.Enchantment;
@ -40,7 +41,7 @@ import java.util.Map;
public class EnchantmentTranslator extends NbtItemStackTranslator {
@Override
public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) {
public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) {
List<Tag> newTags = new ArrayList<>();
if (itemTag.contains("Enchantments")) {
ListTag enchantmentTag = itemTag.get("Enchantments");

View File

@ -26,6 +26,7 @@
package org.geysermc.connector.network.translators.item.translators.nbt;
import com.github.steveice10.opennbt.tag.builtin.*;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
@ -36,7 +37,7 @@ import org.geysermc.connector.utils.MathUtils;
public class FireworkTranslator extends NbtItemStackTranslator {
@Override
public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) {
public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) {
if (!itemTag.contains("Fireworks")) {
return;
}
@ -106,6 +107,9 @@ public class FireworkTranslator extends NbtItemStackTranslator {
fireworks.put(new ByteTag("Flight", MathUtils.convertByte(fireworks.get("Flight").getValue())));
}
if (!itemTag.contains("Explosions")) {
return;
}
ListTag explosions = fireworks.get("Explosions");
for (Tag effect : explosions.getValue()) {
CompoundTag effectData = (CompoundTag) effect;

View File

@ -27,6 +27,7 @@ package org.geysermc.connector.network.translators.item.translators.nbt;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.IntTag;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
import org.geysermc.connector.network.translators.item.ItemEntry;
@ -37,7 +38,7 @@ public class LeatherArmorTranslator extends NbtItemStackTranslator {
private static final String[] ITEMS = new String[]{"minecraft:leather_helmet", "minecraft:leather_chestplate", "minecraft:leather_leggings", "minecraft:leather_boots"};
@Override
public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) {
public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) {
if (!itemTag.contains("display")) {
return;
}

View File

@ -29,6 +29,7 @@ import com.github.steveice10.opennbt.tag.builtin.ByteTag;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.IntTag;
import com.github.steveice10.opennbt.tag.builtin.LongTag;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
@ -37,7 +38,7 @@ import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
public class MapItemTranslator extends NbtItemStackTranslator {
@Override
public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) {
public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) {
IntTag mapId = itemTag.get("map");
if (mapId != null) {

View File

@ -0,0 +1,84 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.item.translators.nbt;
import com.github.steveice10.opennbt.tag.builtin.*;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.item.ItemTranslator;
import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
@ItemRemapper
public class ShulkerBoxItemTranslator extends NbtItemStackTranslator {
@Override
public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) {
if (!itemTag.contains("BlockEntityTag")) return; // Empty shulker box
CompoundTag blockEntityTag = itemTag.get("BlockEntityTag");
if (blockEntityTag.get("Items") == null) return;
ListTag itemsList = new ListTag("Items");
for (Tag item : (ListTag) blockEntityTag.get("Items")) {
CompoundTag itemData = (CompoundTag) item; // Information about the item
CompoundTag boxItemTag = new CompoundTag(""); // Final item tag to add to the list
boxItemTag.put(new ByteTag("Slot", ((ByteTag) itemData.get("Slot")).getValue()));
boxItemTag.put(new ByteTag("WasPickedUp", (byte) 0)); // ???
ItemEntry boxItemEntry = ItemRegistry.getItemEntry(((StringTag) itemData.get("id")).getValue());
String blockName = ItemRegistry.getBedrockIdentifer(boxItemEntry);
boxItemTag.put(new StringTag("Name", blockName));
boxItemTag.put(new ShortTag("Damage", (short) boxItemEntry.getBedrockData()));
boxItemTag.put(new ByteTag("Count", ((ByteTag) itemData.get("Count")).getValue()));
if (itemData.contains("tag")) {
// Only the display name is what we have interest in, so just translate that if relevant
CompoundTag displayTag = itemData.get("tag");
ItemTranslator.translateDisplayProperties(session, displayTag);
boxItemTag.put(displayTag);
}
itemsList.add(boxItemTag);
}
itemTag.put(itemsList);
// Don't actually bother with removing the block entity tag. Too risky to translate
// if the user is on creative and messing with a shulker box
//itemTag.remove("BlockEntityTag");
}
@Override
public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) {
if (itemTag.contains("Items")) { // Remove any extraneous Bedrock tag and don't touch the Java one
itemTag.remove("Items");
}
}
@Override
public boolean acceptItem(ItemEntry itemEntry) {
return itemEntry.getJavaIdentifier().contains("shulker_box");
}
}

View File

@ -41,10 +41,7 @@ import lombok.EqualsAndHashCode;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.item.ItemTranslator;
import org.geysermc.connector.network.translators.item.PotionMixRegistry;
import org.geysermc.connector.network.translators.item.*;
import java.util.*;
import java.util.stream.Collectors;
@ -83,6 +80,24 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator<ServerDeclare
}
break;
}
case CRAFTING_SPECIAL_FIREWORK_ROCKET: {
// Java doesn't actually tell us the recipes so we need to calculate this ahead of time.
craftingDataPacket.getCraftingData().addAll(RecipeRegistry.FIREWORK_ROCKET_RECIPES);
break;
}
case CRAFTING_SPECIAL_FIREWORK_STAR: {
craftingDataPacket.getCraftingData().addAll(RecipeRegistry.FIREWORK_STAR_RECIPES);
break;
}
case CRAFTING_SPECIAL_SHULKERBOXCOLORING: {
craftingDataPacket.getCraftingData().addAll(RecipeRegistry.SHULKER_BOX_DYEING_RECIPES);
break;
}
case CRAFTING_SPECIAL_ARMORDYE: {
// This one's even worse since it's not actually on Bedrock, but it still works!
craftingDataPacket.getCraftingData().addAll(RecipeRegistry.LEATHER_DYEING_RECIPES);
break;
}
}
}
craftingDataPacket.getPotionMixData().addAll(PotionMixRegistry.POTION_MIXES);

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.java;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerDisconnectPacket;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.utils.MessageUtils;
@Translator(packet = ServerDisconnectPacket.class)
public class JavaDisconnectPacket extends PacketTranslator<ServerDisconnectPacket> {
@Override
public void translate(ServerDisconnectPacket packet, GeyserSession session) {
session.disconnect(MessageUtils.getTranslatedBedrockMessage(packet.getReason(), session.getClientData().getLanguageCode(), true));
}
}

View File

@ -88,18 +88,13 @@ public class JavaPlayerListEntryTranslator extends PacketTranslator<ServerPlayer
break;
case REMOVE_PLAYER:
PlayerEntity entity = session.getEntityCache().getPlayerEntity(entry.getProfile().getId());
if (entity != null && entity.isValid()) {
// remove from tablist but player entity is still there
if (entity != null) {
// Just remove the entity's player list status
// Don't despawn the entity - the Java server will also take care of that.
entity.setPlayerList(false);
} else {
if (entity == null) {
// just remove it from caching
session.getEntityCache().removePlayerEntity(entry.getProfile().getId());
} else {
entity.setPlayerList(false);
session.getEntityCache().removeEntity(entity, false);
}
}
// As the player entity is no longer present, we can remove the entry
session.getEntityCache().removePlayerEntity(entry.getProfile().getId());
if (entity == session.getPlayerEntity()) {
// If removing ourself we use our AuthData UUID
translate.getEntries().add(new PlayerListPacket.Entry(session.getAuthData().getUUID()));

View File

@ -41,13 +41,11 @@ public class JavaScoreboardObjectiveTranslator extends PacketTranslator<ServerSc
@Override
public void translate(ServerScoreboardObjectivePacket packet, GeyserSession session) {
WorldCache cache = session.getWorldCache();
Scoreboard scoreboard = cache.getScoreboard();
Scoreboard scoreboard = session.getWorldCache().getScoreboard();
Objective objective = scoreboard.getObjective(packet.getName());
if (objective == null && packet.getAction() != ObjectiveAction.REMOVE) {
objective = scoreboard.registerNewObjective(packet.getName(), true);
objective = scoreboard.registerNewObjective(packet.getName(), false);
}
switch (packet.getAction()) {
@ -61,6 +59,8 @@ public class JavaScoreboardObjectiveTranslator extends PacketTranslator<ServerSc
break;
}
if (objective != null && !objective.isTemp()) scoreboard.onUpdate();
if (objective != null && objective.isActive()) {
scoreboard.onUpdate();
}
}
}

View File

@ -28,10 +28,12 @@ package org.geysermc.connector.network.translators.java.scoreboard;
import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerTeamPacket;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.GeyserLogger;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.scoreboard.Scoreboard;
import org.geysermc.connector.scoreboard.ScoreboardUpdater;
import org.geysermc.connector.scoreboard.Team;
import org.geysermc.connector.scoreboard.UpdateType;
import org.geysermc.connector.utils.LanguageUtils;
@ -42,10 +44,15 @@ import java.util.Set;
@Translator(packet = ServerTeamPacket.class)
public class JavaTeamTranslator extends PacketTranslator<ServerTeamPacket> {
private static final GeyserLogger LOGGER = GeyserConnector.getInstance().getLogger();
@Override
public void translate(ServerTeamPacket packet, GeyserSession session) {
GeyserConnector.getInstance().getLogger().debug("Team packet " + packet.getTeamName() + " " + packet.getAction() + " " + Arrays.toString(packet.getPlayers()));
if (LOGGER.isDebug()) {
LOGGER.debug("Team packet " + packet.getTeamName() + " " + packet.getAction() + " " + Arrays.toString(packet.getPlayers()));
}
int pps = session.getWorldCache().increaseAndGetScoreboardPacketsPerSecond();
Scoreboard scoreboard = session.getWorldCache().getScoreboard();
Team team = scoreboard.getTeam(packet.getTeamName());
@ -54,42 +61,59 @@ public class JavaTeamTranslator extends PacketTranslator<ServerTeamPacket> {
scoreboard.registerNewTeam(packet.getTeamName(), toPlayerSet(packet.getPlayers()))
.setName(MessageUtils.getBedrockMessage(packet.getDisplayName()))
.setColor(packet.getColor())
.setNameTagVisibility(packet.getNameTagVisibility())
.setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getClientData().getLanguageCode()))
.setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode()));
break;
case UPDATE:
if (team != null) {
team.setName(MessageUtils.getBedrockMessage(packet.getDisplayName()))
.setColor(packet.getColor())
.setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getClientData().getLanguageCode()))
.setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode()))
.setUpdateType(UpdateType.UPDATE);
} else {
GeyserConnector.getInstance().getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_not_registered", packet.getAction(), packet.getTeamName()));
if (team == null) {
LOGGER.debug(LanguageUtils.getLocaleStringLog(
"geyser.network.translator.team.failed_not_registered",
packet.getAction(), packet.getTeamName()
));
return;
}
team.setName(MessageUtils.getBedrockMessage(packet.getDisplayName()))
.setColor(packet.getColor())
.setNameTagVisibility(packet.getNameTagVisibility())
.setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getClientData().getLanguageCode()))
.setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode()))
.setUpdateType(UpdateType.UPDATE);
break;
case ADD_PLAYER:
if (team != null) {
team.addEntities(packet.getPlayers());
} else {
GeyserConnector.getInstance().getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_not_registered", packet.getAction(), packet.getTeamName()));
if (team == null) {
LOGGER.debug(LanguageUtils.getLocaleStringLog(
"geyser.network.translator.team.failed_not_registered",
packet.getAction(), packet.getTeamName()
));
return;
}
team.addEntities(packet.getPlayers());
break;
case REMOVE_PLAYER:
if (team != null) {
team.removeEntities(packet.getPlayers());
} else {
GeyserConnector.getInstance().getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_not_registered", packet.getAction(), packet.getTeamName()));
if (team == null) {
LOGGER.debug(LanguageUtils.getLocaleStringLog(
"geyser.network.translator.team.failed_not_registered",
packet.getAction(), packet.getTeamName()
));
return;
}
team.removeEntities(packet.getPlayers());
break;
case REMOVE:
scoreboard.removeTeam(packet.getTeamName());
break;
}
scoreboard.onUpdate();
// ScoreboardUpdater will handle it for us if the packets per second
// (for score and team packets) is higher then the first threshold
if (pps < ScoreboardUpdater.FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) {
scoreboard.onUpdate();
}
}
private Set<String> toPlayerSet(String[] players) {
return new ObjectOpenHashSet<>(Arrays.asList(players));
return new ObjectOpenHashSet<>(players);
}
}

View File

@ -25,48 +25,58 @@
package org.geysermc.connector.network.translators.java.scoreboard;
import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardAction;
import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerUpdateScorePacket;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.GeyserLogger;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.session.cache.WorldCache;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.scoreboard.Objective;
import org.geysermc.connector.scoreboard.Scoreboard;
import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardAction;
import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerUpdateScorePacket;
import org.geysermc.connector.scoreboard.ScoreboardUpdater;
import org.geysermc.connector.utils.LanguageUtils;
@Translator(packet = ServerUpdateScorePacket.class)
public class JavaUpdateScoreTranslator extends PacketTranslator<ServerUpdateScorePacket> {
private final GeyserLogger logger;
public JavaUpdateScoreTranslator() {
logger = GeyserConnector.getInstance().getLogger();
}
@Override
public void translate(ServerUpdateScorePacket packet, GeyserSession session) {
try {
Scoreboard scoreboard = session.getWorldCache().getScoreboard();
WorldCache worldCache = session.getWorldCache();
Scoreboard scoreboard = worldCache.getScoreboard();
int pps = worldCache.increaseAndGetScoreboardPacketsPerSecond();
Objective objective = scoreboard.getObjective(packet.getObjective());
if (objective == null && packet.getAction() != ScoreboardAction.REMOVE) {
GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.translator.score.failed_objective", packet.getObjective()));
return;
}
Objective objective = scoreboard.getObjective(packet.getObjective());
if (objective == null && packet.getAction() != ScoreboardAction.REMOVE) {
logger.info(LanguageUtils.getLocaleStringLog("geyser.network.translator.score.failed_objective", packet.getObjective()));
return;
}
switch (packet.getAction()) {
case ADD_OR_UPDATE:
objective.setScore(packet.getEntry(), packet.getValue());
break;
case REMOVE:
if (objective != null) {
objective.resetScore(packet.getEntry());
} else {
for (Objective objective1 : scoreboard.getObjectives().values()) {
objective1.resetScore(packet.getEntry());
}
switch (packet.getAction()) {
case ADD_OR_UPDATE:
objective.setScore(packet.getEntry(), packet.getValue());
break;
case REMOVE:
if (objective != null) {
objective.removeScore(packet.getEntry());
} else {
for (Objective objective1 : scoreboard.getObjectives().values()) {
objective1.removeScore(packet.getEntry());
}
break;
}
}
break;
}
// ScoreboardUpdater will handle it for us if the packets per second
// (for score and team packets) is higher then the first threshold
if (pps < ScoreboardUpdater.FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) {
scoreboard.onUpdate();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}

View File

@ -25,41 +25,53 @@
package org.geysermc.connector.network.translators.java.world;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerChunkDataPacket;
import com.nukkitx.nbt.NBTOutputStream;
import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtUtils;
import com.nukkitx.network.VarInts;
import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.BiomeTranslator;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.utils.ChunkUtils;
import org.geysermc.connector.network.translators.world.chunk.ChunkSection;
import org.geysermc.connector.utils.ChunkUtils;
@Translator(packet = ServerChunkDataPacket.class)
public class JavaChunkDataTranslator extends PacketTranslator<ServerChunkDataPacket> {
/**
* Determines if we should process non-full chunks
*/
private final boolean isCacheChunks;
public JavaChunkDataTranslator() {
isCacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks();
}
@Override
public void translate(ServerChunkDataPacket packet, GeyserSession session) {
if (session.isSpawned()) {
ChunkUtils.updateChunkPosition(session, session.getPlayerEntity().getPosition().toInt());
}
if (packet.getColumn().getBiomeData() == null) //Non-full chunk
if (packet.getColumn().getBiomeData() == null && !isCacheChunks) {
// Non-full chunk without chunk caching
session.getConnector().getLogger().debug("Not sending non-full chunk because chunk caching is off.");
return;
}
// Non-full chunks don't have all the chunk data, and Bedrock won't accept that
final boolean isNonFullChunk = (packet.getColumn().getBiomeData() == null);
GeyserConnector.getInstance().getGeneralThreadPool().execute(() -> {
try {
ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(packet.getColumn());
ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(session, packet.getColumn(), isNonFullChunk);
ByteBuf byteBuf = Unpooled.buffer(32);
ChunkSection[] sections = chunkData.sections;
@ -74,7 +86,12 @@ public class JavaChunkDataTranslator extends PacketTranslator<ServerChunkDataPac
section.writeToNetwork(byteBuf);
}
byte[] bedrockBiome = BiomeTranslator.toBedrockBiome(packet.getColumn().getBiomeData());
byte[] bedrockBiome;
if (packet.getColumn().getBiomeData() == null) {
bedrockBiome = BiomeTranslator.toBedrockBiome(session.getConnector().getWorldManager().getBiomeDataAt(session, packet.getColumn().getX(), packet.getColumn().getZ()));
} else {
bedrockBiome = BiomeTranslator.toBedrockBiome(packet.getColumn().getBiomeData());
}
byteBuf.writeBytes(bedrockBiome); // Biomes - 256 bytes
byteBuf.writeByte(0); // Border blocks - Edu edition only
@ -99,14 +116,6 @@ public class JavaChunkDataTranslator extends PacketTranslator<ServerChunkDataPac
levelChunkPacket.setData(payload);
session.sendUpstreamPacket(levelChunkPacket);
// Some block entities need to be loaded in later or else text doesn't show (signs) or they crash the game (end gateway blocks)
for (Object2IntMap.Entry<NbtMap> blockEntityEntry : chunkData.getLoadBlockEntitiesLater().object2IntEntrySet()) {
int x = blockEntityEntry.getKey().getInt("x");
int y = blockEntityEntry.getKey().getInt("y");
int z = blockEntityEntry.getKey().getInt("z");
ChunkUtils.updateBlock(session, blockEntityEntry.getIntValue(), new Position(x, y, z));
}
chunkData.getLoadBlockEntitiesLater().clear();
session.getChunkCache().addToCache(packet.getColumn());
} catch (Exception ex) {
ex.printStackTrace();

View File

@ -41,6 +41,7 @@ public class JavaCollectItemTranslator extends PacketTranslator<ServerEntityColl
TakeItemEntityPacket takeItemEntityPacket = new TakeItemEntityPacket();
// Collected entity is the item
Entity collectedEntity = session.getEntityCache().getEntityByJavaId(packet.getCollectedEntityId());
if (collectedEntity == null) return;
// Collector is the entity picking up the item
Entity collectorEntity;
if (packet.getCollectorEntityId() == session.getPlayerEntity().getEntityId()) {
@ -48,6 +49,7 @@ public class JavaCollectItemTranslator extends PacketTranslator<ServerEntityColl
} else {
collectorEntity = session.getEntityCache().getEntityByJavaId(packet.getCollectorEntityId());
}
if (collectorEntity == null) return;
takeItemEntityPacket.setRuntimeEntityId(collectorEntity.getGeyserId());
takeItemEntityPacket.setItemRuntimeEntityId(collectedEntity.getGeyserId());
session.sendUpstreamPacket(takeItemEntityPacket);

View File

@ -43,6 +43,7 @@ import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.inventory.PlayerInventoryTranslator;
import org.geysermc.connector.utils.LocaleUtils;
@Translator(packet = ServerNotifyClientPacket.class)
public class JavaNotifyClientTranslator extends PacketTranslator<ServerNotifyClientPacket> {
@ -141,6 +142,11 @@ public class JavaNotifyClientTranslator extends PacketTranslator<ServerNotifyCli
packet.getValue() == RespawnScreenValue.IMMEDIATE_RESPAWN));
session.sendUpstreamPacket(gamerulePacket);
break;
case INVALID_BED:
// Not sent as a proper message? Odd.
session.sendMessage(LocaleUtils.getLocaleString("block.minecraft.spawn.not_valid",
session.getClientData().getLanguageCode()));
break;
default:
break;
}

View File

@ -50,7 +50,7 @@ public class JavaSpawnParticleTranslator extends PacketTranslator<ServerSpawnPar
LevelEventPacket particle = new LevelEventPacket();
switch (packet.getParticle().getType()) {
case BLOCK:
particle.setType(LevelEventType.PARTICLE_DESTROY_BLOCK);
particle.setType(LevelEventType.PARTICLE_DESTROY_BLOCK_NO_SOUND);
particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ()));
particle.setData(BlockTranslator.getBedrockBlockId(((BlockParticleData) packet.getParticle().getData()).getBlockState()));
session.sendUpstreamPacket(particle);

View File

@ -32,6 +32,7 @@ import org.geysermc.connector.network.translators.Translator;
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerSpawnPositionPacket;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.packet.SetSpawnPositionPacket;
import org.geysermc.connector.utils.DimensionUtils;
@Translator(packet = ServerSpawnPositionPacket.class)
public class JavaSpawnPositionTranslator extends PacketTranslator<ServerSpawnPositionPacket> {
@ -41,6 +42,7 @@ public class JavaSpawnPositionTranslator extends PacketTranslator<ServerSpawnPos
SetSpawnPositionPacket spawnPositionPacket = new SetSpawnPositionPacket();
spawnPositionPacket.setBlockPosition(Vector3i.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ()));
spawnPositionPacket.setSpawnForced(true);
spawnPositionPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getPlayerEntity().getDimension()));
spawnPositionPacket.setSpawnType(SetSpawnPositionPacket.Type.WORLD_SPAWN);
session.sendUpstreamPacket(spawnPositionPacket);
}

View File

@ -25,8 +25,12 @@
package org.geysermc.connector.network.translators.java.world;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.world.block.UpdatedTileType;
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdateTileEntityPacket;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
@ -54,5 +58,15 @@ public class JavaUpdateTileEntityTranslator extends PacketTranslator<ServerUpdat
} else {
BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(id, packet.getNbt(), 0), packet.getPosition());
}
// If block entity is command block, OP permission level is appropriate, player is in creative mode and the NBT is not empty
if (packet.getType() == UpdatedTileType.COMMAND_BLOCK && session.getOpPermissionLevel() >= 2 &&
session.getGameMode() == GameMode.CREATIVE && packet.getNbt().size() > 5) {
ContainerOpenPacket openPacket = new ContainerOpenPacket();
openPacket.setBlockPosition(Vector3i.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ()));
openPacket.setId((byte) 1);
openPacket.setType(ContainerType.COMMAND_BLOCK);
openPacket.setUniqueEntityId(-1);
session.sendUpstreamPacket(openPacket);
}
}
}

View File

@ -25,6 +25,8 @@
package org.geysermc.connector.network.translators.sound;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.utils.FileUtils;
import org.reflections.Reflections;
import java.util.HashMap;
@ -38,7 +40,7 @@ public class SoundHandlerRegistry {
static final Map<SoundHandler, SoundInteractionHandler<?>> INTERACTION_HANDLERS = new HashMap<>();
static {
Reflections ref = new Reflections("org.geysermc.connector.network.translators.sound");
Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.sound") : new Reflections("org.geysermc.connector.network.translators.sound");
for (Class<?> clazz : ref.getTypesAnnotatedWith(SoundHandler.class)) {
try {
SoundInteractionHandler<?> interactionHandler = (SoundInteractionHandler<?>) clazz.newInstance();

View File

@ -32,6 +32,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.chunk.ChunkPosition;
import org.geysermc.connector.utils.GameRule;
public class GeyserWorldManager extends WorldManager {
@ -43,6 +44,13 @@ public class GeyserWorldManager extends WorldManager {
return session.getChunkCache().getBlockAt(new Position(x, y, z));
}
@Override
public int[] getBiomeDataAt(GeyserSession session, int x, int z) {
if (!session.getConnector().getConfig().isCacheChunks())
return new int[1024];
return session.getChunkCache().getChunks().get(new ChunkPosition(x, z)).getBiomeData();
}
@Override
public void setGameRule(GeyserSession session, String name, Object value) {
session.sendDownstreamPacket(new ClientChatPacket("/gamerule " + name + " " + value));

View File

@ -74,6 +74,16 @@ public abstract class WorldManager {
*/
public abstract int getBlockAt(GeyserSession session, int x, int y, int z);
/**
* Gets the biome data for the specified chunk.
*
* @param session the session of the player
* @param x the chunk's X coordinate
* @param z the chunk's Z coordinate
* @return the biome data for the specified region with a length of 1024.
*/
public abstract int[] getBiomeDataAt(GeyserSession session, int x, int z);
/**
* Updates a gamerule value on the Java server
*

View File

@ -39,6 +39,7 @@ public class BlockStateValues {
private static final Int2IntMap BANNER_COLORS = new Int2IntOpenHashMap();
private static final Int2ByteMap BED_COLORS = new Int2ByteOpenHashMap();
private static final Int2ByteMap COMMAND_BLOCK_VALUES = new Int2ByteOpenHashMap();
private static final Int2ObjectMap<DoubleChestValue> DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<String> FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>();
private static final Map<String, NbtMap> FLOWER_POT_BLOCKS = new HashMap<>();
@ -67,6 +68,11 @@ public class BlockStateValues {
return;
}
if (entry.getKey().contains("command_block")) {
COMMAND_BLOCK_VALUES.put(javaBlockState, entry.getKey().contains("conditional=true") ? (byte) 1 : (byte) 0);
return;
}
if (entry.getValue().get("double_chest_position") != null) {
boolean isX = (entry.getValue().get("x") != null);
boolean isDirectionPositive = ((entry.getValue().get("x") != null && entry.getValue().get("x").asBoolean()) ||
@ -138,6 +144,16 @@ public class BlockStateValues {
return -1;
}
/**
* The block state in Java and Bedrock both contain the conditional bit, however command block block entity tags
* in Bedrock need the conditional information.
*
* @return the list of all command blocks and if they are conditional (1 or 0)
*/
public static Int2ByteMap getCommandBlockValues() {
return COMMAND_BLOCK_VALUES;
}
/**
* All double chest values are part of the block state in Java and part of the block entity tag in Bedrock.
* This gives the DoubleChestValue that can be calculated into the final tag.

View File

@ -28,15 +28,12 @@ package org.geysermc.connector.network.translators.world.block;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.nukkitx.nbt.NBTInputStream;
import com.nukkitx.nbt.NbtList;
import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtMapBuilder;
import com.nukkitx.nbt.NbtType;
import com.nukkitx.nbt.NbtUtils;
import com.nukkitx.nbt.*;
import it.unimi.dsi.fastutil.ints.*;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.translators.world.block.entity.BlockEntity;
import org.geysermc.connector.utils.FileUtils;
@ -52,6 +49,11 @@ public class BlockTranslator {
private static final Int2IntMap JAVA_TO_BEDROCK_BLOCK_MAP = new Int2IntOpenHashMap();
private static final Int2IntMap BEDROCK_TO_JAVA_BLOCK_MAP = new Int2IntOpenHashMap();
/**
* Stores a list of differences in block identifiers.
* Items will not be added to this list if the key and value is the same.
*/
private static final Object2ObjectMap<String, String> JAVA_TO_BEDROCK_IDENTIFIERS = new Object2ObjectOpenHashMap<>();
private static final BiMap<String, Integer> JAVA_ID_BLOCK_MAP = HashBiMap.create();
private static final IntSet WATERLOGGED = new IntOpenHashSet();
private static final Object2IntMap<NbtMap> ITEM_FRAMES = new Object2IntOpenHashMap<>();
@ -65,6 +67,11 @@ public class BlockTranslator {
public static final Int2BooleanMap JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND = new Int2BooleanOpenHashMap();
public static final Int2ObjectMap<String> JAVA_RUNTIME_ID_TO_TOOL_TYPE = new Int2ObjectOpenHashMap<>();
/**
* Runtime command block ID, used for fixing command block minecart appearances
*/
public static final int BEDROCK_RUNTIME_COMMAND_BLOCK_ID;
// For block breaking animation math
public static final IntSet JAVA_RUNTIME_WOOL_IDS = new IntOpenHashSet();
public static final int JAVA_RUNTIME_COBWEB_ID;
@ -106,13 +113,14 @@ public class BlockTranslator {
addedStatesMap.defaultReturnValue(-1);
List<NbtMap> paletteList = new ArrayList<>();
Reflections ref = new Reflections("org.geysermc.connector.network.translators.world.block.entity");
Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") : new Reflections("org.geysermc.connector.network.translators.world.block.entity");
ref.getTypesAnnotatedWith(BlockEntity.class);
int waterRuntimeId = -1;
int javaRuntimeId = -1;
int bedrockRuntimeId = 0;
int cobwebRuntimeId = -1;
int commandBlockRuntimeId = -1;
int furnaceRuntimeId = -1;
int furnaceLitRuntimeId = -1;
int spawnerRuntimeId = -1;
@ -140,23 +148,15 @@ public class BlockTranslator {
JAVA_RUNTIME_ID_TO_TOOL_TYPE.put(javaRuntimeId, toolTypeNode.textValue());
}
if (javaId.contains("wool")) {
JAVA_RUNTIME_WOOL_IDS.add(javaRuntimeId);
}
if (javaId.contains("cobweb")) {
cobwebRuntimeId = javaRuntimeId;
}
JAVA_ID_BLOCK_MAP.put(javaId, javaRuntimeId);
// Used for adding all "special" Java block states to block state map
String identifier;
String bedrock_identifer = entry.getValue().get("bedrock_identifier").asText();
String bedrockIdentifier = entry.getValue().get("bedrock_identifier").asText();
for (Class<?> clazz : ref.getTypesAnnotatedWith(BlockEntity.class)) {
identifier = clazz.getAnnotation(BlockEntity.class).regex();
// Endswith, or else the block bedrock gets picked up for bed
if (bedrock_identifer.endsWith(identifier) && !identifier.equals("")) {
if (bedrockIdentifier.endsWith(identifier) && !identifier.equals("")) {
JAVA_ID_TO_BLOCK_ENTITY_MAP.put(javaRuntimeId, clazz.getAnnotation(BlockEntity.class).name());
break;
}
@ -164,9 +164,15 @@ public class BlockTranslator {
BlockStateValues.storeBlockStateValues(entry, javaRuntimeId);
String cleanJavaIdentifier = entry.getKey().split("\\[")[0];
if (!cleanJavaIdentifier.equals(bedrockIdentifier)) {
JAVA_TO_BEDROCK_IDENTIFIERS.put(cleanJavaIdentifier, bedrockIdentifier);
}
// Get the tag needed for non-empty flower pots
if (entry.getValue().get("pottable") != null) {
BlockStateValues.getFlowerPotBlocks().put(entry.getKey().split("\\[")[0], buildBedrockState(entry.getValue()));
BlockStateValues.getFlowerPotBlocks().put(cleanJavaIdentifier, buildBedrockState(entry.getValue()));
}
if ("minecraft:water[level=0]".equals(javaId)) {
@ -197,15 +203,23 @@ public class BlockTranslator {
}
JAVA_TO_BEDROCK_BLOCK_MAP.put(javaRuntimeId, bedrockRuntimeId);
if (javaId.startsWith("minecraft:furnace[facing=north")) {
if (javaId.contains("wool")) {
JAVA_RUNTIME_WOOL_IDS.add(javaRuntimeId);
} else if (javaId.contains("cobweb")) {
cobwebRuntimeId = javaRuntimeId;
} else if (javaId.equals("minecraft:command_block[conditional=false,facing=north]")) {
commandBlockRuntimeId = bedrockRuntimeId;
} else if (javaId.startsWith("minecraft:furnace[facing=north")) {
if (javaId.contains("lit=true")) {
furnaceLitRuntimeId = javaRuntimeId;
} else {
furnaceRuntimeId = javaRuntimeId;
}
}
if (javaId.startsWith("minecraft:spawner")) {
} else if (javaId.startsWith("minecraft:spawner")) {
spawnerRuntimeId = javaRuntimeId;
}
@ -217,6 +231,11 @@ public class BlockTranslator {
}
JAVA_RUNTIME_COBWEB_ID = cobwebRuntimeId;
if (commandBlockRuntimeId == -1) {
throw new AssertionError("Unable to find command block in palette");
}
BEDROCK_RUNTIME_COMMAND_BLOCK_ID = commandBlockRuntimeId;
if (furnaceRuntimeId == -1) {
throw new AssertionError("Unable to find furnace in palette");
}
@ -297,6 +316,14 @@ public class BlockTranslator {
return BEDROCK_TO_JAVA_BLOCK_MAP.get(bedrockId);
}
/**
* @param javaIdentifier the Java identifier of the block to search for
* @return the Bedrock identifier if different, or else the Java identifier
*/
public static String getBedrockBlockIdentifier(String javaIdentifier) {
return JAVA_TO_BEDROCK_IDENTIFIERS.getOrDefault(javaIdentifier, javaIdentifier);
}
public static int getItemFrame(NbtMap tag) {
return ITEM_FRAMES.getOrDefault(tag, -1);
}

View File

@ -27,12 +27,9 @@ package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtType;
import org.geysermc.connector.network.translators.item.translators.BannerTranslator;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@ -65,17 +62,4 @@ public class BannerBlockEntityTranslator extends BlockEntityTranslator implement
return tags;
}
@Override
public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) {
CompoundTag tag = getConstantJavaTag(javaId, x, y, z);
tag.put(new ListTag("Patterns"));
return tag;
}
@Override
public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) {
return getConstantBedrockTag(bedrockId, x, y, z).toBuilder()
.putList("Patterns", NbtType.COMPOUND, new ArrayList<>())
.build();
}
}

View File

@ -26,7 +26,6 @@
package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.nbt.NbtMap;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
import java.util.HashMap;
@ -50,15 +49,4 @@ public class BedBlockEntityTranslator extends BlockEntityTranslator implements R
return tags;
}
@Override
public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) {
return null;
}
@Override
public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) {
return getConstantBedrockTag(bedrockId, x, y, z).toBuilder()
.putByte("color", (byte) 0)
.build();
}
}

View File

@ -28,12 +28,13 @@ package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.IntTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtMapBuilder;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.utils.BlockEntityUtils;
import org.geysermc.connector.utils.FileUtils;
import org.geysermc.connector.utils.LanguageUtils;
import org.reflections.Reflections;
@ -52,6 +53,7 @@ public abstract class BlockEntityTranslator {
{
// Bedrock/Java differences
put("minecraft:enchanting_table", "EnchantTable");
put("minecraft:jigsaw", "JigsawBlock");
put("minecraft:piston_head", "PistonArm");
put("minecraft:trapped_chest", "Chest");
// There are some legacy IDs sent but as far as I can tell they are not needed for things to work properly
@ -66,7 +68,7 @@ public abstract class BlockEntityTranslator {
}
static {
Reflections ref = new Reflections("org.geysermc.connector.network.translators.world.block.entity");
Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") : new Reflections("org.geysermc.connector.network.translators.world.block.entity");
for (Class<?> clazz : ref.getTypesAnnotatedWith(BlockEntity.class)) {
GeyserConnector.getInstance().getLogger().debug("Found annotated block entity: " + clazz.getCanonicalName());
@ -89,10 +91,6 @@ public abstract class BlockEntityTranslator {
public abstract Map<String, Object> translateTag(CompoundTag tag, int blockState);
public abstract CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z);
public abstract NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z);
public NbtMap getBlockEntityTag(String id, CompoundTag tag, int blockState) {
int x = Integer.parseInt(String.valueOf(tag.getValue().get("x").getValue()));
int y = Integer.parseInt(String.valueOf(tag.getValue().get("y").getValue()));
@ -123,7 +121,7 @@ public abstract class BlockEntityTranslator {
}
@SuppressWarnings("unchecked")
protected <T> T getOrDefault(com.github.steveice10.opennbt.tag.builtin.Tag tag, T defaultValue) {
protected <T> T getOrDefault(Tag tag, T defaultValue) {
return (tag != null && tag.getValue() != null) ? (T) tag.getValue() : defaultValue;
}
}

View File

@ -50,18 +50,6 @@ public class CampfireBlockEntityTranslator extends BlockEntityTranslator {
return tags;
}
@Override
public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) {
CompoundTag tag = getConstantJavaTag(javaId, x, y, z);
tag.put(new ListTag("Items"));
return tag;
}
@Override
public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) {
return getConstantBedrockTag(bedrockId, x, y, z);
}
protected NbtMap getItem(CompoundTag tag) {
ItemEntry entry = ItemRegistry.getItemEntry((String) tag.get("id").getValue());
NbtMapBuilder tagBuilder = NbtMap.builder()

View File

@ -0,0 +1,67 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.*;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
import org.geysermc.connector.utils.MessageUtils;
import java.util.HashMap;
import java.util.Map;
@BlockEntity(name = "CommandBlock", regex = "command_block")
public class CommandBlockBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState {
@Override
public Map<String, Object> translateTag(CompoundTag tag, int blockState) {
Map<String, Object> map = new HashMap<>();
if (tag.size() < 5) {
return map; // These values aren't here
}
// Java infers from the block state, but Bedrock needs it in the tag
map.put("conditionalMode", BlockStateValues.getCommandBlockValues().getOrDefault(blockState, (byte) 0));
// Java and Bedrock values
map.put("conditionMet", ((ByteTag) tag.get("conditionMet")).getValue());
map.put("auto", ((ByteTag) tag.get("auto")).getValue());
map.put("CustomName", MessageUtils.getBedrockMessage(((StringTag) tag.get("CustomName")).getValue()));
map.put("powered", ((ByteTag) tag.get("powered")).getValue());
map.put("Command", ((StringTag) tag.get("Command")).getValue());
map.put("SuccessCount", ((IntTag) tag.get("SuccessCount")).getValue());
map.put("TrackOutput", ((ByteTag) tag.get("TrackOutput")).getValue());
map.put("UpdateLastExecution", ((ByteTag) tag.get("UpdateLastExecution")).getValue());
if (tag.get("LastExecution") != null) {
map.put("LastExecution", ((LongTag) tag.get("LastExecution")).getValue());
} else {
map.put("LastExecution", (long) 0);
}
return map;
}
@Override
public boolean isBlock(int blockState) {
return BlockStateValues.getCommandBlockValues().containsKey(blockState);
}
}

View File

@ -27,7 +27,6 @@ package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtMapBuilder;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
@ -92,13 +91,4 @@ public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator impl
return tags;
}
@Override
public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) {
return null;
}
@Override
public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) {
return null;
}
}

View File

@ -26,7 +26,6 @@
package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.nbt.NbtMap;
import java.util.HashMap;
import java.util.Map;
@ -39,13 +38,4 @@ public class EmptyBlockEntityTranslator extends BlockEntityTranslator {
return new HashMap<>();
}
@Override
public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) {
return getConstantJavaTag(javaId, x, y, z);
}
@Override
public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) {
return getConstantBedrockTag(bedrockId, x, y, z);
}
}

View File

@ -26,14 +26,12 @@
package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.LongTag;
import com.github.steveice10.opennbt.tag.builtin.IntTag;
import com.nukkitx.nbt.NbtList;
import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtType;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@ -56,25 +54,11 @@ public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator {
return tags;
}
@Override
public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) {
CompoundTag tag = getConstantJavaTag(javaId, x, y, z);
tag.put(new LongTag("Age"));
return tag;
}
@Override
public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) {
return getConstantBedrockTag(bedrockId, x, y, z).toBuilder()
.putList("ExitPortal", NbtType.INT, Arrays.asList(0, 0, 0))
.build();
}
private int getExitPortalCoordinate(CompoundTag tag, String axis) {
// Return 0 if it doesn't exist, otherwise give proper value
if (tag.get("ExitPortal") != null) {
LinkedHashMap<?, ?> compoundTag = (LinkedHashMap<?, ?>) tag.get("ExitPortal").getValue();
com.github.steveice10.opennbt.tag.builtin.IntTag intTag = (com.github.steveice10.opennbt.tag.builtin.IntTag) compoundTag.get(axis);
IntTag intTag = (IntTag) compoundTag.get(axis);
return intTag.getValue();
} return 0;
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import java.util.HashMap;
import java.util.Map;
@BlockEntity(name = "JigsawBlock", regex = "jigsaw")
public class JigsawBlockBlockEntityTranslator extends BlockEntityTranslator {
@Override
public Map<String, Object> translateTag(CompoundTag tag, int blockState) {
Map<String, Object> map = new HashMap<>();
map.put("joint", ((StringTag) tag.get("joint")).getValue());
map.put("name", ((StringTag) tag.get("name")).getValue());
map.put("target_pool", ((StringTag) tag.get("pool")).getValue());
map.put("final_state", ((StringTag) tag.get("final_state")).getValue());
map.put("target", ((StringTag) tag.get("target")).getValue());
return map;
}
}

View File

@ -26,7 +26,6 @@
package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.nbt.NbtMap;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
import java.util.HashMap;
@ -46,15 +45,4 @@ public class ShulkerBoxBlockEntityTranslator extends BlockEntityTranslator {
return tags;
}
@Override
public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) {
return null;
}
@Override
public NbtMap getDefaultBedrockTag(String bedrockId, int x, int y, int z) {
return getConstantBedrockTag(bedrockId, x, y, z).toBuilder()
.putByte("facing", (byte) 1)
.build();
}
}

Some files were not shown because too many files have changed in this diff Show More