Merge branch 'master' of https://github.com/GeyserMC/Geyser into feature/addons

This commit is contained in:
DoctorMacc 2020-10-20 22:21:20 -04:00
commit 269e72c943
No known key found for this signature in database
GPG key ID: 6D6E7E059F186DB4
194 changed files with 5867 additions and 1975 deletions

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,12 +25,11 @@
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;
import com.nukkitx.protocol.bedrock.BedrockPacketCodec;
import com.nukkitx.protocol.bedrock.BedrockServer;
import com.nukkitx.protocol.bedrock.v407.Bedrock_v407;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.connector.bootstrap.GeyserBootstrap;
@ -50,22 +49,27 @@ 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;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator;
import org.geysermc.connector.utils.DimensionUtils;
import org.geysermc.connector.utils.DockerCheck;
import org.geysermc.connector.utils.LanguageUtils;
import org.geysermc.connector.utils.LocaleUtils;
import org.geysermc.connector.utils.ResourcePack;
import javax.naming.directory.Attribute;
import javax.naming.directory.InitialDirContext;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@ -74,9 +78,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 BedrockPacketCodec BEDROCK_PACKET_CODEC = Bedrock_v407.V407_CODEC;
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
@ -135,12 +137,23 @@ public class GeyserConnector {
ItemTranslator.init();
LocaleUtils.init();
PotionMixRegistry.init();
RecipeRegistry.init();
SoundRegistry.init();
SoundHandlerRegistry.init();
AddonListenerRegistry.init();
if (platformType != PlatformType.STANDALONE) {
DockerCheck.check(bootstrap);
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 {
config.getRemote().setAddress(InetAddress.getLocalHost().getHostAddress());
} catch (UnknownHostException ex) {
logger.debug("Unknown host when trying to find localhost.");
if (config.isDebugMode()) {
ex.printStackTrace();
}
}
}
String remoteAddress = config.getRemote().getAddress();
int remotePort = config.getRemote().getPort();
@ -158,7 +171,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
@ -181,7 +194,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();
@ -190,8 +203,39 @@ public class GeyserConnector {
metrics = new Metrics(this, "GeyserMC", config.getMetrics().getUniqueId(), false, java.util.logging.Logger.getLogger(""));
metrics.addCustomChart(new Metrics.SingleLineChart("servers", () -> 1));
metrics.addCustomChart(new Metrics.SingleLineChart("players", players::size));
metrics.addCustomChart(new Metrics.SimplePie("authMode", authType.name()::toLowerCase));
// Prevent unwanted words best we can
metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> AuthType.getByName(config.getRemote().getAuthType()).toString().toLowerCase()));
metrics.addCustomChart(new Metrics.SimplePie("platform", platformType::getPlatformName));
metrics.addCustomChart(new Metrics.SimplePie("defaultLocale", LanguageUtils::getDefaultLocale));
metrics.addCustomChart(new Metrics.SimplePie("version", () -> GeyserConnector.VERSION));
metrics.addCustomChart(new Metrics.AdvancedPie("playerPlatform", () -> {
Map<String, Integer> valueMap = new HashMap<>();
for (GeyserSession session : players) {
if (session == null) continue;
if (session.getClientData() == null) continue;
String os = session.getClientData().getDeviceOS().toString();
if (!valueMap.containsKey(os)) {
valueMap.put(os, 1);
} else {
valueMap.put(os, valueMap.get(os) + 1);
}
}
return valueMap;
}));
metrics.addCustomChart(new Metrics.AdvancedPie("playerVersion", () -> {
Map<String, Integer> valueMap = new HashMap<>();
for (GeyserSession session : players) {
if (session == null) continue;
if (session.getClientData() == null) continue;
String version = session.getClientData().getGameVersion();
if (!valueMap.containsKey(version)) {
valueMap.put(version, 1);
} else {
valueMap.put(version, valueMap.get(version) + 1);
}
}
return valueMap;
}));
}
boolean isGui = false;
@ -213,6 +257,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() {
@ -298,6 +346,19 @@ public class GeyserConnector {
return bootstrap.getWorldManager();
}
/**
* Whether to use XML reflections in the jar or manually find the reflections.
* Will return true if the version number is not 'DEV' and the platform is not Fabric.
* On Fabric - it complains about being unable to create a default XMLReader.
* On other platforms this should only be true in compiled jars.
*
* @return whether to use XML reflections
*/
public boolean useXmlReflections() {
//noinspection ConstantConditions
return !this.getPlatformType().equals(PlatformType.FABRIC) && !"DEV".equals(GeyserConnector.VERSION);
}
public static GeyserConnector getInstance() {
return instance;
}

View file

@ -36,6 +36,9 @@ public interface GeyserLogger {
/**
* Logs a severe message and an exception to console
*
* @param message the message to log
* @param error the error to throw
*/
void severe(String message, Throwable error);
@ -48,6 +51,9 @@ public interface GeyserLogger {
/**
* Logs an error message and an exception to console
*
* @param message the message to log
* @param error the error to throw
*/
void error(String message, Throwable error);
@ -78,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

@ -30,14 +30,14 @@ import org.geysermc.connector.ping.IGeyserPingPassthrough;
import org.geysermc.connector.configuration.GeyserConfiguration;
import org.geysermc.connector.GeyserLogger;
import org.geysermc.connector.command.CommandManager;
import org.geysermc.connector.network.translators.world.CachedChunkManager;
import org.geysermc.connector.network.translators.world.GeyserWorldManager;
import org.geysermc.connector.network.translators.world.WorldManager;
import java.nio.file.Path;
public interface GeyserBootstrap {
CachedChunkManager DEFAULT_CHUNK_MANAGER = new CachedChunkManager();
GeyserWorldManager DEFAULT_CHUNK_MANAGER = new GeyserWorldManager();
/**
* Called when the GeyserBootstrap is enabled

View file

@ -33,10 +33,12 @@ import org.geysermc.connector.common.ChatColor;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.command.CommandSender;
import org.geysermc.connector.command.GeyserCommand;
import org.geysermc.connector.common.serializer.AsteriskSerializer;
import org.geysermc.connector.dump.DumpInfo;
import org.geysermc.connector.utils.LanguageUtils;
import org.geysermc.connector.utils.WebUtils;
import java.io.FileOutputStream;
import java.io.IOException;
public class DumpCommand extends GeyserCommand {
@ -49,43 +51,80 @@ public class DumpCommand extends GeyserCommand {
super(name, description, permission);
this.connector = connector;
final SimpleFilterProvider filter = new SimpleFilterProvider();
filter.addFilter("dump_user_auth", SimpleBeanPropertyFilter.serializeAllExcept(new String[] {"password"}));
MAPPER.setFilterProvider(filter);
}
@Override
public void execute(CommandSender sender, String[] args) {
boolean showSensitive = false;
boolean offlineDump = false;
if (args.length >= 1) {
for (String arg : args) {
switch (arg) {
case "full":
showSensitive = true;
break;
case "offline":
offlineDump = true;
break;
}
}
}
AsteriskSerializer.showSensitive = showSensitive;
sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.collecting"));
String dumpData = "";
try {
dumpData = MAPPER.writeValueAsString(new DumpInfo());
if (offlineDump) {
dumpData = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(new DumpInfo());
} else {
dumpData = MAPPER.writeValueAsString(new DumpInfo());
}
} catch (IOException e) {
sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.collect_error"));
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.collect_error_short"), e);
return;
}
sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.uploading"));
String response;
JsonNode responseNode;
try {
response = WebUtils.post(DUMP_URL + "documents", dumpData);
responseNode = MAPPER.readTree(response);
} catch (IOException e) {
sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error"));
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error_short"), e);
return;
String uploadedDumpUrl = "";
if (offlineDump) {
sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.writing"));
try {
FileOutputStream outputStream = new FileOutputStream(GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("dump.json").toFile());
outputStream.write(dumpData.getBytes());
outputStream.close();
} catch (IOException e) {
sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.write_error"));
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.write_error_short"), e);
return;
}
uploadedDumpUrl = "dump.json";
} else {
sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.uploading"));
String response;
JsonNode responseNode;
try {
response = WebUtils.post(DUMP_URL + "documents", dumpData);
responseNode = MAPPER.readTree(response);
} catch (IOException e) {
sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error"));
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error_short"), e);
return;
}
if (!responseNode.has("key")) {
sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error_short") + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response));
return;
}
uploadedDumpUrl = DUMP_URL + responseNode.get("key").asText();
}
if (!responseNode.has("key")) {
sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error_short") + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response));
return;
}
String uploadedDumpUrl = DUMP_URL + responseNode.get("key").asText();
sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.message") + " " + ChatColor.DARK_AQUA + uploadedDumpUrl);
if (!sender.isConsole()) {
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.commands.dump.created", sender.getName(), uploadedDumpUrl));

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,10 +26,12 @@
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;
import org.geysermc.connector.common.ChatColor;
import org.geysermc.connector.network.BedrockProtocol;
import org.geysermc.connector.utils.FileUtils;
import org.geysermc.connector.utils.LanguageUtils;
import org.geysermc.connector.utils.WebUtils;
@ -37,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 {
@ -50,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, GeyserConnector.BEDROCK_PACKET_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,11 +32,13 @@ import lombok.Getter;
@AllArgsConstructor
public enum PlatformType {
ANDROID("Android"),
BUNGEECORD("BungeeCord"),
FABRIC("Fabric"),
SPIGOT("Spigot"),
SPONGE("Sponge"),
STANDALONE("Standalone"),
VELOCITY("Velocity");
private String platformName;
private final String platformName;
}

View file

@ -42,34 +42,46 @@ import java.lang.annotation.Target;
import java.util.Optional;
public class AsteriskSerializer extends StdSerializer<Object> implements ContextualSerializer {
public static boolean showSensitive = false;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = AsteriskSerializer.class)
public @interface Asterisk {
String value() default "***";
boolean sensitive() default false;
}
String asterisk;
boolean sensitive;
public AsteriskSerializer() {
super(Object.class);
}
public AsteriskSerializer(String asterisk) {
public AsteriskSerializer(String asterisk, boolean sensitive) {
super(Object.class);
this.asterisk = asterisk;
this.sensitive = sensitive;
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty property) {
Optional<Asterisk> anno = Optional.ofNullable(property)
.map(prop -> prop.getAnnotation(Asterisk.class));
return new AsteriskSerializer(anno.map(Asterisk::value).orElse(null));
return new AsteriskSerializer(anno.map(Asterisk::value).orElse(null), anno.map(Asterisk::sensitive).orElse(null));
}
@Override
public void serialize(Object obj, JsonGenerator gen, SerializerProvider prov) throws IOException {
if (sensitive && showSensitive) {
gen.writeObject(obj);
return;
}
gen.writeString(asterisk);
}
}

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;
@ -36,7 +35,7 @@ import java.util.Map;
public interface GeyserConfiguration {
// Modify this when you update the config
int CURRENT_CONFIG_VERSION = 3;
int CURRENT_CONFIG_VERSION = 4;
IBedrockConfiguration getBedrock();
@ -49,6 +48,9 @@ public interface GeyserConfiguration {
@JsonIgnore
boolean isPassthroughMotd();
@JsonIgnore
boolean isPassthroughProtocolName();
@JsonIgnore
boolean isPassthroughPlayerCounts();
@ -71,12 +73,14 @@ public interface GeyserConfiguration {
String getDefaultLocale();
Path getFloodgateKeyFile();
Path getFloodgateKeyPath();
boolean isAboveBedrockNetherBuilding();
boolean isCacheChunks();
boolean isForceResourcePacks();
int getCacheImages();
IMetricsInfo getMetrics();
@ -92,6 +96,8 @@ public interface GeyserConfiguration {
String getMotd1();
String getMotd2();
String getServerName();
}
interface IRemoteConfiguration {
@ -120,6 +126,11 @@ public interface GeyserConfiguration {
String getUniqueId();
}
int getScoreboardPacketThreshold();
// if u have offline mode enabled pls be safe
boolean isEnableProxyConnections();
int getMtu();
int getConfigVersion();

View file

@ -29,98 +29,116 @@ 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;
import java.util.Map;
import java.util.UUID;
@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
public abstract class GeyserJacksonConfiguration implements GeyserConfiguration {
private BedrockConfiguration bedrock;
private RemoteConfiguration remote;
/**
* If the config was originally 'auto' before the values changed
*/
@Setter
private boolean autoconfiguredRemote = false;
private BedrockConfiguration bedrock = new BedrockConfiguration();
private RemoteConfiguration remote = new RemoteConfiguration();
@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 = 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;
private MetricsInfo metrics;
@JsonProperty("force-resource-packs")
private boolean forceResourcePacks = true;
private MetricsInfo metrics = new MetricsInfo();
@Getter
public static class BedrockConfiguration implements IBedrockConfiguration {
private String address;
@AsteriskSerializer.Asterisk(sensitive = true)
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 = "auto";
@Setter
private String address;
private int port = 25565;
@Setter
private int port;
@JsonProperty("auth-type")
private String authType;
private String authType = "online";
}
@Getter
@ -134,16 +152,21 @@ 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 = UUID.randomUUID().toString();
}
@JsonProperty("scoreboard-packet-threshold")
private int scoreboardPacketThreshold = 10;
@JsonProperty("enable-proxy-connections")
private boolean enableProxyConnections = false;
@JsonProperty("mtu")
private int mtu = 1400;
@JsonProperty("config-version")
private int configVersion;
private int configVersion = 0;
}

View file

@ -31,7 +31,9 @@ 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;
import org.geysermc.connector.utils.DockerCheck;
import org.geysermc.connector.utils.FileUtils;
@ -111,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();
@ -136,8 +143,8 @@ public class DumpInfo {
private final int javaProtocol;
MCInfo() {
this.bedrockVersion = GeyserConnector.BEDROCK_PACKET_CODEC.getMinecraftVersion();
this.bedrockProtocol = GeyserConnector.BEDROCK_PACKET_CODEC.getProtocolVersion();
this.bedrockVersion = BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion();
this.bedrockProtocol = BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion();
this.javaVersion = MinecraftConstants.GAME_VERSION;
this.javaProtocol = MinecraftConstants.PROTOCOL_VERSION;
}

View file

@ -43,6 +43,8 @@ public class AreaEffectCloudEntity extends Entity {
// This disabled client side shrink of the cloud
metadata.put(EntityData.AREA_EFFECT_CLOUD_RADIUS, 0.0f);
metadata.put(EntityData.AREA_EFFECT_CLOUD_CHANGE_RATE, -0.005f);
metadata.put(EntityData.AREA_EFFECT_CLOUD_CHANGE_ON_PICKUP, -0.5f);
}
@Override
@ -50,11 +52,14 @@ public class AreaEffectCloudEntity extends Entity {
if (entityMetadata.getId() == 7) {
metadata.put(EntityData.AREA_EFFECT_CLOUD_RADIUS, entityMetadata.getValue());
metadata.put(EntityData.BOUNDING_BOX_WIDTH, 2.0f * (float) entityMetadata.getValue());
} else if (entityMetadata.getId() == 8) {
metadata.put(EntityData.EFFECT_COLOR, entityMetadata.getValue());
} else if (entityMetadata.getId() == 10) {
Particle particle = (Particle) entityMetadata.getValue();
metadata.put(EntityData.AREA_EFFECT_CLOUD_PARTICLE_ID, EffectRegistry.getParticleString(particle.getType()));
} else if (entityMetadata.getId() == 8) {
metadata.put(EntityData.POTION_AUX_VALUE, entityMetadata.getValue());
int particleId = EffectRegistry.getParticleId(particle.getType());
if (particleId != -1) {
metadata.put(EntityData.AREA_EFFECT_CLOUD_PARTICLE_ID, particleId);
}
}
super.updateBedrockMetadata(entityMetadata, session);
}

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

@ -32,8 +32,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace;
import com.github.steveice10.mc.protocol.data.message.TextMessage;
import com.github.steveice10.mc.protocol.data.message.TranslationMessage;
import com.github.steveice10.mc.protocol.data.message.Message;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerUseItemPacket;
import com.nukkitx.math.vector.Vector3f;
@ -318,13 +317,10 @@ public class Entity {
}
break;
case 2: // custom name
if (entityMetadata.getValue() instanceof TextMessage) {
TextMessage name = (TextMessage) entityMetadata.getValue();
if (name != null)
metadata.put(EntityData.NAMETAG, MessageUtils.getBedrockMessage(name));
} else if (entityMetadata.getValue() instanceof TranslationMessage) {
TranslationMessage message = (TranslationMessage) entityMetadata.getValue();
if (entityMetadata.getValue() instanceof Message) {
Message message = (Message) entityMetadata.getValue();
if (message != null)
// Always translate even if it's a TextMessage since there could be translatable parameters
metadata.put(EntityData.NAMETAG, MessageUtils.getTranslatedBedrockMessage(message, session.getClientData().getLanguageCode(), true));
}
break;

View file

@ -40,6 +40,7 @@ import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.utils.FireworkColor;
import org.geysermc.connector.utils.MathUtils;
import org.geysermc.floodgate.util.DeviceOS;
import java.util.ArrayList;
import java.util.List;
@ -55,13 +56,26 @@ public class FireworkEntity extends Entity {
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 7) {
ItemStack item = (ItemStack) entityMetadata.getValue();
if (item == null) {
return;
}
CompoundTag tag = item.getNbt();
if (tag == null) {
return;
}
// TODO: Remove once Mojang fixes bugs with fireworks crashing clients on these specific devices.
// https://bugs.mojang.com/browse/MCPE-89115
if (session.getClientData().getDeviceOS() == DeviceOS.XBOX_ONE || session.getClientData().getDeviceOS() == DeviceOS.ORBIS) {
return;
}
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,6 +27,7 @@ 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;
@ -35,11 +36,15 @@ 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;
@ -62,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);
@ -75,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;
@ -95,7 +100,7 @@ public class PlayerEntity extends LivingEntity {
addPlayerPacket.setMotion(motion);
addPlayerPacket.setHand(hand);
addPlayerPacket.getAdventureSettings().setCommandPermission(CommandPermission.NORMAL);
addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.VISITOR);
addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.MEMBER);
addPlayerPacket.setDeviceId("");
addPlayerPacket.setPlatformChatId("");
addPlayerPacket.getMetadata().putAll(metadata);
@ -113,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) {
@ -186,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
@ -199,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
@ -211,20 +228,25 @@ 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.getScoreboardCache().getScoreboard().getTeams().values()) {
// session.getConnector().getLogger().info("team name " + team.getName());
// session.getConnector().getLogger().info("team entities " + team.getEntities());
}
String username = this.username;
TextMessage name = (TextMessage) entityMetadata.getValue();
if (name != null) {
username = MessageUtils.getBedrockMessage(name);
}
Team team = session.getScoreboardCache().getScoreboard().getTeamFor(username);
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());
}
}
}
@ -240,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

