1
0
mirror of https://github.com/GeyserMC/Geyser.git synced 2025-12-19 14:59:27 +00:00

Merge remote-tracking branch 'upstream/master' into feature/1.21.9

This commit is contained in:
Eclipse
2025-09-23 15:29:58 +00:00
20 changed files with 287 additions and 142 deletions

View File

@@ -8,7 +8,10 @@ dependencies {
implementation(libs.cloud.bungee) implementation(libs.cloud.bungee)
implementation(libs.adventure.text.serializer.bungeecord) implementation(libs.adventure.text.serializer.bungeecord)
compileOnlyApi(libs.bungeecord.proxy) compileOnlyApi(libs.bungeecord.proxy) {
isTransitive = false
}
compileOnlyApi(libs.bungeecord.api)
} }
platformRelocate("net.md_5.bungee.jni") platformRelocate("net.md_5.bungee.jni")

View File

@@ -26,12 +26,15 @@
package org.geysermc.geyser.platform.mod; package org.geysermc.geyser.platform.mod;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.Permissions; import org.geysermc.geyser.Permissions;
import org.geysermc.geyser.platform.mod.command.ModCommandSource; import org.geysermc.geyser.platform.mod.command.ModCommandSource;
import org.geysermc.geyser.util.VersionCheckUtils; import org.geysermc.geyser.util.VersionCheckUtils;
public final class GeyserModUpdateListener { public final class GeyserModUpdateListener {
public static void onPlayReady(ServerPlayer player) { public static void onPlayReady(ServerPlayer player) {
// We could just not register the listener, but, this allows config reloading
if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) {
// Should be creating this in the supplier, but we need it for the permission check. // Should be creating this in the supplier, but we need it for the permission check.
// Not a big deal currently because ModCommandSource doesn't load locale, so don't need to try to wait for it. // Not a big deal currently because ModCommandSource doesn't load locale, so don't need to try to wait for it.
ModCommandSource source = new ModCommandSource(player.createCommandSourceStack()); ModCommandSource source = new ModCommandSource(player.createCommandSourceStack());
@@ -39,6 +42,7 @@ public final class GeyserModUpdateListener {
VersionCheckUtils.checkForGeyserUpdate(() -> source); VersionCheckUtils.checkForGeyserUpdate(() -> source);
} }
} }
}
private GeyserModUpdateListener() { private GeyserModUpdateListener() {
} }

View File

