BlockMapping is removed

This commit is contained in:
Camotoy 2024-05-19 20:24:19 -04:00
parent 1e9dbaf38b
commit d85549c38d
No known key found for this signature in database
GPG key ID: 7EEFB66FE798081F
37 changed files with 378 additions and 1225 deletions

View file

@ -256,17 +256,6 @@ public class GeyserImpl implements GeyserApi {
}
VersionCheckUtils.checkForOutdatedJava(logger);
for (int i = 0; i < BlockRegistries.JAVA_BLOCKS.get().length; i++) {
String oldIdentifier = BlockRegistries.JAVA_BLOCKS.get(i).getJavaIdentifier();
String newIdentifier = BlockRegistries.BLOCK_STATES.get(i).toString();
if (!oldIdentifier.equals(newIdentifier)) {
System.out.println("Check block " + oldIdentifier + " " + newIdentifier);
break;
}
}
System.out.println(BlockRegistries.JAVA_BLOCKS.get().length);
System.out.println(BlockRegistries.BLOCK_STATES.get().size());
}
private void startInstance() {

View file

@ -34,6 +34,7 @@ import org.cloudburstmc.protocol.bedrock.packet.AddItemEntityPacket;
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.item.ItemTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
@ -137,7 +138,7 @@ public class ItemEntity extends ThrowableEntity {
protected float getDrag() {
if (isOnGround()) {
Vector3i groundBlockPos = position.toInt().down(1);
int blockState = session.getGeyser().getWorldManager().getBlockAt(session, groundBlockPos);
BlockState blockState = session.getGeyser().getWorldManager().blockAt(session, groundBlockPos);
return BlockStateValues.getSlipperiness(blockState) * 0.98f;
}
return 0.98f;

View file

@ -33,8 +33,8 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.packet.MoveEntityAbsolutePacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.type.BlockMapping;
import org.geysermc.geyser.level.block.property.Properties;
import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.VillagerData;
@ -119,28 +119,29 @@ public class VillagerEntity extends AbstractMerchantEntity {
}
// The bed block
int blockId = session.getGeyser().getWorldManager().getBlockAt(session, bedPosition);
String fullIdentifier = BlockRegistries.JAVA_BLOCKS.getOrDefault(blockId, BlockMapping.DEFAULT).getJavaIdentifier();
BlockState state = session.getGeyser().getWorldManager().blockAt(session, bedPosition);
// Set the correct position offset and rotation when sleeping
int bedRotation = 0;
float xOffset = 0;
float zOffset = 0;
if (fullIdentifier.contains("facing=south")) {
// bed is facing south
bedRotation = 180;
zOffset = -.5f;
} else if (fullIdentifier.contains("facing=east")) {
// bed is facing east
bedRotation = 90;
xOffset = -.5f;
} else if (fullIdentifier.contains("facing=west")) {
// bed is facing west
bedRotation = 270;
xOffset = .5f;
} else if (fullIdentifier.contains("facing=north")) {
// rotation does not change because north is 0
zOffset = .5f;
switch (state.getValue(Properties.HORIZONTAL_FACING)) {
case SOUTH -> {
bedRotation = 180;
zOffset = -.5f;
}
case EAST -> {
bedRotation = 90;
xOffset = -.5f;
}
case WEST -> {
bedRotation = 270;
xOffset = .5f;
}
case NORTH -> {
// rotation does not change because north is 0
zOffset = .5f;
}
}
setYaw(yaw);

View file

@ -34,7 +34,10 @@ import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.packet.AddPlayerPacket;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.block.property.Properties;
import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.level.block.type.WallSkullBlock;
import org.geysermc.geyser.level.physics.Direction;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.SkullCache;
import org.geysermc.geyser.skin.SkullSkinManager;
@ -137,20 +140,19 @@ public class SkullPlayerEntity extends PlayerEntity {
float z = skull.getPosition().getZ() + .5f;
float rotation;
int blockState = skull.getBlockState();
byte floorRotation = BlockStateValues.getSkullRotation(blockState);
if (floorRotation == -1) {
// Wall skull
BlockState blockState = skull.getBlockState();
if (blockState.block() instanceof WallSkullBlock) {
y += 0.25f;
rotation = BlockStateValues.getSkullWallDirections().get(blockState);
switch ((int) rotation) {
case 180 -> z += 0.24f; // North
case 0 -> z -= 0.24f; // South
case 90 -> x += 0.24f; // West
case 270 -> x -= 0.24f; // East
Direction direction = blockState.getValue(Properties.HORIZONTAL_FACING);
rotation = WallSkullBlock.getDegrees(direction);
switch (direction) {
case NORTH -> z += 0.24f;
case SOUTH -> z -= 0.24f;
case WEST -> x += 0.24f;
case EAST -> x -= 0.24f;
}
} else {
rotation = (180f + (floorRotation * 22.5f)) % 360;
rotation = (180f + (blockState.getValue(Properties.ROTATION_16) * 22.5f)) % 360;
}
moveAbsolute(Vector3f.from(x, y, z), rotation, 0, rotation, true, true);

View file

@ -25,6 +25,7 @@
package org.geysermc.geyser.inventory;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
import lombok.Getter;
import org.checkerframework.checker.nullness.qual.NonNull;
@ -83,9 +84,9 @@ public class Container extends Inventory {
* Will be overwritten for droppers.
*
* @param usingRealBlock whether this container is using a real container or not
* @param javaBlockId the Java block string of the block, if real
* @param block the Java block, if real
*/
public void setUsingRealBlock(boolean usingRealBlock, String javaBlockId) {
public void setUsingRealBlock(boolean usingRealBlock, Block block) {
isUsingRealBlock = usingRealBlock;
}
}

View file

@ -26,6 +26,8 @@
package org.geysermc.geyser.inventory;
import lombok.Getter;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.Generic3X3InventoryTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
@ -44,10 +46,10 @@ public class Generic3X3Container extends Container {
}
@Override
public void setUsingRealBlock(boolean usingRealBlock, String javaBlockId) {
super.setUsingRealBlock(usingRealBlock, javaBlockId);
public void setUsingRealBlock(boolean usingRealBlock, Block block) {
super.setUsingRealBlock(usingRealBlock, block);
if (usingRealBlock) {
isDropper = javaBlockId.startsWith("minecraft:dropper");
isDropper = block == Blocks.DROPPER;
}
}
}

View file

@ -90,7 +90,7 @@ public class BlockInventoryHolder extends InventoryHolder {
if (isValidBlock(javaBlockString)) {
// We can safely use this block
inventory.setHolderPosition(session.getLastInteractionBlockPosition());
((Container) inventory).setUsingRealBlock(true, javaBlockString[0]);
((Container) inventory).setUsingRealBlock(true, state.block());
setCustomName(session, session.getLastInteractionBlockPosition(), inventory, state);
return true;

View file

@ -59,10 +59,12 @@ import java.util.function.Function;
*/
public abstract class WorldManager {
public final BlockState blockAt(GeyserSession session, Vector3i vector3i) {
return BlockState.of(this.getBlockAt(session, vector3i));
@NonNull
public final BlockState blockAt(GeyserSession session, Vector3i vector) {
return this.blockAt(session, vector.getX(), vector.getY(), vector.getZ());
}
@NonNull
public BlockState blockAt(GeyserSession session, int x, int y, int z) {
return BlockState.of(this.getBlockAt(session, x, y, z));
}

View file

@ -25,7 +25,6 @@
package org.geysermc.geyser.level.block;
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;
@ -36,10 +35,6 @@ 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.util.collection.FixedInt2ByteMap;
import org.geysermc.geyser.util.collection.FixedInt2IntMap;
import org.geysermc.geyser.util.collection.LecternHasBookMap;
import java.util.Locale;
@ -47,35 +42,13 @@ import java.util.Locale;
* Used for block entities if the Java block state contains Bedrock block information.
*/
public final class BlockStateValues {
private static final IntSet ALL_CAULDRONS = new IntOpenHashSet();
private static final Int2IntMap BANNER_COLORS = new FixedInt2IntMap();
private static final Int2ByteMap BED_COLORS = new FixedInt2ByteMap();
private static final Int2IntMap BRUSH_PROGRESS = new Int2IntOpenHashMap();
private static final Int2ObjectMap<DoubleChestValue> DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<String> FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>();
private static final IntSet HORIZONTAL_FACING_JIGSAWS = new IntOpenHashSet();
private static final LecternHasBookMap LECTERN_BOOK_STATES = new LecternHasBookMap();
private static final IntSet NON_WATER_CAULDRONS = new IntOpenHashSet();
private static final Int2IntMap NOTEBLOCK_PITCHES = new FixedInt2IntMap();
private static final Int2BooleanMap PISTON_VALUES = new Int2BooleanOpenHashMap();
private static final IntSet STICKY_PISTONS = new IntOpenHashSet();
private static final Object2IntMap<Direction> PISTON_HEADS = new Object2IntOpenHashMap<>();
private static final Int2ObjectMap<Direction> PISTON_ORIENTATION = new Int2ObjectOpenHashMap<>();
private static final IntSet ALL_PISTON_HEADS = new IntOpenHashSet();
private static final IntSet MOVING_PISTONS = new IntOpenHashSet();
private static final Int2ByteMap SKULL_VARIANTS = new FixedInt2ByteMap();
private static final IntSet SKULL_POWERED = new IntOpenHashSet();
private static final Int2ByteMap SKULL_ROTATIONS = new Int2ByteOpenHashMap();
private static final Int2IntMap SKULL_WALL_DIRECTIONS = new Int2IntOpenHashMap();
private static final Int2ByteMap SHULKERBOX_DIRECTIONS = new FixedInt2ByteMap();
private static final Int2IntMap WATER_LEVEL = new Int2IntOpenHashMap();
private static final IntSet UPPER_DOORS = new IntOpenHashSet();
public static int JAVA_FURNACE_ID;
public static int JAVA_FURNACE_LIT_ID;
public static int JAVA_HONEY_BLOCK_ID;
public static int JAVA_SLIME_BLOCK_ID;
public static int JAVA_SPAWNER_ID;
public static int JAVA_WATER_ID;
public static final int NUM_WATER_LEVELS = 9;
@ -85,66 +58,9 @@ public final class BlockStateValues {
*
* @param javaId The Java Identifier of the block
* @param javaBlockState the Java Block State of the block
* @param blockData JsonNode of info about the block from blocks.json
*/
public static void storeBlockStateValues(String javaId, int javaBlockState, JsonNode blockData) {
JsonNode bannerColor = blockData.get("banner_color");
if (bannerColor != null) {
BANNER_COLORS.put(javaBlockState, (byte) bannerColor.intValue());
return; // There will never be a banner color and a skull variant
}
JsonNode bedColor = blockData.get("bed_color");
if (bedColor != null) {
BED_COLORS.put(javaBlockState, (byte) bedColor.intValue());
return;
}
JsonNode bedrockStates = blockData.get("bedrock_states");
if (bedrockStates != null) {
JsonNode brushedProgress = bedrockStates.get("brushed_progress");
if (brushedProgress != null) {
BRUSH_PROGRESS.put(javaBlockState, brushedProgress.intValue());
return;
}
}
if (blockData.get("double_chest_position") != null) {
boolean isX = (blockData.get("x") != null);
boolean isDirectionPositive = ((blockData.get("x") != null && blockData.get("x").asBoolean()) ||
(blockData.get("z") != null && blockData.get("z").asBoolean()));
boolean isLeft = (blockData.get("double_chest_position").asText().contains("left"));
DOUBLE_CHEST_VALUES.put(javaBlockState, new DoubleChestValue(isX, isDirectionPositive, isLeft));
return;
}
if (javaId.startsWith("minecraft:potted_") || javaId.equals("minecraft:flower_pot")) {
String name = javaId.replace("potted_", "");
if (name.contains("azalea")) {
// Exception to the rule
name = name.replace("_bush", "");
}
FLOWER_POT_VALUES.put(javaBlockState, name);
return;
}
if (javaId.startsWith("minecraft:lectern")) {
LECTERN_BOOK_STATES.put(javaBlockState, javaId.contains("has_book=true"));
return;
}
JsonNode notePitch = blockData.get("note_pitch");
if (notePitch != null) {
NOTEBLOCK_PITCHES.put(javaBlockState, blockData.get("note_pitch").intValue());
return;
}
public static void storeBlockStateValues(String javaId, int javaBlockState) {
if (javaId.contains("piston[")) { // minecraft:moving_piston, minecraft:sticky_piston, minecraft:piston
if (javaId.startsWith("minecraft:moving_piston")) {
MOVING_PISTONS.add(javaBlockState);
} else {
PISTON_VALUES.put(javaBlockState, javaId.contains("extended=true"));
}
if (javaId.contains("sticky")) {
STICKY_PISTONS.add(javaBlockState);
}
@ -158,40 +74,6 @@ public final class BlockStateValues {
return;
}
JsonNode skullVariation = blockData.get("variation");
if (skullVariation != null) {
SKULL_VARIANTS.put(javaBlockState, (byte) skullVariation.intValue());
}
JsonNode skullRotation = blockData.get("skull_rotation");
if (skullRotation != null) {
SKULL_ROTATIONS.put(javaBlockState, (byte) skullRotation.intValue());
}
if (javaId.startsWith("minecraft:dragon_head[") || javaId.startsWith("minecraft:piglin_head[")
|| javaId.startsWith("minecraft:dragon_wall_head[") || javaId.startsWith("minecraft:piglin_wall_head[")) {
if (javaId.contains("powered=true")) {
SKULL_POWERED.add(javaBlockState);
}
}
if (javaId.contains("wall_skull") || javaId.contains("wall_head")) {
String direction = javaId.substring(javaId.lastIndexOf("facing=") + 7, javaId.lastIndexOf("powered=") - 1);
int rotation = switch (direction) {
case "north" -> 180;
case "west" -> 90;
case "east" -> 270;
default -> 0; // Also south
};
SKULL_WALL_DIRECTIONS.put(javaBlockState, rotation);
}
JsonNode shulkerDirection = blockData.get("shulker_direction");
if (shulkerDirection != null) {
BlockStateValues.SHULKERBOX_DIRECTIONS.put(javaBlockState, (byte) shulkerDirection.intValue());
return;
}
if (javaId.startsWith("minecraft:water") && !javaId.contains("cauldron")) {
String strLevel = javaId.substring(javaId.lastIndexOf("level=") + 6, javaId.length() - 1);
int level = Integer.parseInt(strLevel);
@ -205,48 +87,7 @@ public final class BlockStateValues {
if (direction.isHorizontal()) {
HORIZONTAL_FACING_JIGSAWS.add(javaBlockState);
}
return;
}
if (javaId.contains("cauldron")) {
ALL_CAULDRONS.add(javaBlockState);
}
if (javaId.contains("_cauldron") && !javaId.contains("water_")) {
NON_WATER_CAULDRONS.add(javaBlockState);
}
if (javaId.contains("_door[") && javaId.contains("half=upper")) {
UPPER_DOORS.add(javaBlockState);
}
}
/**
* Banner colors are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock.
* This gives an integer color that Bedrock can use.
*
* @param state BlockState of the block
* @return Banner color integer or -1 if no color
*/
public static int getBannerColor(int state) {
return BANNER_COLORS.getOrDefault(state, -1);
}
/**
* @return if this Java block state is a non-empty non-water cauldron
*/
public static boolean isNonWaterCauldron(int state) {
return NON_WATER_CAULDRONS.contains(state);
}
/**
* Cauldrons (since Bedrock 1.18.30) must have a block entity packet sent on chunk load to fix rendering issues.
* <p>
* When using a bucket on a cauldron sending a ServerboundUseItemPacket can result in the liquid being placed.
*
* @return if this Java block state is a cauldron
*/
public static boolean isCauldron(int state) {
return ALL_CAULDRONS.contains(state);
}
/**
@ -256,13 +97,6 @@ public final class BlockStateValues {
return HORIZONTAL_FACING_JIGSAWS;
}
/**
* @return the lectern book state map pointing to book present state
*/
public static LecternHasBookMap getLecternBookStates() {
return LECTERN_BOOK_STATES;
}
public static boolean isStickyPiston(int blockState) {
return STICKY_PISTONS.contains(blockState);
}
@ -354,38 +188,6 @@ public final class BlockStateValues {
};
}
/**
* Skull variations are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock.
* This gives a byte variant ID that Bedrock can use.
*
* @param state BlockState of the block
* @return Skull variant byte or -1 if no variant
*/
public static byte getSkullVariant(int state) {
return SKULL_VARIANTS.getOrDefault(state, (byte) -1);
}
/**
* Skull rotations are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock.
* This gives a byte rotation that Bedrock can use.
*
* @param state BlockState of the block
* @return Skull rotation value or -1 if no value
*/
public static byte getSkullRotation(int state) {
return SKULL_ROTATIONS.getOrDefault(state, (byte) -1);
}
/**
* 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.
*
* @return Skull wall rotation value with the blockstate
*/
public static Int2IntMap getSkullWallDirections() {
return SKULL_WALL_DIRECTIONS;
}
/**
* Get the level of water from the block state.
*
@ -427,14 +229,18 @@ public final class BlockStateValues {
* @param state BlockState of the block
* @return The block's slipperiness
*/
public static float getSlipperiness(int state) {
String blockIdentifier = BlockRegistries.JAVA_BLOCKS.getOrDefault(state, BlockMapping.DEFAULT).getJavaIdentifier();
return switch (blockIdentifier) {
case "minecraft:slime_block" -> 0.8f;
case "minecraft:ice", "minecraft:packed_ice" -> 0.98f;
case "minecraft:blue_ice" -> 0.989f;
default -> 0.6f;
};
public static float getSlipperiness(BlockState state) {
Block block = state.block();
if (block == Blocks.SLIME_BLOCK) {
return 0.8f;
}
if (block == Blocks.ICE || block == Blocks.PACKED_ICE) {
return 0.98f;
}
if (block == Blocks.BLUE_ICE) {
return 0.989f;
}
return 0.6f;
}
private static Direction getBlockDirection(String javaId) {

View file

@ -25,6 +25,7 @@
package org.geysermc.geyser.level.block;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.level.block.property.ChestType;
import org.geysermc.geyser.level.block.type.*;
import org.geysermc.geyser.level.physics.Axis;
@ -308,7 +309,7 @@ public final class Blocks {
public static final Block FERN = register(new Block("fern", builder().pushReaction(PistonBehavior.DESTROY)));
public static final Block DEAD_BUSH = register(new Block("dead_bush", builder().pushReaction(PistonBehavior.DESTROY)));
public static final Block SEAGRASS = register(new Block("seagrass", builder().pushReaction(PistonBehavior.DESTROY)));
public static final Block TALL_SEAGRASS = register(new Block("tall_seagrass", builder().pushReaction(PistonBehavior.DESTROY)
public static final Block TALL_SEAGRASS = register(new Block("tall_seagrass", builder().pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.SEAGRASS)
.enumState(DOUBLE_BLOCK_HALF, "upper", "lower")));
public static final Block PISTON = register(new PistonBlock("piston", builder().destroyTime(1.5f).pushReaction(PistonBehavior.BLOCK)
.booleanState(EXTENDED)
@ -758,9 +759,9 @@ public final class Blocks {
.booleanState(WEST)));
public static final Block PUMPKIN = register(new Block("pumpkin", builder().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)));
public static final Block MELON = register(new Block("melon", builder().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)));
public static final Block ATTACHED_PUMPKIN_STEM = register(new Block("attached_pumpkin_stem", builder().pushReaction(PistonBehavior.DESTROY)
public static final Block ATTACHED_PUMPKIN_STEM = register(new Block("attached_pumpkin_stem", builder().pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.PUMPKIN_SEEDS)
.enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST)));
public static final Block ATTACHED_MELON_STEM = register(new Block("attached_melon_stem", builder().pushReaction(PistonBehavior.DESTROY)
public static final Block ATTACHED_MELON_STEM = register(new Block("attached_melon_stem", builder().pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.MELON_SEEDS)
.enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST)));
public static final Block PUMPKIN_STEM = register(new Block("pumpkin_stem", builder().pushReaction(PistonBehavior.DESTROY)
.intState(AGE_7, 0, 7)));
@ -963,46 +964,46 @@ public final class Blocks {
.enumState(ATTACH_FACE, "floor", "wall", "ceiling")
.enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST)
.booleanState(POWERED)));
public static final Block SKELETON_SKULL = register(new SkullBlock("skeleton_skull", builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
public static final Block SKELETON_SKULL = register(new SkullBlock("skeleton_skull", SkullBlock.Type.SKELETON, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
.booleanState(POWERED)
.intState(ROTATION_16, 0, 15)));
public static final Block SKELETON_WALL_SKULL = register(new SkullBlock("skeleton_wall_skull", builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
public static final Block SKELETON_WALL_SKULL = register(new WallSkullBlock("skeleton_wall_skull", SkullBlock.Type.SKELETON, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
.enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST)
.booleanState(POWERED)));
public static final Block WITHER_SKELETON_SKULL = register(new SkullBlock("wither_skeleton_skull", builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
public static final Block WITHER_SKELETON_SKULL = register(new SkullBlock("wither_skeleton_skull", SkullBlock.Type.WITHER_SKELETON, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
.booleanState(POWERED)
.intState(ROTATION_16, 0, 15)));
public static final Block WITHER_SKELETON_WALL_SKULL = register(new SkullBlock("wither_skeleton_wall_skull", builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
public static final Block WITHER_SKELETON_WALL_SKULL = register(new WallSkullBlock("wither_skeleton_wall_skull", SkullBlock.Type.WITHER_SKELETON, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
.enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST)
.booleanState(POWERED)));
public static final Block ZOMBIE_HEAD = register(new SkullBlock("zombie_head", builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
public static final Block ZOMBIE_HEAD = register(new SkullBlock("zombie_head", SkullBlock.Type.ZOMBIE, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
.booleanState(POWERED)
.intState(ROTATION_16, 0, 15)));
public static final Block ZOMBIE_WALL_HEAD = register(new SkullBlock("zombie_wall_head", builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
public static final Block ZOMBIE_WALL_HEAD = register(new WallSkullBlock("zombie_wall_head", SkullBlock.Type.ZOMBIE, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
.enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST)
.booleanState(POWERED)));
public static final Block PLAYER_HEAD = register(new SkullBlock("player_head", builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
public static final Block PLAYER_HEAD = register(new SkullBlock("player_head", SkullBlock.Type.PLAYER, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
.booleanState(POWERED)
.intState(ROTATION_16, 0, 15)));
public static final Block PLAYER_WALL_HEAD = register(new SkullBlock("player_wall_head", builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
public static final Block PLAYER_WALL_HEAD = register(new WallSkullBlock("player_wall_head", SkullBlock.Type.PLAYER, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
.enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST)
.booleanState(POWERED)));
public static final Block CREEPER_HEAD = register(new SkullBlock("creeper_head", builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
public static final Block CREEPER_HEAD = register(new SkullBlock("creeper_head", SkullBlock.Type.CREEPER, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
.booleanState(POWERED)
.intState(ROTATION_16, 0, 15)));
public static final Block CREEPER_WALL_HEAD = register(new SkullBlock("creeper_wall_head", builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
public static final Block CREEPER_WALL_HEAD = register(new WallSkullBlock("creeper_wall_head", SkullBlock.Type.CREEPER, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
.enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST)
.booleanState(POWERED)));
public static final Block DRAGON_HEAD = register(new SkullBlock("dragon_head", builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
public static final Block DRAGON_HEAD = register(new SkullBlock("dragon_head", SkullBlock.Type.DRAGON, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
.booleanState(POWERED)
.intState(ROTATION_16, 0, 15)));
public static final Block DRAGON_WALL_HEAD = register(new SkullBlock("dragon_wall_head", builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
public static final Block DRAGON_WALL_HEAD = register(new WallSkullBlock("dragon_wall_head", SkullBlock.Type.DRAGON, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
.enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST)
.booleanState(POWERED)));
public static final Block PIGLIN_HEAD = register(new SkullBlock("piglin_head", builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
public static final Block PIGLIN_HEAD = register(new SkullBlock("piglin_head", SkullBlock.Type.PIGLIN, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
.booleanState(POWERED)
.intState(ROTATION_16, 0, 15)));
public static final Block PIGLIN_WALL_HEAD = register(new SkullBlock("piglin_wall_head", builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
public static final Block PIGLIN_WALL_HEAD = register(new WallSkullBlock("piglin_wall_head", SkullBlock.Type.PIGLIN, builder().setBlockEntity().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
.enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST)
.booleanState(POWERED)));
public static final Block ANVIL = register(new Block("anvil", builder().requiresCorrectToolForDrops().destroyTime(5.0f).pushReaction(PistonBehavior.BLOCK)
@ -1694,7 +1695,7 @@ public final class Blocks {
public static final Block BLACK_CONCRETE_POWDER = register(new Block("black_concrete_powder", builder().destroyTime(0.5f)));
public static final Block KELP = register(new Block("kelp", builder().pushReaction(PistonBehavior.DESTROY)
.intState(AGE_25, 0, 25)));
public static final Block KELP_PLANT = register(new Block("kelp_plant", builder().pushReaction(PistonBehavior.DESTROY)));
public static final Block KELP_PLANT = register(new Block("kelp_plant", builder().pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.KELP)));
public static final Block DRIED_KELP_BLOCK = register(new Block("dried_kelp_block", builder().destroyTime(0.5f)));
public static final Block TURTLE_EGG = register(new Block("turtle_egg", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY)
.intState(EGGS, 1, 4)
@ -1787,7 +1788,7 @@ public final class Blocks {
public static final Block BLUE_ICE = register(new Block("blue_ice", builder().destroyTime(2.8f)));
public static final Block CONDUIT = register(new Block("conduit", builder().setBlockEntity().destroyTime(3.0f)
.booleanState(WATERLOGGED)));
public static final Block BAMBOO_SAPLING = register(new Block("bamboo_sapling", builder().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)));
public static final Block BAMBOO_SAPLING = register(new Block("bamboo_sapling", builder().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.BAMBOO)));
public static final Block BAMBOO = register(new Block("bamboo", builder().destroyTime(1.0f).pushReaction(PistonBehavior.DESTROY)
.intState(AGE_1, 0, 1)
.enumState(BAMBOO_LEAVES, "none", "small", "large")
@ -2072,10 +2073,10 @@ public final class Blocks {
public static final Block SHROOMLIGHT = register(new Block("shroomlight", builder().destroyTime(1.0f)));
public static final Block WEEPING_VINES = register(new Block("weeping_vines", builder().pushReaction(PistonBehavior.DESTROY)
.intState(AGE_25, 0, 25)));
public static final Block WEEPING_VINES_PLANT = register(new Block("weeping_vines_plant", builder().pushReaction(PistonBehavior.DESTROY)));
public static final Block WEEPING_VINES_PLANT = register(new Block("weeping_vines_plant", builder().pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.WEEPING_VINES)));
public static final Block TWISTING_VINES = register(new Block("twisting_vines", builder().pushReaction(PistonBehavior.DESTROY)
.intState(AGE_25, 0, 25)));
public static final Block TWISTING_VINES_PLANT = register(new Block("twisting_vines_plant", builder().pushReaction(PistonBehavior.DESTROY)));
public static final Block TWISTING_VINES_PLANT = register(new Block("twisting_vines_plant", builder().pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.TWISTING_VINES)));
public static final Block CRIMSON_ROOTS = register(new Block("crimson_roots", builder().pushReaction(PistonBehavior.DESTROY)));
public static final Block CRIMSON_PLANKS = register(new Block("crimson_planks", builder().destroyTime(2.0f)));
public static final Block WARPED_PLANKS = register(new Block("warped_planks", builder().destroyTime(2.0f)));
@ -2319,39 +2320,39 @@ public final class Blocks {
.intState(CANDLES, 1, 4)
.booleanState(LIT)
.booleanState(WATERLOGGED)));
public static final Block CANDLE_CAKE = register(new Block("candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY)
public static final Block CANDLE_CAKE = register(new Block("candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.CAKE)
.booleanState(LIT)));
public static final Block WHITE_CANDLE_CAKE = register(new Block("white_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY)
public static final Block WHITE_CANDLE_CAKE = register(new Block("white_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.CAKE)
.booleanState(LIT)));
public static final Block ORANGE_CANDLE_CAKE = register(new Block("orange_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY)
public static final Block ORANGE_CANDLE_CAKE = register(new Block("orange_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.CAKE)
.booleanState(LIT)));
public static final Block MAGENTA_CANDLE_CAKE = register(new Block("magenta_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY)
public static final Block MAGENTA_CANDLE_CAKE = register(new Block("magenta_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.CAKE)
.booleanState(LIT)));
public static final Block LIGHT_BLUE_CANDLE_CAKE = register(new Block("light_blue_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY)
public static final Block LIGHT_BLUE_CANDLE_CAKE = register(new Block("light_blue_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.CAKE)
.booleanState(LIT)));
public static final Block YELLOW_CANDLE_CAKE = register(new Block("yellow_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY)
public static final Block YELLOW_CANDLE_CAKE = register(new Block("yellow_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.CAKE)
.booleanState(LIT)));
public static final Block LIME_CANDLE_CAKE = register(new Block("lime_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY)
public static final Block LIME_CANDLE_CAKE = register(new Block("lime_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.CAKE)
.booleanState(LIT)));
public static final Block PINK_CANDLE_CAKE = register(new Block("pink_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY)
public static final Block PINK_CANDLE_CAKE = register(new Block("pink_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.CAKE)
.booleanState(LIT)));
public static final Block GRAY_CANDLE_CAKE = register(new Block("gray_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY)
public static final Block GRAY_CANDLE_CAKE = register(new Block("gray_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.CAKE)
.booleanState(LIT)));
public static final Block LIGHT_GRAY_CANDLE_CAKE = register(new Block("light_gray_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY)
public static final Block LIGHT_GRAY_CANDLE_CAKE = register(new Block("light_gray_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.CAKE)
.booleanState(LIT)));
public static final Block CYAN_CANDLE_CAKE = register(new Block("cyan_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY)
public static final Block CYAN_CANDLE_CAKE = register(new Block("cyan_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.CAKE)
.booleanState(LIT)));
public static final Block PURPLE_CANDLE_CAKE = register(new Block("purple_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY)
public static final Block PURPLE_CANDLE_CAKE = register(new Block("purple_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.CAKE)
.booleanState(LIT)));
public static final Block BLUE_CANDLE_CAKE = register(new Block("blue_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY)
public static final Block BLUE_CANDLE_CAKE = register(new Block("blue_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.CAKE)
.booleanState(LIT)));
public static final Block BROWN_CANDLE_CAKE = register(new Block("brown_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY)
public static final Block BROWN_CANDLE_CAKE = register(new Block("brown_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.CAKE)
.booleanState(LIT)));
public static final Block GREEN_CANDLE_CAKE = register(new Block("green_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY)
public static final Block GREEN_CANDLE_CAKE = register(new Block("green_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.CAKE)
.booleanState(LIT)));
public static final Block RED_CANDLE_CAKE = register(new Block("red_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY)
public static final Block RED_CANDLE_CAKE = register(new Block("red_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.CAKE)
.booleanState(LIT)));
public static final Block BLACK_CANDLE_CAKE = register(new Block("black_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY)
public static final Block BLACK_CANDLE_CAKE = register(new Block("black_candle_cake", builder().destroyTime(0.5f).pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.CAKE)
.booleanState(LIT)));
public static final Block AMETHYST_BLOCK = register(new Block("amethyst_block", builder().requiresCorrectToolForDrops().destroyTime(1.5f)));
public static final Block BUDDING_AMETHYST = register(new Block("budding_amethyst", builder().requiresCorrectToolForDrops().destroyTime(1.5f).pushReaction(PistonBehavior.DESTROY)));
@ -2682,7 +2683,7 @@ public final class Blocks {
public static final Block CAVE_VINES = register(new Block("cave_vines", builder().pushReaction(PistonBehavior.DESTROY)
.intState(AGE_25, 0, 25)
.booleanState(BERRIES)));
public static final Block CAVE_VINES_PLANT = register(new Block("cave_vines_plant", builder().pushReaction(PistonBehavior.DESTROY)
public static final Block CAVE_VINES_PLANT = register(new Block("cave_vines_plant", builder().pushReaction(PistonBehavior.DESTROY).pickItem(() -> Items.GLOW_BERRIES)
.booleanState(BERRIES)));
public static final Block SPORE_BLOSSOM = register(new Block("spore_blossom", builder().pushReaction(PistonBehavior.DESTROY)));
public static final Block AZALEA = register(new Block("azalea", builder().pushReaction(PistonBehavior.DESTROY)));
@ -2811,7 +2812,8 @@ public final class Blocks {
.booleanState(WATERLOGGED)));
private static <T extends Block> T register(T block) {
BlockRegistries.JAVA_BLOCKS_TO_RENAME.get().add(block);
block.setJavaId(BlockRegistries.JAVA_BLOCKS.get().size());
BlockRegistries.JAVA_BLOCKS.get().add(block);
return block;
}

View file

@ -1,40 +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.level.block;
/**
* This stores all values of double chests that are part of the Java block state.
*
* @param isFacingEast If true, then chest is facing east/west; if false, south/north
* @param isDirectionPositive If true, direction is positive (east/south); if false, direction is negative (west/north)
* @param isLeft If true, chest is the left of a pair; if false, chest is the right of a pair.
*/
public record DoubleChestValue(
boolean isFacingEast,
boolean isDirectionPositive,
boolean isLeft) {
}

View file

@ -37,24 +37,33 @@ import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.level.block.property.Properties;
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.item.ItemStack;
import org.intellij.lang.annotations.Subst;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class Block {
public static final int JAVA_AIR_ID = 0;
private final Key javaIdentifier;
/**
* Can you harvest this with your hand.
*/
private final boolean requiresCorrectToolForDrops;
private final boolean hasBlockEntity;
private final float destroyTime;
private final @NonNull PistonBehavior pushReaction;
/**
* Used for classes we don't have implemented yet that override Mojmap getCloneItemStack with their own item.
* A supplier prevents any issues arising where the Items class finishes before the Blocks class.
*/
private final Supplier<Item> pickItem;
protected Item item = null;
private int javaId = -1;
@ -64,10 +73,13 @@ public class Block {
this.hasBlockEntity = builder.hasBlockEntity;
this.destroyTime = builder.destroyTime;
this.pushReaction = builder.pushReaction;
this.pickItem = builder.pickItem;
processStates(builder.build(this));
}
public void updateBlock(GeyserSession session, BlockState state, Vector3i position) {
checkForEmptySkull(session, state, position);
BlockDefinition definition = session.getBlockMappings().getBedrockBlock(state);
sendBlockUpdatePacket(session, state, definition, position);
@ -118,8 +130,7 @@ public class Block {
UpdateBlockPacket waterPacket = new UpdateBlockPacket();
waterPacket.setDataLayer(1);
waterPacket.setBlockPosition(position);
Boolean waterlogged = state.getValue(Properties.WATERLOGGED);
if (waterlogged == Boolean.TRUE) {
if (BlockRegistries.WATERLOGGED.get().get(state.javaId())) {
waterPacket.setDefinition(session.getBlockMappings().getBedrockWater());
} else {
waterPacket.setDefinition(session.getBlockMappings().getBedrockAir());
@ -127,6 +138,13 @@ public class Block {
session.sendUpstreamPacket(waterPacket);
}
protected void checkForEmptySkull(GeyserSession session, BlockState state, Vector3i position) {
if (!(state.block() instanceof SkullBlock)) {
// Skull is gone
session.getSkullCache().removeSkull(position);
}
}
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)) {
@ -141,6 +159,13 @@ public class Block {
return this.item;
}
public ItemStack pickItem(BlockState state) {
if (this.pickItem != null) {
return new ItemStack(this.pickItem.get().javaId());
}
return new ItemStack(this.asItem().javaId());
}
/**
* A list of BlockStates is created pertaining to this block. Do we need any of them? If so, override this method.
*/
@ -198,6 +223,7 @@ public class Block {
private boolean hasBlockEntity = false;
private PistonBehavior pushReaction = PistonBehavior.NORMAL;
private float destroyTime;
private Supplier<Item> pickItem;
/**
* For states that we're just tracking for mirroring Java states.
@ -248,6 +274,11 @@ public class Block {
return this;
}
public Builder pickItem(Supplier<Item> pickItem) {
this.pickItem = pickItem;
return this;
}
private List<BlockState> build(Block block) {
if (states.isEmpty()) {
BlockState state = new BlockState(block, BlockRegistries.BLOCK_STATES.get().size());

View file

@ -37,7 +37,7 @@ public final class BlockState {
private final int javaId;
private final Reference2ObjectMap<Property<?>, Comparable<?>> states;
BlockState(Block block, int javaId) {
public BlockState(Block block, int javaId) {
this(block, javaId, Reference2ObjectMaps.emptyMap());
}
@ -52,6 +52,14 @@ public final class BlockState {
return (T) states.get(property);
}
public boolean getValue(Property<Boolean> property, boolean def) {
var value = states.get(property);
if (value == null) {
return def;
}
return (Boolean) value;
}
public Block block() {
return block;
}

View file

@ -34,6 +34,7 @@ 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;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
public class FlowerPotBlock extends Block implements BedrockChunkWantsBlockEntityTag {
private final Block flower;
@ -68,11 +69,24 @@ public class FlowerPotBlock extends Block implements BedrockChunkWantsBlockEntit
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().asString());
// TODO flattening might make this nicer in the future!
NbtMap plant = session.getBlockMappings().getFlowerPotBlocks().get(this.flower);
if (plant != null) {
tagBuilder.putCompound("PlantBlock", plant.toBuilder().build());
}
}
return tagBuilder.build();
}
@Override
public ItemStack pickItem(BlockState state) {
if (this.flower != Blocks.AIR) {
return new ItemStack(this.flower.asItem().javaId());
}
return super.pickItem(state);
}
public Block flower() {
return flower;
}
}

View file

@ -27,28 +27,58 @@ 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) {
private final Type type;
public SkullBlock(String javaIdentifier, Type type, Builder builder) {
super(javaIdentifier, builder);
this.type = type;
}
@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) {
if (this.type == Type.PLAYER) {
// 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());
SkullCache.Skull skull = session.getSkullCache().updateSkull(position, state);
if (skull != null && skull.getBlockDefinition() != null) {
definition = skull.getBlockDefinition();
}
}
super.sendBlockUpdatePacket(session, state, definition, position);
}
@Override
protected void checkForEmptySkull(GeyserSession session, BlockState state, Vector3i position) {
// It's not an empty skull.
}
public Type skullType() {
return type;
}
/**
* Enum order matches Java.
*/
public enum Type {
SKELETON(0),
WITHER_SKELETON(1),
PLAYER(3),
ZOMBIE(2),
CREEPER(4),
PIGLIN(6),
DRAGON(5);
private final int bedrockId;
Type(int bedrockId) {
this.bedrockId = bedrockId;
}
public int bedrockId() {
return bedrockId;
}
}
}

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,21 +23,27 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.registry.type;
package org.geysermc.geyser.level.block.type;
import lombok.Builder;
import lombok.Value;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.level.block.property.Properties;
import org.geysermc.geyser.level.physics.Direction;
@Builder
@Value
public class BlockMapping {
public static BlockMapping DEFAULT = BlockMapping.builder().javaIdentifier("minecraft:air").build();
public class WallSkullBlock extends SkullBlock {
public WallSkullBlock(String javaIdentifier, Type type, Builder builder) {
super(javaIdentifier, type, builder);
}
String javaIdentifier;
public static int getDegrees(BlockState state) {
return getDegrees(state.getValue(Properties.HORIZONTAL_FACING));
}
@Nullable String pickItem;
boolean isBlockEntity;
boolean isNonVanilla;
public static int getDegrees(Direction direction) {
return switch (direction) {
case NORTH -> 180;
case WEST -> 90;
case EAST -> 270;
case SOUTH -> 0;
default -> throw new IllegalStateException();
};
}
}

View file

@ -42,7 +42,6 @@ import org.geysermc.geyser.registry.loader.RegistryLoaders;
import org.geysermc.geyser.registry.populator.BlockRegistryPopulator;
import org.geysermc.geyser.registry.populator.CustomBlockRegistryPopulator;
import org.geysermc.geyser.registry.populator.CustomSkullRegistryPopulator;
import org.geysermc.geyser.registry.type.BlockMapping;
import org.geysermc.geyser.registry.type.BlockMappings;
import org.geysermc.geyser.registry.type.CustomSkull;
import org.geysermc.geyser.translator.collision.BlockCollision;
@ -67,24 +66,22 @@ public class BlockRegistries {
*/
public static final ListRegistry<BlockState> BLOCK_STATES = ListRegistry.create(RegistryLoaders.empty(ArrayList::new));
public static final ListRegistry<Block> JAVA_BLOCKS_TO_RENAME = ListRegistry.create(RegistryLoaders.empty(ArrayList::new));
/**
* A mapped registry which stores Java to Bedrock block identifiers.
*/
public static final SimpleMappedRegistry<String, String> JAVA_TO_BEDROCK_IDENTIFIERS = SimpleMappedRegistry.create(RegistryLoaders.empty(Object2ObjectOpenHashMap::new));
/**
* A registry which stores Java IDs to {@link BlockMapping}, containing miscellaneous information about
* blocks and their behavior in many cases.
*/
public static final ArrayRegistry<BlockMapping> JAVA_BLOCKS = ArrayRegistry.create(RegistryLoaders.uninitialized());
/**
* A mapped registry containing which holds block IDs to its {@link BlockCollision}.
*/
public static final ListRegistry<BlockCollision> COLLISIONS;
/**
* A registry which stores Java IDs to {@link Block}, containing miscellaneous information about
* blocks and their behavior in many cases.
*/
public static final ListRegistry<Block> JAVA_BLOCKS = ListRegistry.create(RegistryLoaders.empty(ArrayList::new));
/**
* A mapped registry containing the Java identifiers to IDs.
*/

View file

@ -32,13 +32,12 @@ import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.objects.*;
import org.cloudburstmc.blockstateupdater.BlockStateUpdater;
import org.cloudburstmc.blockstateupdater.util.tagupdater.CompoundTagUpdaterContext;
import org.cloudburstmc.nbt.NBTInputStream;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.nbt.NbtType;
import org.cloudburstmc.nbt.*;
import org.cloudburstmc.protocol.bedrock.codec.v622.Bedrock_v622;
import org.cloudburstmc.protocol.bedrock.codec.v630.Bedrock_v630;
import org.cloudburstmc.protocol.bedrock.codec.v649.Bedrock_v649;
@ -50,21 +49,26 @@ import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.block.custom.CustomBlockData;
import org.geysermc.geyser.api.block.custom.CustomBlockState;
import org.geysermc.geyser.api.block.custom.nonvanilla.JavaBlockState;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.block.Blocks;
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.FlowerPotBlock;
import org.geysermc.geyser.level.physics.PistonBehavior;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.type.BlockMapping;
import org.geysermc.geyser.registry.type.BlockMappings;
import org.geysermc.geyser.registry.type.GeyserBedrockBlock;
import org.geysermc.geyser.util.BlockUtils;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import java.io.DataInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Stream;
import java.util.zip.GZIPInputStream;
/**
@ -102,7 +106,7 @@ public final class BlockRegistryPopulator {
public static void populate(Stage stage) {
switch (stage) {
case PRE_INIT, POST_INIT -> nullifyBlocksNode();
case PRE_INIT, POST_INIT -> nullifyBlocksNbt();
case INIT_JAVA -> registerJavaBlocks();
case INIT_BEDROCK -> registerBedrockBlocks();
default -> throw new IllegalArgumentException("Unknown stage: " + stage);
@ -110,14 +114,14 @@ public final class BlockRegistryPopulator {
}
/**
* Stores the raw blocks JSON until it is no longer needed.
* Stores the raw blocks NBT until it is no longer needed.
*/
private static JsonNode BLOCKS_JSON;
private static List<NbtMap> BLOCKS_NBT;
private static int MIN_CUSTOM_RUNTIME_ID = -1;
private static int JAVA_BLOCKS_SIZE = -1;
private static void nullifyBlocksNode() {
BLOCKS_JSON = null;
private static void nullifyBlocksNbt() {
BLOCKS_NBT = null;
}
private static void registerBedrockBlocks() {
@ -218,19 +222,31 @@ public final class BlockRegistryPopulator {
int javaRuntimeId = -1;
List<BlockState> javaBlockStates = BlockRegistries.BLOCK_STATES.get();
GeyserBedrockBlock airDefinition = null;
BlockDefinition commandBlockDefinition = null;
BlockDefinition mobSpawnerBlockDefinition = null;
BlockDefinition waterDefinition = null;
BlockDefinition movingBlockDefinition = null;
Iterator<Map.Entry<String, JsonNode>> blocksIterator = BLOCKS_JSON.fields();
Iterator<NbtMap> blocksIterator = BLOCKS_NBT.iterator();
Remapper stateMapper = blockMappers.get(palette);
GeyserBedrockBlock[] javaToBedrockBlocks = new GeyserBedrockBlock[JAVA_BLOCKS_SIZE];
GeyserBedrockBlock[] javaToVanillaBedrockBlocks = new GeyserBedrockBlock[JAVA_BLOCKS_SIZE];
Map<String, NbtMap> flowerPotBlocks = new Object2ObjectOpenHashMap<>();
// Stream isn't ideal.
List<Block> javaPottable = BlockRegistries.JAVA_BLOCKS.get()
.parallelStream()
.flatMap(block -> {
if (block instanceof FlowerPotBlock flowerPot && flowerPot.flower() != Blocks.AIR) {
return Stream.of(flowerPot.flower());
}
return null;
})
.toList();
Map<Block, NbtMap> flowerPotBlocks = new Object2ObjectOpenHashMap<>();
Map<NbtMap, BlockDefinition> itemFrames = new Object2ObjectOpenHashMap<>();
Set<BlockDefinition> jigsawDefinitions = new ObjectOpenHashSet<>();
@ -239,10 +255,11 @@ public final class BlockRegistryPopulator {
BlockMappings.BlockMappingsBuilder builder = BlockMappings.builder();
while (blocksIterator.hasNext()) {
javaRuntimeId++;
Map.Entry<String, JsonNode> entry = blocksIterator.next();
String javaId = entry.getKey();
NbtMap entry = blocksIterator.next();
BlockState blockState = javaBlockStates.get(javaRuntimeId);
String javaId = blockState.toString();
NbtMap originalBedrockTag = buildBedrockState(entry.getValue());
NbtMap originalBedrockTag = buildBedrockState(blockState, entry);
NbtMap bedrockTag = stateMapper.remap(originalBedrockTag);
GeyserBedrockBlock vanillaBedrockDefinition = blockStateOrderedMap.get(bedrockTag);
@ -274,35 +291,27 @@ public final class BlockRegistryPopulator {
case "minecraft:moving_piston[facing=north,type=normal]" -> movingBlockDefinition = bedrockDefinition;
}
if (javaId.contains("jigsaw")) {
if (blockState.block() == Blocks.JIGSAW) {
jigsawDefinitions.add(bedrockDefinition);
}
if (javaId.contains("structure_block")) {
int modeIndex = javaId.indexOf("mode=");
if (modeIndex != -1) {
int startIndex = modeIndex + 5; // Length of "mode=" is 5
int endIndex = javaId.indexOf("]", startIndex);
if (endIndex != -1) {
String modeValue = javaId.substring(startIndex, endIndex);
structureBlockDefinitions.put(modeValue.toUpperCase(), bedrockDefinition);
}
}
if (blockState.block() == Blocks.STRUCTURE_BLOCK) {
String mode = blockState.getValue(Properties.STRUCTUREBLOCK_MODE);
structureBlockDefinitions.put(mode.toUpperCase(Locale.ROOT), bedrockDefinition);
}
boolean waterlogged = entry.getKey().contains("waterlogged=true")
|| javaId.contains("minecraft:bubble_column") || javaId.contains("minecraft:kelp") || javaId.contains("seagrass");
boolean waterlogged = blockState.getValue(Properties.WATERLOGGED, false)
|| blockState.block() == Blocks.BUBBLE_COLUMN || blockState.block() == Blocks.KELP || blockState.block() == Blocks.SEAGRASS;
if (waterlogged) {
int finalJavaRuntimeId = javaRuntimeId;
BlockRegistries.WATERLOGGED.register(set -> set.set(finalJavaRuntimeId));
}
String cleanJavaIdentifier = BlockUtils.getCleanIdentifier(entry.getKey());
// Get the tag needed for non-empty flower pots
if (entry.getValue().get("pottable") != null) {
flowerPotBlocks.put(cleanJavaIdentifier.intern(), blockStates.get(bedrockDefinition.getRuntimeId()));
if (javaPottable.contains(blockState.block())) {
// Specifically NOT putIfAbsent - mangrove propagule breaks otherwise
flowerPotBlocks.put(blockState.block(), blockStates.get(bedrockDefinition.getRuntimeId()));
}
javaToVanillaBedrockBlocks[javaRuntimeId] = vanillaBedrockDefinition;
@ -386,9 +395,10 @@ public final class BlockRegistryPopulator {
}
private static void registerJavaBlocks() {
JsonNode blocksJson;
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow("mappings/blocks.json")) {
blocksJson = GeyserImpl.JSON_MAPPER.readTree(stream);
List<NbtMap> blocksNbt;
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow("mappings/blocks.nbt")) {
blocksNbt = ((NbtMap) NbtUtils.createGZIPReader(stream).readTag())
.getList("bedrock_mappings", NbtType.COMPOUND);
} catch (Exception e) {
throw new AssertionError("Unable to load Java block mappings", e);
}
@ -399,56 +409,29 @@ public final class BlockRegistryPopulator {
MIN_CUSTOM_RUNTIME_ID = BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().keySet().stream().min(Comparator.comparing(JavaBlockState::javaId)).orElseThrow().javaId();
int maxCustomRuntimeID = BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().keySet().stream().max(Comparator.comparing(JavaBlockState::javaId)).orElseThrow().javaId();
if (MIN_CUSTOM_RUNTIME_ID < blocksJson.size()) {
if (MIN_CUSTOM_RUNTIME_ID < blocksNbt.size()) {
throw new RuntimeException("Non vanilla custom block state overrides runtime ID must start after the last vanilla block state (" + JAVA_BLOCKS_SIZE + ")");
}
JAVA_BLOCKS_SIZE = maxCustomRuntimeID + 1; // Runtime ids start at 0, so we need to add 1
}
BlockRegistries.JAVA_BLOCKS.set(new BlockMapping[JAVA_BLOCKS_SIZE]); // Set array size to number of blockstates
Deque<String> cleanIdentifiers = new ArrayDeque<>();
int javaRuntimeId = -1;
int waterRuntimeId = -1;
Iterator<Map.Entry<String, JsonNode>> blocksIterator = blocksJson.fields();
while (blocksIterator.hasNext()) {
for (BlockState javaBlockState : BlockRegistries.BLOCK_STATES.get()) {
javaRuntimeId++;
Map.Entry<String, JsonNode> entry = blocksIterator.next();
String javaId = entry.getKey();
String javaId = javaBlockState.toString().intern();
BlockMapping.BlockMappingBuilder builder = BlockMapping.builder();
BlockStateValues.storeBlockStateValues(javaId, javaRuntimeId);
JsonNode pickItemNode = entry.getValue().get("pick_item");
if (pickItemNode != null) {
builder.pickItem(pickItemNode.textValue().intern());
}
JsonNode hasBlockEntityNode = entry.getValue().get("has_block_entity");
if (hasBlockEntityNode != null) {
builder.isBlockEntity(hasBlockEntityNode.booleanValue());
} else {
builder.isBlockEntity(false);
}
BlockStateValues.storeBlockStateValues(entry.getKey(), javaRuntimeId, entry.getValue());
String cleanJavaIdentifier = BlockUtils.getCleanIdentifier(entry.getKey());
String bedrockIdentifier = entry.getValue().get("bedrock_identifier").asText();
if (!cleanJavaIdentifier.equals(cleanIdentifiers.peekLast())) {
cleanIdentifiers.add(cleanJavaIdentifier.intern());
}
builder.javaIdentifier(javaId);
//String cleanJavaIdentifier = javaBlockState.block().javaIdentifier().toString();
//String bedrockIdentifier = entry.getValue().get("bedrock_identifier").asText();
BlockRegistries.JAVA_IDENTIFIER_TO_ID.register(javaId, javaRuntimeId);
BlockRegistries.JAVA_BLOCKS.register(javaRuntimeId, builder.build());
// Keeping this here since this is currently unchanged between versions
// It's possible to only have this store differences in names, but the key set of all Java names is used in sending command suggestions
BlockRegistries.JAVA_TO_BEDROCK_IDENTIFIERS.register(cleanJavaIdentifier.intern(), bedrockIdentifier.intern());
//BlockRegistries.JAVA_TO_BEDROCK_IDENTIFIERS.register(cleanJavaIdentifier.intern(), bedrockIdentifier.intern());
if ("minecraft:water[level=0]".equals(javaId)) {
waterRuntimeId = javaRuntimeId;
@ -461,7 +444,7 @@ public final class BlockRegistryPopulator {
BlockStateValues.JAVA_WATER_ID = waterRuntimeId;
if (!BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().isEmpty()) {
Set<Integer> usedNonVanillaRuntimeIDs = new HashSet<>();
IntSet usedNonVanillaRuntimeIDs = new IntOpenHashSet();
for (JavaBlockState javaBlockState : BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().keySet()) {
if (!usedNonVanillaRuntimeIDs.add(javaBlockState.javaId())) {
@ -473,11 +456,6 @@ public final class BlockRegistryPopulator {
String javaId = javaBlockState.identifier();
int stateRuntimeId = javaBlockState.javaId();
String pistonBehavior = javaBlockState.pistonBehavior();
BlockMapping blockMapping = BlockMapping.builder()
.pickItem(javaBlockState.pickItem())
.isNonVanilla(true)
.javaIdentifier(javaId)
.build();
Block.Builder builder = Block.builder()
.destroyTime(javaBlockState.blockHardness())
@ -492,23 +470,25 @@ public final class BlockRegistryPopulator {
String pickItem = javaBlockState.pickItem();
Block block = new Block(cleanJavaIdentifier, builder) {
@Override
public Item asItem() {
public ItemStack pickItem(BlockState state) {
if (this.item == null) {
return Registries.JAVA_ITEM_IDENTIFIERS.get(pickItem);
this.item = Registries.JAVA_ITEM_IDENTIFIERS.get(pickItem);
if (this.item == null) {
GeyserImpl.getInstance().getLogger().warning("We could not find item " + pickItem
+ " for getting the item for block " + javaBlockState.identifier());
this.item = Items.AIR;
}
}
return this.item;
return new ItemStack(this.item.javaId());
}
};
block.setJavaId(javaBlockState.stateGroupId());
String bedrockIdentifier = customBlockState.block().identifier();
if (!cleanJavaIdentifier.equals(cleanIdentifiers.peekLast())) {
cleanIdentifiers.add(cleanJavaIdentifier.intern());
}
BlockRegistries.JAVA_BLOCKS_TO_RENAME.get().add(javaBlockState.stateGroupId(), block); //TODO don't allow duplicates, allow blanks
BlockRegistries.JAVA_BLOCKS.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);
BlockRegistries.BLOCK_STATES.register(stateRuntimeId, new BlockState(block, stateRuntimeId));
// Keeping this here since this is currently unchanged between versions
// It's possible to only have this store differences in names, but the key set of all Java names is used in sending command suggestions
@ -516,7 +496,7 @@ public final class BlockRegistryPopulator {
}
}
BLOCKS_JSON = blocksJson;
BLOCKS_NBT = blocksNbt;
JsonNode blockInteractionsJson;
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow("mappings/interactions.json")) {
@ -537,29 +517,11 @@ public final class BlockRegistryPopulator {
return blockStateSet;
}
private static NbtMap buildBedrockState(JsonNode node) {
private static NbtMap buildBedrockState(BlockState state, NbtMap nbt) {
NbtMapBuilder tagBuilder = NbtMap.builder();
String bedrockIdentifier = node.get("bedrock_identifier").textValue();
String bedrockIdentifier = "minecraft:" + nbt.getString("bedrock_identifier", state.block().javaIdentifier().value());
tagBuilder.putString("name", bedrockIdentifier);
NbtMapBuilder statesBuilder = NbtMap.builder();
// check for states
JsonNode states = node.get("bedrock_states");
if (states != null) {
Iterator<Map.Entry<String, JsonNode>> statesIterator = states.fields();
while (statesIterator.hasNext()) {
Map.Entry<String, JsonNode> stateEntry = statesIterator.next();
JsonNode stateValue = stateEntry.getValue();
switch (stateValue.getNodeType()) {
case BOOLEAN -> statesBuilder.putBoolean(stateEntry.getKey(), stateValue.booleanValue());
case STRING -> statesBuilder.putString(stateEntry.getKey(), stateValue.textValue());
case NUMBER -> statesBuilder.putInt(stateEntry.getKey(), stateValue.intValue());
}
}
}
tagBuilder.put("states", statesBuilder.build());
tagBuilder.put("states", nbt.getCompound("state"));
return tagBuilder.build();
}

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.Block;
import org.geysermc.geyser.level.block.type.BlockState;
import java.util.List;
@ -59,7 +60,7 @@ public class BlockMappings implements DefinitionRegistry<GeyserBedrockBlock> {
BlockDefinition mobSpawnerBlock;
Map<NbtMap, BlockDefinition> itemFrames;
Map<String, NbtMap> flowerPotBlocks;
Map<Block, NbtMap> flowerPotBlocks;
Set<BlockDefinition> jigsawStates;
Map<String, BlockDefinition> structureBlockStates;

View file

@ -25,18 +25,20 @@
package org.geysermc.geyser.session.cache;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.math.vector.Vector3i;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.block.custom.CustomBlockState;
import org.geysermc.geyser.entity.type.player.SkullPlayerEntity;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.block.property.Properties;
import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.level.block.type.WallSkullBlock;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.type.CustomSkull;
import org.geysermc.geyser.session.GeyserSession;
@ -80,7 +82,7 @@ public class SkullCache {
this.skullRenderDistanceSquared = distance * distance;
}
public Skull putSkull(Vector3i position, UUID uuid, String texturesProperty, int blockState) {
public Skull putSkull(Vector3i position, UUID uuid, String texturesProperty, BlockState blockState) {
Skull skull = skulls.computeIfAbsent(position, Skull::new);
skull.uuid = uuid;
if (!texturesProperty.equals(skull.texturesProperty)) {
@ -147,7 +149,7 @@ public class SkullCache {
}
}
public Skull updateSkull(Vector3i position, int blockState) {
public Skull updateSkull(Vector3i position, BlockState blockState) {
Skull skull = skulls.get(position);
if (skull != null) {
putSkull(position, skull.uuid, skull.texturesProperty, blockState);
@ -248,17 +250,14 @@ public class SkullCache {
lastPlayerPosition = null;
}
private @Nullable BlockDefinition translateCustomSkull(String skinHash, int blockState) {
private @Nullable BlockDefinition translateCustomSkull(String skinHash, BlockState blockState) {
CustomSkull customSkull = BlockRegistries.CUSTOM_SKULLS.get(skinHash);
if (customSkull != null) {
byte floorRotation = BlockStateValues.getSkullRotation(blockState);
CustomBlockState customBlockState;
if (floorRotation == -1) {
// Wall skull
int wallDirection = BlockStateValues.getSkullWallDirections().get(blockState);
customBlockState = customSkull.getWallBlockState(wallDirection);
if (blockState.block() instanceof WallSkullBlock) {
customBlockState = customSkull.getWallBlockState(WallSkullBlock.getDegrees(blockState));
} else {
customBlockState = customSkull.getFloorBlockState(floorRotation);
customBlockState = customSkull.getFloorBlockState(blockState.getValue(Properties.ROTATION_16));
}
return session.getBlockMappings().getCustomBlockStateDefinitions().get(customBlockState);
@ -273,7 +272,7 @@ public class SkullCache {
private String texturesProperty;
private String skinHash;
private int blockState;
private BlockState blockState;
private BlockDefinition blockDefinition;
private SkullPlayerEntity entity;

View file

@ -36,10 +36,13 @@ import org.cloudburstmc.protocol.bedrock.packet.ContainerOpenPacket;
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.level.block.Blocks;
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.registry.BlockRegistries;
import org.geysermc.geyser.registry.type.BlockMapping;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator;
import org.geysermc.geyser.translator.level.block.entity.DoubleChestBlockEntityTranslator;
import org.geysermc.geyser.util.InventoryUtils;
@ -55,24 +58,17 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator {
public boolean prepareInventory(GeyserSession session, Inventory inventory) {
// See BlockInventoryHolder - same concept there except we're also dealing with a specific block state
if (session.getLastInteractionPlayerPosition().equals(session.getPlayerEntity().getPosition())) {
int javaBlockId = session.getGeyser().getWorldManager().getBlockAt(session, session.getLastInteractionBlockPosition());
if (!BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get().containsKey(javaBlockId)) {
String[] javaBlockString = BlockRegistries.JAVA_BLOCKS.getOrDefault(javaBlockId, BlockMapping.DEFAULT).getJavaIdentifier().split("\\[");
if (javaBlockString.length > 1 && (javaBlockString[0].equals("minecraft:chest") || javaBlockString[0].equals("minecraft:trapped_chest"))
&& !javaBlockString[1].contains("type=single")) {
BlockState state = session.getGeyser().getWorldManager().blockAt(session, session.getLastInteractionBlockPosition());
if (!BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get().containsKey(state.javaId())) {
if (state.block() == Blocks.CHEST || state.block() == Blocks.TRAPPED_CHEST
&& state.getValue(Properties.CHEST_TYPE) != ChestType.SINGLE) {
inventory.setHolderPosition(session.getLastInteractionBlockPosition());
((Container) inventory).setUsingRealBlock(true, javaBlockString[0]);
((Container) inventory).setUsingRealBlock(true, state.block());
NbtMapBuilder tag = NbtMap.builder()
.putString("id", "Chest")
.putInt("x", session.getLastInteractionBlockPosition().getX())
.putInt("y", session.getLastInteractionBlockPosition().getY())
.putInt("z", session.getLastInteractionBlockPosition().getZ())
.putString("CustomName", inventory.getTitle())
.putString("id", "Chest");
NbtMapBuilder tag = BlockEntityTranslator.getConstantBedrockTag("Chest", session.getLastInteractionBlockPosition())
.putString("CustomName", inventory.getTitle());
BlockState blockState = BlockState.of(javaBlockId);
DoubleChestBlockEntityTranslator.translateChestValue(tag, blockState,
DoubleChestBlockEntityTranslator.translateChestValue(tag, state,
session.getLastInteractionBlockPosition().getX(), session.getLastInteractionBlockPosition().getZ());
BlockEntityDataPacket dataPacket = new BlockEntityDataPacket();

View file

@ -33,9 +33,9 @@ import org.cloudburstmc.nbt.NbtType;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.block.property.Properties;
import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.level.block.type.SkullBlock;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.SkullCache;
import org.geysermc.geyser.skin.SkinProvider;
@ -53,17 +53,12 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements
@Override
public void translateTag(GeyserSession session, NbtMapBuilder bedrockNbt, NbtMap javaNbt, BlockState blockState) {
byte skullVariant = BlockStateValues.getSkullVariant(blockState.javaId()); // TODO
// Just in case...
if (skullVariant == -1) {
skullVariant = 0;
}
Integer rotation = blockState.getValue(Properties.ROTATION_16);
if (rotation != null) {
// Could be a wall skull block
// Could be a wall skull block otherwise, which has rotation in its Bedrock state
bedrockNbt.putFloat("Rotation", rotation * 22.5f);
}
bedrockNbt.putByte("SkullType", skullVariant);
bedrockNbt.putByte("SkullType", (byte) (blockState.block() instanceof SkullBlock skull ? skull.skullType().bedrockId() : 0));
if (blockState.getValue(Properties.POWERED)) {
bedrockNbt.putBoolean("MouthMoving", true);
}
@ -106,7 +101,7 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements
return CompletableFuture.completedFuture(texture);
}
public static @Nullable BlockDefinition translateSkull(GeyserSession session, NbtMap javaNbt, Vector3i blockPosition, int blockState) {
public static @Nullable BlockDefinition translateSkull(GeyserSession session, NbtMap javaNbt, Vector3i blockPosition, BlockState blockState) {
NbtMap profile = javaNbt.getCompound("profile");
if (profile.isEmpty()) {
session.getSkullCache().removeSkull(blockPosition);
@ -150,7 +145,7 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements
return null;
}
private static void putSkull(GeyserSession session, Vector3i blockPosition, UUID uuid, String texturesProperty, int blockState) {
private static void putSkull(GeyserSession session, Vector3i blockPosition, UUID uuid, String texturesProperty, BlockState blockState) {
SkullCache.Skull skull = session.getSkullCache().putSkull(blockPosition, uuid, texturesProperty, blockState);
if (skull.getBlockDefinition() != null) {
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();

View file

@ -32,7 +32,7 @@ import org.geysermc.geyser.entity.type.ItemFrameEntity;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.level.block.type.BannerBlock;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
@ -45,10 +45,10 @@ public class BedrockBlockPickRequestTranslator extends PacketTranslator<BlockPic
@Override
public void translate(GeyserSession session, BlockPickRequestPacket packet) {
Vector3i vector = packet.getBlockPosition();
Block blockToPick = session.getGeyser().getWorldManager().blockAt(session, vector.getX(), vector.getY(), vector.getZ()).block();
BlockState blockToPick = session.getGeyser().getWorldManager().blockAt(session, vector.getX(), vector.getY(), vector.getZ());
// Block is air - chunk caching is probably off
if (blockToPick == Blocks.AIR) {
if (blockToPick.is(Blocks.AIR)) {
// Check for an item frame since the client thinks that's a block when it's an entity in Java
ItemFrameEntity entity = ItemFrameEntity.getItemFrameEntity(session, packet.getBlockPosition());
if (entity != null) {
@ -64,8 +64,8 @@ public class BedrockBlockPickRequestTranslator extends PacketTranslator<BlockPic
return;
}
boolean addExtraData = packet.isAddUserData() && blockToPick.hasBlockEntity(); // Holding down CTRL
if (blockToPick instanceof BannerBlock || addExtraData) { //TODO
boolean addExtraData = packet.isAddUserData() && blockToPick.block().hasBlockEntity(); // Holding down CTRL
if (blockToPick.block() instanceof BannerBlock || addExtraData) {
session.getGeyser().getWorldManager().getPickItemComponents(session, vector.getX(), vector.getY(), vector.getZ(), addExtraData)
.whenComplete((components, ex) -> session.ensureInEventLoop(() -> {
if (components == null) {
@ -73,7 +73,7 @@ public class BedrockBlockPickRequestTranslator extends PacketTranslator<BlockPic
return;
}
ItemStack itemStack = new ItemStack(blockToPick.asItem().javaId(), 1, components);
ItemStack itemStack = new ItemStack(blockToPick.block().asItem().javaId(), 1, components);
InventoryUtils.findOrCreateItem(session, itemStack);
}));
return;
@ -82,7 +82,7 @@ public class BedrockBlockPickRequestTranslator extends PacketTranslator<BlockPic
pickItem(session, blockToPick);
}
private void pickItem(GeyserSession session, Block block) {
InventoryUtils.findOrCreateItem(session, block.asItem());
private void pickItem(GeyserSession session, BlockState state) {
InventoryUtils.findOrCreateItem(session, state.block().pickItem(state));
}
}

View file

@ -57,7 +57,11 @@ import org.geysermc.geyser.item.type.BoatItem;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.item.type.SpawnEggItem;
import org.geysermc.geyser.level.block.BlockStateValues;
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.CauldronBlock;
import org.geysermc.geyser.level.block.type.SkullBlock;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.SkullCache;
@ -311,24 +315,25 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
Item item = session.getPlayerInventory().getItemInHand().asItem();
if (packet.getItemInHand() != null) {
ItemDefinition definition = packet.getItemInHand().getDefinition();
int blockState = session.getGeyser().getWorldManager().getBlockAt(session, packet.getBlockPosition());
BlockState blockState = session.getGeyser().getWorldManager().blockAt(session, packet.getBlockPosition());
// Otherwise boats will not be able to be placed in survival and buckets, lily pads, frogspawn, and glass bottles won't work on mobile
if (item instanceof BoatItem || item == Items.LILY_PAD || item == Items.FROGSPAWN) {
useItem(session, packet, blockState);
useItem(session, packet, blockState.javaId());
} else if (item == Items.GLASS_BOTTLE) {
if (!session.isSneaking() && BlockStateValues.isCauldron(blockState) && !BlockStateValues.isNonWaterCauldron(blockState)) {
Block block = blockState.block();
if (!session.isSneaking() && block instanceof CauldronBlock && block != Blocks.WATER_CAULDRON) {
// ServerboundUseItemPacket is not sent for water cauldrons and glass bottles
return;
}
useItem(session, packet, blockState);
useItem(session, packet, blockState.javaId());
} else if (session.getItemMappings().getBuckets().contains(definition)) {
// Don't send ServerboundUseItemPacket for powder snow buckets
if (definition != session.getItemMappings().getStoredItems().powderSnowBucket().getBedrockDefinition()) {
if (!session.isSneaking() && BlockStateValues.isCauldron(blockState)) {
if (!session.isSneaking() && blockState.block() instanceof CauldronBlock) {
// ServerboundUseItemPacket is not sent for cauldrons and buckets
return;
}
session.setPlacedBucket(useItem(session, packet, blockState));
session.setPlacedBucket(useItem(session, packet, blockState.javaId()));
} else {
session.setPlacedBucket(true);
}
@ -553,10 +558,10 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
* @param blockPos the block position to restore
*/
private void restoreCorrectBlock(GeyserSession session, Vector3i blockPos, InventoryTransactionPacket packet) {
int javaBlockState = session.getGeyser().getWorldManager().getBlockAt(session, blockPos);
BlockState javaBlockState = session.getGeyser().getWorldManager().blockAt(session, blockPos);
BlockDefinition bedrockBlock = session.getBlockMappings().getBedrockBlock(javaBlockState);
if (BlockStateValues.getSkullVariant(javaBlockState) == 3) {
if (javaBlockState.block() instanceof SkullBlock skullBlock && skullBlock.skullType() == SkullBlock.Type.PLAYER) {
// The changed block was a player skull so check if a custom block was defined for this skull
SkullCache.Skull skull = session.getSkullCache().getSkulls().get(blockPos);
if (skull != null && skull.getBlockDefinition() != null) {
@ -574,7 +579,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
UpdateBlockPacket updateWaterPacket = new UpdateBlockPacket();
updateWaterPacket.setDataLayer(1);
updateWaterPacket.setBlockPosition(blockPos);
updateWaterPacket.setDefinition(BlockRegistries.WATERLOGGED.get().get(javaBlockState) ? session.getBlockMappings().getBedrockWater() : session.getBlockMappings().getBedrockAir());
updateWaterPacket.setDefinition(BlockRegistries.WATERLOGGED.get().get(javaBlockState.javaId()) ? session.getBlockMappings().getBedrockWater() : session.getBlockMappings().getBedrockAir());
updateWaterPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY);
session.sendUpstreamPacket(updateWaterPacket);

View file

@ -38,12 +38,12 @@ import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.ItemFrameEntity;
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.block.Blocks;
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.network.GameProtocol;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.type.BlockMapping;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.SkullCache;
@ -180,9 +180,8 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
// Account for fire - the client likes to hit the block behind.
Vector3i fireBlockPos = BlockUtils.getBlockPosition(vector, packet.getFace());
int blockUp = session.getGeyser().getWorldManager().getBlockAt(session, fireBlockPos);
String identifier = BlockRegistries.JAVA_BLOCKS.getOrDefault(blockUp, BlockMapping.DEFAULT).getJavaIdentifier();
if (identifier.startsWith("minecraft:fire") || identifier.startsWith("minecraft:soul_fire")) {
Block block = session.getGeyser().getWorldManager().blockAt(session, fireBlockPos).block();
if (block == Blocks.FIRE || block == Blocks.SOUL_FIRE) {
ServerboundPlayerActionPacket startBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.START_DIGGING, fireBlockPos,
Direction.VALUES[packet.getFace()], session.getWorldCache().nextPredictionSequence());
session.sendDownstreamGamePacket(startBreakingPacket);
@ -336,9 +335,9 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
break;
}
int interactedBlock = session.getGeyser().getWorldManager().getBlockAt(session, vector);
BlockState state = session.getGeyser().getWorldManager().blockAt(session, vector);
if (BlockStateValues.getLecternBookStates().getOrDefault(interactedBlock, false)) {
if (state.getValue(Properties.HAS_BOOK, false)) {
session.setDroppingLecternBook(true);
ServerboundUseItemOnPacket blockPacket = new ServerboundUseItemOnPacket(
@ -352,16 +351,13 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
break;
}
if (session.getItemFrameCache().containsKey(vector)) {
Entity itemFrame = ItemFrameEntity.getItemFrameEntity(session, packet.getBlockPosition());
if (itemFrame != null) {
ServerboundInteractPacket interactPacket = new ServerboundInteractPacket(itemFrame.getEntityId(),
InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking());
session.sendDownstreamGamePacket(interactPacket);
}
break;
Entity itemFrame = ItemFrameEntity.getItemFrameEntity(session, packet.getBlockPosition());
if (itemFrame != null) {
ServerboundInteractPacket interactPacket = new ServerboundInteractPacket(itemFrame.getEntityId(),
InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking());
session.sendDownstreamGamePacket(interactPacket);
}
break;
}
}
}

View file

@ -25,20 +25,21 @@
package org.geysermc.geyser.translator.protocol.bedrock.world;
import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundSwingPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundUseItemOnPacket;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
import org.cloudburstmc.protocol.bedrock.packet.AnimatePacket;
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.block.property.Properties;
import org.geysermc.geyser.level.block.type.BlockState;
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.CooldownUtils;
import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundSwingPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundUseItemOnPacket;
@Translator(packet = LevelSoundEventPacket.class)
public class BedrockLevelSoundEventTranslator extends PacketTranslator<LevelSoundEventPacket> {
@ -77,9 +78,9 @@ public class BedrockLevelSoundEventTranslator extends PacketTranslator<LevelSoun
Vector3f position = packet.getPosition();
Vector3i blockPosition = Vector3i.from(position.getX(), position.getY(), position.getZ());
int potentialLectern = session.getGeyser().getWorldManager().getBlockAt(session, blockPosition);
BlockState potentialLectern = session.getGeyser().getWorldManager().blockAt(session, blockPosition);
if (BlockStateValues.getLecternBookStates().getOrDefault(potentialLectern, false)) {
if (potentialLectern.getValue(Properties.HAS_BOOK, false)) {
session.setDroppingLecternBook(true);
ServerboundUseItemOnPacket blockPacket = new ServerboundUseItemOnPacket(

View file

@ -72,7 +72,7 @@ public class JavaBlockEntityDataTranslator extends PacketTranslator<ClientboundB
// Check for custom skulls.
boolean hasCustomHeadBlock = false;
if (session.getPreferencesCache().showCustomSkulls() && packet.getNbt() != null && packet.getNbt().containsKey("profile")) {
BlockDefinition blockDefinition = SkullBlockEntityTranslator.translateSkull(session, packet.getNbt(), position, blockState.javaId());
BlockDefinition blockDefinition = SkullBlockEntityTranslator.translateSkull(session, packet.getNbt(), position, blockState);
if (blockDefinition != null) {
hasCustomHeadBlock = true;
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();

View file

@ -25,19 +25,17 @@
package org.geysermc.geyser.translator.protocol.java.level;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.level.ClientboundBlockUpdatePacket;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.type.BlockMapping;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.translator.sound.BlockSoundInteractionTranslator;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.level.ClientboundBlockUpdatePacket;
@Translator(packet = ClientboundBlockUpdatePacket.class)
public class JavaBlockUpdateTranslator extends PacketTranslator<ClientboundBlockUpdatePacket> {
@ -102,7 +100,7 @@ public class JavaBlockUpdateTranslator extends PacketTranslator<ClientboundBlock
|| lastInteractPos.getZ() != packet.getEntry().getPosition().getZ())) {
return;
}
String identifier = BlockRegistries.JAVA_BLOCKS.getOrDefault(packet.getEntry().getBlock(), BlockMapping.DEFAULT).getJavaIdentifier();
String identifier = BlockState.of(packet.getEntry().getBlock()).toString(); // This will be yeeted soon. Thanks Chris.
session.setInteracting(false);
BlockSoundInteractionTranslator.handleBlockInteraction(session, lastInteractPos.toFloat(), identifier);
}

View file

@ -171,8 +171,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
int xzy = indexYZXtoXZY(yzx);
section.getBlockStorageArray()[0].setFullBlock(xzy, bedrockId);
Boolean waterlogged = state.getValue(Properties.WATERLOGGED); // TODO performance check
if (waterlogged == Boolean.TRUE) {
if (BlockRegistries.WATERLOGGED.get().get(javaId)) {
section.getBlockStorageArray()[1].setFullBlock(xzy, session.getBlockMappings().getBedrockWater().getRuntimeId());
}
@ -426,7 +425,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
// Check for custom skulls
if (session.getPreferencesCache().showCustomSkulls() && type == BlockEntityType.SKULL && tag != null && tag.containsKey("profile")) {
BlockDefinition blockDefinition = SkullBlockEntityTranslator.translateSkull(session, tag, Vector3i.from(x + chunkBlockX, y, z + chunkBlockZ), blockState.javaId());
BlockDefinition blockDefinition = SkullBlockEntityTranslator.translateSkull(session, tag, Vector3i.from(x + chunkBlockX, y, z + chunkBlockZ), blockState);
if (blockDefinition != null) {
int bedrockSectionY = (y >> 4) - (bedrockDimension.minY() >> 4);
int subChunkIndex = (y >> 4) + (bedrockDimension.minY() >> 4);

View file

@ -94,7 +94,7 @@ public class StatisticsUtils {
for (Object2IntMap.Entry<Statistic> entry : session.getStatistics().object2IntEntrySet()) {
if (entry.getKey() instanceof BreakBlockStatistic statistic) {
Block block = BlockRegistries.JAVA_BLOCKS_TO_RENAME.get(statistic.getId());
Block block = BlockRegistries.JAVA_BLOCKS.get(statistic.getId());
if (block != null) {
String identifier = "block.minecraft." + block.javaIdentifier().value();
content.add(identifier + ": " + entry.getIntValue());

View file

@ -1,120 +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.util.collection;
import it.unimi.dsi.fastutil.ints.AbstractInt2BooleanMap;
import it.unimi.dsi.fastutil.objects.ObjectSet;
import java.io.Serial;
public class FixedInt2BooleanMap extends AbstractInt2BooleanMap {
@Serial
private static final long serialVersionUID = 1L;
protected boolean[] value;
protected int start = -1;
@Override
public int size() {
return value.length;
}
@Override
public ObjectSet<Entry> int2BooleanEntrySet() {
throw new UnsupportedOperationException();
}
@Override
public boolean get(int i) {
return getOrDefault(i, defRetValue);
}
@Override
public boolean getOrDefault(int key, boolean defaultValue) {
int offset = key - start;
if (offset < 0 || offset >= value.length) {
return defaultValue;
}
return value[offset];
}
@Override
public boolean put(int key, boolean value) {
if (start == -1) {
start = key;
this.value = new boolean[] {value};
} else {
int offset = key - start;
if (offset >= 0 && offset < this.value.length) {
boolean curr = this.value[offset];
this.value[offset] = value;
return curr;
} else if (offset != this.value.length) {
throw new IndexOutOfBoundsException("Expected: " + (this.value.length + start) + ", got " + key);
}
boolean[] newValue = new boolean[offset + 1];
System.arraycopy(this.value, 0, newValue, 0, this.value.length);
this.value = newValue;
this.value[offset] = value;
}
return this.defRetValue;
}
@Override
public boolean containsKey(int k) {
int offset = k - start;
return offset >= 0 && offset < value.length;
}
@Override
public boolean containsValue(boolean v) {
for (boolean b : value) {
if (b == v) {
return true;
}
}
return false;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("{");
int index = start;
for (boolean b : value) {
builder.append(index++).append("=>").append(b);
if (index < value.length + start) {
// Add commas while there are still more entries in the list
builder.append(", ");
}
}
return builder.append('}').toString();
}
}

View file

@ -1,121 +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.util.collection;
import it.unimi.dsi.fastutil.ints.AbstractInt2ByteMap;
import it.unimi.dsi.fastutil.ints.Int2ByteMap;
import it.unimi.dsi.fastutil.objects.ObjectSet;
import java.io.Serial;
public class FixedInt2ByteMap extends AbstractInt2ByteMap {
@Serial
private static final long serialVersionUID = 1L;
protected byte[] value;
protected int start = -1;
@Override
public int size() {
return value.length;
}
@Override
public ObjectSet<Int2ByteMap.Entry> int2ByteEntrySet() {
throw new UnsupportedOperationException();
}
@Override
public byte get(int i) {
return getOrDefault(i, defRetValue);
}
@Override
public byte getOrDefault(int key, byte defaultValue) {
int offset = key - start;
if (offset < 0 || offset >= value.length) {
return defaultValue;
}
return value[offset];
}
@Override
public byte put(int key, byte value) {
if (start == -1) {
start = key;
this.value = new byte[] {value};
} else {
int offset = key - start;
if (offset >= 0 && offset < this.value.length) {
byte curr = this.value[offset];
this.value[offset] = value;
return curr;
} else if (offset != this.value.length) {
throw new IndexOutOfBoundsException("Expected: " + (this.value.length + start) + ", got " + key);
}
byte[] newValue = new byte[offset + 1];
System.arraycopy(this.value, 0, newValue, 0, this.value.length);
this.value = newValue;
this.value[offset] = value;
}
return this.defRetValue;
}
@Override
public boolean containsKey(int k) {
int offset = k - start;
return offset >= 0 && offset < value.length;
}
@Override
public boolean containsValue(byte v) {
for (byte i : value) {
if (i == v) {
return true;
}
}
return false;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("{");
int index = start;
for (byte b : value) {
builder.append(index++).append("=>").append(b);
if (index < value.length + start) {
// Add commas while there are still more entries in the list
builder.append(", ");
}
}
return builder.append('}').toString();
}
}

View file

@ -1,120 +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.util.collection;
import it.unimi.dsi.fastutil.ints.AbstractInt2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.objects.ObjectSet;
import java.io.Serial;
public class FixedInt2IntMap extends AbstractInt2IntMap {
protected int[] value;
protected int start = -1;
@Serial
private static final long serialVersionUID = 1L;
@Override
public int size() {
return value.length;
}
@Override
public ObjectSet<Int2IntMap.Entry> int2IntEntrySet() {
throw new UnsupportedOperationException();
}
@Override
public int get(int i) {
return getOrDefault(i, defRetValue);
}
@Override
public int getOrDefault(int key, int defaultValue) {
int offset = key - start;
if (offset < 0 || offset >= value.length) {
return defaultValue;
}
return value[offset];
}
@Override
public int put(int key, int value) {
if (start == -1) {
start = key;
this.value = new int[] {value};
} else {
int offset = key - start;
if (offset >= 0 && offset < this.value.length) {
int curr = this.value[offset];
this.value[offset] = value;
return curr;
} else if (offset != this.value.length) {
throw new IndexOutOfBoundsException("Expected: " + (this.value.length + start) + ", got " + key);
}
int[] newValue = new int[offset + 1];
System.arraycopy(this.value, 0, newValue, 0, this.value.length);
this.value = newValue;
this.value[offset] = value;
}
return this.defRetValue;
}
@Override
public boolean containsKey(int k) {
int offset = k - start;
return offset >= 0 && offset < value.length;
}
@Override
public boolean containsValue(int v) {
for (int i : value) {
if (i == v) {
return true;
}
}
return false;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("{");
int index = start;
for (int i : value) {
builder.append(index++).append("=>").append(i);
if (index < value.length + start) {
// Add commas while there are still more entries in the list
builder.append(", ");
}
}
return builder.append('}').toString();
}
}

View file

@ -1,75 +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.util.collection;
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.session.GeyserSession;
import org.geysermc.geyser.util.BlockEntityUtils;
import java.io.Serial;
/**
* Map that takes advantage of its internals for fast operations on block states to determine if they are lecterns.
*/
public class LecternHasBookMap extends FixedInt2BooleanMap {
@Serial
private static final long serialVersionUID = 1L;
/**
* Update a potential lectern within the world. This is a map method so it can use the internal fields to
* optimize lectern determining.
*/
public void handleBlockChange(GeyserSession session, int blockState, Vector3i position) {
WorldManager worldManager = session.getGeyser().getWorldManager();
int offset = blockState - this.start;
if (offset < 0 || offset >= this.value.length) {
// Block state is out of bounds of this map - lectern has been destroyed, if it existed
if (!worldManager.shouldExpectLecternHandled(session)) {
session.getLecternCache().remove(position);
}
return;
}
boolean newLecternHasBook;
if (worldManager.shouldExpectLecternHandled(session)) {
worldManager.sendLecternData(session, position.getX(), position.getY(), position.getZ());
} else if ((newLecternHasBook = this.value[offset]) != this.get(worldManager.getBlockAt(session, position))) {
// newLecternHasBook = the new lectern block state's "has book" toggle.
if (newLecternHasBook) {
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

@ -1,34 +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
*/
/**
* Contains useful collections for use in Geyser.
* <p>
* Of note are the fixed int maps. Designed for use with block states that are positive and sequential, they do not allow keys to be
* added that are not greater by one versus the previous key. Because of this, speedy operations of {@link java.util.Map#get(java.lang.Object)}
* and {@link java.util.Map#containsKey(java.lang.Object)} can be performed by simply checking the bounds of the map
* size and its "start" integer.
*/
package org.geysermc.geyser.util.collection;

@ -1 +1 @@
Subproject commit 6b661f0d517d895aebc1f55a25d2c86f033beb1d
Subproject commit 968a22bbab02d7d003c5b451a40d8bb2439b0d97

View file

@ -1,181 +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.util.collection;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class GeyserCollectionsTest {
private final byte[] bytes = new byte[] {(byte) 5, (byte) 4, (byte) 3, (byte) 2, (byte) 2, (byte) 1};
private final boolean[] booleans = new boolean[] {true, false, false, true};
private final int[] ints = new int[] {76, 3006, 999, 2323, 888, 0, 111, 999};
private final int[] startBlockRanges = new int[] {0, 70, 600, 450, 787, 1980};
@Test
public void testBytes() {
for (int startRange : startBlockRanges) {
testBytes(startRange, new FixedInt2ByteMap());
}
}
private void testBytes(final int start, final FixedInt2ByteMap map) {
int index = start;
for (byte b : bytes) {
map.put(index++, b);
}
int lastKey = index;
// Easy, understandable out-of-bounds checks
Assertions.assertFalse(map.containsKey(lastKey), "Map contains key bigger by one!");
Assertions.assertTrue(map.containsKey(lastKey - 1), "Map doesn't contain final key!");
// Ensure the first and last values do not throw an exception on get, and test getOrDefault
map.get(start - 1);
map.get(lastKey);
Assertions.assertEquals(map.getOrDefault(start - 1, Byte.MAX_VALUE), Byte.MAX_VALUE);
Assertions.assertEquals(map.getOrDefault(lastKey, Byte.MAX_VALUE), Byte.MAX_VALUE);
Assertions.assertEquals(map.getOrDefault(lastKey, Byte.MIN_VALUE), Byte.MIN_VALUE);
Assertions.assertEquals(map.size(), bytes.length);
for (int i = start; i < bytes.length; i++) {
Assertions.assertTrue(map.containsKey(i));
Assertions.assertEquals(map.get(i), bytes[i - start]);
}
for (int i = start - 1; i >= (start - 6); i--) {
// Lower than expected check
Assertions.assertFalse(map.containsKey(i), i + " is in a map that starts with " + start);
}
for (int i = bytes.length + start; i < bytes.length + 5 + start; i++) {
// Higher than expected check
Assertions.assertFalse(map.containsKey(i), i + " is in a map that ends with " + (start + bytes.length));
}
for (byte b : bytes) {
Assertions.assertTrue(map.containsValue(b));
}
}
@Test
public void testBooleans() {
for (int startRange : startBlockRanges) {
testBooleans(startRange, new FixedInt2BooleanMap());
}
}
private void testBooleans(final int start, final FixedInt2BooleanMap map) {
int index = start;
for (boolean b : booleans) {
map.put(index++, b);
}
int lastKey = index;
// Easy, understandable out-of-bounds checks
Assertions.assertFalse(map.containsKey(lastKey), "Map contains key bigger by one!");
Assertions.assertTrue(map.containsKey(lastKey - 1), "Map doesn't contain final key!");
// Ensure the first and last values do not throw an exception on get
map.get(start - 1);
map.get(lastKey);
Assertions.assertTrue(map.getOrDefault(lastKey, true));
Assertions.assertEquals(map.size(), booleans.length);
for (int i = start; i < booleans.length; i++) {
Assertions.assertTrue(map.containsKey(i));
Assertions.assertEquals(map.get(i), booleans[i - start]);
}
for (int i = start - 1; i >= (start - 6); i--) {
// Lower than expected check
Assertions.assertFalse(map.containsKey(i), i + " is in a map that starts with " + start);
}
for (int i = booleans.length + start; i < booleans.length + start + 5; i++) {
// Higher than expected check
Assertions.assertFalse(map.containsKey(i), i + " is in a map that ends with " + (start + booleans.length));
}
for (boolean b : booleans) {
Assertions.assertTrue(map.containsValue(b));
}
}
@Test
public void testInts() {
for (int startRange : startBlockRanges) {
testInts(startRange, new FixedInt2IntMap());
}
}
private void testInts(final int start, final FixedInt2IntMap map) {
int index = start;
for (int i : ints) {
map.put(index++, i);
}
int lastKey = index;
// Easy, understandable out-of-bounds checks
Assertions.assertFalse(map.containsKey(lastKey), "Map contains key bigger by one!");
Assertions.assertTrue(map.containsKey(lastKey - 1), "Map doesn't contain final key!");
// Ensure the first and last values do not throw an exception on get, and test getOrDefault
map.get(start - 1);
map.get(lastKey);
Assertions.assertEquals(map.getOrDefault(start - 1, Integer.MAX_VALUE), Integer.MAX_VALUE);
Assertions.assertEquals(map.getOrDefault(lastKey, Integer.MAX_VALUE), Integer.MAX_VALUE);
Assertions.assertEquals(map.getOrDefault(lastKey, Integer.MIN_VALUE), Integer.MIN_VALUE);
Assertions.assertEquals(map.size(), ints.length);
for (int i = start; i < ints.length; i++) {
Assertions.assertTrue(map.containsKey(i));
Assertions.assertEquals(map.get(i), ints[i - start]);
}
for (int i = start - 1; i >= (start - 6); i--) {
// Lower than expected check
Assertions.assertFalse(map.containsKey(i), i + " is in a map that starts with " + start);
}
for (int i = ints.length + start; i < ints.length + 5 + start; i++) {
// Higher than expected check
Assertions.assertFalse(map.containsKey(i), i + " is in a map that ends with " + (start + ints.length));
}
for (int i : ints) {
Assertions.assertTrue(map.containsValue(i));
}
}
}