@ -25,12 +25,39 @@
package org.geysermc.connector.entity;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
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.item.TippedArrowPotion;
/**
* Internally this is known as TippedArrowEntity but is used with tipped arrows and normal arrows
*/
public class TippedArrowEntity extends AbstractArrowEntity {
public TippedArrowEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
}
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
// Arrow potion effect color
if (entityMetadata.getId() == 9) {
int potionColor = (int) entityMetadata.getValue();
// -1 means no color
if (potionColor == -1) {
metadata.remove(EntityData.CUSTOM_DISPLAY);
} else {
TippedArrowPotion potion = TippedArrowPotion.getByJavaColor(potionColor);
if (potion != null && potion.getJavaColor() != -1) {
metadata.put(EntityData.CUSTOM_DISPLAY, (byte) potion.getBedrockId());
} else {
metadata.remove(EntityData.CUSTOM_DISPLAY);
}
}
}
super.updateBedrockMetadata(entityMetadata, session);
}
}

View file

@ -34,6 +34,8 @@ import org.geysermc.connector.network.session.GeyserSession;
public class WolfEntity extends TameableEntity {
private byte collarColor;
public WolfEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
}
@ -57,12 +59,13 @@ public class WolfEntity extends TameableEntity {
// Wolf collar color
// Relies on EntityData.OWNER_EID being set in TameableEntity.java
if (entityMetadata.getId() == 19 && !metadata.getFlags().getFlag(EntityFlag.ANGRY)) {
metadata.put(EntityData.COLOR, (byte) (int) entityMetadata.getValue());
metadata.put(EntityData.COLOR, collarColor = (byte) (int) entityMetadata.getValue());
}
// Wolf anger (1.16+)
if (entityMetadata.getId() == 20) {
metadata.getFlags().setFlag(EntityFlag.ANGRY, (int) entityMetadata.getValue() != 0);
metadata.put(EntityData.COLOR, (int) entityMetadata.getValue() != 0 ? (byte) 0 : collarColor);
}
super.updateBedrockMetadata(entityMetadata, session);

View file

@ -0,0 +1,11 @@
package org.geysermc.connector.entity.living.monster;
import com.nukkitx.math.vector.Vector3f;
import org.geysermc.connector.entity.type.EntityType;
public class BasePiglinEntity extends MonsterEntity {
public BasePiglinEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
}
}

View file

@ -33,7 +33,7 @@ import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.item.ItemRegistry;
public class PiglinEntity extends MonsterEntity {
public class PiglinEntity extends BasePiglinEntity {
public PiglinEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);

View file

@ -53,14 +53,21 @@ public class ShulkerEntity extends GolemEntity {
metadata.put(EntityData.SHULKER_ATTACH_POS, Vector3i.from(position.getX(), position.getY(), position.getZ()));
}
}
//TODO Outdated metadata flag SHULKER_PEAK_HEIGHT
// if (entityMetadata.getId() == 17) {
// int height = (byte) entityMetadata.getValue();
// metadata.put(EntityData.SHULKER_PEAK_HEIGHT, height);
// }
if (entityMetadata.getId() == 17) {
int height = (byte) entityMetadata.getValue();
metadata.put(EntityData.SHULKER_PEEK_ID, height);
}
if (entityMetadata.getId() == 18) {
int color = Math.abs((byte) entityMetadata.getValue() - 15);
metadata.put(EntityData.VARIANT, color);
byte color = (byte) entityMetadata.getValue();
if (color == 16) {
// 16 is default on both editions
metadata.put(EntityData.VARIANT, 16);
} else {
// Every other shulker color is offset 15 in bedrock edition
metadata.put(EntityData.VARIANT, Math.abs(color - 15));
}
}
super.updateBedrockMetadata(entityMetadata, session);
}

View file

@ -23,31 +23,27 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.world.chunk;
package org.geysermc.connector.entity.living.monster.raid;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
@Getter
@Setter
@AllArgsConstructor
@EqualsAndHashCode
public class ChunkPosition {
public class PillagerEntity extends AbstractIllagerEntity {
private int x;
private int z;
public Position getBlock(int x, int y, int z) {
return new Position((this.x << 4) + x, y, (this.z << 4) + z);
public PillagerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
}
public Position getChunkBlock(int x, int y, int z) {
int chunkX = x & 15;
int chunkY = y & 15;
int chunkZ = z & 15;
return new Position(chunkX, chunkY, chunkZ);
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 16) {
// Java Edition always has the Pillager entity as positioning the crossbow
metadata.getFlags().setFlag(EntityFlag.USING_ITEM, true);
metadata.getFlags().setFlag(EntityFlag.CHARGED, true);
}
super.updateBedrockMetadata(entityMetadata, session);
}
}

View file

