Merge remote-tracking branch 'upstream/master' into feature/protocol-3.0

# Conflicts:
#	core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EndermanEntity.java
#	core/src/main/java/org/geysermc/geyser/inventory/holder/BlockInventoryHolder.java
#	core/src/main/java/org/geysermc/geyser/network/ConnectorServerEventHandler.java
#	core/src/main/java/org/geysermc/geyser/network/GameProtocol.java
#	core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java
#	core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java
#	core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java
#	core/src/main/java/org/geysermc/geyser/session/GeyserSession.java
#	core/src/main/java/org/geysermc/geyser/text/ChatTypeEntry.java
#	core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java
#	core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java
#	core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaDisguisedChatTranslator.java
#	core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaPlayerChatTranslator.java
#	core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoTranslator.java
#	core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaExplodeTranslator.java
#	core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java
#	core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java
#	gradle/libs.versions.toml
This commit is contained in:
Konicai 2022-12-14 15:58:17 -05:00
commit 84248fa902
No known key found for this signature in database
GPG Key ID: 710D09287708C823
85 changed files with 1947 additions and 11441 deletions

View File

@ -17,7 +17,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
### Currently supporting Minecraft Bedrock 1.19.0 - 1.19.40 and Minecraft Java 1.19.1/1.19.2.
### Currently supporting Minecraft Bedrock 1.19.20 - 1.19.51 and Minecraft Java 1.19.3.
## Setting Up
Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser.

View File

@ -29,7 +29,6 @@ import lombok.Getter;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Plugin;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.text.AsteriskSerializer;
import java.util.ArrayList;
import java.util.Collections;
@ -52,15 +51,17 @@ public class GeyserBungeeDumpInfo extends BootstrapDumpInfo {
this.plugins = new ArrayList<>();
for (net.md_5.bungee.api.config.ListenerInfo listener : proxy.getConfig().getListeners()) {
String hostname = listener.getHost().getHostString();
if (!AsteriskSerializer.showSensitive && !(hostname.equals("") || hostname.equals("0.0.0.0"))) {
hostname = "***";
}
this.listeners.add(new ListenerInfo(hostname, listener.getHost().getPort()));
this.listeners.add(new ListenerInfo(listener.getHost().getHostString(), listener.getHost().getPort()));
}
for (Plugin plugin : proxy.getPluginManager().getPlugins()) {
this.plugins.add(new PluginInfo(true, plugin.getDescription().getName(), plugin.getDescription().getVersion(), plugin.getDescription().getMain(), Collections.singletonList(plugin.getDescription().getAuthor())));
this.plugins.add(new PluginInfo(
true,
plugin.getDescription().getName(),
plugin.getDescription().getVersion(),
plugin.getDescription().getMain(),
Collections.singletonList(plugin.getDescription().getAuthor()))
);
}
}
}

View File