@@ -149,7 +149,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
*/ */
public static final String OAUTH_CLIENT_ID = "204cefd1-4818-4de1-b98d-513fae875d88"; public static final String OAUTH_CLIENT_ID = "204cefd1-4818-4de1-b98d-513fae875d88";
private static final String IP_REGEX = "\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b"; private static final Pattern IP_REGEX = Pattern.compile("\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b");
private final SessionManager sessionManager = new SessionManager(); private final SessionManager sessionManager = new SessionManager();
@@ -410,7 +410,7 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
String remoteAddress = config.getRemote().address(); String remoteAddress = config.getRemote().address();
// Filters whether it is not an IP address or localhost, because otherwise it is not possible to find out an SRV entry. // Filters whether it is not an IP address or localhost, because otherwise it is not possible to find out an SRV entry.
if (!remoteAddress.matches(IP_REGEX) && !remoteAddress.equalsIgnoreCase("localhost")) { if (!IP_REGEX.matcher(remoteAddress).matches() && !remoteAddress.equalsIgnoreCase("localhost")) {
String[] record = WebUtils.findSrvRecord(this, remoteAddress); String[] record = WebUtils.findSrvRecord(this, remoteAddress);
if (record != null) { if (record != null) {
int remotePort = Integer.parseInt(record[2]); int remotePort = Integer.parseInt(record[2]);

View File

@@ -208,7 +208,7 @@ public class HappyGhastEntity extends AnimalEntity implements ClientVehicle {
@Override @Override
public boolean isClientControlled() { public boolean isClientControlled() {
if (!hasBodyArmor() || getFlag(EntityFlag.NO_AI) || staysStill) { if (!hasBodyArmor() || staysStill) {
return false; return false;
} }

View File

@@ -63,7 +63,7 @@ import java.util.regex.Pattern;
@RequiredArgsConstructor @RequiredArgsConstructor
public class GeyserExtensionLoader extends ExtensionLoader { public class GeyserExtensionLoader extends ExtensionLoader {
private static final Pattern[] EXTENSION_FILTERS = new Pattern[] { Pattern.compile("^.+\\.jar$") }; private static final Pattern EXTENSION_FILTER = Pattern.compile("^.+\\.jar$");
private final Object2ObjectMap<String, Class<?>> classes = new Object2ObjectOpenHashMap<>(); private final Object2ObjectMap<String, Class<?>> classes = new Object2ObjectOpenHashMap<>();
private final Map<String, GeyserExtensionClassLoader> classLoaders = new HashMap<>(); private final Map<String, GeyserExtensionClassLoader> classLoaders = new HashMap<>();
@@ -133,8 +133,8 @@ public class GeyserExtensionLoader extends ExtensionLoader {
} }
} }
public Pattern[] extensionFilters() { public Pattern extensionFilter() {
return EXTENSION_FILTERS; return EXTENSION_FILTER;
} }
public Class<?> classByName(final String name) throws ClassNotFoundException{ public Class<?> classByName(final String name) throws ClassNotFoundException{
@@ -249,18 +249,16 @@ public class GeyserExtensionLoader extends ExtensionLoader {
*/ */
private void processExtensionsFolder(Path directory, ThrowingBiConsumer<Path, GeyserExtensionDescription> accept, BiConsumer<Path, Throwable> reject) throws IOException { private void processExtensionsFolder(Path directory, ThrowingBiConsumer<Path, GeyserExtensionDescription> accept, BiConsumer<Path, Throwable> reject) throws IOException {
List<Path> extensionPaths = Files.list(directory).toList(); List<Path> extensionPaths = Files.list(directory).toList();
Pattern[] extensionFilters = this.extensionFilters(); Pattern extensionFilter = this.extensionFilter();
extensionPaths.forEach(path -> { extensionPaths.forEach(path -> {
if (Files.isDirectory(path)) { if (Files.isDirectory(path)) {
return; return;
} }
// Only look at files that meet the extension filter // Only look at files that meet the extension filter
for (Pattern filter : extensionFilters) { if (!extensionFilter.matcher(path.getFileName().toString()).matches()) {
if (!filter.matcher(path.getFileName().toString()).matches()) {
return; return;
} }
}
try { try {
// Try load the description, so we know it's a valid extension // Try load the description, so we know it's a valid extension

View File

@@ -134,7 +134,7 @@ public class BlockInventoryHolder extends InventoryHolder {
// and the bedrock block is vanilla // and the bedrock block is vanilla
BlockState state = session.getGeyser().getWorldManager().blockAt(session, session.getLastInteractionBlockPosition()); BlockState state = session.getGeyser().getWorldManager().blockAt(session, session.getLastInteractionBlockPosition());
if (!BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get().containsKey(state.javaId())) { if (!BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get().containsKey(state.javaId())) {
if (isValidBlock(state)) { if (isValidBlock(session, session.getLastInteractionBlockPosition(), state)) {
// We can safely use this block // We can safely use this block
container.setHolderPosition(session.getLastInteractionBlockPosition()); container.setHolderPosition(session.getLastInteractionBlockPosition());
container.setUsingRealBlock(true, state.block()); container.setUsingRealBlock(true, state.block());
@@ -161,7 +161,7 @@ public class BlockInventoryHolder extends InventoryHolder {
/** /**
* @return true if this Java block ID can be used for player inventory. * @return true if this Java block ID can be used for player inventory.
*/ */
protected boolean isValidBlock(BlockState blockState) { protected boolean isValidBlock(GeyserSession session, Vector3i position, BlockState blockState) {
return this.validBlocks.contains(blockState.block()); return this.validBlocks.contains(blockState.block());
} }

View File

@@ -266,6 +266,7 @@ public class Item {
if (bedrockEnchantment == null) { if (bedrockEnchantment == null) {
String enchantmentTranslation = MinecraftLocale.getLocaleString(enchantment.description(), session.locale()); String enchantmentTranslation = MinecraftLocale.getLocaleString(enchantment.description(), session.locale());
addJavaOnlyEnchantment(session, builder, enchantmentTranslation, level); addJavaOnlyEnchantment(session, builder, enchantmentTranslation, level);
builder.addEnchantmentGlint();
return null; return null;
} }

View File

@@ -32,7 +32,7 @@ import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
import org.cloudburstmc.protocol.bedrock.codec.v818.Bedrock_v818; import org.cloudburstmc.protocol.bedrock.codec.v818.Bedrock_v818;
import org.cloudburstmc.protocol.bedrock.codec.v819.Bedrock_v819; import org.cloudburstmc.protocol.bedrock.codec.v819.Bedrock_v819;
import org.cloudburstmc.protocol.bedrock.codec.v827.Bedrock_v827; import org.cloudburstmc.protocol.bedrock.codec.v827.Bedrock_v827;
import org.cloudburstmc.protocol.bedrock.codec.v843.Bedrock_v843; import org.cloudburstmc.protocol.bedrock.codec.v844.Bedrock_v844;
import org.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketCodec; import org.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketCodec;
import org.geysermc.geyser.api.util.MinecraftVersion; import org.geysermc.geyser.api.util.MinecraftVersion;
import org.geysermc.geyser.impl.MinecraftVersionImpl; import org.geysermc.geyser.impl.MinecraftVersionImpl;
@@ -86,7 +86,7 @@ public final class GameProtocol {
register(Bedrock_v818.CODEC, "1.21.90", "1.21.91", "1.21.92"); register(Bedrock_v818.CODEC, "1.21.90", "1.21.91", "1.21.92");
register(Bedrock_v819.CODEC, "1.21.93", "1.21.94"); register(Bedrock_v819.CODEC, "1.21.93", "1.21.94");
register(Bedrock_v827.CODEC, "1.21.100", "1.21.101"); register(Bedrock_v827.CODEC, "1.21.100", "1.21.101");
register(Bedrock_v843.CODEC, "1.21.110"); register(Bedrock_v844.CODEC, "1.21.110");
MinecraftVersion latestBedrock = SUPPORTED_BEDROCK_VERSIONS.get(SUPPORTED_BEDROCK_VERSIONS.size() - 1); MinecraftVersion latestBedrock = SUPPORTED_BEDROCK_VERSIONS.get(SUPPORTED_BEDROCK_VERSIONS.size() - 1);
DEFAULT_BEDROCK_VERSION = latestBedrock.versionString(); DEFAULT_BEDROCK_VERSION = latestBedrock.versionString();
@@ -142,6 +142,10 @@ public final class GameProtocol {
return session.protocolVersion() == Bedrock_v827.CODEC.getProtocolVersion(); return session.protocolVersion() == Bedrock_v827.CODEC.getProtocolVersion();
} }
public static boolean is1_21_110orHigher(GeyserSession session) {
return session.protocolVersion() >= Bedrock_v844.CODEC.getProtocolVersion();
}
/** /**
* Gets the supported Minecraft: Java Edition version names. * Gets the supported Minecraft: Java Edition version names.
* *
@@ -166,7 +170,7 @@ public final class GameProtocol {
* @return the supported Minecraft: Java Edition version * @return the supported Minecraft: Java Edition version
*/ */
public static String getJavaMinecraftVersion() { public static String getJavaMinecraftVersion() {
return "1.21.9-rc1"; // TODO change to 1.21.9 return "1.21.9";
} }
/** /**

View File

@@ -46,7 +46,7 @@ import org.cloudburstmc.nbt.NbtUtils;
import org.cloudburstmc.protocol.bedrock.codec.v818.Bedrock_v818; import org.cloudburstmc.protocol.bedrock.codec.v818.Bedrock_v818;
import org.cloudburstmc.protocol.bedrock.codec.v819.Bedrock_v819; import org.cloudburstmc.protocol.bedrock.codec.v819.Bedrock_v819;
import org.cloudburstmc.protocol.bedrock.codec.v827.Bedrock_v827; import org.cloudburstmc.protocol.bedrock.codec.v827.Bedrock_v827;
import org.cloudburstmc.protocol.bedrock.codec.v843.Bedrock_v843; import org.cloudburstmc.protocol.bedrock.codec.v844.Bedrock_v844;
import org.cloudburstmc.protocol.bedrock.data.BlockPropertyData; import org.cloudburstmc.protocol.bedrock.data.BlockPropertyData;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
@@ -60,7 +60,7 @@ import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.level.block.type.FlowerPotBlock; import org.geysermc.geyser.level.block.type.FlowerPotBlock;
import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.populator.conversion.Conversion827_819; import org.geysermc.geyser.registry.populator.conversion.Conversion827_819;
import org.geysermc.geyser.registry.populator.conversion.Conversion843_827; import org.geysermc.geyser.registry.populator.conversion.Conversion844_827;
import org.geysermc.geyser.registry.type.BlockMappings; import org.geysermc.geyser.registry.type.BlockMappings;
import org.geysermc.geyser.registry.type.GeyserBedrockBlock; import org.geysermc.geyser.registry.type.GeyserBedrockBlock;
@@ -122,8 +122,8 @@ public final class BlockRegistryPopulator {
var blockMappers = ImmutableMap.<ObjectIntPair<String>, Remapper>builder() var blockMappers = ImmutableMap.<ObjectIntPair<String>, Remapper>builder()
.put(ObjectIntPair.of("1_21_90", Bedrock_v818.CODEC.getProtocolVersion()), Conversion827_819::remapBlock) .put(ObjectIntPair.of("1_21_90", Bedrock_v818.CODEC.getProtocolVersion()), Conversion827_819::remapBlock)
.put(ObjectIntPair.of("1_21_90", Bedrock_v819.CODEC.getProtocolVersion()), Conversion827_819::remapBlock) .put(ObjectIntPair.of("1_21_90", Bedrock_v819.CODEC.getProtocolVersion()), Conversion827_819::remapBlock)
.put(ObjectIntPair.of("1_21_100", Bedrock_v827.CODEC.getProtocolVersion()), Conversion843_827::remapBlock) .put(ObjectIntPair.of("1_21_100", Bedrock_v827.CODEC.getProtocolVersion()), Conversion844_827::remapBlock)
.put(ObjectIntPair.of("1_21_110", Bedrock_v843.CODEC.getProtocolVersion()), tag -> tag) .put(ObjectIntPair.of("1_21_110", Bedrock_v844.CODEC.getProtocolVersion()), tag -> tag)
.build(); .build();
// We can keep this strong as nothing should be garbage collected // We can keep this strong as nothing should be garbage collected

View File

@@ -49,9 +49,12 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.function.Function; import java.util.function.Function;
import java.util.regex.Pattern;
public class CustomSkullRegistryPopulator { public class CustomSkullRegistryPopulator {
private static final Pattern SKULL_HASH_PATTERN = Pattern.compile("^[a-fA-F0-9]{64}$");
public static void populate() { public static void populate() {
SkullResourcePackManager.SKULL_SKINS.clear(); // Remove skins after reloading SkullResourcePackManager.SKULL_SKINS.clear(); // Remove skins after reloading
BlockRegistries.CUSTOM_SKULLS.set(Object2ObjectMaps.emptyMap()); BlockRegistries.CUSTOM_SKULLS.set(Object2ObjectMaps.emptyMap());
@@ -118,7 +121,7 @@ public class CustomSkullRegistryPopulator {
}); });
skinHashes.forEach((skinHash) -> { skinHashes.forEach((skinHash) -> {
if (!skinHash.matches("^[a-fA-F0-9]+$")) { if (!SKULL_HASH_PATTERN.matcher(skinHash).matches()) {
GeyserImpl.getInstance().getLogger().error("Skin hash " + skinHash + " does not match required format ^[a-fA-F0-9]{64}$ and will not be added as a custom block."); GeyserImpl.getInstance().getLogger().error("Skin hash " + skinHash + " does not match required format ^[a-fA-F0-9]{64}$ and will not be added as a custom block.");
return; return;
} }

View File

@@ -48,7 +48,7 @@ import org.cloudburstmc.nbt.NbtUtils;
import org.cloudburstmc.protocol.bedrock.codec.v818.Bedrock_v818; import org.cloudburstmc.protocol.bedrock.codec.v818.Bedrock_v818;
import org.cloudburstmc.protocol.bedrock.codec.v819.Bedrock_v819; import org.cloudburstmc.protocol.bedrock.codec.v819.Bedrock_v819;
import org.cloudburstmc.protocol.bedrock.codec.v827.Bedrock_v827; import org.cloudburstmc.protocol.bedrock.codec.v827.Bedrock_v827;
import org.cloudburstmc.protocol.bedrock.codec.v843.Bedrock_v843; import org.cloudburstmc.protocol.bedrock.codec.v844.Bedrock_v844;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.SimpleItemDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.SimpleItemDefinition;
@@ -73,7 +73,7 @@ import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.level.block.property.Properties; import org.geysermc.geyser.level.block.property.Properties;
import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.populator.conversion.Conversion843_827; import org.geysermc.geyser.registry.populator.conversion.Conversion844_827;
import org.geysermc.geyser.registry.type.BlockMappings; import org.geysermc.geyser.registry.type.BlockMappings;
import org.geysermc.geyser.registry.type.GeyserBedrockBlock; import org.geysermc.geyser.registry.type.GeyserBedrockBlock;
import org.geysermc.geyser.registry.type.GeyserMappingItem; import org.geysermc.geyser.registry.type.GeyserMappingItem;
@@ -192,10 +192,10 @@ public class ItemRegistryPopulator {
eightOneEightFallbacks.put(Items.MUSIC_DISC_LAVA_CHICKEN, Items.MUSIC_DISC_CHIRP); eightOneEightFallbacks.put(Items.MUSIC_DISC_LAVA_CHICKEN, Items.MUSIC_DISC_CHIRP);
List<PaletteVersion> paletteVersions = new ArrayList<>(4); List<PaletteVersion> paletteVersions = new ArrayList<>(4);
paletteVersions.add(new PaletteVersion("1_21_90", Bedrock_v818.CODEC.getProtocolVersion(), eightOneEightFallbacks, Conversion843_827::remapItem)); paletteVersions.add(new PaletteVersion("1_21_90", Bedrock_v818.CODEC.getProtocolVersion(), eightOneEightFallbacks, Conversion844_827::remapItem));
paletteVersions.add(new PaletteVersion("1_21_93", Bedrock_v819.CODEC.getProtocolVersion(), eightOneNineFallbacks, Conversion843_827::remapItem)); paletteVersions.add(new PaletteVersion("1_21_93", Bedrock_v819.CODEC.getProtocolVersion(), eightOneNineFallbacks, Conversion844_827::remapItem));
paletteVersions.add(new PaletteVersion("1_21_100", Bedrock_v827.CODEC.getProtocolVersion(), eightTwoSevenFallbacks, Conversion843_827::remapItem)); paletteVersions.add(new PaletteVersion("1_21_100", Bedrock_v827.CODEC.getProtocolVersion(), eightTwoSevenFallbacks, Conversion844_827::remapItem));
paletteVersions.add(new PaletteVersion("1_21_110", Bedrock_v843.CODEC.getProtocolVersion())); paletteVersions.add(new PaletteVersion("1_21_110", Bedrock_v844.CODEC.getProtocolVersion()));
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap(); GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();

View File

@@ -36,7 +36,7 @@ import it.unimi.dsi.fastutil.objects.ObjectIntPair;
import org.cloudburstmc.protocol.bedrock.codec.v818.Bedrock_v818; import org.cloudburstmc.protocol.bedrock.codec.v818.Bedrock_v818;
import org.cloudburstmc.protocol.bedrock.codec.v819.Bedrock_v819; import org.cloudburstmc.protocol.bedrock.codec.v819.Bedrock_v819;
import org.cloudburstmc.protocol.bedrock.codec.v827.Bedrock_v827; import org.cloudburstmc.protocol.bedrock.codec.v827.Bedrock_v827;
import org.cloudburstmc.protocol.bedrock.codec.v843.Bedrock_v843; import org.cloudburstmc.protocol.bedrock.codec.v844.Bedrock_v844;
import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.item.type.Item;
@@ -70,10 +70,10 @@ public final class TagRegistryPopulator {
List<ObjectIntPair<String>> paletteVersions = List.of( List<ObjectIntPair<String>> paletteVersions = List.of(
ObjectIntPair.of("1_21_90", Bedrock_v818.CODEC.getProtocolVersion()), ObjectIntPair.of("1_21_90", Bedrock_v818.CODEC.getProtocolVersion()),
// Not a typo, they're the same file // Not a typo, it's the same file
ObjectIntPair.of("1_21_90", Bedrock_v819.CODEC.getProtocolVersion()), ObjectIntPair.of("1_21_90", Bedrock_v819.CODEC.getProtocolVersion()),
ObjectIntPair.of("1_21_100", Bedrock_v827.CODEC.getProtocolVersion()), ObjectIntPair.of("1_21_100", Bedrock_v827.CODEC.getProtocolVersion()),
ObjectIntPair.of("1_21_110", Bedrock_v843.CODEC.getProtocolVersion()) ObjectIntPair.of("1_21_110", Bedrock_v844.CODEC.getProtocolVersion())
); );
Type type = new TypeToken<Map<String, List<String>>>() {}.getType(); Type type = new TypeToken<Map<String, List<String>>>() {}.getType();

View File

@@ -30,7 +30,7 @@ import org.cloudburstmc.nbt.NbtMap;
public class Conversion827_819 { public class Conversion827_819 {
public static NbtMap remapBlock(NbtMap nbtMap) { public static NbtMap remapBlock(NbtMap nbtMap) {
nbtMap = Conversion843_827.remapBlock(nbtMap); nbtMap = Conversion844_827.remapBlock(nbtMap);
final String name = nbtMap.getString("name"); final String name = nbtMap.getString("name");
if (name.endsWith("copper_chest")) { if (name.endsWith("copper_chest")) {

View File

@@ -31,7 +31,7 @@ import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.registry.type.GeyserMappingItem; import org.geysermc.geyser.registry.type.GeyserMappingItem;
public class Conversion843_827 { public class Conversion844_827 {
public static NbtMap remapBlock(NbtMap nbtMap) { public static NbtMap remapBlock(NbtMap nbtMap) {
final String name = nbtMap.getString("name"); final String name = nbtMap.getString("name");

View File

@@ -29,11 +29,13 @@ import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.Pair;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.LevelEvent; import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
import org.cloudburstmc.protocol.bedrock.data.PlayerActionType;
import org.cloudburstmc.protocol.bedrock.data.PlayerAuthInputData; import org.cloudburstmc.protocol.bedrock.data.PlayerAuthInputData;
import org.cloudburstmc.protocol.bedrock.data.PlayerBlockActionData; import org.cloudburstmc.protocol.bedrock.data.PlayerBlockActionData;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
@@ -83,6 +85,7 @@ public class BlockBreakHandler {
* The position of the current block being broken. * The position of the current block being broken.
* Null indicates no block breaking in progress. * Null indicates no block breaking in progress.
*/ */
@Getter
protected @Nullable Vector3i currentBlockPos = null; protected @Nullable Vector3i currentBlockPos = null;
/** /**
@@ -92,22 +95,39 @@ public class BlockBreakHandler {
protected @Nullable BlockState currentBlockState = null; protected @Nullable BlockState currentBlockState = null;
/** /**
* The Bedrock client tick in which block breaking of the current block began. * Indicates that we should re-check the current block state for changes
*/
@Setter
protected @Nullable Integer updatedServerBlockStateId;
/**
* Whether we must break the block ourselves.
* Only set when keeping track of custom blocks / custom items breaking blocks. * Only set when keeping track of custom blocks / custom items breaking blocks.
*/ */
protected long blockStartBreakTime = 0; protected boolean serverSideBlockBreaking = false;
/**
* The current block breaking progress
*/
protected float currentProgress = 0.0F;
/** /**
* The last known face of the block the client was breaking. * The last known face of the block the client was breaking.
* Only set when keeping track of custom blocks / custom items breaking blocks. * Only set when keeping track of custom blocks / custom items breaking blocks.
*/ */
protected Direction lastBlockBreakFace = null; protected Direction currentBlockFace = null;
/** /**
* The last block position that was instantly broken. * The last item used to break blocks.
* Used to track whether block breaking should be re-started as the item changed
*/
protected GeyserItemStack currentItemStack = null;
/**
* The last block position that was broken.
* Used to ignore subsequent block actions from the Bedrock client. * Used to ignore subsequent block actions from the Bedrock client.
*/ */
protected Vector3i lastInstaMinedPosition = null; protected Vector3i lastMinedPosition = null;
/** /**
* Caches all blocks we had to restore e.g. due to out-of-range or being unable to mine * Caches all blocks we had to restore e.g. due to out-of-range or being unable to mine
@@ -148,18 +168,19 @@ public class BlockBreakHandler {
restoredBlocks.clear(); restoredBlocks.clear();
this.itemFramePos = null; this.itemFramePos = null;
} else { } else {
tick(); tick(packet.getTick());
} }
} }
protected void tick() { protected void tick(long tick) {
// We need to manually check if a block should be destroyed, and send the client progress updates, when mining a custom block, or with a custom item // We need to manually check if a block should be destroyed, and send the client progress updates, when mining a custom block, or with a custom item
// This is because, in CustomItemRegistryPopulator#computeToolProperties, we set a block break speed of 0, // This is because, in CustomItemRegistryPopulator#computeToolProperties, we set a block break speed of 0,
// meaning the client will only ever send START_BREAK for breaking blocks, and nothing else // meaning the client will only ever send START_BREAK for breaking blocks, and nothing else (as long as no efficiency is applied, lol)
// We also want to tick destroying to ensure that the currently held item did not change
// Check lastBlockBreakFace, currentBlockPos and currentBlockState, just in case // Check lastBlockBreakFace, currentBlockPos and currentBlockState, just in case
if (blockStartBreakTime != 0 && lastBlockBreakFace != null && currentBlockPos != null && currentBlockState != null) { if (currentBlockFace != null && currentBlockPos != null && currentBlockState != null) {
handleContinueDestroy(currentBlockPos, currentBlockState, lastBlockBreakFace, session.getClientTicks()); handleContinueDestroy(currentBlockPos, getCurrentBlockState(currentBlockPos), currentBlockFace, false, false, session.getClientTicks());
} }
} }
@@ -170,51 +191,66 @@ public class BlockBreakHandler {
// Worth noting: the bedrock client, as of version 1.21.101, sends weird values for the face, outside the [0;6] range, when sending ABORT_BREAK // Worth noting: the bedrock client, as of version 1.21.101, sends weird values for the face, outside the [0;6] range, when sending ABORT_BREAK
// Not sure why, but, blockFace isn't used for ABORT_BREAK, so it's fine // Not sure why, but, blockFace isn't used for ABORT_BREAK, so it's fine
// This is why blockFace is individually turned into a Direction in each of the switch statements, except for the ABORT_BREAK one // This is why blockFace is individually turned into a Direction in each of the switch statements, except for the ABORT_BREAK one
switch (actionData.getAction()) { switch (actionData.getAction()) {
case DROP_ITEM -> { case DROP_ITEM -> {
ServerboundPlayerActionPacket dropItemPacket = new ServerboundPlayerActionPacket(PlayerAction.DROP_ITEM, ServerboundPlayerActionPacket dropItemPacket = new ServerboundPlayerActionPacket(PlayerAction.DROP_ITEM,
position, Direction.getUntrusted(actionData, PlayerBlockActionData::getFace).mcpl(), 0); position, Direction.getUntrusted(actionData, PlayerBlockActionData::getFace).mcpl(), 0);
session.sendDownstreamGamePacket(dropItemPacket); session.sendDownstreamGamePacket(dropItemPacket);
} }
// Must do this ugly as it can also be called from the block_continue_destroy case :( case START_BREAK -> {
case START_BREAK -> preStartBreakHandle(position, Direction.getUntrusted(actionData, PlayerBlockActionData::getFace), packet.getTick()); // New block being broken -> ignore previously mined position since that's no longer relevant
case BLOCK_CONTINUE_DESTROY -> { this.lastMinedPosition = null;
if (testForItemFrameEntity(position) || testForLastInstaBreakPosOrReset(position) || abortDueToBlockRestoring(position)) {
if (testForItemFrameEntity(position) || abortDueToBlockRestoring(position)) {
continue; continue;
} }
Direction blockFace = Direction.getUntrusted(actionData, PlayerBlockActionData::getFace); BlockState state = getCurrentBlockState(position);
// Position mismatch == we break a new block! Bedrock won't send START_BREAK when continuously mining if (!canBreak(position, state, actionData.getAction())) {
// That applies in creative mode too! (last test in 1.21.100) BlockUtils.sendBedrockStopBlockBreak(session, position.toFloat());
if (!Objects.equals(position, currentBlockPos) || currentBlockState == null) { restoredBlocks.add(position);
if (currentBlockPos != null) { continue;
handleAbortBreaking(currentBlockPos);
} }
preStartBreakHandle(position, blockFace, packet.getTick());
handleStartBreak(position, state, Direction.getUntrusted(actionData, PlayerBlockActionData::getFace), packet.getTick());
}
case BLOCK_CONTINUE_DESTROY -> {
if (testForItemFrameEntity(position) || testForLastBreakPosOrReset(position) || abortDueToBlockRestoring(position)) {
continue; continue;
} }
// The client loves to send this block action alongside BLOCK_PREDICT_DESTROY in the same packet; // The client loves to send this block action alongside BLOCK_PREDICT_DESTROY in the same packet;
// we can skip handling this action if the same position is updated again in the same tick // we can skip handling this action about the current position if the next action is also about it
if (i < packet.getPlayerActions().size() - 1) { if (Objects.equals(currentBlockPos, position) && i < packet.getPlayerActions().size() - 1) {
PlayerBlockActionData nextAction = packet.getPlayerActions().get(i + 1); PlayerBlockActionData nextAction = packet.getPlayerActions().get(i + 1);
if (Objects.equals(nextAction.getBlockPosition(), position)) { if (Objects.equals(nextAction.getBlockPosition(), position)) {
continue; continue;
} }
} }
BlockState state = session.getGeyser().getWorldManager().blockAt(session, position); BlockState state = getCurrentBlockState(position);
if (!canBreak(position, state)) { if (!canBreak(position, state, actionData.getAction())) {
BlockUtils.sendBedrockStopBlockBreak(session, position.toFloat()); BlockUtils.sendBedrockStopBlockBreak(session, position.toFloat());
restoredBlocks.add(position); restoredBlocks.add(position);
// Also abort old / "current" block breaking, if there is one in progress
if (!Objects.equals(currentBlockPos, position)) {
handleAbortBreaking(position);
}
continue; continue;
} }
handleContinueDestroy(position, state, blockFace, packet.getTick()); handleContinueDestroy(position, state, Direction.getUntrusted(actionData, PlayerBlockActionData::getFace), false, true, packet.getTick());
} }
case BLOCK_PREDICT_DESTROY -> { case BLOCK_PREDICT_DESTROY -> {
if (testForItemFrameEntity(position) || testForLastInstaBreakPosOrReset(position)) { if (testForItemFrameEntity(position)) {
continue;
}
// At this point it's safe to assume that we won't get subsequent block actions on this position
// so reset it and return since we've already broken the block
if (Objects.equals(lastMinedPosition, position)) {
lastMinedPosition = null;
continue; continue;
} }
@@ -225,12 +261,13 @@ public class BlockBreakHandler {
continue; continue;
} }
BlockState state = session.getGeyser().getWorldManager().blockAt(session, position); BlockState state = getCurrentBlockState(position);
boolean valid = currentBlockState != null && Objects.equals(position, currentBlockPos); boolean valid = currentBlockPos != null && Objects.equals(position, currentBlockPos);
if (!canBreak(position, state) || !valid) { if (!canBreak(position, state, actionData.getAction()) || !valid) {
if (!valid) { if (!valid) {
GeyserImpl.getInstance().getLogger().warning("Player %s tried to break block at %s (%s), without starting to destroy it!" GeyserImpl.getInstance().getLogger().warning("Player %s tried to break block at %s (%s), without starting to destroy it!"
.formatted(session.bedrockUsername(), position, currentBlockState)); .formatted(session.bedrockUsername(), position, currentBlockPos));
handleAbortBreaking(currentBlockPos);
} }
BlockUtils.stopBreakAndRestoreBlock(session, position, state); BlockUtils.stopBreakAndRestoreBlock(session, position, state);
restoredBlocks.add(position); restoredBlocks.add(position);
@@ -240,14 +277,6 @@ public class BlockBreakHandler {
handlePredictDestroy(position, state, Direction.getUntrusted(actionData, PlayerBlockActionData::getFace), packet.getTick()); handlePredictDestroy(position, state, Direction.getUntrusted(actionData, PlayerBlockActionData::getFace), packet.getTick());
} }
case ABORT_BREAK -> { case ABORT_BREAK -> {
// Abort break can also be sent after the block on that pos was broken.....
// At that point it's safe to assume that we won't get subsequent block actions on this position
// so reset it and return since there isn't anything to abort
if (Objects.equals(lastInstaMinedPosition, position)) {
lastInstaMinedPosition = null;
continue;
}
// Also handles item frame interactions in adventure mode // Also handles item frame interactions in adventure mode
if (testForItemFrameEntity(position)) { if (testForItemFrameEntity(position)) {
continue; continue;
@@ -265,28 +294,6 @@ public class BlockBreakHandler {
} }
} }
/**
* Called from either a START_BREAK or BLOCK_CONTINUE_DESTROY case, the latter
* if the client switches to a new block. This method then runs pre-break checks.
*/
private void preStartBreakHandle(Vector3i position, Direction blockFace, long tick) {
// New block being broken -> ignore previous insta-mine pos since that's no longer relevant
lastInstaMinedPosition = null;
if (testForItemFrameEntity(position) || abortDueToBlockRestoring(position)) {
return;
}
BlockState state = session.getGeyser().getWorldManager().blockAt(session, position);
if (!canBreak(position, state)) {
BlockUtils.sendBedrockStopBlockBreak(session, position.toFloat());
restoredBlocks.add(position);
return;
}
handleStartBreak(position, state, blockFace, tick);
}
protected void handleStartBreak(@NonNull Vector3i position, @NonNull BlockState state, Direction blockFace, long tick) { protected void handleStartBreak(@NonNull Vector3i position, @NonNull BlockState state, Direction blockFace, long tick) {
GeyserItemStack item = session.getPlayerInventory().getItemInHand(); GeyserItemStack item = session.getPlayerInventory().getItemInHand();
@@ -305,8 +312,8 @@ public class BlockBreakHandler {
// insta-breaking should be treated differently; don't send STOP_BREAK for these // insta-breaking should be treated differently; don't send STOP_BREAK for these
if (session.isInstabuild() || breakProgress >= 1.0F) { if (session.isInstabuild() || breakProgress >= 1.0F) {
// Avoid sending STOP_BREAK for instantly broken blocks // Avoid sending STOP_BREAK for instantly broken blocks
lastInstaMinedPosition = position;
destroyBlock(state, position, blockFace, true); destroyBlock(state, position, blockFace, true);
this.lastMinedPosition = position;
} else { } else {
// If the block is custom or the breaking item is custom, we must keep track of break time ourselves // If the block is custom or the breaking item is custom, we must keep track of break time ourselves
ItemMapping mapping = item.getMapping(session); ItemMapping mapping = item.getMapping(session);
@@ -314,11 +321,10 @@ public class BlockBreakHandler {
CustomBlockState blockStateOverride = BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get(state.javaId()); CustomBlockState blockStateOverride = BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get(state.javaId());
SkullCache.Skull skull = session.getSkullCache().getSkulls().get(position); SkullCache.Skull skull = session.getSkullCache().getSkulls().get(position);
this.blockStartBreakTime = 0; this.serverSideBlockBreaking = false;
if (BlockRegistries.NON_VANILLA_BLOCK_IDS.get().get(state.javaId()) || blockStateOverride != null || if (BlockRegistries.NON_VANILLA_BLOCK_IDS.get().get(state.javaId()) || blockStateOverride != null ||
customItem != null || (skull != null && skull.getBlockDefinition() != null)) { customItem != null || (skull != null && skull.getBlockDefinition() != null)) {
this.blockStartBreakTime = tick; this.serverSideBlockBreaking = true;
this.lastBlockBreakFace = blockFace;
} }
LevelEventPacket startBreak = new LevelEventPacket(); LevelEventPacket startBreak = new LevelEventPacket();
@@ -329,27 +335,45 @@ public class BlockBreakHandler {
BlockUtils.spawnBlockBreakParticles(session, blockFace, position, state); BlockUtils.spawnBlockBreakParticles(session, blockFace, position, state);
this.currentBlockFace = blockFace;
this.currentBlockPos = position; this.currentBlockPos = position;
this.currentBlockState = state; this.currentBlockState = state;
this.currentItemStack = item;
// The Java client calls MultiPlayerGameMode#startDestroyBlock which would set this to zero,
// but also #continueDestroyBlock in the same tick to advance the break progress.
this.currentProgress = breakProgress;
session.sendDownstreamGamePacket(new ServerboundPlayerActionPacket(PlayerAction.START_DIGGING, position, session.sendDownstreamGamePacket(new ServerboundPlayerActionPacket(PlayerAction.START_DIGGING, position,
blockFace.mcpl(), session.getWorldCache().nextPredictionSequence())); blockFace.mcpl(), session.getWorldCache().nextPredictionSequence()));
} }
} }
protected void handleContinueDestroy(Vector3i position, BlockState state, Direction blockFace, long tick) { protected void handleContinueDestroy(@NonNull Vector3i position, @NonNull BlockState state, @NonNull Direction blockFace, boolean bedrockDestroyed, boolean sendParticles, long tick) {
BlockUtils.spawnBlockBreakParticles(session, blockFace, position, state); // Position mismatch == we break a new block! Bedrock won't send START_BREAK when continuously mining
double totalBreakTime = BlockUtils.reciprocal(calculateBreakProgress(state, position, session.getPlayerInventory().getItemInHand())); // That applies in creative mode too! (last test in 1.21.100)
// Further: We should also "start" breaking te block anew if the held item changes.
// As of 1.21.100 it seems like this is in fact NOT done by BDS!
if (currentBlockState != null && Objects.equals(position, currentBlockPos) && sameItemStack()) {
this.currentBlockFace = blockFace;
if (blockStartBreakTime != 0) { final float newProgress = calculateBreakProgress(state, position, session.getPlayerInventory().getItemInHand());
long ticksSinceStart = tick - blockStartBreakTime; this.currentProgress = this.currentProgress + newProgress;
// We need to add a slight delay to the break time, otherwise the client breaks blocks too fast double totalBreakTime = BlockUtils.reciprocal(newProgress);
if (ticksSinceStart >= (totalBreakTime += 2)) {
destroyBlock(state, position, blockFace, false); if (sendParticles || (serverSideBlockBreaking && currentProgress % 4 == 0)) {
return; BlockUtils.spawnBlockBreakParticles(session, blockFace, position, state);
} }
// Update in case it has changed
lastBlockBreakFace = blockFace; // let's be a bit lenient here; the Vanilla server is as well
if (mayBreak(currentProgress, bedrockDestroyed)) {
destroyBlock(state, position, blockFace, false);
if (!bedrockDestroyed) {
// Only store it if we need to ignore subsequent Bedrock block actions
this.lastMinedPosition = position;
}
return;
} else if (bedrockDestroyed) {
BlockUtils.restoreCorrectBlock(session, position, state);
} }
// Update the break time in the event that player conditions changed (jumping, effects applied) // Update the break time in the event that player conditions changed (jumping, effects applied)
@@ -358,13 +382,35 @@ public class BlockBreakHandler {
updateBreak.setPosition(position.toFloat()); updateBreak.setPosition(position.toFloat());
updateBreak.setData((int) (65535 / totalBreakTime)); updateBreak.setData((int) (65535 / totalBreakTime));
session.sendUpstreamPacket(updateBreak); session.sendUpstreamPacket(updateBreak);
} else {
// Don't store last mined position; we don't want to ignore any actions now that we switched!
this.lastMinedPosition = null;
// We have switched - either between blocks, or are between the stack we're using to break the block
if (currentBlockPos != null) {
LevelEventPacket updateBreak = new LevelEventPacket();
updateBreak.setType(LevelEvent.BLOCK_UPDATE_BREAK);
updateBreak.setPosition(position.toFloat());
updateBreak.setData(0);
session.sendUpstreamPacketImmediately(updateBreak);
// Prevent ghost blocks when Bedrock thinks it destroyed a block and wants to "move on",
// while it wasn't actually destroyed on our end.
if (bedrockDestroyed) {
BlockUtils.restoreCorrectBlock(session, currentBlockPos, currentBlockState);
}
handleAbortBreaking(currentBlockPos);
}
handleStartBreak(position, state, blockFace, tick);
}
} }
protected void handlePredictDestroy(Vector3i position, BlockState state, Direction blockFace, long tick) { protected void handlePredictDestroy(Vector3i position, BlockState state, Direction blockFace, long tick) {
destroyBlock(state, position, blockFace, false); handleContinueDestroy(position, state, blockFace, true, true, tick);
} }
protected void handleAbortBreaking(Vector3i position) { private void handleAbortBreaking(Vector3i position) {
// Bedrock edition "confirms" it stopped breaking blocks by sending an abort packet // Bedrock edition "confirms" it stopped breaking blocks by sending an abort packet
// We don't forward those as a Java client wouldn't send those either // We don't forward those as a Java client wouldn't send those either
if (currentBlockPos != null) { if (currentBlockPos != null) {
@@ -374,6 +420,7 @@ public class BlockBreakHandler {
} }
BlockUtils.sendBedrockStopBlockBreak(session, position.toFloat()); BlockUtils.sendBedrockStopBlockBreak(session, position.toFloat());
this.clearCurrentVariables();
} }
/** /**
@@ -411,6 +458,12 @@ public class BlockBreakHandler {
if (!restoredBlocks.isEmpty()) { if (!restoredBlocks.isEmpty()) {
BlockUtils.sendBedrockStopBlockBreak(session, position.toFloat()); BlockUtils.sendBedrockStopBlockBreak(session, position.toFloat());
restoredBlocks.add(position); restoredBlocks.add(position);
if (currentBlockPos != null && !Objects.equals(position, currentBlockPos)) {
restoredBlocks.add(currentBlockPos);
handleAbortBreaking(currentBlockPos);
}
return true; return true;
} }
return false; return false;
@@ -421,7 +474,7 @@ public class BlockBreakHandler {
* This includes world border, "hands busy" (boat steering), and GameMode checks. * This includes world border, "hands busy" (boat steering), and GameMode checks.
*/ */
@SuppressWarnings("BooleanMethodIsAlwaysInverted") @SuppressWarnings("BooleanMethodIsAlwaysInverted")
protected boolean canBreak(Vector3i vector, BlockState state) { protected boolean canBreak(Vector3i vector, BlockState state, PlayerActionType action) {
if (session.isHandsBusy() || !session.getWorldBorder().isInsideBorderBoundaries()) { if (session.isHandsBusy() || !session.getWorldBorder().isInsideBorderBoundaries()) {
return false; return false;
} }
@@ -460,6 +513,11 @@ public class BlockBreakHandler {
return !state.is(Blocks.AIR); return !state.is(Blocks.AIR);
} }
protected boolean mayBreak(float progress, boolean bedrockDestroyed) {
// We're tolerant here to account for e.g. obsidian breaking speeds not matching 1:1 :(
return (serverSideBlockBreaking && progress >= 1.0F) || (bedrockDestroyed && progress >= 0.7F);
}
protected void destroyBlock(BlockState state, Vector3i vector, Direction direction, boolean instamine) { protected void destroyBlock(BlockState state, Vector3i vector, Direction direction, boolean instamine) {
// Send java packet // Send java packet
session.sendDownstreamGamePacket(new ServerboundPlayerActionPacket(instamine ? PlayerAction.START_DIGGING : PlayerAction.FINISH_DIGGING, session.sendDownstreamGamePacket(new ServerboundPlayerActionPacket(instamine ? PlayerAction.START_DIGGING : PlayerAction.FINISH_DIGGING,
@@ -484,21 +542,56 @@ public class BlockBreakHandler {
* This ensures that Geyser does not send a FINISH_DIGGING player action for instantly mined blocks, * This ensures that Geyser does not send a FINISH_DIGGING player action for instantly mined blocks,
* or those mined while in creative mode. * or those mined while in creative mode.
*/ */
protected boolean testForLastInstaBreakPosOrReset(Vector3i position) { protected boolean testForLastBreakPosOrReset(Vector3i position) {
if (Objects.equals(lastInstaMinedPosition, position)) { if (Objects.equals(lastMinedPosition, position)) {
return true; return true;
} }
lastInstaMinedPosition = null; lastMinedPosition = null;
return false; return false;
} }
private boolean sameItemStack() {
if (currentItemStack == null) {
return false;
}
GeyserItemStack stack = session.getPlayerInventory().getItemInHand();
if (currentItemStack.isEmpty() && stack.isEmpty()) {
return true;
}
if (currentItemStack.getJavaId() != stack.getJavaId()) {
return false;
}
return Objects.equals(stack.getComponents(), currentItemStack.getComponents());
}
private @NonNull BlockState getCurrentBlockState(Vector3i position) {
if (Objects.equals(position, currentBlockPos)) {
if (updatedServerBlockStateId != null) {
BlockState updated = BlockState.of(updatedServerBlockStateId);
this.updatedServerBlockStateId = null;
return updated;
}
if (currentBlockState != null) {
return currentBlockState;
}
}
this.updatedServerBlockStateId = null;
return session.getGeyser().getWorldManager().blockAt(session, position);
}
/** /**
* Resets variables after a block was broken. * Resets variables after a block was broken.
*/ */
protected void clearCurrentVariables() { protected void clearCurrentVariables() {
this.currentBlockPos = null; this.currentBlockPos = null;
this.currentBlockState = null; this.currentBlockState = null;
this.blockStartBreakTime = 0L; this.currentBlockFace = null;
this.currentProgress = 0.0F;
this.currentItemStack = null;
this.updatedServerBlockStateId = null;
} }
/** /**
@@ -506,7 +599,7 @@ public class BlockBreakHandler {
*/ */
public void reset() { public void reset() {
clearCurrentVariables(); clearCurrentVariables();
this.lastInstaMinedPosition = null; this.lastMinedPosition = null;
this.destructionStageCache.invalidateAll(); this.destructionStageCache.invalidateAll();
} }

View File

@@ -47,6 +47,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.setting.Difficulty;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Objects;
public final class WorldCache { public final class WorldCache {
private final GeyserSession session; private final GeyserSession session;
@@ -179,6 +180,12 @@ public final class WorldCache {
this.unverifiedPredictions.removeInt(position); this.unverifiedPredictions.removeInt(position);
} }
// Hack to avoid looking up blockstates for the currently broken position each tick
Vector3i clientBreakPos = session.getBlockBreakHandler().getCurrentBlockPos();
if (clientBreakPos != null && Objects.equals(clientBreakPos, position)) {
session.getBlockBreakHandler().setUpdatedServerBlockStateId(blockState);
}
ChunkUtils.updateBlock(session, blockState, position); ChunkUtils.updateBlock(session, blockState, position);
} }

View File

@@ -53,7 +53,7 @@ public class ShulkerInventoryTranslator extends AbstractBlockInventoryTranslator
private final BlockEntityTranslator shulkerBoxTranslator = Registries.BLOCK_ENTITIES.get(BlockEntityType.SHULKER_BOX); private final BlockEntityTranslator shulkerBoxTranslator = Registries.BLOCK_ENTITIES.get(BlockEntityType.SHULKER_BOX);
@Override @Override
protected boolean isValidBlock(BlockState blockState) { protected boolean isValidBlock(GeyserSession session, Vector3i position, BlockState blockState) {
return blockState.block().javaIdentifier().value().contains("shulker_box"); // TODO ew return blockState.block().javaIdentifier().value().contains("shulker_box"); // TODO ew
} }

View File

@@ -25,8 +25,10 @@
package org.geysermc.geyser.translator.inventory.chest; package org.geysermc.geyser.translator.inventory.chest;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType; import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType; import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.inventory.Generic9X3Container; import org.geysermc.geyser.inventory.Generic9X3Container;
import org.geysermc.geyser.inventory.holder.BlockInventoryHolder; import org.geysermc.geyser.inventory.holder.BlockInventoryHolder;
import org.geysermc.geyser.inventory.holder.InventoryHolder; import org.geysermc.geyser.inventory.holder.InventoryHolder;
@@ -34,6 +36,9 @@ import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.level.block.property.ChestType; import org.geysermc.geyser.level.block.property.ChestType;
import org.geysermc.geyser.level.block.property.Properties; import org.geysermc.geyser.level.block.property.Properties;
import org.geysermc.geyser.level.block.type.BlockState; import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.level.physics.Axis;
import org.geysermc.geyser.level.physics.Direction;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
public class SingleChestInventoryTranslator extends ChestInventoryTranslator<Generic9X3Container> { public class SingleChestInventoryTranslator extends ChestInventoryTranslator<Generic9X3Container> {
@@ -44,14 +49,34 @@ public class SingleChestInventoryTranslator extends ChestInventoryTranslator<Gen
this.holder = new BlockInventoryHolder(Blocks.CHEST.defaultBlockState().withValue(Properties.CHEST_TYPE, ChestType.SINGLE), ContainerType.CONTAINER, this.holder = new BlockInventoryHolder(Blocks.CHEST.defaultBlockState().withValue(Properties.CHEST_TYPE, ChestType.SINGLE), ContainerType.CONTAINER,
Blocks.ENDER_CHEST, Blocks.TRAPPED_CHEST, Blocks.BARREL) { Blocks.ENDER_CHEST, Blocks.TRAPPED_CHEST, Blocks.BARREL) {
@Override @Override
protected boolean isValidBlock(BlockState blockState) { protected boolean isValidBlock(GeyserSession session, Vector3i position, BlockState blockState) {
if (blockState.is(Blocks.ENDER_CHEST) || blockState.is(Blocks.BARREL)) { if (blockState.is(Blocks.ENDER_CHEST) || blockState.is(Blocks.BARREL)) {
// Can't have double ender chests or barrels // Can't have double ender chests or barrels
return true; return true;
} }
if (!super.isValidBlock(session, position, blockState)) {
return false;
} else if (blockState.getValue(Properties.CHEST_TYPE) != ChestType.SINGLE) {
// Add provision to ensure this isn't a double chest // Add provision to ensure this isn't a double chest
return super.isValidBlock(blockState) && blockState.getValue(Properties.CHEST_TYPE) == ChestType.SINGLE; return false;
} else if (GameProtocol.is1_21_110orHigher(session)) {
// On 1.21.110 and above the client likes to merge single chests next to each other, even when we
// tell the client not to
// So, check for chests left and right of this chest. If there is a chest facing the same way,
// there is a chance the client has merged them, and we can't use this block
Direction facing = blockState.getValue(Properties.HORIZONTAL_FACING);
Vector3i left = position.add((facing.getAxis() == Axis.X ? Direction.SOUTH : Direction.WEST).getUnitVector());
Vector3i right = position.add((facing.getAxis() == Axis.X ? Direction.NORTH : Direction.EAST).getUnitVector());
BlockState leftState = BlockState.of(GeyserImpl.getInstance().getWorldManager().getBlockAt(session, left));
BlockState rightState = BlockState.of(GeyserImpl.getInstance().getWorldManager().getBlockAt(session, right));
return (!leftState.is(blockState.block()) || leftState.getValue(Properties.HORIZONTAL_FACING) != facing)
&& (!rightState.is(blockState.block()) || rightState.getValue(Properties.HORIZONTAL_FACING) != facing);
}
return true;
} }
}; };
} }

View File

@@ -80,6 +80,11 @@ public final class BedrockItemBuilder {
return this; return this;
} }
public BedrockItemBuilder addEnchantmentGlint() {
putList("ench", NbtType.COMPOUND, List.of());
return this;
}
@NonNull @NonNull
public NbtMapBuilder getOrCreateNbt() { public NbtMapBuilder getOrCreateNbt() {
if (builder == null) { if (builder == null) {

View File

@@ -9,9 +9,9 @@ netty = "4.2.3.Final"
guava = "29.0-jre" guava = "29.0-jre"
gson = "2.3.1" # Provided by Spigot 1.8.8 gson = "2.3.1" # Provided by Spigot 1.8.8
websocket = "1.5.1" websocket = "1.5.1"
protocol-connection = "3.0.0.Beta8-20250929.100800-7" protocol-connection = "3.0.0.Beta8-20250929.213851-8"
protocol-common = "3.0.0.Beta8-20250929.100800-7" protocol-common = "3.0.0.Beta8-20250929.213851-8"
protocol-codec = "3.0.0.Beta8-20250929.100800-7" protocol-codec = "3.0.0.Beta8-20250929.213851-8"
raknet = "1.0.0.CR3-20250811.214335-20" raknet = "1.0.0.CR3-20250811.214335-20"
minecraftauth = "4.1.1" minecraftauth = "4.1.1"
mcprotocollib = "1.21.9-20250929.132357-8" mcprotocollib = "1.21.9-20250929.132357-8"
@@ -30,15 +30,16 @@ cloud-minecraft = "2.0.0-beta.11"
cloud-minecraft-modded = "2.0.0-beta.12" cloud-minecraft-modded = "2.0.0-beta.12"
commodore = "2.2" commodore = "2.2"
bungeecord = "1.21-R0.1-20250215.224541-54" bungeecord = "1.21-R0.1-20250215.224541-54"
bungeecord-api = "1.21-R0.1"
velocity = "3.4.0-SNAPSHOT" velocity = "3.4.0-SNAPSHOT"
viaproxy = "3.3.2-SNAPSHOT" viaproxy = "3.3.2-SNAPSHOT"
fabric-loader = "0.17.2" fabric-loader = "0.17.2"
fabric-api = "0.133.10+1.21.9" fabric-api = "0.133.14+1.21.9"
fabric-permissions-api = "0.4.1-SNAPSHOT" fabric-permissions-api = "0.4.1"
neoforge-minecraft = "21.8.0-beta" neoforge-minecraft = "21.8.0-beta"
mixin = "0.8.5" mixin = "0.8.5"
mixinextras = "0.3.5" mixinextras = "0.3.5"
minecraft = "1.21.9-pre2" minecraft = "1.21.9-rc1"
mockito = "5.+" mockito = "5.+"
runtask = "2.3.1" runtask = "2.3.1"
runpaperversion = "1.21.8" runpaperversion = "1.21.8"
@@ -121,6 +122,7 @@ neoforge-minecraft = { group = "net.neoforged", name = "neoforge", version.ref =
adapters-spigot = { group = "org.geysermc.geyser.adapters", name = "spigot-all", version.ref = "adapters" } adapters-spigot = { group = "org.geysermc.geyser.adapters", name = "spigot-all", version.ref = "adapters" }
adapters-paper = { group = "org.geysermc.geyser.adapters", name = "paper-all", version.ref = "adapters" } adapters-paper = { group = "org.geysermc.geyser.adapters", name = "paper-all", version.ref = "adapters" }
bungeecord-proxy = { group = "net.md-5", name = "bungeecord-proxy", version.ref = "bungeecord" } bungeecord-proxy = { group = "net.md-5", name = "bungeecord-proxy", version.ref = "bungeecord" }
bungeecord-api = { group = "net.md-5", name = "bungeecord-api", version.ref = "bungeecord-api" }
checker-qual = { group = "org.checkerframework", name = "checker-qual", version.ref = "checkerframework" } checker-qual = { group = "org.checkerframework", name = "checker-qual", version.ref = "checkerframework" }
commodore = { group = "me.lucko", name = "commodore", version.ref = "commodore" } commodore = { group = "me.lucko", name = "commodore", version.ref = "commodore" }
guava = { group = "com.google.guava", name = "guava", version.ref = "guava" } guava = { group = "com.google.guava", name = "guava", version.ref = "guava" }