@ -34,6 +34,7 @@ import org.geysermc.connector.entity.living.animal.tameable.*;
import org.geysermc.connector.entity.living.merchant.*;
import org.geysermc.connector.entity.living.monster.*;
import org.geysermc.connector.entity.living.monster.raid.AbstractIllagerEntity;
import org.geysermc.connector.entity.living.monster.raid.PillagerEntity;
import org.geysermc.connector.entity.living.monster.raid.RaidParticipantEntity;
import org.geysermc.connector.entity.living.monster.raid.SpellcasterIllagerEntity;
@ -90,7 +91,7 @@ public enum EntityType {
ENDERMITE(MonsterEntity.class, 55, 0.3f, 0.4f),
AGENT(Entity.class, 56, 0f),
VINDICATOR(AbstractIllagerEntity.class, 57, 1.8f, 0.6f, 0.6f, 1.62f),
PILLAGER(AbstractIllagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f),
PILLAGER(PillagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f),
WANDERING_TRADER(AbstractMerchantEntity.class, 118, 1.8f, 0.6f, 0.6f, 1.62f),
PHANTOM(FlyingEntity.class, 58, 0.5f, 0.9f, 0.9f, 0.6f),
RAVAGER(RaidParticipantEntity.class, 59, 1.9f, 1.2f),
@ -135,7 +136,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"),
@ -155,6 +156,7 @@ public enum EntityType {
HOGLIN(AnimalEntity.class, 124, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:hoglin"),
ZOGLIN(ZoglinEntity.class, 126, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:zoglin"),
PIGLIN(PiglinEntity.class, 123, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin"),
PIGLIN_BRUTE(BasePiglinEntity.class, 127, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin_brute"),
/**
* Item frames are handled differently since they are a block in Bedrock.

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

@ -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;
import com.nukkitx.protocol.bedrock.BedrockPacketCodec;
import com.nukkitx.protocol.bedrock.v407.Bedrock_v407;
import com.nukkitx.protocol.bedrock.v408.Bedrock_v408;
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. 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 List<BedrockPacketCodec> SUPPORTED_BEDROCK_CODECS = new ArrayList<>();
static {
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v407.V407_CODEC);
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC);
}
/**
* Gets the {@link BedrockPacketCodec} of the given protocol version.
* @param protocolVersion The protocol version to attempt to find
* @return The packet codec, or null if the client's protocol is unsupported
*/
public static BedrockPacketCodec getBedrockCodec(int protocolVersion) {
for (BedrockPacketCodec packetCodec : SUPPORTED_BEDROCK_CODECS) {
if (packetCodec.getProtocolVersion() == protocolVersion) {
return packetCodec;
}
}
return null;
}
}

View file

@ -71,7 +71,7 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
pong.setEdition("MCPE");
pong.setGameType("Default");
pong.setNintendoLimited(false);
pong.setProtocolVersion(GeyserConnector.BEDROCK_PACKET_CODEC.getProtocolVersion());
pong.setProtocolVersion(BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion());
pong.setVersion(null); // Server tries to connect either way and it looks better
pong.setIpv4Port(config.getBedrock().getPort());
@ -108,7 +108,8 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
public void onSessionCreation(BedrockServerSession bedrockServerSession) {
bedrockServerSession.setLogging(true);
bedrockServerSession.setPacketHandler(new UpstreamPacketHandler(connector, new GeyserSession(connector, bedrockServerSession)));
bedrockServerSession.setPacketCodec(GeyserConnector.BEDROCK_PACKET_CODEC);
// Set the packet codec to default just in case we need to send disconnect packets.
bedrockServerSession.setPacketCodec(BedrockProtocol.DEFAULT_BEDROCK_CODEC);
}
@Override

View file

@ -141,6 +141,7 @@ public class QueryPacketHandler {
String motd;
String currentPlayerCount;
String maxPlayerCount;
String map;
if (connector.getConfig().isPassthroughMotd() || connector.getConfig().isPassthroughPlayerCounts()) {
pingInfo = connector.getBootstrap().getGeyserPingPassthrough().getPingInformation();
@ -162,14 +163,21 @@ public class QueryPacketHandler {
maxPlayerCount = String.valueOf(connector.getConfig().getMaxPlayers());
}
// If passthrough protocol name is enabled let's get the protocol name from the ping response.
if (connector.getConfig().isPassthroughProtocolName() && pingInfo != null) {
map = String.valueOf((pingInfo.getVersion().getName()));
} else {
map = GeyserConnector.NAME;
}
// Create a hashmap of all game data needed in the query
Map<String, String> gameData = new HashMap<String, String>();
gameData.put("hostname", motd);
gameData.put("gametype", "SMP");
gameData.put("game_id", "MINECRAFT");
gameData.put("version", GeyserConnector.BEDROCK_PACKET_CODEC.getMinecraftVersion());
gameData.put("version", BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion());
gameData.put("plugins", "");
gameData.put("map", GeyserConnector.NAME);
gameData.put("map", map);
gameData.put("numplayers", currentPlayerCount);
gameData.put("maxplayers", maxPlayerCount);
gameData.put("hostport", String.valueOf(connector.getConfig().getBedrock().getPort()));

View file

@ -26,15 +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.addon.FormAddonListener;
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 {
@ -48,15 +57,20 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
@Override
public boolean handle(LoginPacket loginPacket) {
if (loginPacket.getProtocolVersion() > GeyserConnector.BEDROCK_PACKET_CODEC.getProtocolVersion()) {
// Too early to determine session locale
session.disconnect(LanguageUtils.getLocaleStringLog("geyser.network.outdated.server", GeyserConnector.BEDROCK_PACKET_CODEC.getMinecraftVersion()));
return true;
} else if (loginPacket.getProtocolVersion() < GeyserConnector.BEDROCK_PACKET_CODEC.getProtocolVersion()) {
session.disconnect(LanguageUtils.getLocaleStringLog("geyser.network.outdated.client", GeyserConnector.BEDROCK_PACKET_CODEC.getMinecraftVersion()));
return true;
BedrockPacketCodec packetCodec = BedrockProtocol.getBedrockCodec(loginPacket.getProtocolVersion());
if (packetCodec == null) {
if (loginPacket.getProtocolVersion() > BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) {
// Too early to determine session locale
session.disconnect(LanguageUtils.getLocaleStringLog("geyser.network.outdated.server", BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()));
return true;
} else if (loginPacket.getProtocolVersion() < BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) {
session.disconnect(LanguageUtils.getLocaleStringLog("geyser.network.outdated.client", BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()));
return true;
}
}
session.getUpstream().getSession().setPacketCodec(packetCodec);
LoginEncryptionUtils.encryptPlayerConnection(connector, session, loginPacket);
PlayStatusPacket playStatus = new PlayStatusPacket();
@ -64,6 +78,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;
}
@ -75,13 +94,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.setExperimentsPreviouslyToggled(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;
@ -95,6 +143,9 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
if (packet.getFormId() == LoginEncryptionUtils.AUTH_FORM_ID || packet.getFormId() == LoginEncryptionUtils.AUTH_DETAILS_FORM_ID) {
return LoginEncryptionUtils.authenticateFromForm(session, connector, packet.getFormId(), packet.getFormData());
}
if (packet.getFormId() == SettingsUtils.SETTINGS_FORM_ID) {
return SettingsUtils.handleSettingsForm(session, packet.getFormData());
}
FormAddonListener.get().handleResponse(this.session, packet);
return true;
}
@ -143,4 +194,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

@ -28,6 +28,7 @@ package org.geysermc.connector.network.session;
import com.github.steveice10.mc.auth.data.GameProfile;
import com.github.steveice10.mc.auth.exception.request.InvalidCredentialsException;
import com.github.steveice10.mc.auth.exception.request.RequestException;
import com.github.steveice10.mc.protocol.MinecraftConstants;
import com.github.steveice10.mc.protocol.MinecraftProtocol;
import com.github.steveice10.mc.protocol.data.SubProtocol;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
@ -46,6 +47,7 @@ import com.nukkitx.math.vector.*;
import com.nukkitx.protocol.bedrock.BedrockPacket;
import com.nukkitx.protocol.bedrock.BedrockServerSession;
import com.nukkitx.protocol.bedrock.data.*;
import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
import com.nukkitx.protocol.bedrock.packet.*;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
@ -54,6 +56,7 @@ import it.unimi.dsi.fastutil.objects.Object2LongMap;
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.common.window.CustomFormWindow;
import org.geysermc.common.window.FormWindow;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.command.CommandSender;
@ -68,6 +71,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.*;
@ -79,9 +83,8 @@ import java.net.InetSocketAddress;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicInteger;
@Getter
@ -102,7 +105,7 @@ public class GeyserSession implements CommandSender {
private ChunkCache chunkCache;
private EntityCache entityCache;
private InventoryCache inventoryCache;
private ScoreboardCache scoreboardCache;
private WorldCache worldCache;
private WindowCache windowCache;
@Setter
private TeleportCache teleportCache;
@ -116,8 +119,6 @@ public class GeyserSession implements CommandSender {
*/
private final Object2LongMap<Vector3i> itemFrameCache = new Object2LongOpenHashMap<>();
private DataCache<Packet> javaPacketCache;
@Setter
private Vector2i lastChunkPosition = null;
private int renderDistance;
@ -155,11 +156,14 @@ public class GeyserSession implements CommandSender {
@Setter
private boolean interacting;
/**
* Stores the last position of the block the player interacted with. This can either be a block that the client
* placed or an existing block the player interacted with (for example, a chest). <br>
* Initialized as (0, 0, 0) so it is always not-null.
*/
@Setter
private Vector3i lastInteractionPosition;
private Vector3i lastInteractionPosition = Vector3i.ZERO;
@Setter
private boolean switchingDimension = false;
private boolean manyDimPackets = false;
private ServerRespawnPacket lastDimPacket = null;
@ -172,16 +176,28 @@ public class GeyserSession implements CommandSender {
@Setter
private long lastWindowCloseTime = 0;
/**
* Saves the timestamp of the last keep alive packet
*/
@Setter
private long lastKeepAliveTimestamp = 0;
@Setter
private VillagerTrade[] villagerTrades;
@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.
* Setting a default fixes cooldowns not showing up on a fresh world.
*/
@Setter
private double attackSpeed;
private double attackSpeed = 4.0d;
/**
* The time of the last hit. Used to gauge how long the cooldown is taking.
* This is a session variable in order to prevent more scheduled threads than necessary.
@ -189,6 +205,76 @@ public class GeyserSession implements CommandSender {
@Setter
private long lastHitTime;
/**
* Store the last time the player interacted. Used to fix a right-click spam bug.
* See https://github.com/GeyserMC/Geyser/issues/503 for context.
*/
@Setter
private long lastInteractionTime;
/**
* Stores a future interaction to place a bucket. Will be cancelled if the client instead intended to
* interact with a block.
*/
@Setter
private ScheduledFuture<?> bucketScheduledFuture;
private boolean reducedDebugInfo = false;
@Setter
private CustomFormWindow settingsForm;
/**
* The op permission level set by the server
*/
@Setter
private int opPermissionLevel = 0;
/**
* If the current player can fly
*/
@Setter
private boolean canFly = false;
/**
* If the current player is flying
*/
@Setter
private boolean flying = false;
/**
* If the current player is in noclip
*/
@Setter
private boolean noClip = false;
/**
* If the current player can not interact with the world
*/
@Setter
private boolean worldImmutable = false;
/**
* Caches current rain status.
*/
@Setter
private boolean raining = false;
/**
* Caches current thunder status.
*/
@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) {
@ -198,14 +284,12 @@ public class GeyserSession implements CommandSender {
this.chunkCache = new ChunkCache(this);
this.entityCache = new EntityCache(this);
this.inventoryCache = new InventoryCache(this);
this.scoreboardCache = new ScoreboardCache(this);
this.worldCache = new WorldCache(this);
this.windowCache = new WindowCache(this);
this.playerEntity = new PlayerEntity(new GameProfile(UUID.randomUUID(), "unknown"), 1, 1, Vector3f.ZERO, Vector3f.ZERO, Vector3f.ZERO);
this.inventory = new PlayerInventory();
this.javaPacketCache = new DataCache<>();
this.spawned = false;
this.loggedIn = false;
@ -249,6 +333,15 @@ public class GeyserSession implements CommandSender {
attributes.add(new AttributeData("minecraft:movement", 0.0f, 1024f, 0.1f, 0.1f));
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
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);
}
public void login() {
@ -289,7 +382,7 @@ public class GeyserSession implements CommandSender {
PublicKey key = null;
try {
key = EncryptionUtil.getKeyFromFile(
connector.getConfig().getFloodgateKeyFile(),
connector.getConfig().getFloodgateKeyPath(),
PublicKey.class
);
} catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) {
@ -303,6 +396,8 @@ public class GeyserSession implements CommandSender {
}
downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory());
// Let Geyser handle sending the keep alive
downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false);
downstream.getSession().addListener(new SessionAdapter() {
@Override
public void packetSending(PacketSendingEvent event) {
@ -434,7 +529,7 @@ public class GeyserSession implements CommandSender {
this.chunkCache = null;
this.entityCache = null;
this.scoreboardCache = null;
this.worldCache = null;
this.inventoryCache = null;
this.windowCache = null;
@ -533,8 +628,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);
@ -542,7 +639,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);
}
@ -567,7 +664,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");
@ -580,7 +677,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");
@ -599,4 +696,77 @@ public class GeyserSession implements CommandSender {
connector.getLogger().debug("Tried to send downstream packet " + packet.getClass().getSimpleName() + " before connected to the server");
}
}
/**
* Update the cached value for the reduced debug info gamerule.
* This also toggles the coordinates display
*
* @param value The new value for reducedDebugInfo
*/
public void setReducedDebugInfo(boolean value) {
worldCache.setShowCoordinates(!value);
reducedDebugInfo = value;
}
/**
* Send a gamerule value to the client
*
* @param gameRule The gamerule to send
* @param value The value of the gamerule
*/
public void sendGameRule(String gameRule, Object value) {
GameRulesChangedPacket gameRulesChangedPacket = new GameRulesChangedPacket();
gameRulesChangedPacket.getGameRules().add(new GameRuleData<>(gameRule, value));
upstream.sendPacket(gameRulesChangedPacket);
}
/**
* Checks if the given session's player has a permission
*
* @param permission The permission node to check
* @return true if the player has the requested permission, false if not
*/
public Boolean hasPermission(String permission) {
return connector.getWorldManager().hasPermission(this, permission);
}
/**
* Send an AdventureSettingsPacket to the client with the latest flags
*/
public void sendAdventureSettings() {
AdventureSettingsPacket adventureSettingsPacket = new AdventureSettingsPacket();
adventureSettingsPacket.setUniqueEntityId(playerEntity.getGeyserId());
// 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);
// Update the noClip and worldImmutable values based on the current gamemode
noClip = gameMode == GameMode.SPECTATOR;
worldImmutable = gameMode == GameMode.ADVENTURE || gameMode == GameMode.SPECTATOR;
Set<AdventureSetting> flags = new HashSet<>();
if (canFly) {
flags.add(AdventureSetting.MAY_FLY);
}
if (flying) {
flags.add(AdventureSetting.FLYING);
}
if (worldImmutable) {
flags.add(AdventureSetting.WORLD_IMMUTABLE);
}
if (noClip) {
flags.add(AdventureSetting.NO_CLIP);
}
flags.add(AdventureSetting.AUTO_JUMP);
adventureSettingsPacket.getSettings().addAll(flags);
sendUpstreamPacket(adventureSettingsPacket);
}
}

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

@ -27,22 +27,18 @@ package org.geysermc.connector.network.session.cache;
import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
import com.github.steveice10.mc.protocol.data.game.chunk.Column;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import lombok.Getter;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import org.geysermc.connector.bootstrap.GeyserBootstrap;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.network.translators.world.chunk.ChunkPosition;
import java.util.HashMap;
import java.util.Map;
import org.geysermc.connector.utils.MathUtils;
public class ChunkCache {
private final boolean cache;
@Getter
private Map<ChunkPosition, Column> chunks = new HashMap<>();
private final Long2ObjectMap<Column> chunks = new Long2ObjectOpenHashMap<>();
public ChunkCache(GeyserSession session) {
if (session.getConnector().getWorldManager().getClass() == GeyserBootstrap.DEFAULT_CHUNK_MANAGER.getClass()) {
@ -52,52 +48,74 @@ public class ChunkCache {
}
}
public void addToCache(Column chunk) {
public Column addToCache(Column chunk) {
if (!cache) {
return;
return chunk;
}
ChunkPosition position = new ChunkPosition(chunk.getX(), chunk.getZ());
chunks.put(position, chunk);
}
public void updateBlock(Position position, int block) {
if (!cache) {
return;
}
ChunkPosition chunkPosition = new ChunkPosition(position.getX() >> 4, position.getZ() >> 4);
if (!chunks.containsKey(chunkPosition))
return;
Column column = chunks.get(chunkPosition);
Chunk chunk = column.getChunks()[position.getY() >> 4];
Position blockPosition = chunkPosition.getChunkBlock(position.getX(), position.getY(), position.getZ());
if (chunk != null) {
chunk.set(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), block);
long chunkPosition = MathUtils.chunkPositionToLong(chunk.getX(), chunk.getZ());
Column existingChunk;
if (chunk.getBiomeData() == null // Only consider merging columns if the new chunk isn't a full chunk
&& (existingChunk = chunks.getOrDefault(chunkPosition, null)) != null) { // Column is already present in cache, we can merge with existing
boolean changed = false;
for (int i = 0; i < chunk.getChunks().length; i++) { // The chunks member is final, so chunk.getChunks() will probably be inlined and then completely optimized away
if (chunk.getChunks()[i] != null) {
existingChunk.getChunks()[i] = chunk.getChunks()[i];
changed = true;
}
}
return changed ? existingChunk : null;
} else {
chunks.put(chunkPosition, chunk);
return chunk;
}
}
public int getBlockAt(Position position) {
public Column getChunk(int chunkX, int chunkZ) {
long chunkPosition = MathUtils.chunkPositionToLong(chunkX, chunkZ);
return chunks.getOrDefault(chunkPosition, null);
}
public void updateBlock(int x, int y, int z, int block) {
if (!cache) {
return;
}
Column column = this.getChunk(x >> 4, z >> 4);
if (column == null) {
return;
}
Chunk chunk = column.getChunks()[y >> 4];
if (chunk != null) {
chunk.set(x & 0xF, y & 0xF, z & 0xF, block);
}
}
public int getBlockAt(int x, int y, int z) {
if (!cache) {
return BlockTranslator.AIR;
}
ChunkPosition chunkPosition = new ChunkPosition(position.getX() >> 4, position.getZ() >> 4);
if (!chunks.containsKey(chunkPosition))
return BlockTranslator.AIR;
Column column = chunks.get(chunkPosition);
Chunk chunk = column.getChunks()[position.getY() >> 4];
Position blockPosition = chunkPosition.getChunkBlock(position.getX(), position.getY(), position.getZ());
Column column = this.getChunk(x >> 4, z >> 4);
if (column == null) {
return BlockTranslator.AIR;
}
Chunk chunk = column.getChunks()[y >> 4];
if (chunk != null) {
return chunk.get(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
return chunk.get(x & 0xF, y & 0xF, z & 0xF);
}
return BlockTranslator.AIR;
}
public void removeChunk(ChunkPosition position) {
public void removeChunk(int chunkX, int chunkZ) {
if (!cache) {
return;
}
chunks.remove(position);
long chunkPosition = MathUtils.chunkPositionToLong(chunkX, chunkZ);
chunks.remove(chunkPosition);
}
}

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

@ -25,31 +25,53 @@
package org.geysermc.connector.network.session.cache;
import com.github.steveice10.mc.protocol.data.game.setting.Difficulty;
import lombok.Getter;
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 ScoreboardCache {
private GeyserSession session;
private Scoreboard scoreboard;
public class WorldCache {
private final GeyserSession session;
@Setter
private Difficulty difficulty = Difficulty.EASY;
private boolean showCoordinates = true;
public ScoreboardCache(GeyserSession session) {
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
*
* @param value True to show, false to hide
*/
public void setShowCoordinates(boolean value) {
showCoordinates = value;
session.sendGameRule("showcoordinates", value);
}
}

View file

@ -25,7 +25,6 @@
package org.geysermc.connector.network.translators;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerKeepAlivePacket;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPlayerListDataPacket;
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdateLightPacket;
import com.github.steveice10.packetlib.packet.Packet;
@ -33,6 +32,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 +48,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().useXmlReflections() ? 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();
@ -74,7 +74,6 @@ public class PacketTranslatorRegistry<T> {
}
}
IGNORED_PACKETS.add(ServerKeepAlivePacket.class); // Handled by MCProtocolLib
IGNORED_PACKETS.add(ServerUpdateLightPacket.class); // Light is handled on Bedrock for us
IGNORED_PACKETS.add(ServerPlayerListDataPacket.class); // Cant be implemented in bedrock
}

View file

@ -25,7 +25,6 @@
package org.geysermc.connector.network.translators.bedrock;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerAbilitiesPacket;
import com.nukkitx.protocol.bedrock.data.AdventureSetting;
import com.nukkitx.protocol.bedrock.packet.AdventureSettingsPacket;
@ -38,14 +37,8 @@ public class BedrockAdventureSettingsTranslator extends PacketTranslator<Adventu
@Override
public void translate(AdventureSettingsPacket packet, GeyserSession session) {
// Only canFly and flying are used by the server
// https://wiki.vg/Protocol#Player_Abilities_.28serverbound.29
boolean canFly = packet.getSettings().contains(AdventureSetting.MAY_FLY);
boolean flying = packet.getSettings().contains(AdventureSetting.FLYING);
boolean creative = session.getGameMode() == GameMode.CREATIVE;
ClientPlayerAbilitiesPacket abilitiesPacket = new ClientPlayerAbilitiesPacket(
false, canFly, flying, creative
);
ClientPlayerAbilitiesPacket abilitiesPacket =
new ClientPlayerAbilitiesPacket(packet.getSettings().contains(AdventureSetting.FLYING));
session.sendDownstreamPacket(abilitiesPacket);
}
}

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,13 @@ 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.ContainerId;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket;
import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket;
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;
@ -53,8 +58,11 @@ import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.sound.EntitySoundInteractionHandler;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.utils.BlockUtils;
import org.geysermc.connector.utils.InventoryUtils;
import java.util.concurrent.TimeUnit;
@Translator(packet = InventoryTransactionPacket.class)
public class BedrockInventoryTransactionTranslator extends PacketTranslator<InventoryTransactionPacket> {
@ -75,6 +83,17 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
case ITEM_USE:
switch (packet.getActionType()) {
case 0:
// Check to make sure the client isn't spamming interaction
// Based on Nukkit 1.0, with changes to ensure holding down still works
boolean hasAlreadyClicked = System.currentTimeMillis() - session.getLastInteractionTime() < 110.0 &&
packet.getBlockPosition().distanceSquared(session.getLastInteractionPosition()) < 0.00001;
session.setLastInteractionPosition(packet.getBlockPosition());
if (hasAlreadyClicked) {
break;
} else {
// Only update the interaction time if it's valid - that way holding down still works.
session.setLastInteractionTime(System.currentTimeMillis());
}
// Bedrock sends block interact code for a Java entity so we send entity code back to Java
if (BlockTranslator.isItemFrame(packet.getBlockRuntimeId()) &&
@ -98,39 +117,50 @@ 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())) {
ClientPlayerUseItemPacket itemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND);
session.sendDownstreamPacket(itemPacket);
if (packet.getItemInHand() != null && packet.getItemInHand().getId() == ItemRegistry.BOAT.getBedrockId()) {
ClientPlayerUseItemPacket itemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND);
session.sendDownstreamPacket(itemPacket);
}
// Check actions, otherwise buckets may be activated when block inventories are accessed
else if (packet.getItemInHand() != null && packet.getItemInHand().getId() == ItemRegistry.BUCKET.getBedrockId()) {
// Let the server decide if the bucket item should change, not the client, and revert the changes the client made
InventorySlotPacket slotPacket = new InventorySlotPacket();
slotPacket.setContainerId(ContainerId.INVENTORY);
slotPacket.setSlot(packet.getHotbarSlot());
slotPacket.setItem(packet.getItemInHand());
session.sendUpstreamPacket(slotPacket);
// Delay the interaction in case the client doesn't intend to actually use the bucket
// See BedrockActionTranslator.java
session.setBucketScheduledFuture(session.getConnector().getGeneralThreadPool().schedule(() -> {
ClientPlayerUseItemPacket itemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND);
session.sendDownstreamPacket(itemPacket);
}, 5, TimeUnit.MILLISECONDS));
}
Vector3i blockPos = packet.getBlockPosition();
// TODO: Find a better way to do this?
switch (packet.getBlockFace()) {
case 0:
blockPos = blockPos.sub(0, 1, 0);
break;
case 1:
blockPos = blockPos.add(0, 1, 0);
break;
case 2:
blockPos = blockPos.sub(0, 0, 1);
break;
case 3:
blockPos = blockPos.add(0, 0, 1);
break;
case 4:
blockPos = blockPos.sub(1, 0, 0);
break;
case 5:
blockPos = blockPos.add(1, 0, 0);
break;
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 = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getBlockFace());
ItemEntry handItem = ItemRegistry.getItem(packet.getItemInHand());
if (handItem.isBlock()) {
session.setLastBlockPlacePosition(blockPos);
session.setLastBlockPlacedId(handItem.getJavaIdentifier());
}
session.setLastInteractionPosition(packet.getBlockPosition());
session.setInteracting(true);
break;
case 1:
@ -140,15 +170,14 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
break;
}
// Handled in ITEM_USE
if (packet.getItemInHand() != null && packet.getItemInHand().getId() == ItemRegistry.BUCKET.getBedrockId()) {
// Handled in ITEM_USE if the item is not milk
if (packet.getItemInHand() != null && packet.getItemInHand().getId() == ItemRegistry.BUCKET.getBedrockId() &&
packet.getItemInHand().getDamage() != 1) {
break;
}
ClientPlayerUseItemPacket useItemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND);
session.sendDownstreamPacket(useItemPacket);
// Used for sleeping in beds
session.setLastInteractionPosition(packet.getBlockPosition());
break;
case 2:
int blockState = session.getConnector().getWorldManager().getBlockAt(session, packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ());
@ -195,6 +224,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

@ -40,7 +40,8 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator<MobEquipment
@Override
public void translate(MobEquipmentPacket packet, GeyserSession session) {
if (!session.isSpawned() || packet.getHotbarSlot() > 8 ||
packet.getContainerId() != ContainerId.INVENTORY) {
packet.getContainerId() != ContainerId.INVENTORY || session.getInventory().getHeldItemSlot() == packet.getHotbarSlot()) {
// For the last condition - Don't update the slot if the slot is the same - not Java Edition behavior and messes with plugins such as Grief Prevention
return;
}

View file

@ -0,0 +1,46 @@
/*
* 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.ClientKeepAlivePacket;
import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
/**
* Used to send the keep alive packet back to the server
*/
@Translator(packet = NetworkStackLatencyPacket.class)
public class BedrockNetworkStackLatencyTranslator extends PacketTranslator<NetworkStackLatencyPacket> {
@Override
public void translate(NetworkStackLatencyPacket packet, GeyserSession session) {
// The client sends a timestamp back but it's rounded and therefore unreliable when we need the exact number
ClientKeepAlivePacket keepAlivePacket = new ClientKeepAlivePacket(session.getLastKeepAliveTimestamp());
session.sendDownstreamPacket(keepAlivePacket);
}
}

View file

@ -28,7 +28,10 @@ package org.geysermc.connector.network.translators.bedrock;
import com.github.steveice10.mc.protocol.data.game.ClientRequest;
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientRequestPacket;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket;
import com.nukkitx.protocol.bedrock.packet.RespawnPacket;
import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
@ -39,12 +42,30 @@ public class BedrockRespawnTranslator extends PacketTranslator<RespawnPacket> {
@Override
public void translate(RespawnPacket packet, GeyserSession session) {
if (packet.getState() == RespawnPacket.State.CLIENT_READY) {
if (!session.isSpawned()) { // Otherwise when immediate respawn is on the client never loads
RespawnPacket respawnPacket = new RespawnPacket();
respawnPacket.setRuntimeEntityId(0);
respawnPacket.setPosition(Vector3f.ZERO);
respawnPacket.setState(RespawnPacket.State.SERVER_SEARCHING);
session.sendUpstreamPacket(respawnPacket);
// Previously we only sent the respawn packet before the server finished loading
// The message included was 'Otherwise when immediate respawn is on the client never loads'
// But I assume the new if statement below fixes that problem
RespawnPacket respawnPacket = new RespawnPacket();
respawnPacket.setRuntimeEntityId(0);
respawnPacket.setPosition(Vector3f.ZERO);
respawnPacket.setState(RespawnPacket.State.SERVER_READY);
session.sendUpstreamPacket(respawnPacket);
if (session.isSpawned()) {
// Client might be stuck; resend spawn information
PlayerEntity entity = session.getPlayerEntity();
if (entity == null) return;
SetEntityDataPacket entityDataPacket = new SetEntityDataPacket();
entityDataPacket.setRuntimeEntityId(entity.getGeyserId());
entityDataPacket.getMetadata().putAll(entity.getMetadata());
session.sendUpstreamPacket(entityDataPacket);
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
movePlayerPacket.setRuntimeEntityId(entity.getGeyserId());
movePlayerPacket.setPosition(entity.getPosition());
movePlayerPacket.setRotation(entity.getBedrockRotation());
movePlayerPacket.setMode(MovePlayerPacket.Mode.RESPAWN);
session.sendUpstreamPacket(movePlayerPacket);
}
ClientRequestPacket javaRespawnPacket = new ClientRequestPacket(ClientRequest.RESPAWN);

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.bedrock;
import com.nukkitx.protocol.bedrock.packet.ServerSettingsRequestPacket;
import com.nukkitx.protocol.bedrock.packet.ServerSettingsResponsePacket;
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.SettingsUtils;
@Translator(packet = ServerSettingsRequestPacket.class)
public class BedrockServerSettingsRequestTranslator extends PacketTranslator<ServerSettingsRequestPacket> {
@Override
public void translate(ServerSettingsRequestPacket packet, GeyserSession session) {
SettingsUtils.buildForm(session);
ServerSettingsResponsePacket serverSettingsResponsePacket = new ServerSettingsResponsePacket();
serverSettingsResponsePacket.setFormData(session.getSettingsForm().getJSONData());
serverSettingsResponsePacket.setFormId(SettingsUtils.SETTINGS_FORM_ID);
session.sendUpstreamPacket(serverSettingsResponsePacket);
}
}

View file

@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.bedrock;
package org.geysermc.connector.network.translators.bedrock.entity;
import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;

View file

@ -23,10 +23,9 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.bedrock;
package org.geysermc.connector.network.translators.bedrock.entity.player;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction;
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerState;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace;
@ -45,6 +44,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.world.block.BlockTranslator;
import org.geysermc.connector.utils.BlockUtils;
import java.util.concurrent.TimeUnit;
@ -79,9 +79,7 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
break;
case START_GLIDE:
// Otherwise gliding will not work in creative
ClientPlayerAbilitiesPacket playerAbilitiesPacket = new ClientPlayerAbilitiesPacket(
false, false, false, session.getGameMode() == GameMode.CREATIVE
);
ClientPlayerAbilitiesPacket playerAbilitiesPacket = new ClientPlayerAbilitiesPacket(false);
session.sendDownstreamPacket(playerAbilitiesPacket);
case STOP_GLIDE:
ClientPlayerStatePacket glidePacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.START_ELYTRA_FLYING);
@ -116,9 +114,26 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
session.sendDownstreamPacket(stopSleepingPacket);
break;
case BLOCK_INTERACT:
// Handled in BedrockInventoryTransactionTranslator
// Client means to interact with a block; cancel bucket interaction, if any
if (session.getBucketScheduledFuture() != null) {
session.getBucketScheduledFuture().cancel(true);
session.setBucketScheduledFuture(null);
}
// Otherwise handled in BedrockInventoryTransactionTranslator
break;
case START_BREAK:
if (session.getConnector().getConfig().isCacheChunks()) {
// Account for fire - the client likes to hit the block behind.
Vector3i fireBlockPos = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getFace());
int blockUp = session.getConnector().getWorldManager().getBlockAt(session, fireBlockPos);
String identifier = BlockTranslator.getJavaIdBlockMap().inverse().get(blockUp);
if (identifier.startsWith("minecraft:fire") || identifier.startsWith("minecraft:soul_fire")) {
ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, new Position(fireBlockPos.getX(),
fireBlockPos.getY(), fireBlockPos.getZ()), BlockFace.values()[packet.getFace()]);
session.sendDownstreamPacket(startBreakingPacket);
break;
}
}
ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, new Position(packet.getBlockPosition().getX(),
packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()), BlockFace.values()[packet.getFace()]);
session.sendDownstreamPacket(startBreakingPacket);

View file

@ -23,10 +23,10 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.bedrock;
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

@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.bedrock;
package org.geysermc.connector.network.translators.bedrock.entity.player;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityDataMap;

View file

@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.bedrock;
package org.geysermc.connector.network.translators.bedrock.entity.player;
import com.nukkitx.math.vector.Vector3d;
import org.geysermc.connector.common.ChatColor;
@ -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

@ -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.bedrock.entity.player;
import com.nukkitx.protocol.bedrock.packet.SetPlayerGameTypePacket;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
/**
* In vanilla Bedrock, if you have operator status, this sets the player's gamemode without confirmation from the server.
* Since we have a custom server option to request the gamemode, we just reset the gamemode and ignore this.
*/
@Translator(packet = SetPlayerGameTypePacket.class)
public class BedrockSetPlayerGameTypeTranslator extends PacketTranslator<SetPlayerGameTypePacket> {
@Override
public void translate(SetPlayerGameTypePacket packet, GeyserSession session) {
// no
SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket();
playerGameTypePacket.setGamemode(session.getGameMode().ordinal());
session.sendUpstreamPacket(playerGameTypePacket);
}
}

View file

@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.bedrock;
package org.geysermc.connector.network.translators.bedrock.world;
import com.nukkitx.protocol.bedrock.data.SoundEvent;
import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket;

View file

@ -32,10 +32,11 @@ import com.nukkitx.protocol.bedrock.data.LevelEventType;
import com.nukkitx.protocol.bedrock.data.SoundEvent;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import lombok.NonNull;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.utils.FileUtils;
import org.geysermc.connector.utils.LanguageUtils;
import java.io.InputStream;
import java.util.HashMap;
@ -50,8 +51,19 @@ public class EffectRegistry {
public static final Map<SoundEffect, Effect> SOUND_EFFECTS = new HashMap<>();
public static final Int2ObjectMap<SoundEvent> RECORDS = new Int2ObjectOpenHashMap<>();
private static Map<ParticleType, LevelEventType> particleTypeMap = new HashMap<>();
private static Map<ParticleType, String> particleStringMap = new HashMap<>();
/**
* Java particle type to Bedrock particle ID
* Used for area effect clouds.
*/
private static final Object2IntMap<ParticleType> PARTICLE_TO_ID = new Object2IntOpenHashMap<>();
/**
* Java particle type to Bedrock level event
*/
private static final Map<ParticleType, LevelEventType> PARTICLE_TO_LEVEL_EVENT = new HashMap<>();
/**
* Java particle type to Bedrock namespaced string ID
*/
private static final Map<ParticleType, String> PARTICLE_TO_STRING = new HashMap<>();
public static void init() {
// no-op
@ -68,22 +80,24 @@ public class EffectRegistry {
}
Iterator<Map.Entry<String, JsonNode>> particlesIterator = particleEntries.fields();
while (particlesIterator.hasNext()) {
Map.Entry<String, JsonNode> entry = particlesIterator.next();
try {
particleTypeMap.put(ParticleType.valueOf(entry.getKey().toUpperCase()), LevelEventType.valueOf(entry.getValue().asText().toUpperCase()));
} catch (IllegalArgumentException e1) {
try {
particleStringMap.put(ParticleType.valueOf(entry.getKey().toUpperCase()), entry.getValue().asText());
GeyserConnector.getInstance().getLogger().debug("Force to map particle "
+ entry.getKey()
+ "=>"
+ entry.getValue().asText()
+ ", it will take effect.");
} catch (IllegalArgumentException e2){
GeyserConnector.getInstance().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.particle.failed_map", entry.getKey(), entry.getValue().asText()));
try {
while (particlesIterator.hasNext()) {
Map.Entry<String, JsonNode> entry = particlesIterator.next();
JsonNode bedrockId = entry.getValue().get("bedrockId");
JsonNode bedrockIdNumeric = entry.getValue().get("bedrockNumericId");
JsonNode eventType = entry.getValue().get("eventType");
if (bedrockIdNumeric != null) {
PARTICLE_TO_ID.put(ParticleType.valueOf(entry.getKey().toUpperCase()), bedrockIdNumeric.asInt());
}
if (bedrockId != null) {
PARTICLE_TO_STRING.put(ParticleType.valueOf(entry.getKey().toUpperCase()), bedrockId.asText());
}
if (eventType != null) {
PARTICLE_TO_LEVEL_EVENT.put(ParticleType.valueOf(entry.getKey().toUpperCase()), LevelEventType.valueOf(eventType.asText().toUpperCase()));
}
}
} catch (Exception e) {
e.printStackTrace();
}
/* Load effects */
@ -149,11 +163,27 @@ public class EffectRegistry {
}
}
public static LevelEventType getParticleLevelEventType(@NonNull ParticleType type) {
return particleTypeMap.getOrDefault(type, null);
/**
* @param type the Java particle to search for
* @return the Bedrock integer ID of the particle, or -1 if it does not exist
*/
public static int getParticleId(@NonNull ParticleType type) {
return PARTICLE_TO_ID.getOrDefault(type, -1);
}
public static String getParticleString(@NonNull ParticleType type){
return particleStringMap.getOrDefault(type, null);
/**
* @param type the Java particle to search for
* @return the level event equivalent Bedrock particle
*/
public static LevelEventType getParticleLevelEventType(@NonNull ParticleType type) {
return PARTICLE_TO_LEVEL_EVENT.getOrDefault(type, null);
}
/**
* @param type the Java particle to search for
* @return the namespaced ID equivalent for Bedrock
*/
public static String getParticleString(@NonNull ParticleType type) {
return PARTICLE_TO_STRING.getOrDefault(type, null);
}
}

View file

@ -30,12 +30,9 @@ import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
import com.nukkitx.protocol.bedrock.data.inventory.InventorySource;
import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater;
import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater;
import org.geysermc.connector.utils.InventoryUtils;
import java.util.List;
@ -48,7 +45,7 @@ public class CraftingInventoryTranslator extends BlockInventoryTranslator {
@Override
public int bedrockSlotToJava(InventoryActionData action) {
if (action.getSlot() == 50) {
GeyserConnector.getInstance().getLogger().warning("Slot 50 found, please report: " + action);
// Slot 50 is used for crafting with a controller.
return 0;
}

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

@ -35,9 +35,8 @@ import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket;
import lombok.AllArgsConstructor;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
import org.geysermc.connector.utils.LocaleUtils;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
@AllArgsConstructor
public class BlockInventoryHolder extends InventoryHolder {
@ -60,7 +59,7 @@ public class BlockInventoryHolder extends InventoryHolder {
.putInt("x", position.getX())
.putInt("y", position.getY())
.putInt("z", position.getZ())
.putString("CustomName", LocaleUtils.getLocaleString(inventory.getTitle(), session.getClientData().getLanguageCode())).build();
.putString("CustomName", inventory.getTitle()).build();
BlockEntityDataPacket dataPacket = new BlockEntityDataPacket();
dataPacket.setData(tag);
dataPacket.setBlockPosition(position);

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]);
}
@ -210,14 +197,9 @@ public class ItemRegistry {
*/
public static ItemEntry getItem(ItemData data) {
for (ItemEntry itemEntry : ITEM_ENTRIES.values()) {
if (itemEntry.getBedrockId() == data.getId() && (itemEntry.getBedrockData() == data.getDamage() || itemEntry.getJavaIdentifier().endsWith("potion"))) {
return itemEntry;
}
}
// If item find was unsuccessful first time, we try again while ignoring damage
// Fixes piston, sticky pistons, dispensers and droppers turning into air from creative inventory
for (ItemEntry itemEntry : ITEM_ENTRIES.values()) {
if (itemEntry.getBedrockId() == data.getId()) {
if (itemEntry.getBedrockId() == data.getId() && (itemEntry.getBedrockData() == data.getDamage() ||
// Make exceptions for potions and tipped arrows, whose damage values can vary
(itemEntry.getJavaIdentifier().endsWith("potion") || itemEntry.getJavaIdentifier().equals("minecraft:arrow")))) {
return itemEntry;
}
}
@ -240,4 +222,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().useXmlReflections() ? 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

@ -48,7 +48,7 @@ public enum Potion {
STRONG_SWIFTNESS(16),
LONG_SWIFTNESS(15),
SLOWNESS(17),
STRONG_SLOWNESS(18), //does not exist
STRONG_SLOWNESS(42),
LONG_SLOWNESS(18),
WATER_BREATHING(19),
LONG_WATER_BREATHING(20),

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

@ -0,0 +1,151 @@
/*
* 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 lombok.Getter;
import java.util.Locale;
/**
* Potion identifiers and their respective Bedrock IDs used with arrows.
* https://minecraft.gamepedia.com/Arrow#Item_Data
*/
@Getter
public enum TippedArrowPotion {
MUNDANE(2, ArrowParticleColors.NONE), // 3 is extended?
THICK(4, ArrowParticleColors.NONE),
AWKWARD(5, ArrowParticleColors.NONE),
NIGHT_VISION(6, ArrowParticleColors.NIGHT_VISION),
LONG_NIGHT_VISION(7, ArrowParticleColors.NIGHT_VISION),
INVISIBILITY(8, ArrowParticleColors.INVISIBILITY),
LONG_INVISIBILITY(9, ArrowParticleColors.INVISIBILITY),
LEAPING(10, ArrowParticleColors.LEAPING),
LONG_LEAPING(11, ArrowParticleColors.LEAPING),
STRONG_LEAPING(12, ArrowParticleColors.LEAPING),
FIRE_RESISTANCE(13, ArrowParticleColors.FIRE_RESISTANCE),
LONG_FIRE_RESISTANCE(14, ArrowParticleColors.FIRE_RESISTANCE),
SWIFTNESS(15, ArrowParticleColors.SWIFTNESS),
LONG_SWIFTNESS(16, ArrowParticleColors.SWIFTNESS),
STRONG_SWIFTNESS(17, ArrowParticleColors.SWIFTNESS),
SLOWNESS(18, ArrowParticleColors.SLOWNESS),
LONG_SLOWNESS(19, ArrowParticleColors.SLOWNESS),
STRONG_SLOWNESS(43, ArrowParticleColors.SLOWNESS),
WATER_BREATHING(20, ArrowParticleColors.WATER_BREATHING),
LONG_WATER_BREATHING(21, ArrowParticleColors.WATER_BREATHING),
HEALING(22, ArrowParticleColors.HEALING),
STRONG_HEALING(23, ArrowParticleColors.HEALING),
HARMING(24, ArrowParticleColors.HARMING),
STRONG_HARMING(25, ArrowParticleColors.HARMING),
POISON(26, ArrowParticleColors.POISON),
LONG_POISON(27, ArrowParticleColors.POISON),
STRONG_POISON(28, ArrowParticleColors.POISON),
REGENERATION(29, ArrowParticleColors.REGENERATION),
LONG_REGENERATION(30, ArrowParticleColors.REGENERATION),
STRONG_REGENERATION(31, ArrowParticleColors.REGENERATION),
STRENGTH(32, ArrowParticleColors.STRENGTH),
LONG_STRENGTH(33, ArrowParticleColors.STRENGTH),
STRONG_STRENGTH(34, ArrowParticleColors.STRENGTH),
WEAKNESS(35, ArrowParticleColors.WEAKNESS),
LONG_WEAKNESS(36, ArrowParticleColors.WEAKNESS),
LUCK(2, ArrowParticleColors.NONE), // does not exist in Bedrock
TURTLE_MASTER(38, ArrowParticleColors.TURTLE_MASTER),
LONG_TURTLE_MASTER(39, ArrowParticleColors.TURTLE_MASTER),
STRONG_TURTLE_MASTER(40, ArrowParticleColors.TURTLE_MASTER),
SLOW_FALLING(41, ArrowParticleColors.SLOW_FALLING),
LONG_SLOW_FALLING(42, ArrowParticleColors.SLOW_FALLING);
private final String javaIdentifier;
private final short bedrockId;
/**
* The Java color associated with this ID.
* Used for looking up Java arrow color entity metadata as Bedrock potion IDs, which is what is used for entities in Bedrock
*/
private final int javaColor;
TippedArrowPotion(int bedrockId, ArrowParticleColors arrowParticleColor) {
this.javaIdentifier = "minecraft:" + this.name().toLowerCase(Locale.ENGLISH);
this.bedrockId = (short) bedrockId;
this.javaColor = arrowParticleColor.getColor();
}
public static TippedArrowPotion getByJavaIdentifier(String javaIdentifier) {
for (TippedArrowPotion potion : TippedArrowPotion.values()) {
if (potion.javaIdentifier.equals(javaIdentifier)) {
return potion;
}
}
return null;
}
public static TippedArrowPotion getByBedrockId(short bedrockId) {
for (TippedArrowPotion potion : TippedArrowPotion.values()) {
if (potion.bedrockId == bedrockId) {
return potion;
}
}
return null;
}
/**
* @param color the potion color to look up
* @return the tipped arrow potion that most closely resembles that color.
*/
public static TippedArrowPotion getByJavaColor(int color) {
for (TippedArrowPotion potion : TippedArrowPotion.values()) {
if (potion.javaColor == color) {
return potion;
}
}
return null;
}
private enum ArrowParticleColors {
NONE(-1),
NIGHT_VISION(2039713),
INVISIBILITY(8356754),
LEAPING(2293580),
FIRE_RESISTANCE(14981690),
SWIFTNESS(8171462),
SLOWNESS(5926017),
TURTLE_MASTER(7691106),
WATER_BREATHING(3035801),
HEALING(16262179),
HARMING(4393481),
POISON(5149489),
REGENERATION(13458603),
STRENGTH(9643043),
WEAKNESS(4738376),
LUCK(3381504),
SLOW_FALLING(16773073);
@Getter
private final int color;
ArrowParticleColors(int color) {
this.color = color;
}
}
}

View file

@ -50,7 +50,7 @@ import java.util.stream.Collectors;
@ItemRemapper
public class BannerTranslator extends ItemTranslator {
private List<ItemEntry> appliedItems;
private final List<ItemEntry> appliedItems;
public BannerTranslator() {
appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry -> entry.getJavaIdentifier().endsWith("banner")).collect(Collectors.toList());

View file

@ -40,7 +40,7 @@ import java.util.stream.Collectors;
@ItemRemapper
public class CompassTranslator extends ItemTranslator {
private List<ItemEntry> appliedItems;
private final List<ItemEntry> appliedItems;
public CompassTranslator() {
appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry -> entry.getJavaIdentifier().endsWith("compass")).collect(Collectors.toList());

View file

@ -42,7 +42,7 @@ import java.util.stream.Collectors;
@ItemRemapper
public class PotionTranslator extends ItemTranslator {
private List<ItemEntry> appliedItems;
private final List<ItemEntry> appliedItems;
public PotionTranslator() {
appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry -> entry.getJavaIdentifier().endsWith("potion")).collect(Collectors.toList());
@ -57,7 +57,7 @@ public class PotionTranslator extends ItemTranslator {
if (potion != null) {
return ItemData.of(itemEntry.getBedrockId(), potion.getBedrockId(), itemStack.getAmount(), translateNbtToBedrock(itemStack.getNbt()));
}
GeyserConnector.getInstance().getLogger().debug("Unknown java potion: " + potionTag.getValue());
GeyserConnector.getInstance().getLogger().debug("Unknown Java potion: " + potionTag.getValue());
}
return super.translateToBedrock(itemStack, itemEntry);
}

View file

@ -0,0 +1,87 @@
/*
* 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;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import org.geysermc.connector.GeyserConnector;
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.TippedArrowPotion;
import java.util.List;
import java.util.stream.Collectors;
@ItemRemapper
public class TippedArrowTranslator extends ItemTranslator {
private final List<ItemEntry> appliedItems;
private static final int TIPPED_ARROW_JAVA_ID = ItemRegistry.getItemEntry("minecraft:tipped_arrow").getJavaId();
public TippedArrowTranslator() {
appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry ->
entry.getJavaIdentifier().contains("arrow") && !entry.getJavaIdentifier().contains("spectral")).collect(Collectors.toList());
}
@Override
public ItemData translateToBedrock(ItemStack itemStack, ItemEntry itemEntry) {
if (!itemEntry.getJavaIdentifier().equals("minecraft:tipped_arrow") || itemStack.getNbt() == null) {
// We're only concerned about minecraft:arrow when translating Bedrock -> Java
return super.translateToBedrock(itemStack, itemEntry);
}
Tag potionTag = itemStack.getNbt().get("Potion");
if (potionTag instanceof StringTag) {
TippedArrowPotion tippedArrowPotion = TippedArrowPotion.getByJavaIdentifier(((StringTag) potionTag).getValue());
if (tippedArrowPotion != null) {
return ItemData.of(itemEntry.getBedrockId(), tippedArrowPotion.getBedrockId(), itemStack.getAmount(), translateNbtToBedrock(itemStack.getNbt()));
}
GeyserConnector.getInstance().getLogger().debug("Unknown Java potion (tipped arrow): " + potionTag.getValue());
}
return super.translateToBedrock(itemStack, itemEntry);
}
@Override
public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) {
TippedArrowPotion tippedArrowPotion = TippedArrowPotion.getByBedrockId(itemData.getDamage());
ItemStack itemStack = super.translateToJava(itemData, itemEntry);
if (tippedArrowPotion != null) {
itemStack = new ItemStack(TIPPED_ARROW_JAVA_ID, itemStack.getAmount(), itemStack.getNbt());
StringTag potionTag = new StringTag("Potion", tippedArrowPotion.getJavaIdentifier());
itemStack.getNbt().put(potionTag);
}
return itemStack;
}
@Override
public List<ItemEntry> getAppliedItems() {
return appliedItems;
}
}

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;
}
@ -100,7 +101,7 @@ public class BasicItemTranslator extends NbtItemStackTranslator {
if (message.startsWith("§r")) {
message = message.replaceFirst("§r", "");
}
Component component = TextComponent.of(message);
Component component = Component.text(message);
return GsonComponentSerializer.gson().serialize(component);
}

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

@ -25,30 +25,37 @@
package org.geysermc.connector.network.translators.item.translators.nbt;
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 com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.opennbt.tag.builtin.*;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
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;
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 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()) {
CompoundTag projectile = (CompoundTag) chargedProjectiles.getValue().get(0);
CompoundTag newProjectile = new CompoundTag("chargedItem");
newProjectile.put(new ByteTag("Count", (byte) projectile.get("Count").getValue()));
newProjectile.put(new StringTag("Name", (String) projectile.get("id").getValue()));
ItemEntry entry = ItemRegistry.getItemEntry((String) projectile.get("id").getValue());
if (entry == null) return;
CompoundTag tag = projectile.get("tag");
ItemStack itemStack = new ItemStack(itemEntry.getJavaId(), (byte) projectile.get("Count").getValue(), tag);
ItemData itemData = ItemTranslator.translateToBedrock(session, itemStack);
// Not sure what this is for
newProjectile.put(new ByteTag("Damage", (byte) 0));
CompoundTag newProjectile = new CompoundTag("chargedItem");
newProjectile.put(new ByteTag("Count", (byte) itemData.getCount()));
newProjectile.put(new StringTag("Name", ItemRegistry.getBedrockIdentifer(entry)));
newProjectile.put(new ShortTag("Damage", itemData.getDamage()));
itemTag.put(newProjectile);
}

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

@ -40,5 +40,7 @@ public class JavaDifficultyTranslator extends PacketTranslator<ServerDifficultyP
SetDifficultyPacket setDifficultyPacket = new SetDifficultyPacket();
setDifficultyPacket.setDifficulty(packet.getDifficulty().ordinal());
session.sendUpstreamPacket(setDifficultyPacket);
session.getWorldCache().setDifficulty(packet.getDifficulty());
}
}

View file

@ -23,15 +23,19 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.world;
package org.geysermc.connector.network.translators.java;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
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;
public class CachedChunkManager extends WorldManager {
@Translator(packet = ServerDisconnectPacket.class)
public class JavaDisconnectPacket extends PacketTranslator<ServerDisconnectPacket> {
@Override
public int getBlockAt(GeyserSession session, int x, int y, int z) {
return session.getChunkCache().getBlockAt(new Position(x, y, z));
public void translate(ServerDisconnectPacket packet, GeyserSession session) {
session.disconnect(MessageUtils.getTranslatedBedrockMessage(packet.getReason(), session.getClientData().getLanguageCode(), true));
}
}

View file

@ -28,6 +28,7 @@ package org.geysermc.connector.network.translators.java;
import com.github.steveice10.mc.protocol.data.game.entity.player.HandPreference;
import com.github.steveice10.mc.protocol.data.game.setting.ChatVisibility;
import com.github.steveice10.mc.protocol.data.game.setting.SkinPart;
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientPluginMessagePacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientSettingsPacket;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerJoinGamePacket;
import com.nukkitx.protocol.bedrock.data.GameRuleData;
@ -38,6 +39,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.utils.DimensionUtils;
import org.geysermc.connector.utils.PluginMessageUtils;
import java.util.Arrays;
import java.util.List;
@ -51,12 +53,13 @@ public class JavaJoinGameTranslator extends PacketTranslator<ServerJoinGamePacke
entity.setEntityId(packet.getEntityId());
// If the player is already initialized and a join game packet is sent, they
// are swapping servers
String newDimension = DimensionUtils.getNewDimension(packet.getDimension());
if (session.isSpawned()) {
String fakeDim = entity.getDimension().equals(DimensionUtils.OVERWORLD) ? DimensionUtils.NETHER : DimensionUtils.OVERWORLD;
DimensionUtils.switchDimension(session, fakeDim);
DimensionUtils.switchDimension(session, packet.getDimension());
DimensionUtils.switchDimension(session, newDimension);
session.getScoreboardCache().removeScoreboard();
session.getWorldCache().removeScoreboard();
}
AdventureSettingsPacket bedrockPacket = new AdventureSettingsPacket();
@ -91,8 +94,10 @@ public class JavaJoinGameTranslator extends PacketTranslator<ServerJoinGamePacke
ClientSettingsPacket clientSettingsPacket = new ClientSettingsPacket(locale, (byte) session.getRenderDistance(), ChatVisibility.FULL, true, skinParts, HandPreference.RIGHT_HAND);
session.sendDownstreamPacket(clientSettingsPacket);
if (!packet.getDimension().equals(entity.getDimension())) {
DimensionUtils.switchDimension(session, packet.getDimension());
session.sendDownstreamPacket(new ClientPluginMessagePacket("minecraft:brand", PluginMessageUtils.getGeyserBrandData()));
if (!newDimension.equals(entity.getDimension())) {
DimensionUtils.switchDimension(session, newDimension);
}
}
}

View file

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

View file

@ -25,17 +25,15 @@
package org.geysermc.connector.network.translators.java;
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientPluginMessagePacket;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPluginMessagePacket;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.addon.AddonListener;
import org.geysermc.connector.network.addon.AddonListenerRegistry;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientPluginMessagePacket;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPluginMessagePacket;
import org.geysermc.connector.utils.NetworkUtils;
import java.nio.charset.StandardCharsets;
@ -44,24 +42,8 @@ import java.util.Map;
@Translator(packet = ServerPluginMessagePacket.class)
public class JavaPluginMessageTranslator extends PacketTranslator<ServerPluginMessagePacket> {
private static byte[] brandData;
static {
byte[] data = GeyserConnector.NAME.getBytes(StandardCharsets.UTF_8);
byte[] varInt = writeVarInt(data.length);
brandData = new byte[varInt.length + data.length];
System.arraycopy(varInt, 0, brandData, 0, varInt.length);
System.arraycopy(data, 0, brandData, varInt.length, data.length);
}
@Override
public void translate(ServerPluginMessagePacket packet, GeyserSession session) {
if (packet.getChannel().equals("minecraft:brand")) {
session.sendDownstreamPacket(
new ClientPluginMessagePacket(packet.getChannel(), brandData)
);
}
if (packet.getChannel().equals("minecraft:register")) {
session.sendDownstreamPacket(new ClientPluginMessagePacket(packet.getChannel(), AddonListener.PLUGIN_MESSAGE_CHANNEL.getBytes(StandardCharsets.UTF_8)));
}
@ -80,32 +62,4 @@ public class JavaPluginMessageTranslator extends PacketTranslator<ServerPluginMe
entry.getValue().onMessageReceive(session, buffer);
}
}
private static byte[] writeVarInt(int value) {
byte[] data = new byte[getVarIntLength(value)];
int index = 0;
do {
byte temp = (byte)(value & 0b01111111);
value >>>= 7;
if (value != 0) {
temp |= 0b10000000;
}
data[index] = temp;
index++;
} while (value != 0);
return data;
}
private static int getVarIntLength(int number) {
if ((number & 0xFFFFFF80) == 0) {
return 1;
} else if ((number & 0xFFFFC000) == 0) {
return 2;
} else if ((number & 0xFFE00000) == 0) {
return 3;
} else if ((number & 0xF0000000) == 0) {
return 4;
}
return 5;
}
}

View file

@ -37,8 +37,6 @@ import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.utils.DimensionUtils;
import java.util.concurrent.ThreadLocalRandom;
@Translator(packet = ServerRespawnPacket.class)
public class JavaRespawnTranslator extends PacketTranslator<ServerRespawnPacket> {
@ -59,19 +57,23 @@ public class JavaRespawnTranslator extends PacketTranslator<ServerRespawnPacket>
session.sendUpstreamPacket(playerGameTypePacket);
session.setGameMode(packet.getGamemode());
LevelEventPacket stopRainPacket = new LevelEventPacket();
stopRainPacket.setType(LevelEventType.STOP_RAINING);
stopRainPacket.setData(ThreadLocalRandom.current().nextInt(50000) + 10000);
stopRainPacket.setPosition(Vector3f.ZERO);
session.sendUpstreamPacket(stopRainPacket);
if (session.isRaining()) {
LevelEventPacket stopRainPacket = new LevelEventPacket();
stopRainPacket.setType(LevelEventType.STOP_RAINING);
stopRainPacket.setData(0);
stopRainPacket.setPosition(Vector3f.ZERO);
session.sendUpstreamPacket(stopRainPacket);
session.setRaining(false);
}
if (!entity.getDimension().equals(packet.getDimension())) {
DimensionUtils.switchDimension(session, packet.getDimension());
String newDimension = DimensionUtils.getNewDimension(packet.getDimension());
if (!entity.getDimension().equals(newDimension)) {
DimensionUtils.switchDimension(session, newDimension);
} else {
if (session.isManyDimPackets()) { //reloading world
String fakeDim = entity.getDimension().equals(DimensionUtils.OVERWORLD) ? DimensionUtils.NETHER : DimensionUtils.OVERWORLD;
DimensionUtils.switchDimension(session, fakeDim);
DimensionUtils.switchDimension(session, packet.getDimension());
DimensionUtils.switchDimension(session, newDimension);
} else {
// Handled in JavaPlayerPositionRotationTranslator
session.setSpawned(false);

View file

@ -39,15 +39,16 @@ public class JavaTitleTranslator extends PacketTranslator<ServerTitlePacket> {
@Override
public void translate(ServerTitlePacket packet, GeyserSession session) {
SetTitlePacket titlePacket = new SetTitlePacket();
String locale = session.getClientData().getLanguageCode();
switch (packet.getAction()) {
case TITLE:
titlePacket.setType(SetTitlePacket.Type.TITLE);
titlePacket.setText(MessageUtils.getBedrockMessage(packet.getTitle()));
titlePacket.setText(MessageUtils.getTranslatedBedrockMessage(packet.getTitle(), locale));
break;
case SUBTITLE:
titlePacket.setType(SetTitlePacket.Type.SUBTITLE);
titlePacket.setText(MessageUtils.getBedrockMessage(packet.getTitle()));
titlePacket.setText(MessageUtils.getTranslatedBedrockMessage(packet.getTitle(), locale));
break;
case CLEAR:
case RESET:
@ -56,7 +57,7 @@ public class JavaTitleTranslator extends PacketTranslator<ServerTitlePacket> {
break;
case ACTION_BAR:
titlePacket.setType(SetTitlePacket.Type.ACTIONBAR);
titlePacket.setText(MessageUtils.getBedrockMessage(packet.getTitle()));
titlePacket.setText(MessageUtils.getTranslatedBedrockMessage(packet.getTitle(), locale));
break;
case TIMES:
titlePacket.setFadeInTime(packet.getFadeIn());

View file

@ -23,33 +23,52 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.java.world;
package org.geysermc.connector.network.translators.java.entity;
import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityCollectItemPacket;
import com.nukkitx.protocol.bedrock.data.LevelEventType;
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
import com.nukkitx.protocol.bedrock.packet.TakeItemEntityPacket;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.ExpOrbEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
/**
* This packet is called whenever a player picks up an item.
* In Java, this is called for item entities, experience orbs and arrows
* Bedrock uses it for arrows and item entities, but not experience orbs.
*/
@Translator(packet = ServerEntityCollectItemPacket.class)
public class JavaCollectItemTranslator extends PacketTranslator<ServerEntityCollectItemPacket> {
public class JavaEntityCollectItemTranslator extends PacketTranslator<ServerEntityCollectItemPacket> {
@Override
public void translate(ServerEntityCollectItemPacket packet, GeyserSession session) {
// This is the definition of translating - both packets take the same values
TakeItemEntityPacket takeItemEntityPacket = new TakeItemEntityPacket();
// Collected entity is the item
// Collected entity is the other entity
Entity collectedEntity = session.getEntityCache().getEntityByJavaId(packet.getCollectedEntityId());
// Collector is the entity picking up the item
if (collectedEntity == null) return;
// Collector is the entity 'picking up' the item
Entity collectorEntity;
if (packet.getCollectorEntityId() == session.getPlayerEntity().getEntityId()) {
collectorEntity = session.getPlayerEntity();
} else {
collectorEntity = session.getEntityCache().getEntityByJavaId(packet.getCollectorEntityId());
}
takeItemEntityPacket.setRuntimeEntityId(collectorEntity.getGeyserId());
takeItemEntityPacket.setItemRuntimeEntityId(collectedEntity.getGeyserId());
session.sendUpstreamPacket(takeItemEntityPacket);
if (collectorEntity == null) return;
if (collectedEntity instanceof ExpOrbEntity) {
// Player just picked up an experience orb
LevelEventPacket xpPacket = new LevelEventPacket();
xpPacket.setType(LevelEventType.SOUND_EXPERIENCE_ORB_PICKUP);
xpPacket.setPosition(collectedEntity.getPosition());
xpPacket.setData(0);
session.sendUpstreamPacket(xpPacket);
} else {
// Item is being picked up (visual only)
TakeItemEntityPacket takeItemEntityPacket = new TakeItemEntityPacket();
takeItemEntityPacket.setRuntimeEntityId(collectorEntity.getGeyserId());
takeItemEntityPacket.setItemRuntimeEntityId(collectedEntity.getGeyserId());
session.sendUpstreamPacket(takeItemEntityPacket);
}
}
}

View file

@ -82,7 +82,6 @@ public class JavaEntitySetPassengersTranslator extends PacketTranslator<ServerEn
}
passenger.updateBedrockMetadata(session);
this.updateOffset(passenger, entity.getEntityType(), session, rider, true, (passengers.size() > 1));
rider = false;
}
@ -90,6 +89,9 @@ public class JavaEntitySetPassengersTranslator extends PacketTranslator<ServerEn
for (long passengerId : entity.getPassengers()) {
Entity passenger = session.getEntityCache().getEntityByJavaId(passengerId);
if (passengerId == session.getPlayerEntity().getEntityId()) {
passenger = session.getPlayerEntity();
}
if (passenger == null) {
continue;
}
@ -99,66 +101,160 @@ public class JavaEntitySetPassengersTranslator extends PacketTranslator<ServerEn
session.sendUpstreamPacket(linkPacket);
passengers.remove(passenger.getEntityId());
this.updateOffset(passenger, entity.getEntityType(), session, false, false, (passengers.size() > 1));
this.updateOffset(passenger, entity, session, false, false, (packet.getPassengerIds().length > 1));
} else {
this.updateOffset(passenger, entity, session, (packet.getPassengerIds()[0] == passengerId), true, (packet.getPassengerIds().length > 1));
}
// Force an update to the passenger metadata
passenger.updateBedrockMetadata(session);
}
if (entity.getEntityType() == EntityType.HORSE) {
entity.getMetadata().put(EntityData.RIDER_SEAT_POSITION, Vector3f.from(0.0f, 2.3200102f, -0.2f));
entity.getMetadata().put(EntityData.RIDER_MAX_ROTATION, 181.0f);
entity.updateBedrockMetadata(session);
switch (entity.getEntityType()) {
case HORSE:
case SKELETON_HORSE:
case DONKEY:
case MULE:
case RAVAGER:
entity.getMetadata().put(EntityData.RIDER_MAX_ROTATION, 181.0f);
entity.updateBedrockMetadata(session);
break;
}
}
private void updateOffset(Entity passenger, EntityType mountType, GeyserSession session, boolean rider, boolean riding, boolean moreThanOneEntity) {
// Without the Y offset, Bedrock players will find themselves in the floor when mounting
float yOffset = 0;
private float getMountedHeightOffset(Entity mount) {
final EntityType mountType = mount.getEntityType();
float mountedHeightOffset = mountType.getHeight() * 0.75f;
switch (mountType) {
case BOAT:
yOffset = passenger.getEntityType() == EntityType.PLAYER ? 1.02001f : -0.2f;
break;
case MINECART:
yOffset = passenger.getEntityType() == EntityType.PLAYER ? 1.02001f : 0f;
case CHICKEN:
case SPIDER:
mountedHeightOffset = mountType.getHeight() * 0.5f;
break;
case DONKEY:
yOffset = 2.1f;
break;
case HORSE:
case SKELETON_HORSE:
case ZOMBIE_HORSE:
case MULE:
yOffset = 2.3f;
mountedHeightOffset -= 0.25f;
break;
case LLAMA:
case TRADER_LLAMA:
yOffset = 2.5f;
mountedHeightOffset = mountType.getHeight() * 0.67f;
break;
case PIG:
yOffset = 1.85001f;
case MINECART:
case MINECART_HOPPER:
case MINECART_TNT:
case MINECART_CHEST:
case MINECART_FURNACE:
case MINECART_SPAWNER:
case MINECART_COMMAND_BLOCK:
mountedHeightOffset = 0;
break;
case ARMOR_STAND:
yOffset = 1.3f;
case BOAT:
mountedHeightOffset = -0.1f;
break;
case HOGLIN:
case ZOGLIN:
boolean isBaby = mount.getMetadata().getFlags().getFlag(EntityFlag.BABY);
mountedHeightOffset = mountType.getHeight() - (isBaby ? 0.2f : 0.15f);
break;
case PIGLIN:
mountedHeightOffset = mountType.getHeight() * 0.92f;
break;
case RAVAGER:
mountedHeightOffset = 2.1f;
break;
case SKELETON_HORSE:
mountedHeightOffset -= 0.1875f;
break;
case STRIDER:
yOffset = passenger.getEntityType() == EntityType.PLAYER ? 2.8200102f : 1.6f;
mountedHeightOffset = mountType.getHeight() - 0.19f;
break;
}
Vector3f offset = Vector3f.from(0f, yOffset, 0f);
if (mountType == EntityType.STRIDER) {
offset = offset.add(0f, 0f, -0.2f);
}
// Without the X offset, more than one entity on a boat is stacked on top of each other
if (rider && moreThanOneEntity) {
offset = offset.add(Vector3f.from(0.2, 0, 0));
} else if (moreThanOneEntity) {
offset = offset.add(Vector3f.from(-0.6, 0, 0));
return mountedHeightOffset;
}
private float getHeightOffset(Entity passenger) {
boolean isBaby;
switch (passenger.getEntityType()) {
case SKELETON:
case STRAY:
case WITHER_SKELETON:
return -0.6f;
case ARMOR_STAND:
// Armor stand isn't a marker
if (passenger.getMetadata().getFloat(EntityData.BOUNDING_BOX_HEIGHT) != 0.0f) {
return 0.1f;
} else {
return 0.0f;
}
case ENDERMITE:
case SILVERFISH:
return 0.1f;
case PIGLIN:
case PIGLIN_BRUTE:
case ZOMBIFIED_PIGLIN:
isBaby = passenger.getMetadata().getFlags().getFlag(EntityFlag.BABY);
return isBaby ? -0.05f : -0.45f;
case ZOMBIE:
isBaby = passenger.getMetadata().getFlags().getFlag(EntityFlag.BABY);
return isBaby ? 0.0f : -0.45f;
case EVOKER:
case ILLUSIONER:
case PILLAGER:
case RAVAGER:
case VINDICATOR:
case WITCH:
return -0.45f;
case PLAYER:
return -0.35f;
}
return 0f;
}
private void updateOffset(Entity passenger, Entity mount, GeyserSession session, boolean rider, boolean riding, boolean moreThanOneEntity) {
passenger.getMetadata().getFlags().setFlag(EntityFlag.RIDING, riding);
if (riding) {
// Without the Y offset, Bedrock players will find themselves in the floor when mounting
float mountedHeightOffset = getMountedHeightOffset(mount);
float heightOffset = getHeightOffset(passenger);
float xOffset = 0;
float yOffset = mountedHeightOffset + heightOffset;
float zOffset = 0;
switch (mount.getEntityType()) {
case BOAT:
// Without the X offset, more than one entity on a boat is stacked on top of each other
if (rider && moreThanOneEntity) {
xOffset = 0.2f;
} else if (moreThanOneEntity) {
xOffset = -0.6f;
}
break;
case CHICKEN:
zOffset = -0.1f;
break;
case LLAMA:
zOffset = -0.3f;
break;
}
/*
* Bedrock Differences
* Zoglin & Hoglin seem to be taller in Bedrock edition
* Horses are tinier
* Players, Minecarts, and Boats have different origins
*/
if (passenger.getEntityType() == EntityType.PLAYER) {
yOffset += EntityType.PLAYER.getOffset();
}
switch (mount.getEntityType()) {
case MINECART:
case MINECART_HOPPER:
case MINECART_TNT:
case MINECART_CHEST:
case MINECART_FURNACE:
case MINECART_SPAWNER:
case MINECART_COMMAND_BLOCK:
case BOAT:
yOffset -= mount.getEntityType().getHeight() * 0.5f;
}
Vector3f offset = Vector3f.from(xOffset, yOffset, zOffset);
passenger.getMetadata().put(EntityData.RIDER_SEAT_POSITION, offset);
}
passenger.updateBedrockMetadata(session);

View file

@ -30,6 +30,7 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket;
import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
@ -51,6 +52,33 @@ public class JavaEntityStatusTranslator extends PacketTranslator<ServerEntitySta
EntityEventPacket entityEventPacket = new EntityEventPacket();
entityEventPacket.setRuntimeEntityId(entity.getGeyserId());
switch (packet.getStatus()) {
case PLAYER_ENABLE_REDUCED_DEBUG:
session.setReducedDebugInfo(true);
return;
case PLAYER_DISABLE_REDUCED_DEBUG:
session.setReducedDebugInfo(false);
return;
case PLAYER_OP_PERMISSION_LEVEL_0:
session.setOpPermissionLevel(0);
session.sendAdventureSettings();
return;
case PLAYER_OP_PERMISSION_LEVEL_1:
session.setOpPermissionLevel(1);
session.sendAdventureSettings();
return;
case PLAYER_OP_PERMISSION_LEVEL_2:
session.setOpPermissionLevel(2);
session.sendAdventureSettings();
return;
case PLAYER_OP_PERMISSION_LEVEL_3:
session.setOpPermissionLevel(3);
session.sendAdventureSettings();
return;
case PLAYER_OP_PERMISSION_LEVEL_4:
session.setOpPermissionLevel(4);
session.sendAdventureSettings();
return;
// EntityEventType.HURT sends extra data depending on the type of damage. However this appears to have no visual changes
case LIVING_BURN:
case LIVING_DROWN:
@ -68,8 +96,20 @@ public class JavaEntityStatusTranslator extends PacketTranslator<ServerEntitySta
entityEventPacket.setType(EntityEventType.USE_ITEM);
break;
case FISHING_HOOK_PULL_PLAYER:
entityEventPacket.setType(EntityEventType.FISH_HOOK_TEASE); //TODO: CHECK
break;
// Player is pulled from a fishing rod
// The physics of this are clientside on Java
long pulledById = entity.getMetadata().getLong(EntityData.TARGET_EID);
if (session.getPlayerEntity().getGeyserId() == pulledById) {
Entity hookOwner = session.getEntityCache().getEntityByGeyserId(entity.getMetadata().getLong(EntityData.OWNER_EID));
if (hookOwner != null) {
// https://minecraft.gamepedia.com/Fishing_Rod#Hooking_mobs_and_other_entities
SetEntityMotionPacket motionPacket = new SetEntityMotionPacket();
motionPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId());
motionPacket.setMotion(hookOwner.getPosition().sub(session.getPlayerEntity().getPosition()).mul(0.1f));
session.sendUpstreamPacket(motionPacket);
}
}
return;
case TAMEABLE_TAMING_FAILED:
entityEventPacket.setType(EntityEventType.TAME_FAILED);
break;

View file

@ -32,6 +32,7 @@ import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
import com.nukkitx.protocol.bedrock.packet.AdventureSettingsPacket;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
@ -43,24 +44,12 @@ public class JavaPlayerAbilitiesTranslator extends PacketTranslator<ServerPlayer
@Override
public void translate(ServerPlayerAbilitiesPacket packet, GeyserSession session) {
Entity entity = session.getPlayerEntity();
PlayerEntity entity = session.getPlayerEntity();
if (entity == null)
return;
Set<AdventureSetting> playerFlags = new ObjectOpenHashSet<>();
playerFlags.add(AdventureSetting.AUTO_JUMP);
if (packet.isCanFly())
playerFlags.add(AdventureSetting.MAY_FLY);
if (packet.isFlying())
playerFlags.add(AdventureSetting.FLYING);
AdventureSettingsPacket adventureSettingsPacket = new AdventureSettingsPacket();
adventureSettingsPacket.setPlayerPermission(PlayerPermission.MEMBER);
// Required or the packet simply is not sent
adventureSettingsPacket.setCommandPermission(CommandPermission.NORMAL);
adventureSettingsPacket.setUniqueEntityId(entity.getGeyserId());
adventureSettingsPacket.getSettings().addAll(playerFlags);
session.sendUpstreamPacket(adventureSettingsPacket);
session.setCanFly(packet.isCanFly());
session.setFlying(packet.isFlying());
session.sendAdventureSettings();
}
}

View file

@ -57,9 +57,8 @@ public class JavaPlayerListEntryTranslator extends PacketTranslator<ServerPlayer
if (self) {
// Entity is ourself
playerEntity = session.getPlayerEntity();
SkinUtils.requestAndHandleSkinAndCape(playerEntity, session, skinAndCape -> {
GeyserConnector.getInstance().getLogger().debug("Loading Local Bedrock Java Skin Data");
});
SkinUtils.requestAndHandleSkinAndCape(playerEntity, session, skinAndCape ->
GeyserConnector.getInstance().getLogger().debug("Loaded Local Bedrock Java Skin Data"));
} else {
playerEntity = session.getEntityCache().getPlayerEntity(entry.getProfile().getId());
}
@ -88,18 +87,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

@ -36,7 +36,7 @@ public class JavaDisplayScoreboardTranslator extends PacketTranslator<ServerDisp
@Override
public void translate(ServerDisplayScoreboardPacket packet, GeyserSession session) {
session.getScoreboardCache().getScoreboard().registerNewObjective(
session.getWorldCache().getScoreboard().registerNewObjective(
packet.getName(), packet.getPosition()
);
}

View file

@ -26,7 +26,7 @@
package org.geysermc.connector.network.translators.java.scoreboard;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.session.cache.ScoreboardCache;
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;
@ -41,13 +41,11 @@ public class JavaScoreboardObjectiveTranslator extends PacketTranslator<ServerSc
@Override
public void translate(ServerScoreboardObjectivePacket packet, GeyserSession session) {
ScoreboardCache cache = session.getScoreboardCache();
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,54 +44,76 @@ 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()));
}
Scoreboard scoreboard = session.getScoreboardCache().getScoreboard();
int pps = session.getWorldCache().increaseAndGetScoreboardPacketsPerSecond();
Scoreboard scoreboard = session.getWorldCache().getScoreboard();
Team team = scoreboard.getTeam(packet.getTeamName());
switch (packet.getAction()) {
case CREATE:
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.getScoreboardCache().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,18 +25,17 @@
package org.geysermc.connector.network.translators.java.window;
import com.github.steveice10.mc.protocol.data.message.MessageSerializer;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCloseWindowPacket;
import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerOpenWindowPacket;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.nukkitx.protocol.bedrock.packet.ContainerClosePacket;
import org.geysermc.connector.GeyserConnector;
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.inventory.InventoryTranslator;
import org.geysermc.connector.utils.InventoryUtils;
import org.geysermc.connector.utils.LocaleUtils;
import org.geysermc.connector.utils.MessageUtils;
@Translator(packet = ServerOpenWindowPacket.class)
public class JavaOpenWindowTranslator extends PacketTranslator<ServerOpenWindowPacket> {
@ -58,18 +57,10 @@ public class JavaOpenWindowTranslator extends PacketTranslator<ServerOpenWindowP
return;
}
String name = packet.getName();
try {
JsonParser parser = new JsonParser();
JsonObject jsonObject = parser.parse(packet.getName()).getAsJsonObject();
if (jsonObject.has("text")) {
name = jsonObject.get("text").getAsString();
} else if (jsonObject.has("translate")) {
name = jsonObject.get("translate").getAsString();
}
} catch (Exception e) {
GeyserConnector.getInstance().getLogger().debug("JavaOpenWindowTranslator: " + e.toString());
}
String name = MessageUtils.getTranslatedBedrockMessage(MessageSerializer.fromString(packet.getName()),
session.getClientData().getLanguageCode());
name = LocaleUtils.getLocaleString(name, session.getClientData().getLanguageCode());
Inventory newInventory = new Inventory(name, packet.getWindowId(), packet.getType(), newTranslator.size + 36);
session.getInventoryCache().cacheInventory(newInventory);

View file

@ -25,89 +25,111 @@
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.data.game.chunk.Column;
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.ByteBufAllocator;
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;
}
// Merge received column with cache on network thread
Column mergedColumn = session.getChunkCache().addToCache(packet.getColumn());
if (mergedColumn == null) { // There were no changes?!?
return;
}
boolean isNonFullChunk = packet.getColumn().getBiomeData() == null;
GeyserConnector.getInstance().getGeneralThreadPool().execute(() -> {
try {
ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(packet.getColumn());
ByteBuf byteBuf = Unpooled.buffer(32);
ChunkSection[] sections = chunkData.sections;
ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(session, mergedColumn, isNonFullChunk);
ChunkSection[] sections = chunkData.getSections();
// Find highest section
int sectionCount = sections.length - 1;
while (sectionCount >= 0 && sections[sectionCount].isEmpty()) {
while (sectionCount >= 0 && sections[sectionCount] == null) {
sectionCount--;
}
sectionCount++;
// Estimate chunk size
int size = 0;
for (int i = 0; i < sectionCount; i++) {
ChunkSection section = chunkData.sections[i];
section.writeToNetwork(byteBuf);
ChunkSection section = sections[i];
size += (section != null ? section : ChunkUtils.EMPTY_SECTION).estimateNetworkSize();
}
size += 256; // Biomes
size += 1; // Border blocks
size += 1; // Extra data length (always 0)
size += chunkData.getBlockEntities().length * 64; // Conservative estimate of 64 bytes per tile entity
byte[] bedrockBiome = BiomeTranslator.toBedrockBiome(packet.getColumn().getBiomeData());
// Allocate output buffer
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(size);
byte[] payload;
try {
for (int i = 0; i < sectionCount; i++) {
ChunkSection section = sections[i];
(section != null ? section : ChunkUtils.EMPTY_SECTION).writeToNetwork(byteBuf);
}
byteBuf.writeBytes(bedrockBiome); // Biomes - 256 bytes
byteBuf.writeByte(0); // Border blocks - Edu edition only
VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now
byteBuf.writeBytes(BiomeTranslator.toBedrockBiome(mergedColumn.getBiomeData())); // Biomes - 256 bytes
byteBuf.writeByte(0); // Border blocks - Edu edition only
VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now
ByteBufOutputStream stream = new ByteBufOutputStream(Unpooled.buffer());
NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(stream);
for (NbtMap blockEntity : chunkData.getBlockEntities()) {
nbtStream.writeTag(blockEntity);
// Encode tile entities into buffer
NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(new ByteBufOutputStream(byteBuf));
for (NbtMap blockEntity : chunkData.getBlockEntities()) {
nbtStream.writeTag(blockEntity);
}
// Copy data into byte[], because the protocol lib really likes things that are s l o w
byteBuf.readBytes(payload = new byte[byteBuf.readableBytes()]);
} finally {
byteBuf.release(); // Release buffer to allow buffer pooling to be useful
}
byteBuf.writeBytes(stream.buffer());
byte[] payload = new byte[byteBuf.writerIndex()];
byteBuf.readBytes(payload);
LevelChunkPacket levelChunkPacket = new LevelChunkPacket();
levelChunkPacket.setSubChunksLength(sectionCount);
levelChunkPacket.setCachingEnabled(false);
levelChunkPacket.setChunkX(packet.getColumn().getX());
levelChunkPacket.setChunkZ(packet.getColumn().getZ());
levelChunkPacket.setChunkX(mergedColumn.getX());
levelChunkPacket.setChunkZ(mergedColumn.getZ());
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

@ -32,6 +32,7 @@ import com.nukkitx.protocol.bedrock.data.LevelEventType;
import com.nukkitx.protocol.bedrock.data.SoundEvent;
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket;
import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
@ -64,5 +65,12 @@ public class JavaExplosionTranslator extends PacketTranslator<ServerExplosionPac
levelSoundEventPacket.setIdentifier(":");
levelSoundEventPacket.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ()));
session.sendUpstreamPacket(levelSoundEventPacket);
if (packet.getPushX() > 0f || packet.getPushY() > 0f || packet.getPushZ() > 0f) {
SetEntityMotionPacket motionPacket = new SetEntityMotionPacket();
motionPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId());
motionPacket.setMotion(Vector3f.from(packet.getPushX(), packet.getPushY(), packet.getPushZ()));
session.sendUpstreamPacket(motionPacket);
}
}
}

View file

@ -28,34 +28,29 @@ package org.geysermc.connector.network.translators.java.world;
import com.github.steveice10.mc.protocol.data.game.ClientRequest;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.world.notify.EnterCreditsValue;
import com.github.steveice10.mc.protocol.data.game.world.notify.RainStrengthValue;
import com.github.steveice10.mc.protocol.data.game.world.notify.RespawnScreenValue;
import com.github.steveice10.mc.protocol.data.game.world.notify.ThunderStrengthValue;
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientRequestPacket;
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerNotifyClientPacket;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.AdventureSetting;
import com.nukkitx.protocol.bedrock.data.GameRuleData;
import com.nukkitx.protocol.bedrock.data.LevelEventType;
import com.nukkitx.protocol.bedrock.data.PlayerPermission;
import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
import com.nukkitx.protocol.bedrock.packet.*;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.inventory.PlayerInventoryTranslator;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import org.geysermc.connector.utils.LocaleUtils;
@Translator(packet = ServerNotifyClientPacket.class)
public class JavaNotifyClientTranslator extends PacketTranslator<ServerNotifyClientPacket> {
@Override
public void translate(ServerNotifyClientPacket packet, GeyserSession session) {
Entity entity = session.getPlayerEntity();
PlayerEntity entity = session.getPlayerEntity();
if (entity == null)
return;
@ -63,51 +58,58 @@ public class JavaNotifyClientTranslator extends PacketTranslator<ServerNotifyCli
case START_RAIN:
LevelEventPacket startRainPacket = new LevelEventPacket();
startRainPacket.setType(LevelEventType.START_RAINING);
startRainPacket.setData(ThreadLocalRandom.current().nextInt(50000) + 10000);
startRainPacket.setData(Integer.MAX_VALUE);
startRainPacket.setPosition(Vector3f.ZERO);
session.sendUpstreamPacket(startRainPacket);
session.setRaining(true);
break;
case STOP_RAIN:
LevelEventPacket stopRainPacket = new LevelEventPacket();
stopRainPacket.setType(LevelEventType.STOP_RAINING);
stopRainPacket.setData(ThreadLocalRandom.current().nextInt(50000) + 10000);
stopRainPacket.setData(0);
stopRainPacket.setPosition(Vector3f.ZERO);
session.sendUpstreamPacket(stopRainPacket);
session.setRaining(false);
break;
case RAIN_STRENGTH:
// While the above values are used, they CANNOT BE TRUSTED on a vanilla server as they are swapped around
// Spigot and forks implement it correctly
// Rain strength is your best way for determining if there is any rain
RainStrengthValue value = (RainStrengthValue) packet.getValue();
boolean isCurrentlyRaining = value.getStrength() > 0f;
// Java sends the rain level. Bedrock doesn't care, so we don't care if it's already raining.
if (isCurrentlyRaining != session.isRaining()) {
LevelEventPacket changeRainPacket = new LevelEventPacket();
changeRainPacket.setType(isCurrentlyRaining ? LevelEventType.START_RAINING : LevelEventType.STOP_RAINING);
changeRainPacket.setData(Integer.MAX_VALUE); // Dunno what this does; used to be implemented with ThreadLocalRandom
changeRainPacket.setPosition(Vector3f.ZERO);
session.sendUpstreamPacket(changeRainPacket);
session.setRaining(isCurrentlyRaining);
}
break;
case THUNDER_STRENGTH:
// See above, same process
ThunderStrengthValue thunderValue = (ThunderStrengthValue) packet.getValue();
boolean isCurrentlyThundering = thunderValue.getStrength() > 0f;
if (isCurrentlyThundering != session.isThunder()) {
LevelEventPacket changeThunderPacket = new LevelEventPacket();
changeThunderPacket.setType(isCurrentlyThundering ? LevelEventType.START_THUNDERSTORM : LevelEventType.STOP_THUNDERSTORM);
changeThunderPacket.setData(Integer.MAX_VALUE);
changeThunderPacket.setPosition(Vector3f.ZERO);
session.sendUpstreamPacket(changeThunderPacket);
session.setThunder(isCurrentlyThundering);
}
break;
case CHANGE_GAMEMODE:
Set<AdventureSetting> playerFlags = new ObjectOpenHashSet<>();
GameMode gameMode = (GameMode) packet.getValue();
if (gameMode == GameMode.ADVENTURE)
playerFlags.add(AdventureSetting.WORLD_IMMUTABLE);
if (gameMode == GameMode.CREATIVE)
playerFlags.add(AdventureSetting.MAY_FLY);
if (gameMode == GameMode.SPECTATOR) {
playerFlags.add(AdventureSetting.MAY_FLY);
playerFlags.add(AdventureSetting.NO_CLIP);
playerFlags.add(AdventureSetting.FLYING);
playerFlags.add(AdventureSetting.WORLD_IMMUTABLE);
gameMode = GameMode.CREATIVE; // spectator doesnt exist on bedrock
}
playerFlags.add(AdventureSetting.AUTO_JUMP);
session.sendAdventureSettings();
SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket();
playerGameTypePacket.setGamemode(gameMode.ordinal());
session.sendUpstreamPacket(playerGameTypePacket);
session.setGameMode(gameMode);
// We need to delay this because otherwise it's overridden by the adventure settings from the abilities packet
session.getConnector().getGeneralThreadPool().schedule(() -> {
AdventureSettingsPacket adventureSettingsPacket = new AdventureSettingsPacket();
adventureSettingsPacket.setPlayerPermission(PlayerPermission.MEMBER);
adventureSettingsPacket.setCommandPermission(CommandPermission.NORMAL);
adventureSettingsPacket.setUniqueEntityId(entity.getGeyserId());
adventureSettingsPacket.getSettings().addAll(playerFlags);
session.sendUpstreamPacket(adventureSettingsPacket);
}, 50, TimeUnit.MILLISECONDS);
// Update the crafting grid to add/remove barriers for creative inventory
PlayerInventoryTranslator.updateCraftingGrid(session, session.getInventory());
break;
@ -138,6 +140,19 @@ 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;
case ARROW_HIT_PLAYER:
PlaySoundPacket arrowSoundPacket = new PlaySoundPacket();
arrowSoundPacket.setSound("random.orb");
arrowSoundPacket.setPitch(0.5f);
arrowSoundPacket.setVolume(0.5f);
arrowSoundPacket.setPosition(entity.getPosition());
session.sendUpstreamPacket(arrowSoundPacket);
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);
@ -78,7 +78,7 @@ public class JavaSpawnParticleTranslator extends PacketTranslator<ServerSpawnPar
int r = (int) (data.getRed()*255);
int g = (int) (data.getGreen()*255);
int b = (int) (data.getBlue()*255);
particle.setType(LevelEventType.PARTICLE_FALLING_DUST);
particle.setType(LevelEventType.PARTICLE_REDSTONE);
particle.setData(((0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff));
particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ()));
session.sendUpstreamPacket(particle);

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