@ -76,7 +76,7 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
// Copied from ViaVersion.
// https://github.com/ViaVersion/ViaVersion/blob/b8072aad86695cc8ec6f5e4103e43baf3abf6cc5/bungee/src/main/java/us/myles/ViaVersion/BungeePlugin.java#L43
try {
ProtocolConstants.class.getField("MINECRAFT_1_19_1");
ProtocolConstants.class.getField("MINECRAFT_1_19_3");
} catch (NoSuchFieldException e) {
getLogger().warning(" / \\");
getLogger().warning(" / \\");

View File

@ -1,8 +1,5 @@
plugins {
id("fabric-loom") version "1.0-SNAPSHOT"
id("maven-publish")
id("com.github.johnrengelman.shadow")
id("java")
}
java {

View File

@ -25,65 +25,58 @@
package org.geysermc.geyser.platform.fabric;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.fabricmc.api.EnvType;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.fabricmc.loader.api.metadata.ModMetadata;
import net.fabricmc.loader.api.metadata.Person;
import net.minecraft.server.MinecraftServer;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.text.AsteriskSerializer;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@SuppressWarnings("unused") // The way that the dump renders makes them used
@Getter
public class GeyserFabricDumpInfo extends BootstrapDumpInfo {
private String platformVersion = null;
private final EnvType environmentType;
@AsteriskSerializer.Asterisk(isIp = true)
private final String serverIP;
private final int serverPort;
private final List<ModInfo> mods;
public GeyserFabricDumpInfo(MinecraftServer server) {
super();
for (ModContainer modContainer : FabricLoader.getInstance().getAllMods()) {
if (modContainer.getMetadata().getId().equals("fabricloader")) {
this.platformVersion = modContainer.getMetadata().getVersion().getFriendlyString();
break;
}
}
FabricLoader.getInstance().getModContainer("fabricloader").ifPresent(mod ->
this.platformVersion = mod.getMetadata().getVersion().getFriendlyString());
this.environmentType = FabricLoader.getInstance().getEnvironmentType();
if (AsteriskSerializer.showSensitive || (server.getLocalIp() == null || server.getLocalIp().equals("") || server.getLocalIp().equals("0.0.0.0"))) {
this.serverIP = server.getLocalIp();
} else {
this.serverIP = "***";
}
this.serverIP = server.getLocalIp() == null ? "unknown" : server.getLocalIp();
this.serverPort = server.getPort();
this.mods = new ArrayList<>();
for (ModContainer mod : FabricLoader.getInstance().getAllMods()) {
this.mods.add(new ModInfo(mod));
ModMetadata meta = mod.getMetadata();
this.mods.add(new ModInfo(
FabricLoader.getInstance().isModLoaded(meta.getId()),
meta.getId(),
meta.getVersion().getFriendlyString(),
meta.getAuthors().stream().map(Person::getName).collect(Collectors.toList()))
);
}
}
public String getPlatformVersion() {
return platformVersion;
}
public EnvType getEnvironmentType() {
return environmentType;
}
public String getServerIP() {
return this.serverIP;
}
public int getServerPort() {
return this.serverPort;
}
public List<ModInfo> getMods() {
return this.mods;
@Getter
@AllArgsConstructor
public static class ModInfo {
public boolean enabled;
public String name;
public String version;
public List<String> authors;
}
}

View File

@ -246,7 +246,10 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
@Override
public InputStream getResourceOrNull(String resource) {
// We need to handle this differently, because Fabric shares the classloader across multiple mods
Path path = this.mod.getPath(resource);
Path path = this.mod.findPath(resource).orElse(null);
if (path == null) {
return null;
}
try {
return path.getFileSystem()

View File

@ -1,66 +0,0 @@
/*
* Copyright (c) 2019-2022 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.geyser.platform.fabric;
import net.fabricmc.loader.api.ModContainer;
import java.util.ArrayList;
import java.util.List;
/**
* A wrapper for Fabric mod information to be presented in a Geyser dump
*/
public class ModInfo {
private final String name;
private final String id;
private final String version;
private final List<String> authors;
public ModInfo(ModContainer mod) {
this.name = mod.getMetadata().getName();
this.id = mod.getMetadata().getId();
this.authors = new ArrayList<>();
mod.getMetadata().getAuthors().forEach((person) -> this.authors.add(person.getName()));
this.version = mod.getMetadata().getVersion().getFriendlyString();
}
public String getName() {
return this.name;
}
public String getId() {
return this.id;
}
public String getVersion() {
return this.version;
}
public List<String> getAuthors() {
return this.authors;
}
}

View File

@ -41,6 +41,8 @@ public class GeyserSpigotDumpInfo extends BootstrapDumpInfo {
private final String platformVersion;
private final String platformAPIVersion;
private final boolean onlineMode;
@AsteriskSerializer.Asterisk(isIp = true)
private final String serverIP;
private final int serverPort;
private final List<PluginInfo> plugins;
@ -51,12 +53,7 @@ public class GeyserSpigotDumpInfo extends BootstrapDumpInfo {
this.platformVersion = Bukkit.getVersion();
this.platformAPIVersion = Bukkit.getBukkitVersion();
this.onlineMode = Bukkit.getOnlineMode();
String ip = Bukkit.getIp();
if (AsteriskSerializer.showSensitive || (ip.equals("") || ip.equals("0.0.0.0"))) {
this.serverIP = ip;
} else {
this.serverIP = "***";
}
this.serverIP = Bukkit.getIp();
this.serverPort = Bukkit.getPort();
this.plugins = new ArrayList<>();

View File

@ -32,6 +32,7 @@ import org.geysermc.geyser.adapters.spigot.SpigotAdapters;
import org.geysermc.geyser.adapters.spigot.SpigotWorldAdapter;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.session.GeyserSession;
import org.jetbrains.annotations.Nullable;
public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager {
protected final SpigotWorldAdapter adapter;
@ -49,4 +50,12 @@ public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager {
}
return adapter.getBlockAt(player.getWorld(), x, y, z);
}
@Nullable
@Override
public String[] getBiomeIdentifiers(boolean withTags) {
// Biome identifiers will basically always be the same for one server, since you have to re-send the
// ClientboundLoginPacket to change the registry. Therefore, don't bother caching for each player.
return adapter.getBiomeSuggestions(withTags);
}
}

View File

@ -38,7 +38,7 @@ import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.plugin.Plugin;
import org.geysermc.geyser.level.GameRule;
import org.geysermc.geyser.level.GeyserWorldManager;
import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.session.GeyserSession;
@ -51,7 +51,7 @@ import java.util.List;
/**
* The base world manager to use when there is no supported NMS revision
*/
public class GeyserSpigotWorldManager extends GeyserWorldManager {
public class GeyserSpigotWorldManager extends WorldManager {
private final Plugin plugin;
public GeyserSpigotWorldManager(Plugin plugin) {
@ -151,12 +151,12 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager {
return true;
}
public Boolean getGameRuleBool(GeyserSession session, GameRule gameRule) {
public boolean getGameRuleBool(GeyserSession session, GameRule gameRule) {
String value = Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getGameRuleValue(gameRule.getJavaID());
if (!value.isEmpty()) {
return Boolean.parseBoolean(value);
}
return (Boolean) gameRule.getDefaultValue();
return gameRule.getDefaultBooleanValue();
}
@Override
@ -165,7 +165,7 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager {
if (!value.isEmpty()) {
return Integer.parseInt(value);
}
return (int) gameRule.getDefaultValue();
return gameRule.getDefaultIntValue();
}
@Override

View File

@ -45,6 +45,8 @@ public class GeyserSpongeDumpInfo extends BootstrapDumpInfo {
private final String platformName;
private final String platformVersion;
private final boolean onlineMode;
@AsteriskSerializer.Asterisk(isIp = true)
private final String serverIP;
private final int serverPort;
private final List<PluginInfo> plugins;
@ -56,12 +58,7 @@ public class GeyserSpongeDumpInfo extends BootstrapDumpInfo {
this.platformVersion = platformMeta.version().getQualifier();
this.onlineMode = Sponge.server().isOnlineModeEnabled();
Optional<InetSocketAddress> socketAddress = Sponge.server().boundAddress();
String hostString = socketAddress.map(InetSocketAddress::getHostString).orElse("unknown");
if (AsteriskSerializer.showSensitive || (hostString.equals("") || hostString.equals("0.0.0.0") || hostString.equals("unknown"))) {
this.serverIP = hostString;
} else {
this.serverIP = "***";
}
this.serverIP = socketAddress.map(InetSocketAddress::getHostString).orElse("unknown");
this.serverPort = socketAddress.map(InetSocketAddress::getPort).orElse(-1);
this.plugins = new ArrayList<>();

View File

@ -41,6 +41,8 @@ public class GeyserVelocityDumpInfo extends BootstrapDumpInfo {
private final String platformVersion;
private final String platformVendor;
private final boolean onlineMode;
@AsteriskSerializer.Asterisk(isIp = true)
private final String serverIP;
private final int serverPort;
private final List<PluginInfo> plugins;
@ -51,12 +53,7 @@ public class GeyserVelocityDumpInfo extends BootstrapDumpInfo {
this.platformVersion = proxy.getVersion().getVersion();
this.platformVendor = proxy.getVersion().getVendor();
this.onlineMode = proxy.getConfiguration().isOnlineMode();
String hostString = proxy.getBoundAddress().getHostString();
if (AsteriskSerializer.showSensitive || (hostString.equals("") || hostString.equals("0.0.0.0"))) {
this.serverIP = hostString;
} else {
this.serverIP = "***";
}
this.serverIP = proxy.getBoundAddress().getHostString();
this.serverPort = proxy.getBoundAddress().getPort();
this.plugins = new ArrayList<>();

View File

@ -16,11 +16,11 @@ dependencies {
// Within the gradle plugin classpath, there is a version conflict between loom and some other
// plugin for databind. This fixes it: minimum 2.13.2 is required by loom.
implementation("com.fasterxml.jackson.core:jackson-databind:2.13.3")
implementation("com.fasterxml.jackson.core:jackson-databind:2.14.0")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
jvmTarget = "16"
}
}
}

View File

@ -111,6 +111,8 @@ public interface GeyserConfiguration {
boolean isNotifyOnNewBedrockUpdate();
String getUnusableSpaceBlock();
IMetricsInfo getMetrics();
int getPendingAuthenticationTimeout();

View File

@ -154,6 +154,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
@JsonProperty("notify-on-new-bedrock-update")
private boolean notifyOnNewBedrockUpdate = true;
@JsonProperty("unusable-space-block")
private String unusableSpaceBlock = "minecraft:barrier";
private MetricsInfo metrics = new MetricsInfo();
@JsonProperty("pending-authentication-timeout")

View File

@ -29,6 +29,7 @@ import lombok.AllArgsConstructor;
import lombok.Getter;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.text.AsteriskSerializer;
import java.util.List;
@ -53,6 +54,8 @@ public class BootstrapDumpInfo {
@Getter
@AllArgsConstructor
public static class ListenerInfo {
@AsteriskSerializer.Asterisk(isIp = true)
public String ip;
public int port;
}

View File

@ -60,6 +60,7 @@ public final class EntityDefinitions {
public static final EntityDefinition<BeeEntity> BEE;
public static final EntityDefinition<BlazeEntity> BLAZE;
public static final EntityDefinition<BoatEntity> BOAT;
public static final EntityDefinition<CamelEntity> CAMEL;
public static final EntityDefinition<CatEntity> CAT;
public static final EntityDefinition<SpiderEntity> CAVE_SPIDER;
public static final EntityDefinition<MinecartEntity> CHEST_MINECART;
@ -859,6 +860,13 @@ public final class EntityDefinitions {
.addTranslator(MetadataType.BYTE, AbstractHorseEntity::setHorseFlags)
.addTranslator(null) // UUID of owner
.build();
CAMEL = EntityDefinition.inherited(CamelEntity::new, abstractHorseEntityBase)
.type(EntityType.CAMEL)
.identifier("minecraft:llama") // todo 1.20
.height(2.375f).width(1.7f)
.addTranslator(MetadataType.BOOLEAN, CamelEntity::setDashing)
.addTranslator(null) // Last pose change tick
.build();
HORSE = EntityDefinition.inherited(HorseEntity::new, abstractHorseEntityBase)
.type(EntityType.HORSE)
.height(1.6f).width(1.3965f)

View File

@ -48,6 +48,8 @@ import org.cloudburstmc.protocol.bedrock.packet.RemoveEntityPacket;
import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.GeyserDirtyMetadata;
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.EntityUtils;
@ -360,6 +362,7 @@ public class Entity {
setFlag(EntityFlag.ON_FIRE, ((xd & 0x01) == 0x01) && !getFlag(EntityFlag.FIRE_IMMUNE)); // Otherwise immune entities sometimes flicker onfire
setFlag(EntityFlag.SNEAKING, (xd & 0x02) == 0x02);
setFlag(EntityFlag.SPRINTING, (xd & 0x08) == 0x08);
// Swimming is ignored here and instead we rely on the pose
setFlag(EntityFlag.GLIDING, (xd & 0x80) == 0x80);

View File

@ -0,0 +1,70 @@
/*
* Copyright (c) 2019-2022 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.geyser.entity.type.living.animal.horse;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
import java.util.UUID;
public class CamelEntity extends AbstractHorseEntity {
private static final float SITTING_HEIGHT_DIFFERENCE = 1.43F;
public CamelEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
}
@Override
protected void initializeMetadata() {
super.initializeMetadata();
this.dirtyMetadata.put(EntityData.VARIANT, 2); // Closest llama colour to camel
}
@Override
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
return "cactus".equals(javaIdentifierStripped);
}
@Override
protected void setDimensions(Pose pose) {
if (pose == Pose.SITTING) {
setBoundingBoxWidth(definition.height() - SITTING_HEIGHT_DIFFERENCE);
setBoundingBoxWidth(definition.width());
} else {
super.setDimensions(pose);
}
}
public void setDashing(BooleanEntityMetadata entityMetadata) {
}
}

View File

@ -25,6 +25,8 @@
package org.geysermc.geyser.entity.type.living.monster;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.OptionalIntMetadataType;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
@ -35,6 +37,7 @@ import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEvent2Packet;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
import java.util.OptionalInt;
import java.util.UUID;
public class EndermanEntity extends MonsterEntity {
@ -43,8 +46,15 @@ public class EndermanEntity extends MonsterEntity {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
}
public void setCarriedBlock(IntEntityMetadata entityMetadata) {
dirtyMetadata.put(EntityDataTypes.BLOCK, session.getBlockMappings().getBedrockBlock(entityMetadata.getPrimitiveValue()));
public void setCarriedBlock(EntityMetadata<OptionalInt, OptionalIntMetadataType> entityMetadata) {
int bedrockBlockId;
if (entityMetadata.getValue().isPresent()) {
bedrockBlockId = session.getBlockMappings().getBedrockBlockId(entityMetadata.getValue().getAsInt());
} else {
bedrockBlockId = session.getBlockMappings().getBedrockAirId();
}
dirtyMetadata.put(EntityData.BLOCK, bedrockBlockId);
}
/**

View File

@ -120,6 +120,16 @@ public class SessionPlayerEntity extends PlayerEntity {
refreshSpeed = true;
}
/**
* Since 1.19.40, the client must be re-informed of its bounding box on respawn
* See https://github.com/GeyserMC/Geyser/issues/3370
*/
public void updateBoundingBox() {
dirtyMetadata.put(EntityData.BOUNDING_BOX_HEIGHT, getBoundingBoxHeight());
dirtyMetadata.put(EntityData.BOUNDING_BOX_WIDTH, getBoundingBoxWidth());
updateBedrockMetadata();
}
@Override
public boolean setBoundingBoxHeight(float height) {
if (super.setBoundingBoxHeight(height)) {

View File

@ -76,7 +76,7 @@ public class AnvilContainer extends Container {
String originalName = ItemUtils.getCustomName(getInput().getNbt());
String plainOriginalName = MessageTranslator.convertToPlainText(originalName, session.locale());
String plainNewName = MessageTranslator.convertToPlainText(rename, session.locale());
String plainNewName = MessageTranslator.convertToPlainText(rename);
if (!plainOriginalName.equals(plainNewName)) {
// Strip out formatting since Java Edition does not allow it
correctRename = plainNewName;

View File

@ -39,6 +39,7 @@ import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.util.BlockUtils;
import org.geysermc.geyser.util.InventoryUtils;
import java.util.Collections;
import java.util.HashSet;
@ -63,14 +64,14 @@ public class BlockInventoryHolder extends InventoryHolder {
Set<String> validBlocksTemp = new HashSet<>(validBlocks.length + 1);
Collections.addAll(validBlocksTemp, validBlocks);
validBlocksTemp.add(BlockUtils.getCleanIdentifier(javaBlockIdentifier));
this.validBlocks = ImmutableSet.copyOf(validBlocksTemp);
this.validBlocks = Set.copyOf(validBlocksTemp);
} else {
this.validBlocks = Collections.singleton(BlockUtils.getCleanIdentifier(javaBlockIdentifier));
}
}
@Override
public void prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
public boolean prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
// Check to see if there is an existing block we can use that the player just selected.
// First, verify that the player's position has not changed, so we don't try to select a block wildly out of range.
// (This could be a virtual inventory that the player is opening)
@ -83,13 +84,16 @@ public class BlockInventoryHolder extends InventoryHolder {
inventory.setHolderPosition(session.getLastInteractionBlockPosition());
((Container) inventory).setUsingRealBlock(true, javaBlockString[0]);
setCustomName(session, session.getLastInteractionBlockPosition(), inventory, javaBlockId);
return;
return true;
}
}
// Otherwise, time to conjure up a fake block!
Vector3i position = session.getPlayerEntity().getPosition().toInt();
position = position.add(Vector3i.UP);
Vector3i position = InventoryUtils.findAvailableWorldSpace(session);
if (position == null) {
return false;
}
UpdateBlockPacket blockPacket = new UpdateBlockPacket();
blockPacket.setDataLayer(0);
blockPacket.setBlockPosition(position);
@ -99,6 +103,8 @@ public class BlockInventoryHolder extends InventoryHolder {
inventory.setHolderPosition(position);
setCustomName(session, position, inventory, defaultJavaBlockState);
return true;
}
/**

View File

@ -30,7 +30,7 @@ import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
public abstract class InventoryHolder {
public abstract void prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory);
public abstract boolean prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory);
public abstract void openInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory);
public abstract void closeInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory);
}

View File

@ -32,43 +32,41 @@ import lombok.Getter;
* It is used to construct the list for the settings menu
*/
public enum GameRule {
ANNOUNCEADVANCEMENTS("announceAdvancements", Boolean.class, true), // JE only
COMMANDBLOCKOUTPUT("commandBlockOutput", Boolean.class, true),
DISABLEELYTRAMOVEMENTCHECK("disableElytraMovementCheck", Boolean.class, false), // JE only
DISABLERAIDS("disableRaids", Boolean.class, false), // JE only
DODAYLIGHTCYCLE("doDaylightCycle", Boolean.class, true),
DOENTITYDROPS("doEntityDrops", Boolean.class, true),
DOFIRETICK("doFireTick", Boolean.class, true),
DOIMMEDIATERESPAWN("doImmediateRespawn", Boolean.class, false),
DOINSOMNIA("doInsomnia", Boolean.class, true),
DOLIMITEDCRAFTING("doLimitedCrafting", Boolean.class, false), // JE only
DOMOBLOOT("doMobLoot", Boolean.class, true),
DOMOBSPAWNING("doMobSpawning", Boolean.class, true),
DOPATROLSPAWNING("doPatrolSpawning", Boolean.class, true), // JE only
DOTILEDROPS("doTileDrops", Boolean.class, true),
DOTRADERSPAWNING("doTraderSpawning", Boolean.class, true), // JE only
DOWEATHERCYCLE("doWeatherCycle", Boolean.class, true),
DROWNINGDAMAGE("drowningDamage", Boolean.class, true),
FALLDAMAGE("fallDamage", Boolean.class, true),
FIREDAMAGE("fireDamage", Boolean.class, true),
FREEZEDAMAGE("freezeDamage", Boolean.class, true),
FORGIVEDEADPLAYERS("forgiveDeadPlayers", Boolean.class, true), // JE only
KEEPINVENTORY("keepInventory", Boolean.class, false),
LOGADMINCOMMANDS("logAdminCommands", Boolean.class, true), // JE only
MAXCOMMANDCHAINLENGTH("maxCommandChainLength", Integer.class, 65536),
MAXENTITYCRAMMING("maxEntityCramming", Integer.class, 24), // JE only
MOBGRIEFING("mobGriefing", Boolean.class, true),
NATURALREGENERATION("naturalRegeneration", Boolean.class, true),
PLAYERSSLEEPINGPERCENTAGE("playersSleepingPercentage", Integer.class, 100), // JE only
RANDOMTICKSPEED("randomTickSpeed", Integer.class, 3),
REDUCEDDEBUGINFO("reducedDebugInfo", Boolean.class, false), // JE only
SENDCOMMANDFEEDBACK("sendCommandFeedback", Boolean.class, true),
SHOWDEATHMESSAGES("showDeathMessages", Boolean.class, true),
SPAWNRADIUS("spawnRadius", Integer.class, 10),
SPECTATORSGENERATECHUNKS("spectatorsGenerateChunks", Boolean.class, true), // JE only
UNIVERSALANGER("universalAnger", Boolean.class, false), // JE only
UNKNOWN("unknown", Object.class);
ANNOUNCEADVANCEMENTS("announceAdvancements", true), // JE only
COMMANDBLOCKOUTPUT("commandBlockOutput", true),
DISABLEELYTRAMOVEMENTCHECK("disableElytraMovementCheck", false), // JE only
DISABLERAIDS("disableRaids", false), // JE only
DODAYLIGHTCYCLE("doDaylightCycle", true),
DOENTITYDROPS("doEntityDrops", true),
DOFIRETICK("doFireTick", true),
DOIMMEDIATERESPAWN("doImmediateRespawn", false),
DOINSOMNIA("doInsomnia", true),
DOLIMITEDCRAFTING("doLimitedCrafting", false), // JE only
DOMOBLOOT("doMobLoot", true),
DOMOBSPAWNING("doMobSpawning", true),
DOPATROLSPAWNING("doPatrolSpawning", true), // JE only
DOTILEDROPS("doTileDrops", true),
DOTRADERSPAWNING("doTraderSpawning", true), // JE only
DOWEATHERCYCLE("doWeatherCycle", true),
DROWNINGDAMAGE("drowningDamage", true),
FALLDAMAGE("fallDamage", true),
FIREDAMAGE("fireDamage", true),
FREEZEDAMAGE("freezeDamage", true),
FORGIVEDEADPLAYERS("forgiveDeadPlayers", true), // JE only
KEEPINVENTORY("keepInventory", false),
LOGADMINCOMMANDS("logAdminCommands", true), // JE only
MAXCOMMANDCHAINLENGTH("maxCommandChainLength", 65536),
MAXENTITYCRAMMING("maxEntityCramming", 24), // JE only
MOBGRIEFING("mobGriefing", true),
NATURALREGENERATION("naturalRegeneration", true),
PLAYERSSLEEPINGPERCENTAGE("playersSleepingPercentage", 100), // JE only
RANDOMTICKSPEED("randomTickSpeed", 3),
REDUCEDDEBUGINFO("reducedDebugInfo", false), // JE only
SENDCOMMANDFEEDBACK("sendCommandFeedback", true),
SHOWDEATHMESSAGES("showDeathMessages", true),
SPAWNRADIUS("spawnRadius", 10),
SPECTATORSGENERATECHUNKS("spectatorsGenerateChunks", true), // JE only
UNIVERSALANGER("universalAnger", false); // JE only
public static final GameRule[] VALUES = values();
@ -78,48 +76,25 @@ public enum GameRule {
@Getter
private final Class<?> type;
@Getter
private final Object defaultValue;
private final int defaultValue;
GameRule(String javaID, Class<?> type) {
this(javaID, type, null);
GameRule(String javaID, boolean defaultValue) {
this.javaID = javaID;
this.type = Boolean.class;
this.defaultValue = defaultValue ? 1 : 0;
}
GameRule(String javaID, Class<?> type, Object defaultValue) {
GameRule(String javaID, int defaultValue) {
this.javaID = javaID;
this.type = type;
this.type = Integer.class;
this.defaultValue = defaultValue;
}
/**
* Convert a string to an object of the correct type for the current gamerule
*
* @param value The string value to convert
* @return The converted and formatted value
*/
public Object convertValue(String value) {
if (type.equals(Boolean.class)) {
return Boolean.parseBoolean(value);
} else if (type.equals(Integer.class)) {
return Integer.parseInt(value);
}
return null;
public boolean getDefaultBooleanValue() {
return defaultValue != 0;
}
/**
* Fetch a game rule by the given Java ID
*
* @param id The ID of the gamerule
* @return A {@link GameRule} object representing the requested ID or {@link GameRule#UNKNOWN}
*/
public static GameRule fromJavaID(String id) {
for (GameRule gamerule : VALUES) {
if (gamerule.javaID.equals(id)) {
return gamerule;
}
}
return UNKNOWN;
public int getDefaultIntValue() {
return defaultValue;
}
}

View File

@ -25,8 +25,6 @@
package org.geysermc.geyser.level;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.setting.Difficulty;
import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtMapBuilder;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
@ -36,11 +34,8 @@ import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.ChunkCache;
import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator;
import java.util.Locale;
public class GeyserWorldManager extends WorldManager {
private static final Object2ObjectMap<String, String> gameruleCache = new Object2ObjectOpenHashMap<>();
private final Object2ObjectMap<String, String> gameruleCache = new Object2ObjectOpenHashMap<>();
@Override
public int getBlockAt(GeyserSession session, int x, int y, int z) {
@ -82,18 +77,18 @@ public class GeyserWorldManager extends WorldManager {
@Override
public void setGameRule(GeyserSession session, String name, Object value) {
session.sendCommand("gamerule " + name + " " + value);
super.setGameRule(session, name, value);
gameruleCache.put(name, String.valueOf(value));
}
@Override
public Boolean getGameRuleBool(GeyserSession session, GameRule gameRule) {
public boolean getGameRuleBool(GeyserSession session, GameRule gameRule) {
String value = gameruleCache.get(gameRule.getJavaID());
if (value != null) {
return Boolean.parseBoolean(value);
}
return gameRule.getDefaultValue() != null ? (Boolean) gameRule.getDefaultValue() : false;
return gameRule.getDefaultBooleanValue();
}
@Override
@ -103,17 +98,7 @@ public class GeyserWorldManager extends WorldManager {
return Integer.parseInt(value);
}
return gameRule.getDefaultValue() != null ? (int) gameRule.getDefaultValue() : 0;
}
@Override
public void setPlayerGameMode(GeyserSession session, GameMode gameMode) {
session.sendCommand("gamemode " + gameMode.name().toLowerCase(Locale.ROOT));
}
@Override
public void setDifficulty(GeyserSession session, Difficulty difficulty) {
session.sendCommand("difficulty " + difficulty.name().toLowerCase(Locale.ROOT));
return gameRule.getDefaultIntValue();
}
@Override

View File

@ -31,6 +31,9 @@ import org.cloudburstmc.math.vector.Vector3i;
import com.nukkitx.nbt.NbtMap;
import org.geysermc.geyser.session.GeyserSession;
import javax.annotation.Nullable;
import java.util.Locale;
/**
* Class that manages or retrieves various information
* from the world. Everything in this class should be
@ -105,7 +108,9 @@ public abstract class WorldManager {
* @param name The gamerule to change
* @param value The new value for the gamerule
*/
public abstract void setGameRule(GeyserSession session, String name, Object value);
public void setGameRule(GeyserSession session, String name, Object value) {
session.sendCommand("gamerule " + name + " " + value);
}
/**
* Gets a gamerule value as a boolean
@ -114,7 +119,7 @@ public abstract class WorldManager {
* @param gameRule The gamerule to fetch the value of
* @return The boolean representation of the value
*/
public abstract Boolean getGameRuleBool(GeyserSession session, GameRule gameRule);
public abstract boolean getGameRuleBool(GeyserSession session, GameRule gameRule);
/**
* Get a gamerule value as an integer
@ -131,7 +136,9 @@ public abstract class WorldManager {
* @param session The session of the player to change the game mode of
* @param gameMode The game mode to change the player to
*/
public abstract void setPlayerGameMode(GeyserSession session, GameMode gameMode);
public void setPlayerGameMode(GeyserSession session, GameMode gameMode) {
session.sendCommand("gamemode " + gameMode.name().toLowerCase(Locale.ROOT));
}
/**
* Change the difficulty of the Java server
@ -139,7 +146,9 @@ public abstract class WorldManager {
* @param session The session of the user that requested the change
* @param difficulty The difficulty to change to
*/
public abstract void setDifficulty(GeyserSession session, Difficulty difficulty);
public void setDifficulty(GeyserSession session, Difficulty difficulty) {
session.sendCommand("difficulty " + difficulty.name().toLowerCase(Locale.ROOT));
}
/**
* Checks if the given session's player has a permission
@ -149,4 +158,12 @@ public abstract class WorldManager {
* @return True if the player has the requested permission, false if not
*/
public abstract boolean hasPermission(GeyserSession session, String permission);
/**
* Returns a list of biome identifiers available on the server.
*/
@Nullable
public String[] getBiomeIdentifiers(boolean withTags) {
return null;
}
}

View File

@ -49,7 +49,7 @@ public final class GameProtocol {
* Default Bedrock codec that should act as a fallback. Should represent the latest available
* release of the game that Geyser supports.
*/
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = Bedrock_v557.CODEC;
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = Bedrock_v560.CODEC;
/**
* A list of all supported Bedrock versions that can join Geyser
*/
@ -62,12 +62,6 @@ public final class GameProtocol {
private static final PacketCodec DEFAULT_JAVA_CODEC = MinecraftCodec.CODEC;
static {
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v527.CODEC.toBuilder()
.minecraftVersion("1.19.0/1.19.2")
.build());
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v534.CODEC.toBuilder()
.minecraftVersion("1.19.10/1.19.11")
.build());
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v544.CODEC);
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v545.CODEC.toBuilder()
.minecraftVersion("1.19.21/1.19.22")
@ -75,7 +69,12 @@ public final class GameProtocol {
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v554.CODEC.toBuilder()
.minecraftVersion("1.19.30/1.19.31")
.build());
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC);
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v557.CODEC.toBuilder()
.minecraftVersion("1.19.40/1.19.41")
.build());
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder()
.minecraftVersion("1.19.50/1.19.51")
.build());
}
/**
@ -94,14 +93,14 @@ public final class GameProtocol {
/* Bedrock convenience methods to gatekeep features and easily remove the check on version removal */
public static boolean supports1_19_10(GeyserSession session) {
return session.getUpstream().getProtocolVersion() >= Bedrock_v534.CODEC.getProtocolVersion();
}
public static boolean supports1_19_30(GeyserSession session) {
return session.getUpstream().getProtocolVersion() >= Bedrock_v554.CODEC.getProtocolVersion();
}
public static boolean supports1_19_50(GeyserSession session) {
return session.getUpstream().getProtocolVersion() >= Bedrock_v560.V560_CODEC.getProtocolVersion();
}
/**
* Gets the {@link PacketCodec} for Minecraft: Java Edition.
*
@ -117,7 +116,7 @@ public final class GameProtocol {
* @return the supported Minecraft: Java Edition version names
*/
public static List<String> getJavaVersions() {
return List.of(DEFAULT_JAVA_CODEC.getMinecraftVersion(), "1.19.2");
return List.of(DEFAULT_JAVA_CODEC.getMinecraftVersion());
}
/**

View File

@ -61,9 +61,13 @@ import org.geysermc.geyser.util.MathUtils;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayDeque;
import java.util.Deque;
public class UpstreamPacketHandler extends LoggingPacketHandler {
private Deque<String> packsToSent = new ArrayDeque<>();
public UpstreamPacketHandler(GeyserImpl geyser, GeyserSession session) {
super(geyser, session);
}
@ -186,24 +190,8 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
break;
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.RESOURCES);
session.sendUpstreamPacket(data);
}
packsToSent.addAll(packet.getPackIds());
sendPackDataInfo(packsToSent.pop());
break;
case HAVE_ALL_PACKS:
@ -296,7 +284,8 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
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)];
long remainingSize = pack.getFile().length() - offset;
byte[] packData = new byte[(int) MathUtils.constrain(remainingSize, 0, ResourcePack.CHUNK_SIZE)];
try (InputStream inputStream = new FileInputStream(pack.getFile())) {
inputStream.skip(offset);
@ -308,6 +297,31 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
data.setData(Unpooled.wrappedBuffer(packData));
session.sendUpstreamPacket(data);
// Check if it is the last chunk and send next pack in queue when available.
if (remainingSize <= ResourcePack.CHUNK_SIZE && !packsToSent.isEmpty()) {
sendPackDataInfo(packsToSent.pop());
}
return PacketSignal.HANDLED;
}
private void sendPackDataInfo(String id) {
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.RESOURCES);
session.sendUpstreamPacket(data);
}
}

View File

@ -181,12 +181,15 @@ public final class Registries {
POTION_MIXES = SimpleRegistry.create(PotionMixRegistryLoader::new);
ENCHANTMENTS = SimpleMappedRegistry.create("mappings/enchantments.json", EnchantmentRegistryLoader::new);
// TEMPORARY FIX TO MAKE OLD BIOMES NBT WORK WITH 1.19.30
// Remove unneeded client generation data from NbtMapBuilder
NbtMapBuilder biomesNbt = NbtMap.builder();
for (Map.Entry<String, Object> entry : BIOMES_NBT.get().entrySet()) {
String key = entry.getKey();
NbtMapBuilder value = ((NbtMap) entry.getValue()).toBuilder();
value.put("name_hash", key);
value.remove("minecraft:consolidated_features");
value.remove("minecraft:multinoise_generation_rules");
value.remove("minecraft:surface_material_adjustments");
value.remove( "minecraft:surface_parameters");
biomesNbt.put(key, value.build());
}
BIOMES_NBT.set(biomesNbt.build());

View File

@ -84,13 +84,9 @@ public final class BlockRegistryPopulator {
private static void registerBedrockBlocks() {
BiFunction<String, NbtMapBuilder, String> emptyMapper = (bedrockIdentifier, statesBuilder) -> null;
ImmutableMap<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>> blockMappers = ImmutableMap.<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>>builder()
.put(ObjectIntPair.of("1_19_0", Bedrock_v527.CODEC.getProtocolVersion()), (bedrockIdentifier, statesBuilder) -> {
if (bedrockIdentifier.equals("minecraft:muddy_mangrove_roots")) {
statesBuilder.remove("pillar_axis");
}
return null;
})
.put(ObjectIntPair.of("1_19_20", Bedrock_v544.CODEC.getProtocolVersion()), emptyMapper).build();
.put(ObjectIntPair.of("1_19_20", Bedrock_v544.CODEC.getProtocolVersion()), emptyMapper)
.put(ObjectIntPair.of("1_19_50", Bedrock_v560.CODEC.getProtocolVersion()), emptyMapper)
.build();
for (Map.Entry<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>> palette : blockMappers.entrySet()) {
NbtList<NbtMap> blocksTag;

View File

@ -174,6 +174,33 @@ public class CustomItemRegistryPopulator {
computeArmorProperties(mapping.getArmorType(), mapping.getProtectionValue(), componentBuilder);
}
if (mapping.getFirstBlockRuntimeId() != null) {
computeBlockItemProperties(mapping.getBedrockIdentifier(), componentBuilder);
}
if (mapping.isEdible()) {
computeConsumableProperties(itemProperties, componentBuilder, 1, false);
}
if (mapping.isEntityPlacer()) {
computeEntityPlacerProperties(componentBuilder);
}
switch (mapping.getBedrockIdentifier()) {
case "minecraft:fire_charge", "minecraft:flint_and_steel" -> {
computeBlockItemProperties("minecraft:fire", componentBuilder);
}
case "minecraft:bow", "minecraft:crossbow", "minecraft:trident" -> {
computeChargeableProperties(itemProperties, componentBuilder);
}
case "minecraft:honey_bottle", "minecraft:milk_bucket", "minecraft:potion" -> {
computeConsumableProperties(itemProperties, componentBuilder, 2, true);
}
case "minecraft:experience_bottle", "minecraft:egg", "minecraft:ender_pearl", "minecraft:ender_eye", "minecraft:lingering_potion", "minecraft:snowball", "minecraft:splash_potion" -> {
computeThrowableProperties(componentBuilder);
}
}
computeRenderOffsets(false, customItemData, componentBuilder);
componentBuilder.putCompound("item_properties", itemProperties.build());
@ -310,6 +337,48 @@ public class CustomItemRegistryPopulator {
}
}
private static void computeBlockItemProperties(String blockItem, NbtMapBuilder componentBuilder) {
// carved pumpkin should be able to be worn and for that we would need to add wearable and armor with protection 0 here
// however this would have the side effect of preventing carved pumpkins from working as an attachable on the RP side outside the head slot
// it also causes the item to glitch when right clicked to "equip" so this should only be added here later if these issues can be overcome
// all block items registered should be given this component to prevent double placement
componentBuilder.putCompound("minecraft:block_placer", NbtMap.builder().putString("block", blockItem).build());
}
private static void computeChargeableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) {
// setting high use_duration prevents the consume animation from playing
itemProperties.putInt("use_duration", Integer.MAX_VALUE);
// display item as tool (mainly for crossbow and bow)
itemProperties.putBoolean("hand_equipped", true);
// ensure client moves at slow speed while charging (note: this was calculated by hand as the movement modifer value does not seem to scale linearly)
componentBuilder.putCompound("minecraft:chargeable", NbtMap.builder().putFloat("movement_modifier", 0.35F).build());
}
private static void computeConsumableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int useAnimation, boolean canAlwaysEat) {
// this is the duration of the use animation in ticks; note that in behavior packs this is set as a float in seconds, but over the network it is an int in ticks
itemProperties.putInt("use_duration", 32);
// this dictates that the item will use the eat or drink animation (in the first person) and play eat or drink sounds
// note that in behavior packs this is set as the string "eat" or "drink", but over the network it as an int, with these values being 1 and 2 respectively
itemProperties.putInt("use_animation", useAnimation);
// this component is required to allow the eat animation to play
componentBuilder.putCompound("minecraft:food", NbtMap.builder().putBoolean("can_always_eat", canAlwaysEat).build());
}
private static void computeEntityPlacerProperties(NbtMapBuilder componentBuilder) {
// all items registered that place entities should be given this component to prevent double placement
// it is okay that the entity here does not match the actual one since we control what entity actually spawns
componentBuilder.putCompound("minecraft:entity_placer", NbtMap.builder().putString("entity", "minecraft:minecart").build());
}
private static void computeThrowableProperties(NbtMapBuilder componentBuilder) {
// allows item to be thrown when holding down right click (individual presses are required w/o this component)
componentBuilder.putCompound("minecraft:throwable", NbtMap.builder().putBoolean("do_swing_animation", true).build());
// this must be set to something for the swing animation to play
// it is okay that the projectile here does not match the actual one since we control what entity actually spawns
componentBuilder.putCompound("minecraft:projectile", NbtMap.builder().putString("projectile_entity", "minecraft:snowball").build());
}
private static void computeRenderOffsets(boolean isHat, CustomItemData customItemData, NbtMapBuilder componentBuilder) {
if (isHat) {
componentBuilder.remove("minecraft:render_offsets");

View File

@ -90,10 +90,8 @@ public class ItemRegistryPopulator {
public static void populate() {
Map<String, PaletteVersion> paletteVersions = new Object2ObjectOpenHashMap<>();
paletteVersions.put("1_19_0", new PaletteVersion(Bedrock_v527.CODEC.getProtocolVersion(),
Collections.singletonMap("minecraft:trader_llama_spawn_egg", "minecraft:llama_spawn_egg")));
paletteVersions.put("1_19_10", new PaletteVersion(Bedrock_v534.CODEC.getProtocolVersion(), Collections.emptyMap()));
paletteVersions.put("1_19_20", new PaletteVersion(Bedrock_v544.CODEC.getProtocolVersion(), Collections.emptyMap()));
paletteVersions.put("1_19_50", new PaletteVersion(Bedrock_v560.CODEC.getProtocolVersion(), Collections.emptyMap()));
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();

View File

@ -90,8 +90,6 @@ public class RecipeRegistryPopulator {
Collections.singletonList(CraftingData.fromMulti(UUID.fromString("d392b075-4ba1-40ae-8789-af868d56f6ce"), ++LAST_RECIPE_NET_ID)));
craftingData.put(RecipeType.CRAFTING_SPECIAL_MAPCLONING,
Collections.singletonList(CraftingData.fromMulti(UUID.fromString("85939755-ba10-4d9d-a4cc-efb7a8e943c4"), ++LAST_RECIPE_NET_ID)));
craftingData.put(RecipeType.CRAFTING_SPECIAL_BANNERADDPATTERN,
Collections.singletonList(CraftingData.fromMulti(UUID.fromString("b5c5d105-75a2-4076-af2b-923ea2bf4bf0"), ++LAST_RECIPE_NET_ID)));
// https://github.com/pmmp/PocketMine-MP/blob/stable/src/pocketmine/inventory/MultiRecipe.php

View File

@ -48,4 +48,6 @@ public class GeyserMappingItem {
@JsonProperty("repair_materials") List<String> repairMaterials;
@JsonProperty("has_suspicious_stew_effect") boolean hasSuspiciousStewEffect = false;
@JsonProperty("dye_color") int dyeColor = -1;
@JsonProperty("is_edible") boolean edible = false;
@JsonProperty("is_entity_placer") boolean entityPlacer = false;
}

View File

@ -70,7 +70,6 @@ import com.nimbusds.jwt.SignedJWT;
import com.nukkitx.nbt.NbtMap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoop;
import it.unimi.dsi.fastutil.bytes.ByteArrays;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
@ -137,7 +136,6 @@ import org.geysermc.geyser.inventory.recipe.GeyserStonecutterData;
import org.geysermc.geyser.level.JavaDimension;
import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.level.physics.CollisionManager;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.network.netty.LocalSession;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.type.BlockMappings;
@ -336,6 +334,11 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
*/
@Setter
private String worldName = null;
/**
* As of Java 1.19.3, the client only uses these for commands.
*/
@Setter
private String[] levels;
private boolean sneaking;
@ -656,6 +659,12 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
creativePacket.setContents(this.itemMappings.getCreativeItems());
upstream.sendPacket(creativePacket);
// Potion mixes are registered by default, as they are needed to be able to put ingredients into the brewing stand.
CraftingDataPacket craftingDataPacket = new CraftingDataPacket();
craftingDataPacket.setCleanRecipes(true);
craftingDataPacket.getPotionMixData().addAll(Registries.POTION_MIXES.get());
upstream.sendPacket(craftingDataPacket);
PlayStatusPacket playStatusPacket = new PlayStatusPacket();
playStatusPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN);
upstream.sendPacket(playStatusPacket);
@ -1389,14 +1398,14 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
* Sends a chat message to the Java server.
*/
public void sendChat(String message) {
sendDownstreamPacket(new ServerboundChatPacket(message, Instant.now().toEpochMilli(), 0L, ByteArrays.EMPTY_ARRAY, false, Collections.emptyList(), null));
sendDownstreamPacket(new ServerboundChatPacket(message, Instant.now().toEpochMilli(), 0L, null, 0, new BitSet()));
}
/**
* Sends a command to the Java server.
*/
public void sendCommand(String command) {
sendDownstreamPacket(new ServerboundChatCommandPacket(command, Instant.now().toEpochMilli(), 0L, Collections.emptyList(), false, Collections.emptyList(), null));
sendDownstreamPacket(new ServerboundChatCommandPacket(command, Instant.now().toEpochMilli(), 0L, Collections.emptyList(), 0, new BitSet()));
}
public void setServerRenderDistance(int renderDistance) {
@ -1454,7 +1463,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
startGamePacket.setRotation(Vector2f.from(1, 1));
startGamePacket.setSeed(-1L);
startGamePacket.setDimensionId(DimensionUtils.javaToBedrock(dimension));
startGamePacket.setDimensionId(DimensionUtils.javaToBedrock(chunkCache.getBedrockDimension()));
startGamePacket.setGeneratorId(1);
startGamePacket.setLevelGameType(GameType.SURVIVAL);
startGamePacket.setDifficulty(1);
@ -1655,76 +1664,40 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
boolean spectator = gameMode == GameMode.SPECTATOR;
boolean worldImmutable = gameMode == GameMode.ADVENTURE || spectator;
if (GameProtocol.supports1_19_10(this)) {
UpdateAdventureSettingsPacket adventureSettingsPacket = new UpdateAdventureSettingsPacket();
adventureSettingsPacket.setNoMvP(false);
adventureSettingsPacket.setNoPvM(false);
adventureSettingsPacket.setImmutableWorld(worldImmutable);
adventureSettingsPacket.setShowNameTags(false);
adventureSettingsPacket.setAutoJump(true);
sendUpstreamPacket(adventureSettingsPacket);
UpdateAdventureSettingsPacket adventureSettingsPacket = new UpdateAdventureSettingsPacket();
adventureSettingsPacket.setNoMvP(false);
adventureSettingsPacket.setNoPvM(false);
adventureSettingsPacket.setImmutableWorld(worldImmutable);
adventureSettingsPacket.setShowNameTags(false);
adventureSettingsPacket.setAutoJump(true);
sendUpstreamPacket(adventureSettingsPacket);
UpdateAbilitiesPacket updateAbilitiesPacket = new UpdateAbilitiesPacket();
updateAbilitiesPacket.setUniqueEntityId(bedrockId);
updateAbilitiesPacket.setCommandPermission(commandPermission);
updateAbilitiesPacket.setPlayerPermission(playerPermission);
UpdateAbilitiesPacket updateAbilitiesPacket = new UpdateAbilitiesPacket();
updateAbilitiesPacket.setUniqueEntityId(bedrockId);
updateAbilitiesPacket.setCommandPermission(commandPermission);
updateAbilitiesPacket.setPlayerPermission(playerPermission);
AbilityLayer abilityLayer = new AbilityLayer();
Set<Ability> abilities = abilityLayer.getAbilityValues();
if (canFly || spectator) {
abilities.add(Ability.MAY_FLY);
}
// Default stuff we have to fill in
abilities.add(Ability.BUILD);
abilities.add(Ability.MINE);
// Needed so you can drop items
abilities.add(Ability.DOORS_AND_SWITCHES);
if (gameMode == GameMode.CREATIVE) {
// Needed so the client doesn't attempt to take away items
abilities.add(Ability.INSTABUILD);
}
if (commandPermission == CommandPermission.GAME_DIRECTORS) {
// Fixes a bug? since 1.19.11 where the player can change their gamemode in Bedrock settings and
// a packet is not sent to the server.
// https://github.com/GeyserMC/Geyser/issues/3191
abilities.add(Ability.OPERATOR_COMMANDS);
}
if (flying || spectator) {
if (spectator && !flying) {
// We're "flying locked" in this gamemode
flying = true;
ServerboundPlayerAbilitiesPacket abilitiesPacket = new ServerboundPlayerAbilitiesPacket(true);
sendDownstreamPacket(abilitiesPacket);
}
abilities.add(Ability.FLYING);
}
if (spectator) {
abilities.add(Ability.NO_CLIP);
}
abilityLayer.setLayerType(AbilityLayer.Type.BASE);
abilityLayer.setFlySpeed(flySpeed);
// https://github.com/GeyserMC/Geyser/issues/3139 as of 1.19.10
abilityLayer.setWalkSpeed(walkSpeed == 0f ? 0.01f : walkSpeed);
Collections.addAll(abilityLayer.getAbilitiesSet(), USED_ABILITIES);
updateAbilitiesPacket.getAbilityLayers().add(abilityLayer);
sendUpstreamPacket(updateAbilitiesPacket);
return;
AbilityLayer abilityLayer = new AbilityLayer();
Set<Ability> abilities = abilityLayer.getAbilityValues();
if (canFly || spectator) {
abilities.add(Ability.MAY_FLY);
}
AdventureSettingsPacket adventureSettingsPacket = new AdventureSettingsPacket();
adventureSettingsPacket.setUniqueEntityId(bedrockId);
adventureSettingsPacket.setCommandPermission(commandPermission);
adventureSettingsPacket.setPlayerPermission(playerPermission);
// Default stuff we have to fill in
abilities.add(Ability.BUILD);
abilities.add(Ability.MINE);
// Needed so you can drop items
abilities.add(Ability.DOORS_AND_SWITCHES);
if (gameMode == GameMode.CREATIVE) {
// Needed so the client doesn't attempt to take away items
abilities.add(Ability.INSTABUILD);
}
Set<AdventureSetting> flags = adventureSettingsPacket.getSettings();
if (canFly || spectator) {
flags.add(AdventureSetting.MAY_FLY);
if (commandPermission == CommandPermission.GAME_DIRECTORS) {
// Fixes a bug? since 1.19.11 where the player can change their gamemode in Bedrock settings and
// a packet is not sent to the server.
// https://github.com/GeyserMC/Geyser/issues/3191
abilities.add(Ability.OPERATOR_COMMANDS);
}
if (flying || spectator) {
@ -1734,20 +1707,21 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
ServerboundPlayerAbilitiesPacket abilitiesPacket = new ServerboundPlayerAbilitiesPacket(true);
sendDownstreamPacket(abilitiesPacket);
}
flags.add(AdventureSetting.FLYING);
}
if (worldImmutable) {
flags.add(AdventureSetting.WORLD_IMMUTABLE);
abilities.add(Ability.FLYING);
}
if (spectator) {
flags.add(AdventureSetting.NO_CLIP);
abilities.add(Ability.NO_CLIP);
}
flags.add(AdventureSetting.AUTO_JUMP);
abilityLayer.setLayerType(AbilityLayer.Type.BASE);
abilityLayer.setFlySpeed(flySpeed);
// https://github.com/GeyserMC/Geyser/issues/3139 as of 1.19.10
abilityLayer.setWalkSpeed(walkSpeed == 0f ? 0.01f : walkSpeed);
Collections.addAll(abilityLayer.getAbilitiesSet(), USED_ABILITIES);
sendUpstreamPacket(adventureSettingsPacket);
updateAbilitiesPacket.getAbilityLayers().add(abilityLayer);
sendUpstreamPacket(updateAbilitiesPacket);
}
private int getRenderDistance() {

View File

@ -43,6 +43,8 @@ import java.util.Optional;
public class AsteriskSerializer extends StdSerializer<Object> implements ContextualSerializer {
public static final String[] NON_SENSITIVE_ADDRESSES = {"", "0.0.0.0", "localhost", "127.0.0.1", "auto", "unknown"};
public static boolean showSensitive = false;
@Target({ElementType.FIELD})
@ -91,11 +93,11 @@ public class AsteriskSerializer extends StdSerializer<Object> implements Context
}
private boolean isSensitiveIp(String ip) {
if (ip.equalsIgnoreCase("localhost") || ip.equalsIgnoreCase("auto")) {
// `auto` should not be shown unless there is an obscure issue with setting the localhost address
return false;
for (String address : NON_SENSITIVE_ADDRESSES) {
if (address.equalsIgnoreCase(ip)) {
return false;
}
}
return !ip.isEmpty() && !ip.equals("0.0.0.0") && !ip.equals("127.0.0.1");
return true;
}
}

View File

@ -65,8 +65,8 @@ public abstract class AbstractBlockInventoryTranslator extends BaseInventoryTran
}
@Override
public void prepareInventory(GeyserSession session, Inventory inventory) {
holder.prepareInventory(this, session, inventory);
public boolean prepareInventory(GeyserSession session, Inventory inventory) {
return holder.prepareInventory(this, session, inventory);
}
@Override

View File

@ -59,10 +59,12 @@ public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator {
CraftRecipeOptionalStackRequestActionData data = (CraftRecipeOptionalStackRequestActionData) request.getActions()[0];
AnvilContainer container = (AnvilContainer) inventory;
// Required as of 1.18.30 - FilterTextPackets no longer appear to be sent
String name = request.getFilterStrings()[data.getFilteredStringIndex()];
if (!Objects.equals(name, container.getNewName())) {
container.checkForRename(session, name);
if (request.getFilterStrings().length != 0) {
// Required as of 1.18.30 - FilterTextPackets no longer appear to be sent
String name = request.getFilterStrings()[data.getFilteredStringIndex()];
if (!Objects.equals(name, container.getNewName())) { // TODO is this still necessary after pre-1.19.50 support is dropped?
container.checkForRename(session, name);
}
}
return super.translateRequest(session, inventory, request);

View File

@ -105,7 +105,7 @@ public abstract class InventoryTranslator {
public final int size;
public abstract void prepareInventory(GeyserSession session, Inventory inventory);
public abstract boolean prepareInventory(GeyserSession session, Inventory inventory);
public abstract void openInventory(GeyserSession session, Inventory inventory);
public abstract void closeInventory(GeyserSession session, Inventory inventory);
public abstract void updateProperty(GeyserSession session, Inventory inventory, int key, int value);

View File

@ -55,7 +55,8 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator {
}
@Override
public void prepareInventory(GeyserSession session, Inventory inventory) {
public boolean prepareInventory(GeyserSession session, Inventory inventory) {
return true;
}
@Override

View File

@ -98,7 +98,7 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator {
}
@Override
public void prepareInventory(GeyserSession session, Inventory inventory) {
public boolean prepareInventory(GeyserSession session, Inventory inventory) {
MerchantContainer merchantInventory = (MerchantContainer) inventory;
if (merchantInventory.getVillager() == null) {
long geyserId = session.getEntityCache().getNextEntityId().incrementAndGet();
@ -121,6 +121,8 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator {
merchantInventory.setVillager(villager);
}
return true;
}
@Override

View File

@ -527,7 +527,8 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
}
@Override
public void prepareInventory(GeyserSession session, Inventory inventory) {
public boolean prepareInventory(GeyserSession session, Inventory inventory) {
return true;
}
@Override

View File

@ -41,6 +41,7 @@ import org.geysermc.geyser.level.block.DoubleChestValue;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.level.block.entity.DoubleChestBlockEntityTranslator;
import org.geysermc.geyser.util.InventoryUtils;
public class DoubleChestInventoryTranslator extends ChestInventoryTranslator {
private final int defaultJavaBlockState;
@ -51,7 +52,7 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator {
}
@Override
public void prepareInventory(GeyserSession session, Inventory inventory) {
public boolean prepareInventory(GeyserSession session, Inventory inventory) {
// See BlockInventoryHolder - same concept there except we're also dealing with a specific block state
if (session.getLastInteractionPlayerPosition().equals(session.getPlayerEntity().getPosition())) {
int javaBlockId = session.getGeyser().getWorldManager().getBlockAt(session, session.getLastInteractionBlockPosition());
@ -77,11 +78,16 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator {
dataPacket.setData(tag.build());
dataPacket.setBlockPosition(session.getLastInteractionBlockPosition());
session.sendUpstreamPacket(dataPacket);
return;
return true;
}
}
Vector3i position = session.getPlayerEntity().getPosition().toInt().add(Vector3i.UP);
Vector3i position = InventoryUtils.findAvailableWorldSpace(session);
if (position == null) {
return false;
}
Vector3i pairPosition = position.add(Vector3i.UNIT_X);
BlockDefinition definition = session.getBlockMappings().getBedrockBlock(defaultJavaBlockState);
@ -126,6 +132,8 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator {
session.sendUpstreamPacket(dataPacket);
inventory.setHolderPosition(position);
return true;
}
@Override

View File

@ -52,8 +52,8 @@ public class SingleChestInventoryTranslator extends ChestInventoryTranslator {
}
@Override
public void prepareInventory(GeyserSession session, Inventory inventory) {
holder.prepareInventory(this, session, inventory);
public boolean prepareInventory(GeyserSession session, Inventory inventory) {
return holder.prepareInventory(this, session, inventory);
}
@Override

View File

@ -40,7 +40,8 @@ public abstract class AbstractHorseInventoryTranslator extends BaseInventoryTran
}
@Override
public void prepareInventory(GeyserSession session, Inventory inventory) {
public boolean prepareInventory(GeyserSession session, Inventory inventory) {
return true;
}
@Override

View File

@ -32,7 +32,7 @@ import com.nukkitx.nbt.NbtMapBuilder;
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.SignUtils;
@BlockEntity(type = BlockEntityType.SIGN)
@BlockEntity(type = {BlockEntityType.SIGN, BlockEntityType.HANGING_SIGN})
public class SignBlockEntityTranslator extends BlockEntityTranslator {
/**
* Maps a color stored in a sign's Color tag to its ARGB value.
@ -88,6 +88,7 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator {
signWidth += SignUtils.getCharacterWidth(c);
}
// todo 1.20: update for hanging signs (smaller width). Currently OK because bedrock sees hanging signs as normal signs
if (signWidth <= SignUtils.BEDROCK_CHARACTER_WIDTH_MAX) {
finalSignLine.append(c);
} else {

View File

@ -27,6 +27,7 @@ package org.geysermc.geyser.translator.level.block.entity;
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import com.nukkitx.nbt.NbtMapBuilder;
import org.geysermc.geyser.entity.EntityDefinition;
@ -68,16 +69,18 @@ public class SpawnerBlockEntityTranslator extends BlockEntityTranslator {
CompoundTag spawnData = tag.get("SpawnData");
if (spawnData != null) {
String entityID = (String) ((CompoundTag) spawnData.get("entity"))
.get("id")
.getValue();
builder.put("EntityIdentifier", entityID);
StringTag idTag = ((CompoundTag) spawnData.get("entity")).get("id");
if (idTag != null) {
// As of 1.19.3, spawners can be empty
String entityId = idTag.getValue();
builder.put("EntityIdentifier", entityId);
EntityDefinition<?> definition = Registries.JAVA_ENTITY_IDENTIFIERS.get(entityID);
if (definition != null) {
builder.put("DisplayEntityWidth", definition.width());
builder.put("DisplayEntityHeight", definition.height());
builder.put("DisplayEntityScale", 1.0f);
EntityDefinition<?> definition = Registries.JAVA_ENTITY_IDENTIFIERS.get(entityId);
if (definition != null) {
builder.put("DisplayEntityWidth", definition.width());
builder.put("DisplayEntityHeight", definition.height());
builder.put("DisplayEntityScale", 1.0f);
}
}
}

View File

@ -57,6 +57,10 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator<BlockEnti
// This converts the message into the array'd message Java wants
for (char character : text.toCharArray()) {
widthCount += SignUtils.getCharacterWidth(character);
// todo 1.20: update for hanging signs (smaller width). Currently bedrock thinks hanging signs are normal,
// so it thinks hanging signs have more width than they actually do. Seems like JE just truncates it.
// 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

View File

@ -29,6 +29,7 @@ import org.cloudburstmc.protocol.bedrock.packet.CommandRequestPacket;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.translator.text.MessageTranslator;
@ -38,16 +39,14 @@ public class BedrockCommandRequestTranslator extends PacketTranslator<CommandReq
@Override
public void translate(GeyserSession session, CommandRequestPacket packet) {
String command = packet.getCommand().replace("/", "");
String command = MessageTranslator.convertToPlainText(packet.getCommand());
if (!(session.getGeyser().getPlatformType() == PlatformType.STANDALONE
&& GeyserImpl.getInstance().commandManager().runCommand(session, command))) {
String message = packet.getCommand().trim();
if (MessageTranslator.isTooLong(message, session)) {
&& GeyserImpl.getInstance().commandManager().runCommand(session, command.substring(1)))) {
if (MessageTranslator.isTooLong(command, session)) {
return;
}
session.sendCommand(message.substring(1));
session.sendCommand(command.substring(1));
}
}
}

View File

@ -64,7 +64,6 @@ import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.PlayerInventory;
import org.geysermc.geyser.inventory.click.Click;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.registry.type.ItemMappings;
@ -478,10 +477,8 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
InteractAction.ATTACK, session.isSneaking());
session.sendDownstreamPacket(attackPacket);
if (GameProtocol.supports1_19_10(session)) {
// Since 1.19.10, LevelSoundEventPackets are no longer sent by the client when attacking entities
CooldownUtils.sendCooldown(session);
}
// Since 1.19.10, LevelSoundEventPackets are no longer sent by the client when attacking entities
CooldownUtils.sendCooldown(session);
}
}
break;

View File

@ -37,21 +37,7 @@ public class BedrockTextTranslator extends PacketTranslator<TextPacket> {
@Override
public void translate(GeyserSession session, TextPacket packet) {
String message = packet.getMessage();
// The order here is important - strip out illegal characters first, then check if it's blank
// (in case the message is blank after removing)
if (message.indexOf(ChatColor.ESCAPE) != -1) {
// Filter out all escape characters - Java doesn't let you type these
StringBuilder builder = new StringBuilder();
for (int i = 0; i < message.length(); i++) {
char c = message.charAt(i);
if (c != ChatColor.ESCAPE) {
builder.append(c);
}
}
message = builder.toString();
}
String message = MessageTranslator.convertToPlainText(packet.getMessage());
if (message.isBlank()) {
// Java Edition (as of 1.17.1) just doesn't pass on these messages, so... we won't either!

View File

@ -83,6 +83,9 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
attributesPacket.setRuntimeEntityId(entity.getGeyserId());
attributesPacket.getAttributes().addAll(entity.getAttributes().values());
session.sendUpstreamPacket(attributesPacket);
// Bounding box must be sent after a player dies and respawns since 1.19.40
entity.updateBoundingBox();
break;
case START_SWIMMING:
if (!entity.getFlag(EntityFlag.SWIMMING)) {

View File

@ -45,6 +45,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap;
import lombok.Getter;
import lombok.ToString;
import net.kyori.adventure.text.format.NamedTextColor;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.event.downstream.ServerDefineCommandsEvent;
import org.geysermc.geyser.command.GeyserCommandManager;
@ -138,7 +139,7 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
// Get and update the commandArgs list with the found arguments
if (node.getChildIndices().length >= 1) {
for (int childIndex : node.getChildIndices()) {
commandArgs.computeIfAbsent(nodeIndex, ArrayList::new).add(nodes[childIndex]);
commandArgs.computeIfAbsent(nodeIndex, ($) -> new ArrayList<>()).add(nodes[childIndex]);
}
}
@ -205,7 +206,7 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
if (commandNode.getChildIndices().length >= 1) {
// Create the root param node and build all the children
ParamInfo rootParam = new ParamInfo(commandNode, null);
rootParam.buildChildren(session, allNodes);
rootParam.buildChildren(new CommandBuilderContext(session), allNodes);
List<CommandParamData[]> treeData = rootParam.getTree();
@ -218,11 +219,11 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
/**
* Convert Java edition command types to Bedrock edition
*
* @param session the session
* @param context the session's command context
* @param node Command type to convert
* @return Bedrock parameter data type
*/
private static Object mapCommandType(GeyserSession session, CommandNode node) {
private static Object mapCommandType(CommandBuilderContext context, CommandNode node) {
CommandParser parser = node.getParser();
if (parser == null) {
return CommandParam.STRING;
@ -239,21 +240,24 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
case RESOURCE_LOCATION, FUNCTION -> CommandParam.FILE_PATH;
case BOOL -> ENUM_BOOLEAN;
case OPERATION -> CommandParam.OPERATOR; // ">=", "==", etc
case BLOCK_STATE -> BlockRegistries.JAVA_TO_BEDROCK_IDENTIFIERS.get().keySet().toArray(new String[0]);
case ITEM_STACK -> session.getItemMappings().getItemNames();
case ITEM_ENCHANTMENT -> Enchantment.JavaEnchantment.ALL_JAVA_IDENTIFIERS;
case ENTITY_SUMMON -> Registries.JAVA_ENTITY_IDENTIFIERS.get().keySet().toArray(new String[0]);
case BLOCK_STATE -> context.getBlockStates();
case ITEM_STACK -> context.session.getItemMappings().getItemNames();
case COLOR -> VALID_COLORS;
case SCOREBOARD_SLOT -> VALID_SCOREBOARD_SLOTS;
case MOB_EFFECT -> ALL_EFFECT_IDENTIFIERS;
case RESOURCE, RESOURCE_OR_TAG -> {
String resource = ((ResourceProperties) node.getProperties()).getRegistryKey();
if (resource.equals("minecraft:attribute")) {
yield ATTRIBUTES;
} else {
yield CommandParam.STRING;
}
}
case RESOURCE -> handleResource(context, ((ResourceProperties) node.getProperties()).getRegistryKey(), false);
case RESOURCE_OR_TAG -> handleResource(context, ((ResourceProperties) node.getProperties()).getRegistryKey(), true);
case DIMENSION -> context.session.getLevels();
default -> CommandParam.STRING;
};
}
private static Object handleResource(CommandBuilderContext context, String resource, boolean tags) {
return switch (resource) {
case "minecraft:attribute" -> ATTRIBUTES;
case "minecraft:enchantment" -> Enchantment.JavaEnchantment.ALL_JAVA_IDENTIFIERS;
case "minecraft:entity_type" -> context.getEntityTypes();
case "minecraft:mob_effect" -> ALL_EFFECT_IDENTIFIERS;
case "minecraft:worldgen/biome" -> tags ? context.getBiomesWithTags() : context.getBiomes();
default -> CommandParam.STRING;
};
}
@ -261,7 +265,55 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
/**
* Stores the command description and parameter data for best optimizing the Bedrock commands packet.
*/
private static record BedrockCommandInfo(String name, String description, CommandParamData[][] paramData) implements ServerDefineCommandsEvent.CommandInfo {
private record BedrockCommandInfo(String name, String description, CommandParamData[][] paramData) implements ServerDefineCommandsEvent.CommandInfo {
}
/**
* Stores command completions so we don't have to rebuild the same values multiple times.
*/
@MonotonicNonNull
private static class CommandBuilderContext {
private final GeyserSession session;
private Object biomesWithTags;
private Object biomesNoTags;
private String[] blockStates;
private String[] entityTypes;
CommandBuilderContext(GeyserSession session) {
this.session = session;
}
private Object getBiomes() {
if (biomesNoTags != null) {
return biomesNoTags;
}
String[] identifiers = session.getGeyser().getWorldManager().getBiomeIdentifiers(false);
return (biomesNoTags = identifiers != null ? identifiers : CommandParam.STRING);
}
private Object getBiomesWithTags() {
if (biomesWithTags != null) {
return biomesWithTags;
}
String[] identifiers = session.getGeyser().getWorldManager().getBiomeIdentifiers(true);
return (biomesWithTags = identifiers != null ? identifiers : CommandParam.STRING);
}
private String[] getBlockStates() {
if (blockStates != null) {
return blockStates;
}
return (blockStates = BlockRegistries.JAVA_TO_BEDROCK_IDENTIFIERS.get().keySet().toArray(new String[0]));
}
private String[] getEntityTypes() {
if (entityTypes != null) {
return entityTypes;
}
return (entityTypes = Registries.JAVA_ENTITY_IDENTIFIERS.get().keySet().toArray(new String[0]));
}
}
@Getter
@ -286,10 +338,10 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
/**
* Build the array of all the child parameters (recursive)
*
* @param session the session
* @param context the session's command builder context
* @param allNodes Every command node
*/
public void buildChildren(GeyserSession session, CommandNode[] allNodes) {
public void buildChildren(CommandBuilderContext context, CommandNode[] allNodes) {
for (int paramID : paramNode.getChildIndices()) {
CommandNode paramNode = allNodes[paramID];
@ -343,7 +395,7 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
}
} else {
// Put the non-enum param into the list
Object mappedType = mapCommandType(session, paramNode);
Object mappedType = mapCommandType(context, paramNode);
CommandEnumData enumData = null;
CommandParam type = null;
boolean optional = this.paramNode.isExecutable();
@ -353,7 +405,7 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
map.put(s, Set.of());
}
enumData = new CommandEnumData(paramNode.getParser().name().toLowerCase(Locale.ROOT), map, false);
enumData = new CommandEnumData(getEnumDataName(paramNode).toLowerCase(Locale.ROOT), map, false);
} else {
type = (CommandParam) mappedType;
// Bedrock throws a fit if an optional message comes after a string or target
@ -377,10 +429,25 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
// Recursively build all child options
for (ParamInfo child : children) {
child.buildChildren(session, allNodes);
child.buildChildren(context, allNodes);
}
}
/**
* Mitigates https://github.com/GeyserMC/Geyser/issues/3411. Not a perfect solution.
*/
private static String getEnumDataName(CommandNode node) {
if (node.getProperties() instanceof ResourceProperties properties) {
String registryKey = properties.getRegistryKey();
int identifierSplit = registryKey.indexOf(':');
if (identifierSplit != -1) {
return registryKey.substring(identifierSplit);
}
return registryKey;
}
return node.getParser().name();
}
/**
* Comparing CommandNode type a and b, determine if they are in the same overload.
* <p>

View File

@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.translator.protocol.java.level;
package org.geysermc.geyser.translator.protocol.java;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundCustomSoundPacket;
import org.cloudburstmc.math.vector.Vector3f;
@ -31,19 +31,13 @@ import org.cloudburstmc.protocol.bedrock.packet.PlaySoundPacket;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.util.SoundUtils;
import org.geysermc.geyser.translator.text.MessageTranslator;
@Translator(packet = ClientboundCustomSoundPacket.class)
public class JavaCustomSoundTranslator extends PacketTranslator<ClientboundCustomSoundPacket> {
@Translator(packet = ClientboundDisguisedChatPacket.class)
public class JavaDisguisedChatTranslator extends PacketTranslator<ClientboundDisguisedChatPacket> {
@Override
public void translate(GeyserSession session, ClientboundCustomSoundPacket packet) {
PlaySoundPacket playSoundPacket = new PlaySoundPacket();
playSoundPacket.setSound(SoundUtils.translatePlaySound(packet.getSound()));
playSoundPacket.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ()));
playSoundPacket.setVolume(packet.getVolume());
playSoundPacket.setPitch(packet.getPitch());
session.sendUpstreamPacket(playSoundPacket);
public void translate(GeyserSession session, ClientboundDisguisedChatPacket packet) {
MessageTranslator.handleChatPacket(session, packet.getMessage(), packet.getChatType(), packet.getTargetName(), packet.getName());
}
}

View File

@ -87,6 +87,7 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
session.getWorldCache().removeScoreboard();
}
session.setWorldName(packet.getWorldName());
session.setLevels(packet.getWorldNames());
BiomeTranslator.loadServerBiomes(session, packet.getRegistry());
session.getTagCache().clear();
@ -99,16 +100,11 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
if (needsSpawnPacket) {
// The player has yet to spawn so let's do that using some of the information in this Java packet
session.setDimension(newDimension);
session.setDimensionType(dimensions.get(newDimension));
ChunkUtils.loadDimension(session);
DimensionUtils.setBedrockDimension(session, newDimension);
session.connect();
// It is now safe to send these packets
session.getUpstream().sendPostStartGamePackets();
} else if (!session.isSpawned()) {
// Called for online mode, being presented with a GUI before logging ing
session.setDimensionType(dimensions.get(newDimension));
ChunkUtils.loadDimension(session);
}
AdventureSettingsPacket bedrockPacket = new AdventureSettingsPacket();
@ -151,5 +147,7 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
// If the player is spawning into the "fake" nether, send them some fog
session.sendFog("minecraft:fog_hell");
}
ChunkUtils.loadDimension(session);
}
}

View File

@ -28,55 +28,17 @@ package org.geysermc.geyser.translator.protocol.java;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundPlayerChatPacket;
import org.cloudburstmc.protocol.bedrock.packet.TextPacket;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TranslatableComponent;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.TextDecoration;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.translator.text.MessageTranslator;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@Translator(packet = ClientboundPlayerChatPacket.class)
public class JavaPlayerChatTranslator extends PacketTranslator<ClientboundPlayerChatPacket> {
@Override
public void translate(GeyserSession session, ClientboundPlayerChatPacket packet) {
TextPacket textPacket = new TextPacket();
textPacket.setPlatformChatId("");
textPacket.setSourceName("");
textPacket.setXuid(session.getAuthData().xuid());
textPacket.setType(TextPacket.Type.CHAT);
textPacket.setNeedsTranslation(false);
Component message = packet.getUnsignedContent() == null ? packet.getMessageDecorated() : packet.getUnsignedContent();
TextDecoration decoration = session.getChatTypes().get(packet.getChatType());
if (decoration != null) {
// As of 1.19 - do this to apply all the styling for signed messages
// Though, Bedrock cannot care about the signed stuff.
TranslatableComponent.Builder withDecoration = Component.translatable()
.key(decoration.translationKey())
.style(decoration.style());
Set<TextDecoration.Parameter> parameters = decoration.parameters();
List<Component> args = new ArrayList<>(3);
if (parameters.contains(TextDecoration.Parameter.TARGET)) {
args.add(packet.getTargetName());
}
if (parameters.contains(TextDecoration.Parameter.SENDER)) {
args.add(packet.getName());
}
if (parameters.contains(TextDecoration.Parameter.CONTENT)) {
args.add(message);
}
withDecoration.args(args);
textPacket.setMessage(MessageTranslator.convertMessage(withDecoration.build(), session.locale()));
} else {
textPacket.setMessage(MessageTranslator.convertMessage(message, session.locale()));
}
session.sendUpstreamPacket(textPacket);
Component message = packet.getUnsignedContent() == null ? Component.text(packet.getContent()) : packet.getUnsignedContent();
MessageTranslator.handleChatPacket(session, message, packet.getChatType(), packet.getTargetName(), packet.getName());
}
}

View File

@ -37,6 +37,7 @@ import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.util.ChunkUtils;
import org.geysermc.geyser.util.DimensionUtils;
@Translator(packet = ClientboundRespawnPacket.class)
@ -83,16 +84,15 @@ public class JavaRespawnTranslator extends PacketTranslator<ClientboundRespawnPa
String newDimension = packet.getDimension();
if (!session.getDimension().equals(newDimension) || !packet.getWorldName().equals(session.getWorldName())) {
// Switching to a new world (based off the world name change); send a fake dimension change
if (!packet.getWorldName().equals(session.getWorldName()) && (session.getDimension().equals(newDimension)
// Ensure that the player never ever dimension switches to the same dimension - BAD
// Can likely be removed if the Above Bedrock Nether Building option can be removed
|| DimensionUtils.javaToBedrock(session.getDimension()) == DimensionUtils.javaToBedrock(newDimension))) {
// Switching to a new world (based off the world name change or new dimension); send a fake dimension change
if (DimensionUtils.javaToBedrock(session.getDimension()) == DimensionUtils.javaToBedrock(newDimension)) {
String fakeDim = DimensionUtils.getTemporaryDimension(session.getDimension(), newDimension);
DimensionUtils.switchDimension(session, fakeDim);
}
session.setWorldName(packet.getWorldName());
DimensionUtils.switchDimension(session, newDimension);
ChunkUtils.loadDimension(session);
}
}
}

View File

@ -40,6 +40,6 @@ public class JavaSoundEntityTranslator extends PacketTranslator<ClientboundSound
if (entity == null) {
return;
}
SoundUtils.playBuiltinSound(session, packet.getSound(), entity.getPosition(), packet.getVolume(), packet.getPitch());
SoundUtils.playSound(session, packet.getSound(), entity.getPosition(), packet.getVolume(), packet.getPitch());
}
}

View File

@ -39,7 +39,7 @@ public class JavaPlayerCombatKillTranslator extends PacketTranslator<Clientbound
@Override
public void translate(GeyserSession session, ClientboundPlayerCombatKillPacket packet) {
if (packet.getPlayerId() == session.getPlayerEntity().getEntityId() && GameProtocol.supports1_19_10(session)) {
if (packet.getPlayerId() == session.getPlayerEntity().getEntityId()) {
Component deathMessage = packet.getMessage();
// TODO - could inject score in, but as of 1.19.10 newlines don't center and start at the left of the first text
DeathInfoPacket deathInfoPacket = new DeathInfoPacket();

View File

@ -0,0 +1,63 @@
/*
* Copyright (c) 2019-2022 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.geyser.translator.protocol.java.entity.player;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundPlayerInfoRemovePacket;
import com.nukkitx.protocol.bedrock.packet.PlayerListPacket;
import org.geysermc.geyser.entity.type.player.PlayerEntity;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import java.util.UUID;
@Translator(packet = ClientboundPlayerInfoRemovePacket.class)
public class JavaPlayerInfoRemoveTranslator extends PacketTranslator<ClientboundPlayerInfoRemovePacket> {
@Override
public void translate(GeyserSession session, ClientboundPlayerInfoRemovePacket packet) {
PlayerListPacket translate = new PlayerListPacket();
translate.setAction(PlayerListPacket.Action.REMOVE);
for (UUID id : packet.getProfileIds()) {
// As the player entity is no longer present, we can remove the entry
PlayerEntity entity = session.getEntityCache().removePlayerEntity(id);
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);
}
if (entity == session.getPlayerEntity()) {
// If removing ourself we use our AuthData UUID
translate.getEntries().add(new PlayerListPacket.Entry(session.getAuthData().uuid()));
} else {
translate.getEntries().add(new PlayerListPacket.Entry(id));
}
}
session.sendUpstreamPacket(translate);
}
}

View File

@ -1,125 +0,0 @@
/*
* Copyright (c) 2019-2022 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.geyser.translator.protocol.java.entity.player;
import com.github.steveice10.mc.auth.data.GameProfile;
import com.github.steveice10.mc.protocol.data.game.PlayerListEntry;
import com.github.steveice10.mc.protocol.data.game.PlayerListEntryAction;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundPlayerInfoPacket;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.packet.PlayerListPacket;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.entity.type.player.PlayerEntity;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.skin.SkinManager;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
@Translator(packet = ClientboundPlayerInfoPacket.class)
public class JavaPlayerInfoTranslator extends PacketTranslator<ClientboundPlayerInfoPacket> {
@Override
public void translate(GeyserSession session, ClientboundPlayerInfoPacket packet) {
if (packet.getAction() != PlayerListEntryAction.ADD_PLAYER && packet.getAction() != PlayerListEntryAction.REMOVE_PLAYER)
return;
PlayerListPacket translate = new PlayerListPacket();
translate.setAction(packet.getAction() == PlayerListEntryAction.ADD_PLAYER ? PlayerListPacket.Action.ADD : PlayerListPacket.Action.REMOVE);
for (PlayerListEntry entry : packet.getEntries()) {
switch (packet.getAction()) {
case ADD_PLAYER -> {
GameProfile profile = entry.getProfile();
PlayerEntity playerEntity;
boolean self = profile.getId().equals(session.getPlayerEntity().getUuid());
if (self) {
// Entity is ourself
playerEntity = session.getPlayerEntity();
} else {
playerEntity = session.getEntityCache().getPlayerEntity(profile.getId());
}
GameProfile.Property textures = profile.getProperty("textures");
String texturesProperty = textures == null ? null : textures.getValue();
if (playerEntity == null) {
// It's a new player
playerEntity = new PlayerEntity(
session,
-1,
session.getEntityCache().getNextEntityId().incrementAndGet(),
profile.getId(),
Vector3f.ZERO,
Vector3f.ZERO,
0, 0, 0,
profile.getName(),
texturesProperty
);
session.getEntityCache().addPlayerEntity(playerEntity);
} else {
playerEntity.setUsername(profile.getName());
playerEntity.setTexturesProperty(texturesProperty);
}
playerEntity.setPlayerList(true);
// We'll send our own PlayerListEntry in requestAndHandleSkinAndCape
// But we need to send other player's entries so they show up in the player list
// without processing their skin information - that'll be processed when they spawn in
if (self) {
SkinManager.requestAndHandleSkinAndCape(playerEntity, session, skinAndCape ->
GeyserImpl.getInstance().getLogger().debug("Loaded Local Bedrock Java Skin Data for " + session.getClientData().getUsername()));
} else {
playerEntity.setValid(true);
PlayerListPacket.Entry playerListEntry = SkinManager.buildCachedEntry(session, playerEntity);
translate.getEntries().add(playerListEntry);
}
}
case REMOVE_PLAYER -> {
// As the player entity is no longer present, we can remove the entry
PlayerEntity entity = session.getEntityCache().removePlayerEntity(entry.getProfile().getId());
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);
}
if (entity == session.getPlayerEntity()) {
// If removing ourself we use our AuthData UUID
translate.getEntries().add(new PlayerListPacket.Entry(session.getAuthData().uuid()));
} else {
translate.getEntries().add(new PlayerListPacket.Entry(entry.getProfile().getId()));
}
}
}
}
if (!translate.getEntries().isEmpty()) {
session.sendUpstreamPacket(translate);
}
}
}

View File

@ -0,0 +1,107 @@
/*
* Copyright (c) 2019-2022 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.geyser.translator.protocol.java.entity.player;
import com.github.steveice10.mc.auth.data.GameProfile;
import com.github.steveice10.mc.protocol.data.game.PlayerListEntry;
import com.github.steveice10.mc.protocol.data.game.PlayerListEntryAction;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundPlayerInfoUpdatePacket;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.packet.PlayerListPacket;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.entity.type.player.PlayerEntity;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.skin.SkinManager;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
@Translator(packet = ClientboundPlayerInfoUpdatePacket.class)
public class JavaPlayerInfoUpdateTranslator extends PacketTranslator<ClientboundPlayerInfoUpdatePacket> {
@Override
public void translate(GeyserSession session, ClientboundPlayerInfoUpdatePacket packet) {
if (!packet.getActions().contains(PlayerListEntryAction.ADD_PLAYER)) {
return;
}
PlayerListPacket translate = new PlayerListPacket();
translate.setAction(PlayerListPacket.Action.ADD);
for (PlayerListEntry entry : packet.getEntries()) {
GameProfile profile = entry.getProfile();
PlayerEntity playerEntity;
boolean self = profile.getId().equals(session.getPlayerEntity().getUuid());
if (self) {
// Entity is ourself
playerEntity = session.getPlayerEntity();
} else {
playerEntity = session.getEntityCache().getPlayerEntity(profile.getId());
}
GameProfile.Property textures = profile.getProperty("textures");
String texturesProperty = textures == null ? null : textures.getValue();
if (playerEntity == null) {
// It's a new player
playerEntity = new PlayerEntity(
session,
-1,
session.getEntityCache().getNextEntityId().incrementAndGet(),
profile.getId(),
Vector3f.ZERO,
Vector3f.ZERO,
0, 0, 0,
profile.getName(),
texturesProperty
);
session.getEntityCache().addPlayerEntity(playerEntity);
} else {
playerEntity.setUsername(profile.getName());
playerEntity.setTexturesProperty(texturesProperty);
}
playerEntity.setPlayerList(true);
// We'll send our own PlayerListEntry in requestAndHandleSkinAndCape
// But we need to send other player's entries so they show up in the player list
// without processing their skin information - that'll be processed when they spawn in
if (self) {
SkinManager.requestAndHandleSkinAndCape(playerEntity, session, skinAndCape ->
GeyserImpl.getInstance().getLogger().debug("Loaded Local Bedrock Java Skin Data for " + session.getClientData().getUsername()));
} else {
playerEntity.setValid(true);
PlayerListPacket.Entry playerListEntry = SkinManager.buildCachedEntry(session, playerEntity);
translate.getEntries().add(playerListEntry);
}
}
if (!translate.getEntries().isEmpty()) {
session.sendUpstreamPacket(translate);
}
}
}

View File

@ -31,6 +31,7 @@ import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.translator.inventory.PlayerInventoryTranslator;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.util.InventoryUtils;
@ -46,7 +47,7 @@ public class JavaContainerSetContentTranslator extends PacketTranslator<Clientbo
int inventorySize = inventory.getSize();
for (int i = 0; i < packet.getItems().length; i++) {
if (i > inventorySize) {
if (i >= inventorySize) {
GeyserImpl geyser = session.getGeyser();
geyser.getLogger().warning("ClientboundContainerSetContentPacket sent to " + session.bedrockUsername()
+ " that exceeds inventory size!");
@ -54,10 +55,7 @@ public class JavaContainerSetContentTranslator extends PacketTranslator<Clientbo
geyser.getLogger().debug(packet);
geyser.getLogger().debug(inventory);
}
InventoryTranslator translator = session.getInventoryTranslator();
if (translator != null) {
translator.updateInventory(session, inventory);
}
updateInventory(session, inventory, packet.getContainerId());
// 1.18.1 behavior: the previous items will be correctly set, but the state ID and carried item will not
// as this produces a stack trace on the client.
// If Java processes this correctly in the future, we can revert this behavior
@ -68,10 +66,7 @@ public class JavaContainerSetContentTranslator extends PacketTranslator<Clientbo
inventory.setItem(i, newItem, session);
}
InventoryTranslator translator = session.getInventoryTranslator();
if (translator != null) {
translator.updateInventory(session, inventory);
}
updateInventory(session, inventory, packet.getContainerId());
int stateId = packet.getStateId();
session.setEmulatePost1_16Logic(stateId > 0 || stateId != inventory.getStateId());
@ -80,4 +75,14 @@ public class JavaContainerSetContentTranslator extends PacketTranslator<Clientbo
session.getPlayerInventory().setCursor(GeyserItemStack.from(packet.getCarriedItem()), session);
InventoryUtils.updateCursor(session);
}
private void updateInventory(GeyserSession session, Inventory inventory, int containerId) {
InventoryTranslator translator = session.getInventoryTranslator();
if (containerId == 0 && !(translator instanceof PlayerInventoryTranslator)) {
// In rare cases, the window ID can still be 0 but Java treats it as valid
InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR.updateInventory(session, inventory);
} else if (translator != null) {
translator.updateInventory(session, inventory);
}
}
}

View File

@ -49,9 +49,9 @@ public class JavaExplodeTranslator extends PacketTranslator<ClientboundExplodePa
LevelEventGenericPacket levelEventPacket = new LevelEventGenericPacket();
levelEventPacket.setEventId(2026/*LevelEvent.PARTICLE_BLOCK_EXPLOSION*/);
NbtMapBuilder builder = NbtMap.builder();
builder.putFloat("originX", packet.getX());
builder.putFloat("originY", packet.getY());
builder.putFloat("originZ", packet.getZ());
builder.putFloat("originX", (float) packet.getX());
builder.putFloat("originY", (float) packet.getY());
builder.putFloat("originZ", (float) packet.getZ());
builder.putFloat("radius", packet.getRadius());
builder.putInt("size", packet.getExploded().size());
int i = 0;

View File

@ -51,6 +51,8 @@ public class JavaMapItemDataTranslator extends PacketTranslator<ClientboundMapIt
mapItemDataPacket.setLocked(packet.isLocked());
mapItemDataPacket.setOrigin(Vector3i.ZERO); // Required since 1.19.20
mapItemDataPacket.setScale(packet.getScale());
// Required as of 1.19.50
mapItemDataPacket.getTrackedEntityIds().add(packet.getMapId());
MapData data = packet.getData();
if (data != null) {

View File

@ -38,6 +38,6 @@ public class JavaSoundTranslator extends PacketTranslator<ClientboundSoundPacket
@Override
public void translate(GeyserSession session, ClientboundSoundPacket packet) {
Vector3f position = Vector3f.from(packet.getX(), packet.getY(), packet.getZ());
SoundUtils.playBuiltinSound(session, packet.getSound(), position, packet.getVolume(), packet.getPitch());
SoundUtils.playSound(session, packet.getSound(), position, packet.getVolume(), packet.getPitch());
}
}

View File

@ -27,7 +27,9 @@ package org.geysermc.geyser.translator.text;
import com.github.steveice10.mc.protocol.data.DefaultComponentSerializer;
import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor;
import com.nukkitx.protocol.bedrock.packet.TextPacket;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TranslatableComponent;
import net.kyori.adventure.text.renderer.TranslatableComponentRenderer;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
@ -36,8 +38,7 @@ import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.*;
import java.util.EnumMap;
import java.util.Map;
import java.util.*;
public class MessageTranslator {
// These are used for handling the translations of the messages
@ -201,6 +202,28 @@ public class MessageTranslator {
return GSON_SERIALIZER.serialize(component);
}
/**
* Convert legacy format message to plain text
*
* @param message Message to convert
* @return The plain text of the message
*/
public static String convertToPlainText(String message) {
char[] input = message.toCharArray();
char[] output = new char[input.length];
int outputSize = 0;
for (int i = 0, inputLength = input.length; i < inputLength; i++) {
char c = input[i];
if (c == ChatColor.ESCAPE) {
i++;
} else {
output[outputSize++] = c;
}
}
return new String(output, 0, outputSize);
}
/**
* Convert JSON and legacy format message to plain text
*
@ -228,6 +251,46 @@ public class MessageTranslator {
return PlainTextComponentSerializer.plainText().serialize(messageComponent);
}
public static void handleChatPacket(GeyserSession session, Component message, int chatType, Component targetName, Component sender) {
TextPacket textPacket = new TextPacket();
textPacket.setPlatformChatId("");
textPacket.setSourceName("");
textPacket.setXuid(session.getAuthData().xuid());
textPacket.setType(TextPacket.Type.CHAT);
textPacket.setNeedsTranslation(false);
TextDecoration decoration = session.getChatTypes().get(chatType);
if (decoration != null) {
// As of 1.19 - do this to apply all the styling for signed messages
// Though, Bedrock cannot care about the signed stuff.
TranslatableComponent.Builder withDecoration = Component.translatable()
.key(decoration.translationKey())
.style(decoration.style());
Set<TextDecoration.Parameter> parameters = decoration.parameters();
List<Component> args = new ArrayList<>(3);
if (parameters.contains(TextDecoration.Parameter.TARGET)) {
args.add(targetName);
}
if (parameters.contains(TextDecoration.Parameter.SENDER)) {
args.add(sender);
}
if (parameters.contains(TextDecoration.Parameter.CONTENT)) {
args.add(message);
}
withDecoration.args(args);
textPacket.setMessage(MessageTranslator.convertMessage(withDecoration.build(), session.locale()));
} else {
session.getGeyser().getLogger().debug("Likely illegal chat type detection found.");
if (session.getGeyser().getConfig().isDebugMode()) {
Thread.dumpStack();
}
textPacket.setMessage(MessageTranslator.convertMessage(message, session.locale()));
}
session.sendUpstreamPacket(textPacket);
}
/**
* Convert a team color to a chat color
*

View File

@ -218,7 +218,8 @@ public class ChunkUtils {
* This must be done after the player has switched dimensions so we know what their dimension is
*/
public static void loadDimension(GeyserSession session) {
JavaDimension dimension = session.getDimensionType();
JavaDimension dimension = session.getDimensions().get(session.getDimension());
session.setDimensionType(dimension);
int minY = dimension.minY();
int maxY = dimension.maxY();
@ -229,13 +230,7 @@ public class ChunkUtils {
throw new RuntimeException("Maximum Y must be a multiple of 16!");
}
BedrockDimension bedrockDimension = switch (session.getDimension()) {
case DimensionUtils.THE_END -> BedrockDimension.THE_END;
case DimensionUtils.NETHER -> DimensionUtils.isCustomBedrockNetherId() ? BedrockDimension.THE_END : BedrockDimension.THE_NETHER;
default -> BedrockDimension.OVERWORLD;
};
session.getChunkCache().setBedrockDimension(bedrockDimension);
BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension();
// Yell in the console if the world height is too height in the current scenario
// The constraints change depending on if the player is in the overworld or not, and if experimental height is enabled
// (Ignore this for the Nether. We can't change that at the moment without the workaround. :/ )

View File

@ -32,6 +32,8 @@ import org.cloudburstmc.protocol.bedrock.packet.ChunkRadiusUpdatedPacket;
import org.cloudburstmc.protocol.bedrock.packet.MobEffectPacket;
import org.cloudburstmc.protocol.bedrock.packet.StopSoundPacket;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.level.BedrockDimension;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.session.GeyserSession;
import java.util.Set;
@ -93,9 +95,10 @@ public class DimensionUtils {
changeDimensionPacket.setRespawn(true);
changeDimensionPacket.setPosition(pos);
session.sendUpstreamPacket(changeDimensionPacket);
session.setDimension(javaDimension);
session.setDimensionType(session.getDimensions().get(javaDimension));
ChunkUtils.loadDimension(session);
setBedrockDimension(session, javaDimension);
player.setPosition(pos);
session.setSpawned(false);
session.setLastChunkPosition(null);
@ -117,6 +120,19 @@ public class DimensionUtils {
stopSoundPacket.setSoundName("");
session.sendUpstreamPacket(stopSoundPacket);
// Kind of silly but Bedrock 1.19.50 requires an acknowledgement after the
// initial chunks are sent, prior to the client acknowledgement
if (GameProtocol.supports1_19_50(session)) {
// Note: send this before chunks are sent. Fixed https://github.com/GeyserMC/Geyser/issues/3421
PlayerActionPacket ackPacket = new PlayerActionPacket();
ackPacket.setRuntimeEntityId(player.getGeyserId());
ackPacket.setAction(PlayerActionType.DIMENSION_CHANGE_SUCCESS);
ackPacket.setBlockPosition(Vector3i.ZERO);
ackPacket.setResultPosition(Vector3i.ZERO);
ackPacket.setFace(0);
session.sendUpstreamPacket(ackPacket);
}
// TODO - fix this hack of a fix by sending the final dimension switching logic after sections have been sent.
// The client wants sections sent to it before it can successfully respawn.
ChunkUtils.sendEmptyChunks(session, player.getPosition().toInt(), 3, true);
@ -133,6 +149,24 @@ public class DimensionUtils {
}
}
public static void setBedrockDimension(GeyserSession session, String javaDimension) {
session.getChunkCache().setBedrockDimension(switch (javaDimension) {
case DimensionUtils.THE_END -> BedrockDimension.THE_END;
case DimensionUtils.NETHER -> DimensionUtils.isCustomBedrockNetherId() ? BedrockDimension.THE_END : BedrockDimension.THE_NETHER;
default -> BedrockDimension.OVERWORLD;
});
}
public static int javaToBedrock(BedrockDimension dimension) {
if (dimension == BedrockDimension.THE_NETHER) {
return BEDROCK_NETHER_ID;
} else if (dimension == BedrockDimension.THE_END) {
return 2;
} else {
return 0;
}
}
/**
* Map the Java edition dimension IDs to Bedrock edition
*
@ -171,7 +205,9 @@ public class DimensionUtils {
// Prevents rare instances of Bedrock locking up
return javaToBedrock(newDimension) == 2 ? OVERWORLD : NETHER;
}
return currentDimension.equals(OVERWORLD) ? NETHER : OVERWORLD;
// Check current Bedrock dimension and not just the Java dimension.
// Fixes rare instances like https://github.com/GeyserMC/Geyser/issues/3161
return javaToBedrock(currentDimension) == 0 ? NETHER : OVERWORLD;
}
public static boolean isCustomBedrockNetherId() {

View File

@ -31,6 +31,7 @@ import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundPickItemPacket;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSetCreativeModeSlotPacket;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtMapBuilder;
import com.nukkitx.nbt.NbtType;
@ -46,6 +47,7 @@ import org.geysermc.geyser.inventory.click.Click;
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe;
import org.geysermc.geyser.level.BedrockDimension;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
@ -85,8 +87,7 @@ public class InventoryUtils {
public static void displayInventory(GeyserSession session, Inventory inventory) {
InventoryTranslator translator = session.getInventoryTranslator();
if (translator != null) {
translator.prepareInventory(session, inventory);
if (translator != null && translator.prepareInventory(session, inventory)) {
if (translator instanceof DoubleChestInventoryTranslator && !((Container) inventory).isUsingRealBlock()) {
session.scheduleInEventLoop(() -> {
Inventory openInv = session.getOpenInventory();
@ -103,7 +104,6 @@ public class InventoryUtils {
translator.updateInventory(session, inventory);
}
} else {
// Precaution - as of 1.16 every inventory should be translated so this shouldn't happen
session.setOpenInventory(null);
}
}
@ -136,6 +136,28 @@ public class InventoryUtils {
}
}
/**
* Finds a usable block space in the world to place a fake inventory block, and returns the position.
*/
@Nullable
public static Vector3i findAvailableWorldSpace(GeyserSession session) {
// Check if a fake block can be placed, either above the player or beneath.
BedrockDimension dimension = session.getChunkCache().getBedrockDimension();
int minY = dimension.minY(), maxY = minY + dimension.height();
Vector3i flatPlayerPosition = session.getPlayerEntity().getPosition().toInt();
Vector3i position = flatPlayerPosition.add(Vector3i.UP);
if (position.getY() < minY) {
return null;
}
if (position.getY() >= maxY) {
position = flatPlayerPosition.sub(0, 4, 0);
if (position.getY() >= maxY) {
return null;
}
}
return position;
}
public static void updateCursor(GeyserSession session) {
InventorySlotPacket cursorPacket = new InventorySlotPacket();
cursorPacket.setContainerId(ContainerId.UI);
@ -150,18 +172,6 @@ public class InventoryUtils {
return item1.getJavaId() == item2.getJavaId() && Objects.equals(item1.getNbt(), item2.getNbt());
}
public static boolean canStack(ItemStack item1, ItemStack item2) {
if (item1 == null || item2 == null)
return false;
return item1.getId() == item2.getId() && Objects.equals(item1.getNbt(), item2.getNbt());
}
public static boolean canStack(ItemData item1, ItemData item2) {
if (item1 == null || item2 == null)
return false;
return item1.equals(item2, false, true, true);
}
/**
* Checks to see if an item stack represents air or has no count.
*/
@ -186,11 +196,22 @@ public class InventoryUtils {
root.put("display", display.build());
return protocolVersion -> ItemData.builder()
.definition(Registries.ITEMS.forVersion(protocolVersion).getStoredItems().barrier().getBedrockDefinition())
.definition(getUnusableSpaceBlockID(protocolVersion))
.count(1)
.tag(root.build()).build();
}
private static int getUnusableSpaceBlockID(int protocolVersion) {
String unusableSpaceBlock = GeyserImpl.getInstance().getConfig().getUnusableSpaceBlock();
ItemMapping unusableSpaceBlockID = Registries.ITEMS.forVersion(protocolVersion).getMapping(unusableSpaceBlock);
if (unusableSpaceBlockID != null) {
return unusableSpaceBlockID.getBedrockId();
} else {
GeyserImpl.getInstance().getLogger().error("Invalid value" + unusableSpaceBlock + ". Resorting to barrier block.");
return Registries.ITEMS.forVersion(protocolVersion).getStoredItems().barrier().getBedrockId();
}
}
/**
* See {@link #findOrCreateItem(GeyserSession, String)}. This is for finding a specified {@link ItemStack}.
*

View File

@ -100,11 +100,7 @@ public class SettingsUtils {
.translator(MinecraftLocale::getLocaleString); // we need translate gamerules next
WorldManager worldManager = GeyserImpl.getInstance().getWorldManager();
for (GameRule gamerule : GameRule.values()) {
if (gamerule.equals(GameRule.UNKNOWN)) {
continue;
}
for (GameRule gamerule : GameRule.VALUES) {
// Add the relevant form item based on the gamerule type
if (Boolean.class.equals(gamerule.getType())) {
builder.toggle("gamerule." + gamerule.getJavaID(), worldManager.getGameRuleBool(session, gamerule));
@ -146,10 +142,6 @@ public class SettingsUtils {
if (showGamerules) {
for (GameRule gamerule : GameRule.VALUES) {
if (gamerule.equals(GameRule.UNKNOWN)) {
continue;
}
if (Boolean.class.equals(gamerule.getType())) {
boolean value = response.next();
if (value != session.getGeyser().getWorldManager().getGameRuleBool(session, gamerule)) {

View File

@ -25,8 +25,6 @@
package org.geysermc.geyser.util;
import com.github.steveice10.mc.protocol.data.game.level.sound.BuiltinSound;
import com.github.steveice10.mc.protocol.data.game.level.sound.CustomSound;
import com.github.steveice10.mc.protocol.data.game.level.sound.Sound;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
@ -63,30 +61,20 @@ public final class SoundUtils {
/**
* Translates a Java Custom or Builtin Sound to its Bedrock equivalent
*
* @param sound the sound to translate
* @param javaIdentifier the sound to translate
* @return a Bedrock sound
*/
public static String translatePlaySound(Sound sound) {
String packetSound;
if (sound instanceof BuiltinSound builtinSound) {
packetSound = builtinSound.getName();
} else if (sound instanceof CustomSound customSound) {
packetSound = customSound.getName();
} else {
GeyserImpl.getInstance().getLogger().debug("Unknown sound, we were unable to map this. " + sound);
return "";
}
public static String translatePlaySound(String javaIdentifier) {
// Drop the Minecraft namespace if applicable
if (packetSound.startsWith("minecraft:")) {
packetSound = packetSound.substring("minecraft:".length());
if (javaIdentifier.startsWith("minecraft:")) {
javaIdentifier = javaIdentifier.substring("minecraft:".length());
}
SoundMapping soundMapping = Registries.SOUNDS.get(packetSound);
SoundMapping soundMapping = Registries.SOUNDS.get(javaIdentifier);
if (soundMapping == null || soundMapping.getPlaysound() == null) {
// no mapping
GeyserImpl.getInstance().getLogger().debug("[PlaySound] Defaulting to sound server gave us for " + sound);
return packetSound;
GeyserImpl.getInstance().getLogger().debug("[PlaySound] Defaulting to sound server gave us for " + javaIdentifier);
return javaIdentifier;
}
return soundMapping.getPlaysound();
}
@ -99,7 +87,7 @@ public final class SoundUtils {
* @param position the position
* @param pitch the pitch
*/
public static void playBuiltinSound(GeyserSession session, BuiltinSound javaSound, Vector3f position, float volume, float pitch) {
public static void playSound(GeyserSession session, Sound javaSound, Vector3f position, float volume, float pitch) {
String packetSound = javaSound.getName();
SoundMapping soundMapping = Registries.SOUNDS.get(packetSound);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@
},
{
"name" : "minecraft:acacia_chest_boat",
"id" : 642
"id" : 645
},
{
"name" : "minecraft:acacia_door",
@ -19,6 +19,10 @@
"name" : "minecraft:acacia_fence_gate",
"id" : 187
},
{
"name" : "minecraft:acacia_hanging_sign",
"id" : -504
},
{
"name" : "minecraft:acacia_pressure_plate",
"id" : -150
@ -131,17 +135,97 @@
"name" : "minecraft:bamboo",
"id" : -163
},
{
"name" : "minecraft:bamboo_button",
"id" : -511
},
{
"name" : "minecraft:bamboo_chest_raft",
"id" : 648
},
{
"name" : "minecraft:bamboo_door",
"id" : -517
},
{
"name" : "minecraft:bamboo_double_slab",
"id" : -521
},
{
"name" : "minecraft:bamboo_fence",
"id" : -515
},
{
"name" : "minecraft:bamboo_fence_gate",
"id" : -516
},
{
"name" : "minecraft:bamboo_hanging_sign",
"id" : -522
},
{
"name" : "minecraft:bamboo_mosaic",
"id" : -509
},
{
"name" : "minecraft:bamboo_mosaic_double_slab",
"id" : -525
},
{
"name" : "minecraft:bamboo_mosaic_slab",
"id" : -524
},
{
"name" : "minecraft:bamboo_mosaic_stairs",
"id" : -523
},
{
"name" : "minecraft:bamboo_planks",
"id" : -510
},
{
"name" : "minecraft:bamboo_pressure_plate",
"id" : -514
},
{
"name" : "minecraft:bamboo_raft",
"id" : 638
},
{
"name" : "minecraft:bamboo_sapling",
"id" : -164
},
{
"name" : "minecraft:bamboo_sign",
"id" : 637
},
{
"name" : "minecraft:bamboo_slab",
"id" : -513
},
{
"name" : "minecraft:bamboo_stairs",
"id" : -512
},
{
"name" : "minecraft:bamboo_standing_sign",
"id" : -518
},
{
"name" : "minecraft:bamboo_trapdoor",
"id" : -520
},
{
"name" : "minecraft:bamboo_wall_sign",
"id" : -519
},
{
"name" : "minecraft:banner",
"id" : 567
},
{
"name" : "minecraft:banner_pattern",
"id" : 651
"id" : 655
},
{
"name" : "minecraft:barrel",
@ -217,7 +301,7 @@
},
{
"name" : "minecraft:birch_chest_boat",
"id" : 639
"id" : 642
},
{
"name" : "minecraft:birch_door",
@ -227,6 +311,10 @@
"name" : "minecraft:birch_fence_gate",
"id" : 184
},
{
"name" : "minecraft:birch_hanging_sign",
"id" : -502
},
{
"name" : "minecraft:birch_pressure_plate",
"id" : -151
@ -329,7 +417,7 @@
},
{
"name" : "minecraft:boat",
"id" : 649
"id" : 653
},
{
"name" : "minecraft:bone",
@ -435,6 +523,10 @@
"name" : "minecraft:calcite",
"id" : -326
},
{
"name" : "minecraft:camel_spawn_egg",
"id" : 633
},
{
"name" : "minecraft:camera",
"id" : 593
@ -541,7 +633,7 @@
},
{
"name" : "minecraft:chest_boat",
"id" : 645
"id" : 649
},
{
"name" : "minecraft:chest_minecart",
@ -555,6 +647,10 @@
"name" : "minecraft:chicken_spawn_egg",
"id" : 435
},
{
"name" : "minecraft:chiseled_bookshelf",
"id" : -526
},
{
"name" : "minecraft:chiseled_deepslate",
"id" : -395
@ -827,6 +923,10 @@
"name" : "minecraft:crimson_fungus",
"id" : -228
},
{
"name" : "minecraft:crimson_hanging_sign",
"id" : -506
},
{
"name" : "minecraft:crimson_hyphae",
"id" : -299
@ -921,7 +1021,7 @@
},
{
"name" : "minecraft:dark_oak_chest_boat",
"id" : 643
"id" : 646
},
{
"name" : "minecraft:dark_oak_door",
@ -931,6 +1031,10 @@
"name" : "minecraft:dark_oak_fence_gate",
"id" : 186
},
{
"name" : "minecraft:dark_oak_hanging_sign",
"id" : -505
},
{
"name" : "minecraft:dark_oak_pressure_plate",
"id" : -152
@ -1121,7 +1225,7 @@
},
{
"name" : "minecraft:disc_fragment_5",
"id" : 637
"id" : 640
},
{
"name" : "minecraft:dispenser",
@ -1193,11 +1297,11 @@
},
{
"name" : "minecraft:dye",
"id" : 650
"id" : 654
},
{
"name" : "minecraft:echo_shard",
"id" : 647
"id" : 651
},
{
"name" : "minecraft:egg",
@ -1725,7 +1829,7 @@
},
{
"name" : "minecraft:end_crystal",
"id" : 653
"id" : 657
},
{
"name" : "minecraft:end_gateway",
@ -1933,7 +2037,7 @@
},
{
"name" : "minecraft:glow_berries",
"id" : 654
"id" : 658
},
{
"name" : "minecraft:glow_frame",
@ -2405,7 +2509,7 @@
},
{
"name" : "minecraft:jungle_chest_boat",
"id" : 640
"id" : 643
},
{
"name" : "minecraft:jungle_door",
@ -2415,6 +2519,10 @@
"name" : "minecraft:jungle_fence_gate",
"id" : 185
},
{
"name" : "minecraft:jungle_hanging_sign",
"id" : -503
},
{
"name" : "minecraft:jungle_pressure_plate",
"id" : -153
@ -2665,7 +2773,7 @@
},
{
"name" : "minecraft:mangrove_boat",
"id" : 635
"id" : 636
},
{
"name" : "minecraft:mangrove_button",
@ -2673,11 +2781,11 @@
},
{
"name" : "minecraft:mangrove_chest_boat",
"id" : 644
"id" : 647
},
{
"name" : "minecraft:mangrove_door",
"id" : 633
"id" : 634
},
{
"name" : "minecraft:mangrove_double_slab",
@ -2691,6 +2799,10 @@
"name" : "minecraft:mangrove_fence_gate",
"id" : -492
},
{
"name" : "minecraft:mangrove_hanging_sign",
"id" : -508
},
{
"name" : "minecraft:mangrove_leaves",
"id" : -472
@ -2717,7 +2829,7 @@
},
{
"name" : "minecraft:mangrove_sign",
"id" : 634
"id" : 635
},
{
"name" : "minecraft:mangrove_slab",
@ -2861,7 +2973,7 @@
},
{
"name" : "minecraft:music_disc_5",
"id" : 636
"id" : 639
},
{
"name" : "minecraft:music_disc_blocks",
@ -3037,7 +3149,11 @@
},
{
"name" : "minecraft:oak_chest_boat",
"id" : 638
"id" : 641
},
{
"name" : "minecraft:oak_hanging_sign",
"id" : -500
},
{
"name" : "minecraft:oak_sign",
@ -3473,7 +3589,7 @@
},
{
"name" : "minecraft:recovery_compass",
"id" : 646
"id" : 650
},
{
"name" : "minecraft:red_candle",
@ -3781,7 +3897,7 @@
},
{
"name" : "minecraft:spawn_egg",
"id" : 652
"id" : 656
},
{
"name" : "minecraft:spider_eye",
@ -3813,7 +3929,7 @@
},
{
"name" : "minecraft:spruce_chest_boat",
"id" : 641
"id" : 644
},
{
"name" : "minecraft:spruce_door",
@ -3823,6 +3939,10 @@
"name" : "minecraft:spruce_fence_gate",
"id" : 183
},
{
"name" : "minecraft:spruce_hanging_sign",
"id" : -501
},
{
"name" : "minecraft:spruce_pressure_plate",
"id" : -154
@ -4081,7 +4201,7 @@
},
{
"name" : "minecraft:trader_llama_spawn_egg",
"id" : 648
"id" : 652
},
{
"name" : "minecraft:trapdoor",
@ -4223,6 +4343,10 @@
"name" : "minecraft:warped_fungus_on_a_stick",
"id" : 618
},
{
"name" : "minecraft:warped_hanging_sign",
"id" : -507
},
{
"name" : "minecraft:warped_hyphae",
"id" : -298

View File

@ -183,6 +183,10 @@ log-player-ip-addresses: true
# auto-update.
notify-on-new-bedrock-update: true
# Which item to use to mark unavailable slots in a Bedrock player inventory. Examples of this are the 2x2 crafting grid while in creative,
# or custom inventory menus with sizes different from the usual 3x9. A barrier block is the default item.
unusable-space-block: minecraft:barrier
# bStats is a stat tracker that is entirely anonymous and tracks only basic information
# about Geyser, such as how many people are online, how many servers are using Geyser,
# what OS is being used, etc. You can learn more about bStats here: https://bstats.org/.

@ -1 +1 @@
Subproject commit f1c9c2fbba0e102dc4f8c96dd9485f7ec9768174
Subproject commit f9d62b3f73db270bd4e0c833b7728b30d29e1369

View File

@ -85,6 +85,7 @@ public class MessageTranslatorTest {
@Test
public void convertToPlainText() {
Assert.assertEquals("JSON message is not handled properly", "Many colors here", MessageTranslator.convertToPlainText("{\"extra\":[{\"color\":\"red\",\"text\":\"M\"},{\"color\":\"gold\",\"text\":\"a\"},{\"color\":\"yellow\",\"text\":\"n\"},{\"color\":\"green\",\"text\":\"y \"},{\"color\":\"aqua\",\"text\":\"c\"},{\"color\":\"dark_purple\",\"text\":\"o\"},{\"color\":\"red\",\"text\":\"l\"},{\"color\":\"gold\",\"text\":\"o\"},{\"color\":\"yellow\",\"text\":\"r\"},{\"color\":\"green\",\"text\":\"s \"},{\"color\":\"aqua\",\"text\":\"h\"},{\"color\":\"dark_purple\",\"text\":\"e\"},{\"color\":\"red\",\"text\":\"r\"},{\"color\":\"gold\",\"text\":\"e\"}],\"text\":\"\"}", "en_US"));
Assert.assertEquals("Legacy formatted message is not handled properly (Colors)", "Many colors here", MessageTranslator.convertToPlainText("§cM§6a§en§ay §bc§5o§cl§6o§er§as §bh§5e§cr§6e"));
Assert.assertEquals("Legacy formatted message is not handled properly (Colors)", "Many colors here", MessageTranslator.convertToPlainText("§cM§6a§en§ay §bc§5o§cl§6o§er§as §bh§5e§cr§6e", "en_US"));
Assert.assertEquals("Legacy formatted message is not handled properly (Style)", "Obf Bold Strikethrough Underline Italic Reset", MessageTranslator.convertToPlainText("§kObf §lBold §mStrikethrough §nUnderline §oItalic §rReset", "en_US"));
Assert.assertEquals("Valid lenient JSON is not handled properly", "Strange", MessageTranslator.convertToPlainText("§rStrange", "en_US"));

View File

@ -1,14 +1,14 @@
[versions]
jackson = "2.13.4"
jackson = "2.14.0"
fastutil = "8.5.2"
netty = "4.1.80.Final"
guava = "29.0-jre"
gson = "2.3.1" # Provided by Spigot 1.8.8
websocket = "1.5.1"
protocol = "3.0.0.Beta2-SNAPSHOT"
protocol = "3.0.0.Beta1-SNAPSHOT"
raknet = "0.0.1.Final-SNAPSHOT"
mcauthlib = "d9d773e"
mcprotocollib = "1.19.2-SNAPSHOT"
mcprotocollib = "1.19.3-SNAPSHOT"
packetlib = "3.0"
adventure = "4.12.0-20220629.025215-9"
adventure-platform = "4.1.2"
@ -21,7 +21,7 @@ jline = "3.21.0"
terminalconsoleappender = "1.2.0"
paper = "1.19-R0.1-SNAPSHOT"
viaversion = "4.0.0"
adapters = "1.5-SNAPSHOT"
adapters = "1.6-SNAPSHOT"
commodore = "2.2"
bungeecord = "a7c6ede"
velocity = "3.0.0"