Pistons now use the new block stuff

This commit is contained in:
Camotoy 2024-05-17 20:55:34 -04:00
parent 06dc0d1ca8
commit beef01f3fc
No known key found for this signature in database
GPG key ID: 7EEFB66FE798081F
29 changed files with 978 additions and 829 deletions

View file

@ -26,11 +26,12 @@
package org.geysermc.geyser.erosion;
import io.netty.channel.Channel;
import it.unimi.dsi.fastutil.Pair;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import lombok.Getter;
import lombok.Setter;
import org.cloudburstmc.math.vector.Vector3i;
@ -44,6 +45,7 @@ import org.geysermc.erosion.packet.backendbound.BackendboundPacket;
import org.geysermc.erosion.packet.geyserbound.*;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.level.physics.Direction;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.session.GeyserSession;
@ -153,9 +155,10 @@ public final class GeyserboundPacketHandlerImpl extends AbstractGeyserboundPacke
var stream = packet.getAttachedBlocks()
.object2IntEntrySet()
.stream()
.filter(entry -> BlockStateValues.canPistonMoveBlock(entry.getIntValue(), isExtend));
Object2IntMap<Vector3i> attachedBlocks = new Object2IntArrayMap<>();
stream.forEach(entry -> attachedBlocks.put(entry.getKey(), entry.getIntValue()));
.map(entry -> Pair.of(entry.getKey(), BlockState.of(entry.getIntValue())))
.filter(pair -> BlockStateValues.canPistonMoveBlock(pair.value(), isExtend));
Object2ObjectMap<Vector3i, BlockState> attachedBlocks = new Object2ObjectOpenHashMap<>();
stream.forEach(pair -> attachedBlocks.put(pair.key(), pair.value()));
session.executeInEventLoop(() -> {
PistonCache pistonCache = session.getPistonCache();

View file

@ -33,6 +33,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3i;
import org.geysermc.erosion.util.BlockPositionIterator;
import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponent;
@ -58,6 +59,14 @@ import java.util.function.Function;
*/
public abstract class WorldManager {
public final BlockState blockAt(GeyserSession session, Vector3i vector3i) {
return BlockState.of(this.getBlockAt(session, vector3i));
}
public BlockState blockAt(GeyserSession session, int x, int y, int z) {
return BlockState.of(this.getBlockAt(session, x, y, z));
}
/**
* Gets the Java block state at the specified location
*

View file

@ -29,12 +29,14 @@ import com.fasterxml.jackson.databind.JsonNode;
import it.unimi.dsi.fastutil.ints.*;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import org.geysermc.geyser.level.block.property.Properties;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.level.block.type.PistonBlock;
import org.geysermc.geyser.level.physics.Direction;
import org.geysermc.geyser.level.physics.PistonBehavior;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.type.BlockMapping;
import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntityTranslator;
import org.geysermc.geyser.util.collection.FixedInt2ByteMap;
import org.geysermc.geyser.util.collection.FixedInt2IntMap;
import org.geysermc.geyser.util.collection.LecternHasBookMap;
@ -229,28 +231,6 @@ public final class BlockStateValues {
return BANNER_COLORS.getOrDefault(state, -1);
}
/**
* Bed colors are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock.
* This gives a byte color that Bedrock can use - Bedrock needs a byte in the final tag.
*
* @param state BlockState of the block
* @return Bed color byte or -1 if no color
*/
public static byte getBedColor(int state) {
return BED_COLORS.getOrDefault(state, (byte) -1);
}
/**
* The brush progress of suspicious sand/gravel is not sent by the java server when it updates the block entity.
* Although brush progress is part of the bedrock block state, it must be included in the block entity update.
*
* @param state BlockState of the block
* @return brush progress or 0 if the lookup failed
*/
public static int getBrushProgress(int state) {
return BRUSH_PROGRESS.getOrDefault(state, 0);
}
/**
* @return if this Java block state is a non-empty non-water cauldron
*/
@ -341,18 +321,6 @@ public final class BlockStateValues {
return PISTON_HEADS.getOrDefault(direction, Block.JAVA_AIR_ID);
}
/**
* Check if a block is a minecraft:moving_piston
* This is used in ChunkUtils to prevent them from being placed as it causes
* pistons to flicker and it is not needed
*
* @param state Block state of the block
* @return True if the block is a moving_piston
*/
public static boolean isMovingPiston(int state) {
return MOVING_PISTONS.contains(state);
}
/**
* This is used in GeyserPistonEvents.java and accepts minecraft:piston,
* minecraft:sticky_piston, and minecraft:moving_piston.
@ -371,8 +339,9 @@ public final class BlockStateValues {
* @param state The block state
* @return True if the block sticks to adjacent blocks
*/
public static boolean isBlockSticky(int state) {
return state == JAVA_SLIME_BLOCK_ID || state == JAVA_HONEY_BLOCK_ID;
public static boolean isBlockSticky(BlockState state) {
Block block = state.block();
return block == Blocks.SLIME_BLOCK || block == Blocks.HONEY_BLOCK;
}
/**
@ -382,13 +351,13 @@ public final class BlockStateValues {
* @param stateB The block state of block b
* @return True if the blocks are attached to each other
*/
public static boolean isBlockAttached(int stateA, int stateB) {
public static boolean isBlockAttached(BlockState stateA, BlockState stateB) {
boolean aSticky = isBlockSticky(stateA);
boolean bSticky = isBlockSticky(stateB);
if (aSticky && bSticky) {
// Only matching sticky blocks are attached together
// Honey + Honey & Slime + Slime
return stateA == stateB;
return stateA.block() == stateB.block();
}
return aSticky || bSticky;
}
@ -397,27 +366,30 @@ public final class BlockStateValues {
* @param state The block state of the block
* @return true if a piston can break the block
*/
public static boolean canPistonDestroyBlock(int state) {
return BlockRegistries.JAVA_BLOCKS.getOrDefault(state, BlockMapping.DEFAULT).getPistonBehavior() == PistonBehavior.DESTROY;
public static boolean canPistonDestroyBlock(BlockState state) {
return state.block().pushReaction() == PistonBehavior.DESTROY;
}
public static boolean canPistonMoveBlock(int javaId, boolean isPushing) {
if (javaId == Block.JAVA_AIR_ID) {
public static boolean canPistonMoveBlock(BlockState state, boolean isPushing) {
Block block = state.block();
if (block == Blocks.AIR) {
return true;
}
// Pistons can only be moved if they aren't extended
if (PistonBlockEntityTranslator.isBlock(javaId)) {
return !PISTON_VALUES.get(javaId);
}
BlockMapping block = BlockRegistries.JAVA_BLOCKS.getOrDefault(javaId, BlockMapping.DEFAULT);
// Bedrock, End portal frames, etc. can't be moved
if (block.getHardness() == -1.0d) {
if (block == Blocks.OBSIDIAN || block == Blocks.CRYING_OBSIDIAN || block == Blocks.RESPAWN_ANCHOR || block == Blocks.REINFORCED_DEEPSLATE) { // Hardcoded as of 1.20.5
return false;
}
return switch (block.getPistonBehavior()) {
// Pistons can only be moved if they aren't extended
if (block instanceof PistonBlock) {
return !state.getValue(Properties.EXTENDED);
}
// Bedrock, End portal frames, etc. can't be moved
if (block.destroyTime() == -1.0f) {
return false;
}
return switch (block.pushReaction()) {
case BLOCK, DESTROY -> false;
case PUSH_ONLY -> isPushing; // Glazed terracotta can only be pushed
default -> !block.isBlockEntity(); // Pistons can't move block entities
default -> !block.hasBlockEntity(); // Pistons can't move block entities
};
}
@ -443,17 +415,6 @@ public final class BlockStateValues {
return SKULL_ROTATIONS.getOrDefault(state, (byte) -1);
}
/**
* As of Java 1.20.2:
* Skull powered states are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock.
*
* @param state BlockState of the block
* @return true if this skull is currently being powered.
*/
public static boolean isSkullPowered(int state) {
return SKULL_POWERED.contains(state);
}
/**
* Skull rotations are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock.
* This gives a integer rotation that Bedrock can use.
@ -464,17 +425,6 @@ public final class BlockStateValues {
return SKULL_WALL_DIRECTIONS;
}
/**
* Shulker box directions are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock.
* This gives a byte direction that Bedrock can use.
*
* @param state BlockState of the block
* @return Shulker direction value or -1 if no value
*/
public static byte getShulkerBoxDirection(int state) {
return SHULKERBOX_DIRECTIONS.getOrDefault(state, (byte) -1);
}
/**
* Get the level of water from the block state.
*

View file

@ -118,7 +118,7 @@ public final class Properties {
public static final Property<Integer> RESPAWN_ANCHOR_CHARGES = Property.create("charges");
public static final Property<Integer> ROTATION_16 = Property.create("rotation");
public static final Property<String> BED_PART = Property.create("part");
public static final Property<String> CHEST_TYPE = Property.create("type");
public static final Property<ChestType> CHEST_TYPE = Property.create("type");
public static final Property<String> MODE_COMPARATOR = Property.create("mode");
public static final Property<String> DOOR_HINGE = Property.create("hinge");
public static final Property<String> NOTEBLOCK_INSTRUMENT = Property.create("instrument");

View file

@ -30,8 +30,15 @@ import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.level.block.property.Property;
import org.geysermc.geyser.level.physics.PistonBehavior;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.Identifier;
import java.util.*;
@ -44,6 +51,7 @@ public class Block {
private final boolean requiresCorrectToolForDrops;
private final boolean hasBlockEntity;
private final float destroyTime;
private final @NonNull PistonBehavior pushReaction;
private int javaId = -1;
public Block(String javaIdentifier, Builder builder) {
@ -51,9 +59,76 @@ public class Block {
this.requiresCorrectToolForDrops = builder.requiresCorrectToolForDrops;
this.hasBlockEntity = builder.hasBlockEntity;
this.destroyTime = builder.destroyTime;
this.pushReaction = builder.pushReaction;
builder.build(this);
}
public void updateBlock(GeyserSession session, BlockState state, Vector3i position) {
BlockDefinition definition = session.getBlockMappings().getBedrockBlock(state);
sendBlockUpdatePacket(session, state, definition, position);
{
// Extended collision boxes for custom blocks
if (!session.getBlockMappings().getExtendedCollisionBoxes().isEmpty()) {
int aboveBlock = session.getGeyser().getWorldManager().getBlockAt(session, position.getX(), position.getY() + 1, position.getZ());
BlockDefinition aboveBedrockExtendedCollisionDefinition = session.getBlockMappings().getExtendedCollisionBoxes().get(state.javaId());
int belowBlock = session.getGeyser().getWorldManager().getBlockAt(session, position.getX(), position.getY() - 1, position.getZ());
BlockDefinition belowBedrockExtendedCollisionDefinition = session.getBlockMappings().getExtendedCollisionBoxes().get(belowBlock);
if (belowBedrockExtendedCollisionDefinition != null && state.is(Blocks.AIR)) {
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
updateBlockPacket.setDataLayer(0);
updateBlockPacket.setBlockPosition(position);
updateBlockPacket.setDefinition(belowBedrockExtendedCollisionDefinition);
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK);
session.sendUpstreamPacket(updateBlockPacket);
} else if (aboveBedrockExtendedCollisionDefinition != null && aboveBlock == Block.JAVA_AIR_ID) {
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
updateBlockPacket.setDataLayer(0);
updateBlockPacket.setBlockPosition(position.add(0, 1, 0));
updateBlockPacket.setDefinition(aboveBedrockExtendedCollisionDefinition);
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK);
session.sendUpstreamPacket(updateBlockPacket);
} else if (aboveBlock == Block.JAVA_AIR_ID) {
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
updateBlockPacket.setDataLayer(0);
updateBlockPacket.setBlockPosition(position.add(0, 1, 0));
updateBlockPacket.setDefinition(session.getBlockMappings().getBedrockAir());
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK);
session.sendUpstreamPacket(updateBlockPacket);
}
}
}
handleLecternBlockUpdate(session, state, position);
}
protected void sendBlockUpdatePacket(GeyserSession session, BlockState state, BlockDefinition definition, Vector3i position) {
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
updateBlockPacket.setDataLayer(0);
updateBlockPacket.setBlockPosition(position);
updateBlockPacket.setDefinition(definition);
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS);
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK);
session.sendUpstreamPacket(updateBlockPacket);
UpdateBlockPacket waterPacket = new UpdateBlockPacket();
waterPacket.setDataLayer(1);
waterPacket.setBlockPosition(position);
if (BlockRegistries.WATERLOGGED.get().get(state.javaId())) {
waterPacket.setDefinition(session.getBlockMappings().getBedrockWater());
} else {
waterPacket.setDefinition(session.getBlockMappings().getBedrockAir());
}
session.sendUpstreamPacket(waterPacket);
}
protected void handleLecternBlockUpdate(GeyserSession session, BlockState state, Vector3i position) {
// Block state is out of bounds of this map - lectern has been destroyed, if it existed
if (!session.getGeyser().getWorldManager().shouldExpectLecternHandled(session)) {
session.getLecternCache().remove(position);
}
}
public String javaIdentifier() {
return javaIdentifier;
}
@ -70,6 +145,11 @@ public class Block {
return destroyTime;
}
@NonNull
public PistonBehavior pushReaction() {
return this.pushReaction;
}
public int javaId() {
return javaId;
}
@ -97,6 +177,7 @@ public class Block {
private final Map<Property<?>, List<Comparable<?>>> states = new LinkedHashMap<>();
private boolean requiresCorrectToolForDrops = false;
private boolean hasBlockEntity = false;
private PistonBehavior pushReaction = PistonBehavior.NORMAL;
private float destroyTime;
/**
@ -143,6 +224,11 @@ public class Block {
return this;
}
public Builder pushReaction(PistonBehavior pushReaction) {
this.pushReaction = pushReaction;
return this;
}
private void build(Block block) {
if (states.isEmpty()) {
BlockRegistries.BLOCK_STATES.get().add(new BlockState(block, BlockRegistries.BLOCK_STATES.get().size()));

View file

@ -58,6 +58,10 @@ public final class BlockState {
return javaId;
}
public boolean is(Block block) {
return this.block == block;
}
public static BlockState of(int javaId) {
return BlockRegistries.BLOCK_STATES.get(javaId);
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2024 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.level.block.type;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NbtList;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtType;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.level.block.entity.BedrockChunkWantsBlockEntityTag;
import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator;
public class CauldronBlock extends Block implements BedrockChunkWantsBlockEntityTag {
public CauldronBlock(String javaIdentifier, Builder builder) {
super(javaIdentifier, builder);
}
@Override
public NbtMap createTag(GeyserSession session, Vector3i position, BlockState blockState) {
// As of 1.18.30: this is required to make rendering not look weird on chunk load (lava and snow cauldrons look dim)
return BlockEntityTranslator.getConstantBedrockTag("Cauldron", position.getX(), position.getY(), position.getZ())
.putByte("isMovable", (byte) 0)
.putShort("PotionId", (short) -1)
.putShort("PotionType", (short) -1)
.putList("Items", NbtType.END, NbtList.EMPTY)
.build();
}
}

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2024 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.level.block.type;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NbtMapBuilder;
import org.geysermc.geyser.level.block.property.ChestType;
import org.geysermc.geyser.level.block.property.Properties;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator;
import org.geysermc.geyser.util.BlockEntityUtils;
import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityType;
public class ChestBlock extends Block {
public ChestBlock(String javaIdentifier, Builder builder) {
super(javaIdentifier, builder);
}
@Override
public void updateBlock(GeyserSession session, BlockState state, Vector3i position) {
super.updateBlock(session, state, position);
if (state.getValue(Properties.CHEST_TYPE) != ChestType.SINGLE) {
NbtMapBuilder tagBuilder = BlockEntityTranslator.getConstantBedrockTag(BlockEntityType.CHEST, position.getX(), position.getY(), position.getZ());
BlockEntityUtils.getBlockEntityTranslator(BlockEntityType.CHEST).translateTag(session, tagBuilder, null, state); //TODO
BlockEntityUtils.updateBlockEntity(session, tagBuilder.build(), position);
}
}
}

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2024 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.level.block.type;
import org.cloudburstmc.math.vector.Vector3i;
import org.geysermc.geyser.level.block.property.Properties;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.ChunkUtils;
public class DoorBlock extends Block {
public DoorBlock(String javaIdentifier, Builder builder) {
super(javaIdentifier, builder);
}
@Override
public void updateBlock(GeyserSession session, BlockState state, Vector3i position) {
super.updateBlock(session, state, position);
if (state.getValue(Properties.DOUBLE_BLOCK_HALF).equals("upper")) {
// Update the lower door block as Bedrock client doesn't like door to be closed from the top
// See https://github.com/GeyserMC/Geyser/issues/4358
Vector3i belowDoorPosition = position.sub(0, 1, 0);
BlockState belowDoorBlockState = session.getGeyser().getWorldManager().blockAt(session, belowDoorPosition.getX(), belowDoorPosition.getY(), belowDoorPosition.getZ());
ChunkUtils.updateBlock(session, belowDoorBlockState, belowDoorPosition);
}
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
* Copyright (c) 2024 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
@ -23,65 +23,35 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.translator.level.block.entity;
package org.geysermc.geyser.level.block.type;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.level.block.entity.BedrockChunkWantsBlockEntityTag;
import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator;
import org.geysermc.geyser.util.BlockEntityUtils;
public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity {
/**
* @param blockState the Java block state of a potential flower pot block
* @return true if the block is a flower pot
*/
public static boolean isFlowerBlock(int blockState) {
return BlockStateValues.getFlowerPotValues().containsKey(blockState);
}
public class FlowerPotBlock extends Block implements BedrockChunkWantsBlockEntityTag {
private final Block flower;
/**
* Get the Nukkit CompoundTag of the flower pot.
*
* @param blockState Java block state of flower pot.
* @param position Bedrock position of flower pot.
* @return Bedrock tag of flower pot.
*/
public static NbtMap getTag(GeyserSession session, int blockState, Vector3i position) {
NbtMapBuilder tagBuilder = NbtMap.builder()
.putInt("x", position.getX())
.putInt("y", position.getY())
.putInt("z", position.getZ())
.putByte("isMovable", (byte) 1)
.putString("id", "FlowerPot");
// Get the Java name of the plant inside. e.g. minecraft:oak_sapling
String name = BlockStateValues.getFlowerPotValues().get(blockState);
if (name != null) {
// Get the Bedrock CompoundTag of the block.
// This is where we need to store the *Java* name because Bedrock has six minecraft:sapling blocks with different block states.
NbtMap plant = session.getBlockMappings().getFlowerPotBlocks().get(name);
if (plant != null) {
tagBuilder.put("PlantBlock", plant.toBuilder().build());
}
}
return tagBuilder.build();
public FlowerPotBlock(String javaIdentifier, Block flower, Builder builder) {
super(javaIdentifier, builder);
this.flower = flower;
}
@Override
public boolean isBlock(int blockState) {
return isFlowerBlock(blockState);
}
public void updateBlock(GeyserSession session, BlockState state, Vector3i position) {
super.updateBlock(session, state, position);
@Override
public void updateBlock(GeyserSession session, BlockState blockState, Vector3i position) {
NbtMap tag = getTag(session, blockState.javaId(), position);
NbtMap tag = createTag(session, position, state);
BlockEntityUtils.updateBlockEntity(session, tag, position);
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
updateBlockPacket.setDataLayer(0);
updateBlockPacket.setDefinition(session.getBlockMappings().getBedrockBlock(blockState.javaId()));
updateBlockPacket.setDefinition(session.getBlockMappings().getBedrockBlock(state));
updateBlockPacket.setBlockPosition(position);
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS);
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK);
@ -89,4 +59,20 @@ public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity {
session.sendUpstreamPacket(updateBlockPacket);
BlockEntityUtils.updateBlockEntity(session, tag, position);
}
@Override
public NbtMap createTag(GeyserSession session, Vector3i position, BlockState blockState) {
NbtMapBuilder tagBuilder = BlockEntityTranslator.getConstantBedrockTag("FlowerPot", position.getX(), position.getY(), position.getZ())
.putByte("isMovable", (byte) 1);
// Get the Java name of the plant inside. e.g. minecraft:oak_sapling
if (this.flower != Blocks.AIR) {
// Get the Bedrock CompoundTag of the block.
// This is where we need to store the *Java* name because Bedrock has six minecraft:sapling blocks with different block states.
NbtMap plant = session.getBlockMappings().getFlowerPotBlocks().get(this.flower.javaIdentifier());
if (plant != null) {
tagBuilder.putCompound("PlantBlock", plant.toBuilder().build());
}
}
return tagBuilder.build();
}
}

View file

@ -0,0 +1,61 @@
/*
* Copyright (c) 2024 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.level.block.type;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NbtMap;
import org.geysermc.erosion.util.LecternUtils;
import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.level.block.property.Properties;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.BlockEntityUtils;
public class LecternBlock extends Block {
public LecternBlock(String javaIdentifier, Builder builder) {
super(javaIdentifier, builder);
}
@Override
protected void handleLecternBlockUpdate(GeyserSession session, BlockState state, Vector3i position) {
WorldManager worldManager = session.getGeyser().getWorldManager();
if (worldManager.shouldExpectLecternHandled(session)) {
worldManager.sendLecternData(session, position.getX(), position.getY(), position.getZ());
return;
}
boolean currentHasBook = state.getValue(Properties.HAS_BOOK);
Boolean previousHasBook = worldManager.blockAt(session, position).getValue(Properties.HAS_BOOK); // Can be null if not a lectern, watch out
if (currentHasBook != previousHasBook) {
if (currentHasBook) {
worldManager.sendLecternData(session, position.getX(), position.getY(), position.getZ());
} else {
session.getLecternCache().remove(position);
NbtMap newLecternTag = LecternUtils.getBaseLecternTag(position.getX(), position.getY(), position.getZ(), 0).build();
BlockEntityUtils.updateBlockEntity(session, newLecternTag, position);
}
}
}
}

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2024 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.level.block.type;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.geysermc.geyser.session.GeyserSession;
public class MovingPistonBlock extends Block {
public MovingPistonBlock(String javaIdentifier, Builder builder) {
super(javaIdentifier, builder);
}
@Override
protected void sendBlockUpdatePacket(GeyserSession session, BlockState state, BlockDefinition definition, Vector3i position) {
// Prevent moving_piston from being placed
// It's used for extending piston heads, but it isn't needed on Bedrock and causes pistons to flicker
}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2024 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.level.block.type;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NbtMap;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.level.block.property.Properties;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.level.block.entity.BedrockChunkWantsBlockEntityTag;
import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity;
public class PistonBlock extends Block implements BedrockChunkWantsBlockEntityTag {
public PistonBlock(String javaIdentifier, Builder builder) {
super(javaIdentifier, builder);
}
@Override
public NbtMap createTag(GeyserSession session, Vector3i position, BlockState blockState) {
boolean extended = blockState.getValue(Properties.EXTENDED);
boolean sticky = blockState.is(Blocks.STICKY_PISTON);
return PistonBlockEntity.buildStaticPistonTag(position, extended, sticky);
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright (c) 2024 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.level.block.type;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.SkullCache;
public class SkullBlock extends Block {
public SkullBlock(String javaIdentifier, Builder builder) {
super(javaIdentifier, builder);
}
@Override
protected void sendBlockUpdatePacket(GeyserSession session, BlockState state, BlockDefinition definition, Vector3i position) {
int skullVariant = BlockStateValues.getSkullVariant(state.javaId()); // TODO
if (skullVariant == -1) {
// Skull is gone
session.getSkullCache().removeSkull(position);
} else if (skullVariant == 3) {
// The changed block was a player skull so check if a custom block was defined for this skull
SkullCache.Skull skull = session.getSkullCache().updateSkull(position, state.javaId());
if (skull != null && skull.getBlockDefinition() != null) {
definition = skull.getBlockDefinition();
}
}
super.sendBlockUpdatePacket(session, state, definition, position);
}
}

View file

@ -424,39 +424,12 @@ public final class BlockRegistryPopulator {
// TODO fix this, (no block should have a null hardness)
BlockMapping.BlockMappingBuilder builder = BlockMapping.builder();
JsonNode hardnessNode = entry.getValue().get("block_hardness");
if (hardnessNode != null) {
builder.hardness(hardnessNode.floatValue());
}
JsonNode canBreakWithHandNode = entry.getValue().get("can_break_with_hand");
if (canBreakWithHandNode != null) {
builder.canBreakWithHand(canBreakWithHandNode.booleanValue());
} else {
builder.canBreakWithHand(false);
}
JsonNode collisionIndexNode = entry.getValue().get("collision_index");
if (hardnessNode != null) {
builder.collisionIndex(collisionIndexNode.intValue());
}
JsonNode pickItemNode = entry.getValue().get("pick_item");
if (pickItemNode != null) {
builder.pickItem(pickItemNode.textValue().intern());
}
if (javaId.equals("minecraft:obsidian") || javaId.equals("minecraft:crying_obsidian") || javaId.startsWith("minecraft:respawn_anchor") || javaId.startsWith("minecraft:reinforced_deepslate")) {
builder.pistonBehavior(PistonBehavior.BLOCK);
} else {
JsonNode pistonBehaviorNode = entry.getValue().get("piston_behavior");
if (pistonBehaviorNode != null) {
builder.pistonBehavior(PistonBehavior.getByName(pistonBehaviorNode.textValue()));
} else {
builder.pistonBehavior(PistonBehavior.NORMAL);
}
}
JsonNode hasBlockEntityNode = entry.getValue().get("has_block_entity");
if (hasBlockEntityNode != null) {
builder.isBlockEntity(hasBlockEntityNode.booleanValue());
@ -475,7 +448,6 @@ public final class BlockRegistryPopulator {
}
builder.javaIdentifier(javaId);
builder.javaBlockId(uniqueJavaId);
BlockRegistries.JAVA_IDENTIFIER_TO_ID.register(javaId, javaRuntimeId);
BlockRegistries.JAVA_BLOCKS.register(javaRuntimeId, builder.build());
@ -547,26 +519,23 @@ public final class BlockRegistryPopulator {
int stateRuntimeId = javaBlockState.javaId();
String pistonBehavior = javaBlockState.pistonBehavior();
BlockMapping blockMapping = BlockMapping.builder()
.canBreakWithHand(javaBlockState.canBreakWithHand())
.pickItem(javaBlockState.pickItem())
.isNonVanilla(true)
.javaIdentifier(javaId)
.javaBlockId(javaBlockState.stateGroupId())
.hardness(javaBlockState.blockHardness())
.pistonBehavior(pistonBehavior == null ? PistonBehavior.NORMAL : PistonBehavior.getByName(pistonBehavior))
.isBlockEntity(javaBlockState.hasBlockEntity())
.build();
Block.Builder builder = Block.builder()
.destroyTime(javaBlockState.blockHardness());
.destroyTime(javaBlockState.blockHardness())
.pushReaction(pistonBehavior == null ? PistonBehavior.NORMAL : PistonBehavior.getByName(pistonBehavior));
if (!javaBlockState.canBreakWithHand()) {
builder.requiresCorrectToolForDrops();
}
if (javaBlockState.hasBlockEntity()) {
builder.setBlockEntity();
}
String cleanJavaIdentifier = BlockUtils.getCleanIdentifier(javaBlockState.identifier());
Block block = new Block(cleanJavaIdentifier, builder);
String bedrockIdentifier = customBlockState.block().identifier();
if (!cleanJavaIdentifier.equals(cleanIdentifiers.peekLast())) {
@ -574,6 +543,7 @@ public final class BlockRegistryPopulator {
cleanIdentifiers.add(cleanJavaIdentifier.intern());
}
BlockRegistries.JAVA_BLOCKS_TO_RENAME.get().add(javaBlockState.stateGroupId(), block); //TODO don't allow duplicates, allow blanks
BlockRegistries.JAVA_IDENTIFIER_TO_ID.register(javaId, stateRuntimeId);
BlockRegistries.JAVA_BLOCKS.register(stateRuntimeId, blockMapping);

View file

@ -27,32 +27,19 @@ package org.geysermc.geyser.registry.type;
import lombok.Builder;
import lombok.Value;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.level.physics.PistonBehavior;
import org.geysermc.geyser.util.BlockUtils;
@Builder
@Value
public class BlockMapping {
public static BlockMapping DEFAULT = BlockMapping.builder().javaIdentifier("minecraft:air").pistonBehavior(PistonBehavior.NORMAL).build();
public static BlockMapping DEFAULT = BlockMapping.builder().javaIdentifier("minecraft:air").build();
String javaIdentifier;
/**
* The block ID shared between all different block states of this block.
* NOT the runtime ID!
*/
int javaBlockId;
float hardness;
boolean canBreakWithHand;
/**
* The index of this collision in collision.json
*/
int collisionIndex;
@Nullable String pickItem;
@NonNull PistonBehavior pistonBehavior;
boolean isBlockEntity;
boolean isNonVanilla;

View file

@ -35,6 +35,7 @@ import org.cloudburstmc.protocol.bedrock.data.BlockPropertyData;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.common.DefinitionRegistry;
import org.geysermc.geyser.api.block.custom.CustomBlockState;
import org.geysermc.geyser.level.block.type.BlockState;
import java.util.List;
import java.util.Map;
@ -78,6 +79,10 @@ public class BlockMappings implements DefinitionRegistry<GeyserBedrockBlock> {
return this.javaToBedrockBlocks[javaState];
}
public GeyserBedrockBlock getBedrockBlock(BlockState javaState) {
return this.getBedrockBlock(javaState.javaId());
}
public GeyserBedrockBlock getVanillaBedrockBlock(int javaState) {
if (javaState < 0 || javaState >= this.javaToVanillaBedrockBlocks.length) {
return bedrockAir;

View file

@ -30,7 +30,6 @@ import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.registry.type.BlockMapping;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.BlockTag;
import org.geysermc.geyser.session.cache.tags.ItemTag;
@ -107,17 +106,6 @@ public final class TagCache {
return false;
}
/**
* @return true if the block tag is present and contains this block mapping's Java ID.
*/
public boolean is(BlockTag tag, BlockMapping mapping) {
IntList values = this.blocks.get(tag);
if (values != null) {
return values.contains(mapping.getJavaBlockId());
}
return false;
}
/**
* @return true if the item tag is present and contains this item stack's Java ID.
*/

View file

@ -180,7 +180,7 @@ public final class WorldCache {
// This block may be out of sync with the server
// In 1.19.0 Java, you can verify this by trying to mine in spawn protection
Vector3i position = entry.getKey();
ChunkUtils.updateBlockClientSide(session, session.getGeyser().getWorldManager().getBlockAt(session, position), position);
ChunkUtils.updateBlockClientSide(session, session.getGeyser().getWorldManager().blockAt(session, position), position);
it.remove();
}
}

View file

@ -27,32 +27,18 @@ package org.geysermc.geyser.translator.level.block.entity;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NbtMap;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.session.GeyserSession;
/**
* Pistons are a special case where they are only a block entity on Bedrock.
* Implemented only if a block is a block entity in Bedrock and not Java Edition.
*/
public class PistonBlockEntityTranslator {
public interface BedrockChunkWantsBlockEntityTag extends RequiresBlockState {
/**
* Used in ChunkUtils to determine if the block is a piston.
*
* Get the tag of the Bedrock-only block entity. Used during chunk loading.
* @param position Bedrock position of block.
* @param blockState Java BlockState of block.
* @return if block is a piston or not.
* @return Bedrock tag
*/
public static boolean isBlock(int blockState) {
return BlockStateValues.getPistonValues().containsKey(blockState);
}
/**
* Calculates the Nukkit CompoundTag to send to the client on chunk
*
* @param blockState Java block state of block.
* @param position Bedrock position of piston.
* @return Bedrock tag of piston.
*/
public static NbtMap getTag(int blockState, Vector3i position) {
boolean extended = BlockStateValues.getPistonValues().get(blockState);
boolean sticky = BlockStateValues.isStickyPiston(blockState);
return PistonBlockEntity.buildStaticPistonTag(position, extended, sticky);
}
NbtMap createTag(GeyserSession session, Vector3i position, BlockState blockState);
}

View file

@ -1,82 +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.level.block.entity;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NbtList;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtType;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.session.GeyserSession;
/**
* Implemented only if a block is a block entity in Bedrock and not Java Edition.
*/
public interface BedrockOnlyBlockEntity extends RequiresBlockState {
/**
* Determines if block is part of class
* @param blockState BlockState to be compared
* @return true if part of the class
*/
boolean isBlock(int blockState);
/**
* Update the block on Bedrock Edition.
* @param session GeyserConnection.
* @param blockState The Java block state.
* @param position The Bedrock block position.
*/
void updateBlock(GeyserSession session, BlockState blockState, Vector3i position);
/**
* Get the tag of the Bedrock-only block entity
* @param position Bedrock position of block.
* @param blockState Java BlockState of block.
* @return Bedrock tag, or null if not a Bedrock-only Block Entity
*/
static @Nullable NbtMap getTag(GeyserSession session, Vector3i position, int blockState) {
if (FlowerPotBlockEntityTranslator.isFlowerBlock(blockState)) {
return FlowerPotBlockEntityTranslator.getTag(session, blockState, position);
} else if (PistonBlockEntityTranslator.isBlock(blockState)) {
return PistonBlockEntityTranslator.getTag(blockState, position);
} else if (BlockStateValues.isCauldron(blockState)) {
// As of 1.18.30: this is required to make rendering not look weird on chunk load (lava and snow cauldrons look dim)
return NbtMap.builder()
.putString("id", "Cauldron")
.putByte("isMovable", (byte) 0)
.putShort("PotionId", (short) -1)
.putShort("PotionType", (short) -1)
.putList("Items", NbtType.END, NbtList.EMPTY)
.putInt("x", position.getX())
.putInt("y", position.getY())
.putInt("z", position.getZ())
.build();
}
return null;
}
}

View file

@ -25,33 +25,20 @@
package org.geysermc.geyser.translator.level.block.entity;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.block.property.ChestType;
import org.geysermc.geyser.level.block.property.Properties;
import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.level.physics.Direction;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.BlockEntityUtils;
import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityType;
/**
* Chests have more block entity properties in Bedrock, which is solved by implementing the BedrockOnlyBlockEntity
* Chests have more block entity properties in Bedrock, which is solved by implementing the BedrockChunkWantsBlockEntityTag
*/
@BlockEntity(type = { BlockEntityType.CHEST, BlockEntityType.TRAPPED_CHEST })
public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator implements BedrockOnlyBlockEntity {
@Override
public boolean isBlock(int blockState) {
return BlockStateValues.getDoubleChestValues().containsKey(blockState);
}
@Override
public void updateBlock(GeyserSession session, BlockState blockState, Vector3i position) {
NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(BlockEntityType.CHEST), position.getX(), position.getY(), position.getZ());
translateTag(session, tagBuilder, null, blockState);
BlockEntityUtils.updateBlockEntity(session, tagBuilder.build(), position);
}
public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator {
@Override
public void translateTag(GeyserSession session, NbtMapBuilder bedrockNbt, NbtMap javaNbt, BlockState blockState) {
@ -71,7 +58,7 @@ public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator impl
public static void translateChestValue(NbtMapBuilder builder, BlockState state, int x, int z) {
// Calculate the position of the other chest based on the Java block state
Direction facing = state.getValue(Properties.HORIZONTAL_FACING);
boolean isLeft = state.getValue(Properties.CHEST_TYPE).equals("left"); //TODO enum
boolean isLeft = state.getValue(Properties.CHEST_TYPE) == ChestType.LEFT;
switch (facing) {
case EAST -> z = z + (isLeft ? 1 : -1);
case WEST -> z = z + (isLeft ? -1 : 1);

View file

@ -25,7 +25,11 @@
package org.geysermc.geyser.translator.level.block.entity;
import it.unimi.dsi.fastutil.objects.*;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.level.block.type.PistonBlock;
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.PistonValueType;
import org.cloudburstmc.math.vector.Vector3d;
import org.cloudburstmc.math.vector.Vector3f;
@ -33,9 +37,6 @@ import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import lombok.Getter;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.level.block.BlockStateValues;
@ -69,7 +70,7 @@ public class PistonBlockEntity {
/**
* A map of attached block positions to Java ids.
*/
private final Object2IntMap<Vector3i> attachedBlocks = new Object2IntOpenHashMap<>();
private final Object2ObjectMap<Vector3i, BlockState> attachedBlocks = new Object2ObjectOpenHashMap<>();
/**
* A flattened array of the positions of attached blocks, stored in XYZ order.
*/
@ -158,7 +159,7 @@ public class PistonBlockEntity {
BlockEntityUtils.updateBlockEntity(session, buildPistonTag(), position);
}
public void setAction(PistonValueType action, Object2IntMap<Vector3i> attachedBlocks) {
public void setAction(PistonValueType action, Map<Vector3i, BlockState> attachedBlocks) {
// Don't check if this.action == action, since on some Paper versions BlockPistonRetractEvent is called multiple times
// with the first 1-2 events being empty.
placeFinalBlocks();
@ -255,13 +256,13 @@ public class PistonBlockEntity {
if (!blocksChecked.add(blockPos)) {
continue;
}
int blockId = session.getGeyser().getWorldManager().getBlockAt(session, blockPos);
if (blockId == Block.JAVA_AIR_ID) {
BlockState state = session.getGeyser().getWorldManager().blockAt(session, blockPos);
if (state.block() == Blocks.AIR) {
continue;
}
if (BlockStateValues.canPistonMoveBlock(blockId, action == PistonValueType.PUSHING)) {
attachedBlocks.put(blockPos, blockId);
if (BlockStateValues.isBlockSticky(blockId)) {
if (BlockStateValues.canPistonMoveBlock(state, action == PistonValueType.PUSHING)) {
attachedBlocks.put(blockPos, state);
if (BlockStateValues.isBlockSticky(state)) {
// For honey blocks and slime blocks check the blocks adjacent to it
for (Direction direction : Direction.VALUES) {
Vector3i offset = direction.getUnitVector();
@ -278,13 +279,13 @@ public class PistonBlockEntity {
if (action == PistonValueType.PULLING && position.add(directionOffset).equals(adjacentPos)) {
continue;
}
int adjacentBlockId = session.getGeyser().getWorldManager().getBlockAt(session, adjacentPos);
if (adjacentBlockId != Block.JAVA_AIR_ID && BlockStateValues.isBlockAttached(blockId, adjacentBlockId) && BlockStateValues.canPistonMoveBlock(adjacentBlockId, false)) {
BlockState adjacentBlockState = session.getGeyser().getWorldManager().blockAt(session, adjacentPos);
if (adjacentBlockState.block() != Blocks.AIR && BlockStateValues.isBlockAttached(state, adjacentBlockState) && BlockStateValues.canPistonMoveBlock(adjacentBlockState, false)) {
// If it is another slime/honey block we need to check its adjacent blocks
if (BlockStateValues.isBlockSticky(adjacentBlockId)) {
if (BlockStateValues.isBlockSticky(adjacentBlockState)) {
blocksToCheck.add(adjacentPos);
} else {
attachedBlocks.put(adjacentPos, adjacentBlockId);
attachedBlocks.put(adjacentPos, adjacentBlockState);
blocksChecked.add(adjacentPos);
blocksToCheck.add(adjacentPos.add(movement));
}
@ -293,7 +294,7 @@ public class PistonBlockEntity {
}
// Check next block in line
blocksToCheck.add(blockPos.add(movement));
} else if (!BlockStateValues.canPistonDestroyBlock(blockId)) {
} else if (!BlockStateValues.canPistonDestroyBlock(state)) {
// Block can't be moved or destroyed, so it blocks all block movement
moveBlocks = false;
break;
@ -350,24 +351,24 @@ public class PistonBlockEntity {
playerBoundingBox.setSizeZ(playerBoundingBox.getSizeZ() - shrink.getZ());
// Resolve collision with the piston head
int pistonHeadId = BlockStateValues.getPistonHead(orientation);
BlockState pistonHeadId = BlockState.of(BlockStateValues.getPistonHead(orientation));
pushPlayerBlock(pistonHeadId, getPistonHeadPos().toDouble(), blockMovement, playerBoundingBox);
// Resolve collision with any attached moving blocks, but skip slime blocks
// This prevents players from being launched by slime blocks covered by other blocks
for (Object2IntMap.Entry<Vector3i> entry : attachedBlocks.object2IntEntrySet()) {
int blockId = entry.getIntValue();
if (blockId != BlockStateValues.JAVA_SLIME_BLOCK_ID) {
for (Map.Entry<Vector3i, BlockState> entry : Object2ObjectMaps.fastIterable(attachedBlocks)) {
BlockState state = entry.getValue();
if (!state.is(Blocks.SLIME_BLOCK)) {
Vector3d blockPos = entry.getKey().toDouble();
pushPlayerBlock(blockId, blockPos, blockMovement, playerBoundingBox);
pushPlayerBlock(state, blockPos, blockMovement, playerBoundingBox);
}
}
// Resolve collision with slime blocks
for (Object2IntMap.Entry<Vector3i> entry : attachedBlocks.object2IntEntrySet()) {
int blockId = entry.getIntValue();
if (blockId == BlockStateValues.JAVA_SLIME_BLOCK_ID) {
for (Map.Entry<Vector3i, BlockState> entry : Object2ObjectMaps.fastIterable(attachedBlocks)) {
BlockState state = entry.getValue();
if (state.is(Blocks.SLIME_BLOCK)) {
Vector3d blockPos = entry.getKey().toDouble();
pushPlayerBlock(blockId, blockPos, blockMovement, playerBoundingBox);
pushPlayerBlock(state, blockPos, blockMovement, playerBoundingBox);
}
}
@ -463,7 +464,7 @@ public class PistonBlockEntity {
return maxIntersection;
}
private void pushPlayerBlock(int javaId, Vector3d startingPos, double blockMovement, BoundingBox playerBoundingBox) {
private void pushPlayerBlock(BlockState state, Vector3d startingPos, double blockMovement, BoundingBox playerBoundingBox) {
PistonCache pistonCache = session.getPistonCache();
Vector3d movement = getMovement().toDouble();
// Check if the player collides with the movingBlock block entity
@ -471,14 +472,14 @@ public class PistonBlockEntity {
if (SOLID_BOUNDING_BOX.checkIntersection(finalBlockPos, playerBoundingBox)) {
pistonCache.setPlayerCollided(true);
if (javaId == BlockStateValues.JAVA_SLIME_BLOCK_ID) {
if (state.is(Blocks.SLIME_BLOCK)) {
pistonCache.setPlayerSlimeCollision(true);
applySlimeBlockMotion(finalBlockPos, Vector3d.from(playerBoundingBox.getMiddleX(), playerBoundingBox.getMiddleY(), playerBoundingBox.getMiddleZ()));
}
}
Vector3d blockPos = startingPos.add(movement.mul(blockMovement));
if (javaId == BlockStateValues.JAVA_HONEY_BLOCK_ID && isPlayerAttached(blockPos, playerBoundingBox)) {
if (state.is(Blocks.HONEY_BLOCK) && isPlayerAttached(blockPos, playerBoundingBox)) {
pistonCache.setPlayerCollided(true);
pistonCache.setPlayerAttachedToHoney(true);
@ -486,7 +487,7 @@ public class PistonBlockEntity {
pistonCache.displacePlayer(movement.mul(delta));
} else {
// Move the player out of collision
BlockCollision blockCollision = BlockRegistries.COLLISIONS.get(javaId);
BlockCollision blockCollision = BlockRegistries.COLLISIONS.get(state.javaId());
if (blockCollision != null) {
Vector3d extend = movement.mul(Math.min(1 - blockMovement, 0.5));
Direction movementDirection = orientation;
@ -499,7 +500,7 @@ public class PistonBlockEntity {
pistonCache.setPlayerCollided(true);
pistonCache.displacePlayer(movement.mul(intersection + 0.01d));
if (javaId == BlockStateValues.JAVA_SLIME_BLOCK_ID) {
if (state.is(Blocks.SLIME_BLOCK)) {
pistonCache.setPlayerSlimeCollision(true);
applySlimeBlockMotion(blockPos, Vector3d.from(playerBoundingBox.getMiddleX(), playerBoundingBox.getMiddleY(), playerBoundingBox.getMiddleZ()));
}
@ -509,7 +510,7 @@ public class PistonBlockEntity {
}
private BlockCollision getCollision(Vector3i blockPos) {
return BlockUtils.getCollision(getAttachedBlockId(blockPos));
return BlockUtils.getCollision(getAttachedBlockId(blockPos).javaId());
}
/**
@ -533,7 +534,7 @@ public class PistonBlockEntity {
double y = blockPos.getY() + movementVec.getY() * movementProgress;
double z = blockPos.getZ() + movementVec.getZ() * movementProgress;
double adjustedMovement = blockCollision.computeCollisionOffset(x, y, z, boundingBox, axis, movement);
if (getAttachedBlockId(blockPos) == BlockStateValues.JAVA_SLIME_BLOCK_ID && adjustedMovement != movement) {
if (getAttachedBlockId(blockPos).is(Blocks.SLIME_BLOCK) && adjustedMovement != movement) {
session.getPistonCache().setPlayerSlimeCollision(true);
}
return adjustedMovement;
@ -557,11 +558,11 @@ public class PistonBlockEntity {
return false;
}
private int getAttachedBlockId(Vector3i blockPos) {
private BlockState getAttachedBlockId(Vector3i blockPos) {
if (blockPos.equals(getPistonHeadPos())) {
return BlockStateValues.getPistonHead(orientation);
return BlockState.of(BlockStateValues.getPistonHead(orientation));
} else {
return attachedBlocks.getOrDefault(blockPos, Block.JAVA_AIR_ID);
return attachedBlocks.getOrDefault(blockPos, BlockState.of(Block.JAVA_AIR_ID)); //FIXME
}
}
@ -582,12 +583,12 @@ public class PistonBlockEntity {
playerBoundingBox.setSizeX(playerBoundingBox.getSizeX() + 0.5);
playerBoundingBox.setSizeZ(playerBoundingBox.getSizeZ() + 0.5);
}
attachedBlocks.forEach((blockPos, javaId) -> {
attachedBlocks.forEach((blockPos, state) -> {
Vector3i newPos = blockPos.add(movement);
if (SOLID_BOUNDING_BOX.checkIntersection(blockPos.toDouble(), playerBoundingBox) ||
SOLID_BOUNDING_BOX.checkIntersection(newPos.toDouble(), playerBoundingBox)) {
session.getPistonCache().setPlayerCollided(true);
if (javaId == BlockStateValues.JAVA_SLIME_BLOCK_ID) {
if (state.is(Blocks.SLIME_BLOCK)) {
session.getPistonCache().setPlayerSlimeCollision(true);
}
// Don't place moving blocks that collide with the player
@ -603,7 +604,7 @@ public class PistonBlockEntity {
updateBlockPacket.setDataLayer(0);
session.sendUpstreamPacket(updateBlockPacket);
// Update moving block with correct details
BlockEntityUtils.updateBlockEntity(session, buildMovingBlockTag(newPos, javaId, position), newPos);
BlockEntityUtils.updateBlockEntity(session, buildMovingBlockTag(newPos, state, position), newPos);
});
}
@ -773,13 +774,13 @@ public class PistonBlockEntity {
* Create a moving block tag of a block that will be moved by a piston
*
* @param position The ending position of the block (The location of the movingBlock block entity)
* @param javaId The Java Id of the block that is moving
* @param state The Java BlockState of the block that is moving
* @param pistonPosition The position for the base of the piston that's moving the block
* @return A moving block data tag
*/
private NbtMap buildMovingBlockTag(Vector3i position, int javaId, Vector3i pistonPosition) {
private NbtMap buildMovingBlockTag(Vector3i position, BlockState state, Vector3i pistonPosition) {
// Get Bedrock block state data
NbtMap movingBlock = session.getBlockMappings().getBedrockBlock(javaId).getState();
NbtMap movingBlock = session.getBlockMappings().getBedrockBlock(state).getState();
NbtMapBuilder builder = NbtMap.builder()
.putString("id", "MovingBlock")
.putBoolean("expanding", action == PistonValueType.PUSHING)
@ -791,8 +792,8 @@ public class PistonBlockEntity {
.putInt("x", position.getX())
.putInt("y", position.getY())
.putInt("z", position.getZ());
if (PistonBlockEntityTranslator.isBlock(javaId)) {
builder.putCompound("movingEntity", PistonBlockEntityTranslator.getTag(javaId, position));
if (state.block() instanceof PistonBlock piston) {
builder.putCompound("movingEntity", piston.createTag(session, position, state));
}
return builder.build();
}

View file

@ -25,13 +25,12 @@
package org.geysermc.geyser.translator.protocol.bedrock;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.packet.BlockPickRequestPacket;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.ItemFrameEntity;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.type.BlockMapping;
import org.geysermc.geyser.registry.type.ItemMapping;
@ -39,6 +38,7 @@ 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.InventoryUtils;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
@Translator(packet = BlockPickRequestPacket.class)
public class BedrockBlockPickRequestTranslator extends PacketTranslator<BlockPickRequestPacket> {

View file

@ -25,23 +25,25 @@
package org.geysermc.geyser.translator.protocol.java.level;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.*;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.level.ClientboundBlockEventPacket;
import it.unimi.dsi.fastutil.objects.Object2ObjectMaps;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.protocol.bedrock.packet.BlockEntityDataPacket;
import org.cloudburstmc.protocol.bedrock.packet.BlockEventPacket;
import it.unimi.dsi.fastutil.objects.Object2IntMaps;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.block.property.Properties;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.level.physics.Direction;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.PistonCache;
import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator;
import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.*;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.level.ClientboundBlockEventPacket;
@Translator(packet = ClientboundBlockEventPacket.class)
public class JavaBlockEventTranslator extends PacketTranslator<ClientboundBlockEventPacket> {
@ -63,7 +65,7 @@ public class JavaBlockEventTranslator extends PacketTranslator<ClientboundBlockE
session.sendUpstreamPacket(blockEventPacket);
} else if (value instanceof NoteBlockValue) {
session.getGeyser().getWorldManager().getBlockAtAsync(session, position).thenAccept(blockState -> {
blockEventPacket.setEventData(BlockStateValues.getNoteblockPitch(blockState));
blockEventPacket.setEventData(BlockState.of(blockState).getValue(Properties.NOTE));
session.sendUpstreamPacket(blockEventPacket);
});
} else if (value instanceof PistonValue pistonValue) {
@ -90,7 +92,7 @@ public class JavaBlockEventTranslator extends PacketTranslator<ClientboundBlockE
}
PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(position, pos -> new PistonBlockEntity(session, pos, direction, true, true));
if (blockEntity.getAction() != action) {
blockEntity.setAction(action, Object2IntMaps.emptyMap());
blockEntity.setAction(action, Object2ObjectMaps.emptyMap());
}
}
} else {
@ -110,11 +112,7 @@ public class JavaBlockEventTranslator extends PacketTranslator<ClientboundBlockE
BlockEntityDataPacket blockEntityPacket = new BlockEntityDataPacket();
blockEntityPacket.setBlockPosition(position);
NbtMapBuilder builder = NbtMap.builder();
builder.putInt("x", position.getX());
builder.putInt("y", position.getY());
builder.putInt("z", position.getZ());
builder.putString("id", "Bell");
NbtMapBuilder builder = BlockEntityTranslator.getConstantBedrockTag("Bell", position.getX(), position.getY(), position.getZ());
int bedrockRingDirection = switch (bellValue.getDirection()) {
case SOUTH -> 0;
case WEST -> 1;

View file

@ -41,7 +41,6 @@ import org.cloudburstmc.protocol.bedrock.packet.LevelChunkPacket;
import org.geysermc.erosion.util.LecternUtils;
import org.geysermc.geyser.entity.type.ItemFrameEntity;
import org.geysermc.geyser.level.BedrockDimension;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.block.property.Properties;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.level.block.type.BlockState;
@ -53,7 +52,7 @@ import org.geysermc.geyser.level.chunk.bitarray.SingletonBitArray;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.level.BiomeTranslator;
import org.geysermc.geyser.translator.level.block.entity.BedrockOnlyBlockEntity;
import org.geysermc.geyser.translator.level.block.entity.BedrockChunkWantsBlockEntityTag;
import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator;
import org.geysermc.geyser.translator.level.block.entity.SkullBlockEntityTranslator;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
@ -193,11 +192,12 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
}
}
BlockState state = BlockState.of(javaId);
// Check if block is piston or flower to see if we'll need to create additional block entities, as they're only block entities in Bedrock
if (BlockStateValues.getFlowerPotValues().containsKey(javaId) || BlockStateValues.getPistonValues().containsKey(javaId) || BlockStateValues.isCauldron(javaId)) {
bedrockBlockEntities.add(BedrockOnlyBlockEntity.getTag(session,
if (state.block() instanceof BedrockChunkWantsBlockEntityTag blockEntity) {
bedrockBlockEntities.add(blockEntity.createTag(session,
Vector3i.from((packet.getX() << 4) + (yzx & 0xF), ((sectionY + yOffset) << 4) + ((yzx >> 8) & 0xF), (packet.getZ() << 4) + ((yzx >> 4) & 0xF)),
javaId
state
));
}
}
@ -253,7 +253,9 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
}
// Check if block is piston, flower or cauldron to see if we'll need to create additional block entities, as they're only block entities in Bedrock
if (BlockStateValues.getFlowerPotValues().containsKey(javaId) || BlockStateValues.getPistonValues().containsKey(javaId) || BlockStateValues.isCauldron(javaId)) {
// TODO this needs a performance check when my head is clearer
BlockState state = BlockState.of(javaId);
if (state.block() instanceof BedrockChunkWantsBlockEntityTag) {
bedrockOnlyBlockEntityIds.set(i);
}
}
@ -265,9 +267,10 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) {
int paletteId = javaData.get(yzx);
if (bedrockOnlyBlockEntityIds.get(paletteId)) {
bedrockBlockEntities.add(BedrockOnlyBlockEntity.getTag(session,
BlockState state = BlockState.of(javaPalette.idToState(paletteId));
bedrockBlockEntities.add(((BedrockChunkWantsBlockEntityTag) state.block()).createTag(session,
Vector3i.from((packet.getX() << 4) + (yzx & 0xF), ((sectionY + yOffset) << 4) + ((yzx >> 8) & 0xF), (packet.getZ() << 4) + ((yzx >> 4) & 0xF)),
javaPalette.idToState(paletteId)
state
));
}
}

View file

@ -31,25 +31,13 @@ import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.protocol.bedrock.packet.BlockEntityDataPacket;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.level.block.entity.BedrockOnlyBlockEntity;
import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator;
import org.geysermc.geyser.translator.level.block.entity.FlowerPotBlockEntityTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityType;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class BlockEntityUtils {
/**
* A list of all block entities that require the Java block state in order to fill out their block entity information.
* This list will be smaller with cache sections on as we don't need to double-cache data
*/
public static final List<BedrockOnlyBlockEntity> BEDROCK_ONLY_BLOCK_ENTITIES = List.of(
(BedrockOnlyBlockEntity) Registries.BLOCK_ENTITIES.get().get(BlockEntityType.CHEST),
new FlowerPotBlockEntityTranslator()
);
/**
* Contains a list of irregular block entity name translations that can't be fit into the regex
*/

View file

@ -33,24 +33,19 @@ import lombok.experimental.UtilityClass;
import org.cloudburstmc.math.GenericMath;
import org.cloudburstmc.math.vector.Vector2i;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.packet.LevelChunkPacket;
import org.cloudburstmc.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket;
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
import org.geysermc.geyser.entity.type.ItemFrameEntity;
import org.geysermc.geyser.level.BedrockDimension;
import org.geysermc.geyser.level.JavaDimension;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.level.chunk.BlockStorage;
import org.geysermc.geyser.level.chunk.GeyserChunkSection;
import org.geysermc.geyser.level.chunk.bitarray.SingletonBitArray;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.SkullCache;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.translator.level.block.entity.BedrockOnlyBlockEntity;
@UtilityClass
public class ChunkUtils {
@ -119,18 +114,30 @@ public class ChunkUtils {
* @param position the position of the block
*/
public static void updateBlock(GeyserSession session, int blockState, Vector3i position) {
updateBlockClientSide(session, blockState, position);
updateBlockClientSide(session, BlockState.of(blockState), position);
session.getChunkCache().updateBlock(position.getX(), position.getY(), position.getZ(), blockState);
}
/**
* Sends a block update to the Bedrock client. If the platform does not have an integrated world manager, this also
* adds that block to the cache.
* @param session the Bedrock session to send/register the block to
* @param blockState the Java block state of the block
* @param position the position of the block
*/
public static void updateBlock(GeyserSession session, BlockState blockState, Vector3i position) {
updateBlockClientSide(session, blockState, position);
session.getChunkCache().updateBlock(position.getX(), position.getY(), position.getZ(), blockState.javaId());
}
/**
* Updates a block, but client-side only.
*/
public static void updateBlockClientSide(GeyserSession session, int blockState, Vector3i position) {
public static void updateBlockClientSide(GeyserSession session, BlockState blockState, Vector3i position) {
// Checks for item frames so they aren't tripped up and removed
ItemFrameEntity itemFrameEntity = ItemFrameEntity.getItemFrameEntity(session, position);
if (itemFrameEntity != null) {
if (blockState == Block.JAVA_AIR_ID) { // Item frame is still present and no block overrides that; refresh it
if (blockState.is(Blocks.AIR)) { // Item frame is still present and no block overrides that; refresh it
itemFrameEntity.updateBlock(true);
// Still update the chunk cache with the new block if updateBlock is called
return;
@ -138,91 +145,7 @@ public class ChunkUtils {
// Otherwise, let's still store our reference to the item frame, but let the new block take precedence for now
}
BlockDefinition definition = session.getBlockMappings().getBedrockBlock(blockState);
int skullVariant = BlockStateValues.getSkullVariant(blockState);
if (skullVariant == -1) {
// Skull is gone
session.getSkullCache().removeSkull(position);
} else if (skullVariant == 3) {
// The changed block was a player skull so check if a custom block was defined for this skull
SkullCache.Skull skull = session.getSkullCache().updateSkull(position, blockState);
if (skull != null && skull.getBlockDefinition() != null) {
definition = skull.getBlockDefinition();
}
}
// Prevent moving_piston from being placed
// It's used for extending piston heads, but it isn't needed on Bedrock and causes pistons to flicker
if (!BlockStateValues.isMovingPiston(blockState)) {
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
updateBlockPacket.setDataLayer(0);
updateBlockPacket.setBlockPosition(position);
updateBlockPacket.setDefinition(definition);
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS);
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK);
session.sendUpstreamPacket(updateBlockPacket);
UpdateBlockPacket waterPacket = new UpdateBlockPacket();
waterPacket.setDataLayer(1);
waterPacket.setBlockPosition(position);
if (BlockRegistries.WATERLOGGED.get().get(blockState)) {
waterPacket.setDefinition(session.getBlockMappings().getBedrockWater());
} else {
waterPacket.setDefinition(session.getBlockMappings().getBedrockAir());
}
session.sendUpstreamPacket(waterPacket);
}
// Extended collision boxes for custom blocks
if (!session.getBlockMappings().getExtendedCollisionBoxes().isEmpty()) {
int aboveBlock = session.getGeyser().getWorldManager().getBlockAt(session, position.getX(), position.getY() + 1, position.getZ());
BlockDefinition aboveBedrockExtendedCollisionDefinition = session.getBlockMappings().getExtendedCollisionBoxes().get(blockState);
int belowBlock = session.getGeyser().getWorldManager().getBlockAt(session, position.getX(), position.getY() - 1, position.getZ());
BlockDefinition belowBedrockExtendedCollisionDefinition = session.getBlockMappings().getExtendedCollisionBoxes().get(belowBlock);
if (belowBedrockExtendedCollisionDefinition != null && blockState == Block.JAVA_AIR_ID) {
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
updateBlockPacket.setDataLayer(0);
updateBlockPacket.setBlockPosition(position);
updateBlockPacket.setDefinition(belowBedrockExtendedCollisionDefinition);
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK);
session.sendUpstreamPacket(updateBlockPacket);
} else if (aboveBedrockExtendedCollisionDefinition != null && aboveBlock == Block.JAVA_AIR_ID) {
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
updateBlockPacket.setDataLayer(0);
updateBlockPacket.setBlockPosition(position.add(0, 1, 0));
updateBlockPacket.setDefinition(aboveBedrockExtendedCollisionDefinition);
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK);
session.sendUpstreamPacket(updateBlockPacket);
} else if (aboveBlock == Block.JAVA_AIR_ID) {
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
updateBlockPacket.setDataLayer(0);
updateBlockPacket.setBlockPosition(position.add(0, 1, 0));
updateBlockPacket.setDefinition(session.getBlockMappings().getBedrockAir());
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK);
session.sendUpstreamPacket(updateBlockPacket);
}
}
BlockStateValues.getLecternBookStates().handleBlockChange(session, blockState, position);
// Iterates through all Bedrock-only block entity translators and determines if a manual block entity packet
// needs to be sent
for (BedrockOnlyBlockEntity bedrockOnlyBlockEntity : BlockEntityUtils.BEDROCK_ONLY_BLOCK_ENTITIES) {
if (bedrockOnlyBlockEntity.isBlock(blockState)) {
// Flower pots are block entities only in Bedrock and are not updated anywhere else like note blocks
bedrockOnlyBlockEntity.updateBlock(session, BlockState.of(blockState), position); //TODO blockState
break; //No block will be a part of two classes
}
}
if (BlockStateValues.isUpperDoor(blockState)) {
// Update the lower door block as Bedrock client doesn't like door to be closed from the top
// See https://github.com/GeyserMC/Geyser/issues/4358
Vector3i belowDoorPosition = position.sub(0, 1, 0);
int belowDoorBlockState = session.getGeyser().getWorldManager().getBlockAt(session, belowDoorPosition.getX(), belowDoorPosition.getY(), belowDoorPosition.getZ());
updateBlock(session, belowDoorBlockState, belowDoorPosition);
}
blockState.block().updateBlock(session, blockState, position);
}
public static void sendEmptyChunk(GeyserSession session, int chunkX, int chunkZ, boolean forceUpdate) {