It's almost done.

This commit is contained in:
Camotoy 2024-05-18 16:37:06 -04:00
parent a439f3e3d7
commit 6c904b2378
No known key found for this signature in database
GPG key ID: 7EEFB66FE798081F
40 changed files with 1359 additions and 1189 deletions

View file

@ -258,10 +258,10 @@ public class GeyserImpl implements GeyserApi {
VersionCheckUtils.checkForOutdatedJava(logger); VersionCheckUtils.checkForOutdatedJava(logger);
for (int i = 0; i < BlockRegistries.JAVA_BLOCKS.get().length; i++) { for (int i = 0; i < BlockRegistries.JAVA_BLOCKS.get().length; i++) {
String cleanIdentifier = BlockRegistries.JAVA_BLOCKS.get(i).getCleanJavaIdentifier(); String oldIdentifier = BlockRegistries.JAVA_BLOCKS.get(i).getJavaIdentifier();
String newIdentifier = BlockRegistries.BLOCK_STATES.get(i).block().javaIdentifier(); String newIdentifier = BlockRegistries.BLOCK_STATES.get(i).toString();
if (!cleanIdentifier.equals(newIdentifier)) { if (!oldIdentifier.equals(newIdentifier)) {
System.out.println("Check block " + BlockRegistries.BLOCK_STATES.get(i).block().javaIdentifier()); System.out.println("Check block " + oldIdentifier + " " + newIdentifier);
break; break;
} }
} }

View file

@ -25,14 +25,14 @@
package org.geysermc.geyser.entity.type; package org.geysermc.geyser.entity.type;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.block.type.FurnaceBlock;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult; import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID; import java.util.UUID;
@ -51,7 +51,7 @@ public class FurnaceMinecartEntity extends DefaultBlockMinecartEntity {
@Override @Override
public void updateDefaultBlockMetadata() { public void updateDefaultBlockMetadata() {
dirtyMetadata.put(EntityDataTypes.DISPLAY_BLOCK_STATE, session.getBlockMappings().getBedrockBlock(hasFuel ? BlockStateValues.JAVA_FURNACE_LIT_ID : BlockStateValues.JAVA_FURNACE_ID)); dirtyMetadata.put(EntityDataTypes.DISPLAY_BLOCK_STATE, session.getBlockMappings().getBedrockBlock(FurnaceBlock.state(hasFuel)));
dirtyMetadata.put(EntityDataTypes.DISPLAY_OFFSET, 6); dirtyMetadata.put(EntityDataTypes.DISPLAY_OFFSET, 6);
} }

View file

@ -28,7 +28,7 @@ package org.geysermc.geyser.entity.type;
import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.block.type.SpawnerBlock;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import java.util.UUID; import java.util.UUID;
@ -41,7 +41,7 @@ public class SpawnerMinecartEntity extends DefaultBlockMinecartEntity {
@Override @Override
public void updateDefaultBlockMetadata() { public void updateDefaultBlockMetadata() {
dirtyMetadata.put(EntityDataTypes.DISPLAY_BLOCK_STATE, session.getBlockMappings().getBedrockBlock(BlockStateValues.JAVA_SPAWNER_ID)); dirtyMetadata.put(EntityDataTypes.DISPLAY_BLOCK_STATE, session.getBlockMappings().getBedrockBlock(SpawnerBlock.state()));
dirtyMetadata.put(EntityDataTypes.DISPLAY_OFFSET, 6); dirtyMetadata.put(EntityDataTypes.DISPLAY_OFFSET, 6);
} }
} }

View file

@ -136,7 +136,7 @@ public final class GeyserboundPacketHandlerImpl extends AbstractGeyserboundPacke
placeBlockSoundPacket.setIdentifier(":"); placeBlockSoundPacket.setIdentifier(":");
session.sendUpstreamPacket(placeBlockSoundPacket); session.sendUpstreamPacket(placeBlockSoundPacket);
session.setLastBlockPlacePosition(null); session.setLastBlockPlacePosition(null);
session.setLastBlockPlacedId(null); session.setLastBlockPlaced(null);
} }
@Override @Override

View file

@ -36,8 +36,9 @@ import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.inventory.Container; import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.LecternContainer; import org.geysermc.geyser.inventory.LecternContainer;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.type.BlockMapping;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.util.BlockUtils; import org.geysermc.geyser.util.BlockUtils;
@ -59,12 +60,14 @@ public class BlockInventoryHolder extends InventoryHolder {
private final ContainerType containerType; private final ContainerType containerType;
private final Set<String> validBlocks; private final Set<String> validBlocks;
public BlockInventoryHolder(String javaBlockIdentifier, ContainerType containerType, String... validBlocks) { public BlockInventoryHolder(String javaBlockIdentifier, ContainerType containerType, Block... validBlocks) {
this.defaultJavaBlockState = BlockRegistries.JAVA_IDENTIFIER_TO_ID.get().getInt(javaBlockIdentifier); this.defaultJavaBlockState = BlockRegistries.JAVA_IDENTIFIER_TO_ID.get().getInt(javaBlockIdentifier);
this.containerType = containerType; this.containerType = containerType;
if (validBlocks != null) { if (validBlocks != null) {
Set<String> validBlocksTemp = new HashSet<>(validBlocks.length + 1); Set<String> validBlocksTemp = new HashSet<>(validBlocks.length + 1);
Collections.addAll(validBlocksTemp, validBlocks); for (Block block : validBlocks) {
validBlocksTemp.add(block.javaIdentifier().toString());
}
validBlocksTemp.add(BlockUtils.getCleanIdentifier(javaBlockIdentifier)); validBlocksTemp.add(BlockUtils.getCleanIdentifier(javaBlockIdentifier));
this.validBlocks = Set.copyOf(validBlocksTemp); this.validBlocks = Set.copyOf(validBlocksTemp);
} else { } else {
@ -80,14 +83,15 @@ public class BlockInventoryHolder extends InventoryHolder {
if (checkInteractionPosition(session)) { if (checkInteractionPosition(session)) {
// Then, check to see if the interacted block is valid for this inventory by ensuring the block state identifier is valid // Then, check to see if the interacted block is valid for this inventory by ensuring the block state identifier is valid
// and the bedrock block is vanilla // and the bedrock block is vanilla
int javaBlockId = session.getGeyser().getWorldManager().getBlockAt(session, session.getLastInteractionBlockPosition()); BlockState state = session.getGeyser().getWorldManager().blockAt(session, session.getLastInteractionBlockPosition());
if (!BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get().containsKey(javaBlockId)) { if (!BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get().containsKey(state.javaId())) {
String[] javaBlockString = BlockRegistries.JAVA_BLOCKS.getOrDefault(javaBlockId, BlockMapping.DEFAULT).getJavaIdentifier().split("\\["); // TODO TODO TODO
String[] javaBlockString = state.toString().split("\\[");
if (isValidBlock(javaBlockString)) { if (isValidBlock(javaBlockString)) {
// We can safely use this block // We can safely use this block
inventory.setHolderPosition(session.getLastInteractionBlockPosition()); inventory.setHolderPosition(session.getLastInteractionBlockPosition());
((Container) inventory).setUsingRealBlock(true, javaBlockString[0]); ((Container) inventory).setUsingRealBlock(true, javaBlockString[0]);
setCustomName(session, session.getLastInteractionBlockPosition(), inventory, javaBlockId); setCustomName(session, session.getLastInteractionBlockPosition(), inventory, state);
return true; return true;
} }
@ -107,7 +111,7 @@ public class BlockInventoryHolder extends InventoryHolder {
session.sendUpstreamPacket(blockPacket); session.sendUpstreamPacket(blockPacket);
inventory.setHolderPosition(position); inventory.setHolderPosition(position);
setCustomName(session, position, inventory, defaultJavaBlockState); setCustomName(session, position, inventory, BlockState.of(defaultJavaBlockState));
return true; return true;
} }
@ -129,7 +133,7 @@ public class BlockInventoryHolder extends InventoryHolder {
return this.validBlocks.contains(javaBlockString[0]); return this.validBlocks.contains(javaBlockString[0]);
} }
protected void setCustomName(GeyserSession session, Vector3i position, Inventory inventory, int javaBlockState) { protected void setCustomName(GeyserSession session, Vector3i position, Inventory inventory, BlockState javaBlockState) {
NbtMap tag = NbtMap.builder() NbtMap tag = NbtMap.builder()
.putInt("x", position.getX()) .putInt("x", position.getX())
.putInt("y", position.getY()) .putInt("y", position.getY())

File diff suppressed because it is too large Load diff

View file

@ -35,6 +35,7 @@ import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtType; import org.cloudburstmc.nbt.NbtType;
import org.geysermc.geyser.inventory.item.BannerPattern; import org.geysermc.geyser.inventory.item.BannerPattern;
import org.geysermc.geyser.inventory.item.DyeColor; import org.geysermc.geyser.inventory.item.DyeColor;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.registry.JavaRegistry; import org.geysermc.geyser.session.cache.registry.JavaRegistry;
@ -199,8 +200,8 @@ public class BannerItem extends BlockItem {
return null; return null;
} }
public BannerItem(String javaIdentifier, Builder builder) { public BannerItem(Builder builder, Block block, Block... otherBlocks) {
super(javaIdentifier, builder); super(builder, block, otherBlocks);
} }
@Override @Override

View file

@ -25,11 +25,26 @@
package org.geysermc.geyser.item.type; package org.geysermc.geyser.item.type;
/** import org.geysermc.geyser.level.block.type.Block;
* TODO needed?
*/
public class BlockItem extends Item { public class BlockItem extends Item {
public BlockItem(String javaIdentifier, Builder builder) { public BlockItem(Builder builder, Block block, Block... otherBlocks) {
super(block.javaIdentifier().value(), builder);
// Ensure this item can be looked up by its block(s)
registerBlock(block, this);
for (Block otherBlock : otherBlocks) {
registerBlock(otherBlock, this);
}
}
// Use this constructor if the item name is not the same as its primary block
public BlockItem(String javaIdentifier, Builder builder, Block block, Block... otherBlocks) {
super(javaIdentifier, builder); super(javaIdentifier, builder);
registerBlock(block, this);
for (Block otherBlock : otherBlocks) {
registerBlock(otherBlock, this);
}
} }
} }

View file

@ -27,6 +27,7 @@ package org.geysermc.geyser.item.type;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.nbt.NbtType; import org.cloudburstmc.nbt.NbtType;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.item.BedrockItemBuilder; import org.geysermc.geyser.translator.item.BedrockItemBuilder;
@ -38,8 +39,8 @@ import java.util.List;
public class DecoratedPotItem extends BlockItem { public class DecoratedPotItem extends BlockItem {
public DecoratedPotItem(String javaIdentifier, Builder builder) { public DecoratedPotItem(Builder builder, Block block, Block... otherBlocks) {
super(javaIdentifier, builder); super(builder, block, otherBlocks);
} }
@Override @Override

View file

@ -35,6 +35,7 @@ import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.inventory.item.Enchantment; import org.geysermc.geyser.inventory.item.Enchantment;
import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.geyser.registry.type.ItemMappings;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
@ -49,20 +50,12 @@ import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponen
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ItemEnchantments; import org.geysermc.mcprotocollib.protocol.data.game.item.component.ItemEnchantments;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
public class Item { public class Item {
/** private static final Map<Block, Item> BLOCK_TO_ITEM = new HashMap<>();
* This is a map from Java-only enchantments to their translation keys so that we can
* map these enchantments to Bedrock clients, since they don't actually exist there.
*/
private static final Map<Enchantment.JavaEnchantment, String> ENCHANTMENT_TRANSLATION_KEYS = Map.of(
Enchantment.JavaEnchantment.SWEEPING_EDGE, "enchantment.minecraft.sweeping",
Enchantment.JavaEnchantment.DENSITY, "enchantment.minecraft.density",
Enchantment.JavaEnchantment.BREACH, "enchantment.minecraft.breach",
Enchantment.JavaEnchantment.WIND_BURST, "enchantment.minecraft.wind_burst");
private final String javaIdentifier; private final String javaIdentifier;
private int javaId = -1; private int javaId = -1;
private final int stackSize; private final int stackSize;
@ -233,6 +226,16 @@ public class Item {
// } // }
} }
/**
* This is a map from Java-only enchantments to their translation keys so that we can
* map these enchantments to Bedrock clients, since they don't actually exist there.
*/
private static final Map<Enchantment.JavaEnchantment, String> ENCHANTMENT_TRANSLATION_KEYS = Map.of(
Enchantment.JavaEnchantment.SWEEPING_EDGE, "enchantment.minecraft.sweeping",
Enchantment.JavaEnchantment.DENSITY, "enchantment.minecraft.density",
Enchantment.JavaEnchantment.BREACH, "enchantment.minecraft.breach",
Enchantment.JavaEnchantment.WIND_BURST, "enchantment.minecraft.wind_burst");
protected final @Nullable NbtMap remapEnchantment(GeyserSession session, int enchantId, int level, BedrockItemBuilder builder) { protected final @Nullable NbtMap remapEnchantment(GeyserSession session, int enchantId, int level, BedrockItemBuilder builder) {
// TODO verify // TODO verify
// TODO streamline Enchantment process // TODO streamline Enchantment process
@ -281,6 +284,18 @@ public class Item {
'}'; '}';
} }
/**
* @return the block associated with this item, or air if nothing
*/
@NonNull
public static Item byBlock(Block block) {
return BLOCK_TO_ITEM.getOrDefault(block, Items.AIR);
}
protected static void registerBlock(Block block, Item item) {
BLOCK_TO_ITEM.put(block, item);
}
public static Builder builder() { public static Builder builder() {
return new Builder(); return new Builder();
} }

View file

@ -27,6 +27,7 @@ package org.geysermc.geyser.item.type;
import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.auth.data.GameProfile;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.text.MinecraftLocale;
@ -34,9 +35,9 @@ import org.geysermc.geyser.translator.item.BedrockItemBuilder;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
public class PlayerHeadItem extends Item { public class PlayerHeadItem extends BlockItem {
public PlayerHeadItem(String javaIdentifier, Builder builder) { public PlayerHeadItem(Builder builder, Block block, Block... otherBlocks) {
super(javaIdentifier, builder); super(builder, block, otherBlocks);
} }
@Override @Override

View file

@ -30,6 +30,7 @@ import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder; import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.nbt.NbtType; import org.cloudburstmc.nbt.NbtType;
import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.item.BedrockItemBuilder; import org.geysermc.geyser.translator.item.BedrockItemBuilder;
@ -42,8 +43,8 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
public class ShulkerBoxItem extends BlockItem { public class ShulkerBoxItem extends BlockItem {
public ShulkerBoxItem(String javaIdentifier, Builder builder) { public ShulkerBoxItem(Builder builder, Block block, Block... otherBlocks) {
super(javaIdentifier, builder); super(builder, block, otherBlocks);
} }
@Override @Override

View file

@ -249,25 +249,6 @@ public final class BlockStateValues {
return ALL_CAULDRONS.contains(state); return ALL_CAULDRONS.contains(state);
} }
/**
* All double chest values are part of the block state in Java and part of the block entity tag in Bedrock.
* This gives the DoubleChestValue that can be calculated into the final tag.
*
* @return The map of all DoubleChestValues.
*/
public static Int2ObjectMap<DoubleChestValue> getDoubleChestValues() {
return DOUBLE_CHEST_VALUES;
}
/**
* Get the Int2ObjectMap of flower pot block states to containing plant
*
* @return Int2ObjectMap of flower pot values
*/
public static Int2ObjectMap<String> getFlowerPotValues() {
return FLOWER_POT_VALUES;
}
/** /**
* @return a set of all forward-facing jigsaws, to use as a fallback if NBT is missing. * @return a set of all forward-facing jigsaws, to use as a fallback if NBT is missing.
*/ */
@ -282,26 +263,6 @@ public final class BlockStateValues {
return LECTERN_BOOK_STATES; return LECTERN_BOOK_STATES;
} }
/**
* The note that noteblocks output when hit is part of the block state in Java but sent as a BlockEventPacket in Bedrock.
* This gives an integer pitch that Bedrock can use.
*
* @param state BlockState of the block
* @return note block note integer or -1 if not present
*/
public static int getNoteblockPitch(int state) {
return NOTEBLOCK_PITCHES.getOrDefault(state, -1);
}
/**
* Get the Int2BooleanMap showing if a piston block state is extended or not.
*
* @return the Int2BooleanMap of piston extensions.
*/
public static Int2BooleanMap getPistonValues() {
return PISTON_VALUES;
}
public static boolean isStickyPiston(int blockState) { public static boolean isStickyPiston(int blockState) {
return STICKY_PISTONS.contains(blockState); return STICKY_PISTONS.contains(blockState);
} }
@ -435,16 +396,6 @@ public final class BlockStateValues {
return WATER_LEVEL.getOrDefault(state, -1); return WATER_LEVEL.getOrDefault(state, -1);
} }
/**
* Check if a block is the upper half of a door.
*
* @param state BlockState of the block
* @return True if the block is the upper half of a door
*/
public static boolean isUpperDoor(int state) {
return UPPER_DOORS.contains(state);
}
/** /**
* Get the height of water from the block state * Get the height of water from the block state
* This is used in FishingHookEntity to create splash sounds when the hook hits the water. In addition, * This is used in FishingHookEntity to create splash sounds when the hook hits the water. In addition,

View file

@ -82,7 +82,7 @@ public final class Blocks {
.intState(STAGE, 0, 1) .intState(STAGE, 0, 1)
.booleanState(WATERLOGGED))); .booleanState(WATERLOGGED)));
public static final Block BEDROCK = register(new Block("bedrock", builder().destroyTime(-1.0f))); public static final Block BEDROCK = register(new Block("bedrock", builder().destroyTime(-1.0f)));
public static final Block WATER = register(new Block("water", builder().destroyTime(100.0f).pushReaction(PistonBehavior.DESTROY) public static final Block WATER = register(new WaterBlock("water", builder().destroyTime(100.0f).pushReaction(PistonBehavior.DESTROY)
.intState(LEVEL, 0, 15))); .intState(LEVEL, 0, 15)));
public static final Block LAVA = register(new Block("lava", builder().destroyTime(100.0f).pushReaction(PistonBehavior.DESTROY) public static final Block LAVA = register(new Block("lava", builder().destroyTime(100.0f).pushReaction(PistonBehavior.DESTROY)
.intState(LEVEL, 0, 15))); .intState(LEVEL, 0, 15)));
@ -379,7 +379,7 @@ public final class Blocks {
.booleanState(UP) .booleanState(UP)
.booleanState(WEST))); .booleanState(WEST)));
public static final Block SOUL_FIRE = register(new Block("soul_fire", builder().pushReaction(PistonBehavior.DESTROY))); public static final Block SOUL_FIRE = register(new Block("soul_fire", builder().pushReaction(PistonBehavior.DESTROY)));
public static final Block SPAWNER = register(new Block("spawner", builder().setBlockEntity().requiresCorrectToolForDrops().destroyTime(5.0f))); public static final Block SPAWNER = register(new SpawnerBlock("spawner", builder().setBlockEntity().requiresCorrectToolForDrops().destroyTime(5.0f)));
public static final Block OAK_STAIRS = register(new Block("oak_stairs", builder().destroyTime(2.0f) public static final Block OAK_STAIRS = register(new Block("oak_stairs", builder().destroyTime(2.0f)
.enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST)
.enumState(HALF, "top", "bottom") .enumState(HALF, "top", "bottom")
@ -403,7 +403,7 @@ public final class Blocks {
.intState(AGE_7, 0, 7))); .intState(AGE_7, 0, 7)));
public static final Block FARMLAND = register(new Block("farmland", builder().destroyTime(0.6f) public static final Block FARMLAND = register(new Block("farmland", builder().destroyTime(0.6f)
.intState(MOISTURE, 0, 7))); .intState(MOISTURE, 0, 7)));
public static final Block FURNACE = register(new Block("furnace", builder().setBlockEntity().requiresCorrectToolForDrops().destroyTime(3.5f) public static final Block FURNACE = register(new FurnaceBlock("furnace", builder().setBlockEntity().requiresCorrectToolForDrops().destroyTime(3.5f)
.enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST)
.booleanState(LIT))); .booleanState(LIT)));
public static final Block OAK_SIGN = register(new Block("oak_sign", builder().setBlockEntity().destroyTime(1.0f) public static final Block OAK_SIGN = register(new Block("oak_sign", builder().setBlockEntity().destroyTime(1.0f)
@ -823,10 +823,10 @@ public final class Blocks {
.booleanState(HAS_BOTTLE_1) .booleanState(HAS_BOTTLE_1)
.booleanState(HAS_BOTTLE_2))); .booleanState(HAS_BOTTLE_2)));
public static final Block CAULDRON = register(new CauldronBlock("cauldron", builder().requiresCorrectToolForDrops().destroyTime(2.0f))); public static final Block CAULDRON = register(new CauldronBlock("cauldron", builder().requiresCorrectToolForDrops().destroyTime(2.0f)));
public static final Block WATER_CAULDRON = register(new Block("water_cauldron", builder().requiresCorrectToolForDrops().destroyTime(2.0f) public static final Block WATER_CAULDRON = register(new CauldronBlock("water_cauldron", builder().requiresCorrectToolForDrops().destroyTime(2.0f)
.intState(LEVEL_CAULDRON, 1, 3))); .intState(LEVEL_CAULDRON, 1, 3)));
public static final Block LAVA_CAULDRON = register(new Block("lava_cauldron", builder().requiresCorrectToolForDrops().destroyTime(2.0f))); public static final Block LAVA_CAULDRON = register(new CauldronBlock("lava_cauldron", builder().requiresCorrectToolForDrops().destroyTime(2.0f)));
public static final Block POWDER_SNOW_CAULDRON = register(new Block("powder_snow_cauldron", builder().requiresCorrectToolForDrops().destroyTime(2.0f) public static final Block POWDER_SNOW_CAULDRON = register(new CauldronBlock("powder_snow_cauldron", builder().requiresCorrectToolForDrops().destroyTime(2.0f)
.intState(LEVEL_CAULDRON, 1, 3))); .intState(LEVEL_CAULDRON, 1, 3)));
public static final Block END_PORTAL = register(new Block("end_portal", builder().setBlockEntity().destroyTime(-1.0f).pushReaction(PistonBehavior.BLOCK))); public static final Block END_PORTAL = register(new Block("end_portal", builder().setBlockEntity().destroyTime(-1.0f).pushReaction(PistonBehavior.BLOCK)));
public static final Block END_PORTAL_FRAME = register(new Block("end_portal_frame", builder().destroyTime(-1.0f) public static final Block END_PORTAL_FRAME = register(new Block("end_portal_frame", builder().destroyTime(-1.0f)
@ -2179,7 +2179,7 @@ public final class Blocks {
public static final Block BEEHIVE = register(new Block("beehive", builder().setBlockEntity().destroyTime(0.6f) public static final Block BEEHIVE = register(new Block("beehive", builder().setBlockEntity().destroyTime(0.6f)
.enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST) .enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST)
.intState(LEVEL_HONEY, 0, 5))); .intState(LEVEL_HONEY, 0, 5)));
public static final Block HONEY_BLOCK = register(new Block("honey_block", builder())); public static final Block HONEY_BLOCK = register(new HoneyBlock("honey_block", builder()));
public static final Block HONEYCOMB_BLOCK = register(new Block("honeycomb_block", builder().destroyTime(0.6f))); public static final Block HONEYCOMB_BLOCK = register(new Block("honeycomb_block", builder().destroyTime(0.6f)));
public static final Block NETHERITE_BLOCK = register(new Block("netherite_block", builder().requiresCorrectToolForDrops().destroyTime(50.0f))); public static final Block NETHERITE_BLOCK = register(new Block("netherite_block", builder().requiresCorrectToolForDrops().destroyTime(50.0f)));
public static final Block ANCIENT_DEBRIS = register(new Block("ancient_debris", builder().requiresCorrectToolForDrops().destroyTime(30.0f))); public static final Block ANCIENT_DEBRIS = register(new Block("ancient_debris", builder().requiresCorrectToolForDrops().destroyTime(30.0f)));

View file

@ -32,6 +32,10 @@ public class Property<T extends Comparable<T>> {
this.name = name; this.name = name;
} }
public String name() {
return name;
}
@Override @Override
public String toString() { public String toString() {
return getClass().getSimpleName() + "[" + name + "]"; return getClass().getSimpleName() + "[" + name + "]";

View file

@ -25,21 +25,24 @@
package org.geysermc.geyser.level.block.type; package org.geysermc.geyser.level.block.type;
import it.unimi.dsi.fastutil.Pair;
import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMaps;
import net.kyori.adventure.key.Key;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket; 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.Blocks;
import org.geysermc.geyser.level.block.property.Properties;
import org.geysermc.geyser.level.block.property.Property; import org.geysermc.geyser.level.block.property.Property;
import org.geysermc.geyser.level.physics.PistonBehavior; import org.geysermc.geyser.level.physics.PistonBehavior;
import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.Identifier; import org.intellij.lang.annotations.Subst;
import java.util.*; import java.util.*;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -47,20 +50,21 @@ import java.util.stream.Stream;
public class Block { public class Block {
public static final int JAVA_AIR_ID = 0; public static final int JAVA_AIR_ID = 0;
private final String javaIdentifier; private final Key javaIdentifier;
private final boolean requiresCorrectToolForDrops; private final boolean requiresCorrectToolForDrops;
private final boolean hasBlockEntity; private final boolean hasBlockEntity;
private final float destroyTime; private final float destroyTime;
private final @NonNull PistonBehavior pushReaction; private final @NonNull PistonBehavior pushReaction;
protected Item item = null;
private int javaId = -1; private int javaId = -1;
public Block(String javaIdentifier, Builder builder) { public Block(@Subst("empty") String javaIdentifier, Builder builder) {
this.javaIdentifier = Identifier.formalize(javaIdentifier).intern(); this.javaIdentifier = Key.key(javaIdentifier);
this.requiresCorrectToolForDrops = builder.requiresCorrectToolForDrops; this.requiresCorrectToolForDrops = builder.requiresCorrectToolForDrops;
this.hasBlockEntity = builder.hasBlockEntity; this.hasBlockEntity = builder.hasBlockEntity;
this.destroyTime = builder.destroyTime; this.destroyTime = builder.destroyTime;
this.pushReaction = builder.pushReaction; this.pushReaction = builder.pushReaction;
builder.build(this); processStates(builder.build(this));
} }
public void updateBlock(GeyserSession session, BlockState state, Vector3i position) { public void updateBlock(GeyserSession session, BlockState state, Vector3i position) {
@ -114,7 +118,8 @@ public class Block {
UpdateBlockPacket waterPacket = new UpdateBlockPacket(); UpdateBlockPacket waterPacket = new UpdateBlockPacket();
waterPacket.setDataLayer(1); waterPacket.setDataLayer(1);
waterPacket.setBlockPosition(position); waterPacket.setBlockPosition(position);
if (BlockRegistries.WATERLOGGED.get().get(state.javaId())) { Boolean waterlogged = state.getValue(Properties.WATERLOGGED);
if (waterlogged == Boolean.TRUE) {
waterPacket.setDefinition(session.getBlockMappings().getBedrockWater()); waterPacket.setDefinition(session.getBlockMappings().getBedrockWater());
} else { } else {
waterPacket.setDefinition(session.getBlockMappings().getBedrockAir()); waterPacket.setDefinition(session.getBlockMappings().getBedrockAir());
@ -129,7 +134,21 @@ public class Block {
} }
} }
public String javaIdentifier() { public Item asItem() {
if (this.item == null) {
return this.item = Item.byBlock(this);
}
return this.item;
}
/**
* A list of BlockStates is created pertaining to this block. Do we need any of them? If so, override this method.
*/
protected void processStates(List<BlockState> states) {
}
@NonNull
public Key javaIdentifier() {
return javaIdentifier; return javaIdentifier;
} }
@ -163,7 +182,7 @@ public class Block {
@Override @Override
public String toString() { public String toString() {
return "Item{" + return "Block{" +
"javaIdentifier='" + javaIdentifier + '\'' + "javaIdentifier='" + javaIdentifier + '\'' +
", javaId=" + javaId + ", javaId=" + javaId +
'}'; '}';
@ -229,14 +248,27 @@ public class Block {
return this; return this;
} }
private void build(Block block) { private List<BlockState> build(Block block) {
if (states.isEmpty()) { if (states.isEmpty()) {
BlockRegistries.BLOCK_STATES.get().add(new BlockState(block, BlockRegistries.BLOCK_STATES.get().size())); BlockState state = new BlockState(block, BlockRegistries.BLOCK_STATES.get().size());
BlockRegistries.BLOCK_STATES.get().add(state);
return List.of(state);
} else if (states.size() == 1) {
// We can optimize because we don't need to worry about combinations
Map.Entry<Property<?>, List<Comparable<?>>> property = this.states.entrySet().stream().findFirst().orElseThrow();
List<BlockState> states = new ArrayList<>(property.getValue().size());
property.getValue().forEach(value -> {
Reference2ObjectMap<Property<?>, Comparable<?>> propertyMap = Reference2ObjectMaps.singleton(property.getKey(), value);
BlockState state = new BlockState(block, BlockRegistries.BLOCK_STATES.get().size(), propertyMap);
BlockRegistries.BLOCK_STATES.get().add(state);
states.add(state);
});
return states;
} else { } else {
// Think of this stream as another list containing, at the start, one empty list. // Think of this stream as another list containing, at the start, one empty list.
// It's two collections. Not a stream from the empty list. // It's two collections. Not a stream from the empty list.
Stream<List<Pair<Property<?>, Comparable<?>>>> stream = Stream.of(Collections.emptyList()); Stream<List<Comparable<?>>> stream = Stream.of(Collections.emptyList());
for (var state : this.states.entrySet()) { for (var values : this.states.values()) {
// OK, so here's how I understand this works. Because this was staring at vanilla Java code trying // OK, so here's how I understand this works. Because this was staring at vanilla Java code trying
// to figure out exactly how it works so we don't have any discrepencies. // to figure out exactly how it works so we don't have any discrepencies.
// For each existing pair in the list, a new list is created, adding one of the new values. // For each existing pair in the list, a new list is created, adding one of the new values.
@ -247,24 +279,29 @@ public class Block {
// or it may be populated if this is not the first property. // or it may be populated if this is not the first property.
// We're about to create a new stream, each with a new list, // We're about to create a new stream, each with a new list,
// for every previous property // for every previous property
state.getValue().stream().map(value -> { values.stream().map(value -> {
var newProperties = new ArrayList<>(aPreviousPropertiesList); var newProperties = new ArrayList<>(aPreviousPropertiesList);
newProperties.add(Pair.of(state.getKey(), value)); newProperties.add(value);
return newProperties; return newProperties;
})); }));
} }
List<BlockState> states = new ArrayList<>();
// Now we have a list of Pair<Property, Value>s. Each list is a block state! // Now we have a list of Pair<Property, Value>s. Each list is a block state!
// If we have two boolean properties: up [true/false] and down [true/false], // If we have two boolean properties: up [true/false] and down [true/false],
// We'll see [up=true,down=true], [up=false,down=true], [up=true,down=false], [up=false,down=false] // We'll see [up=true,down=true], [up=false,down=true], [up=true,down=false], [up=false,down=false]
stream.forEach(properties -> { List<List<Comparable<?>>> result = stream.toList();
Reference2ObjectMap<Property<?>, Comparable<?>> propertyMap = new Reference2ObjectArrayMap<>(properties.size()); // Ensure each block state shares the same key array. Creating a keySet here shouldn't be an issue since
for (int i = 0; i < properties.size(); i++) { // this states map should be removed after build.
Pair<Property<?>, Comparable<?>> property = properties.get(i); Property<?>[] keys = this.states.keySet().toArray(new Property<?>[0]);
propertyMap.put(property.key(), property.value()); result.forEach(properties -> {
} Comparable<?>[] values = properties.toArray(new Comparable<?>[0]);
BlockRegistries.BLOCK_STATES.get().add(new BlockState(block, BlockRegistries.BLOCK_STATES.get().size(), propertyMap)); Reference2ObjectMap<Property<?>, Comparable<?>> propertyMap = new Reference2ObjectArrayMap<>(keys, values);
BlockState state = new BlockState(block, BlockRegistries.BLOCK_STATES.get().size(), propertyMap);
BlockRegistries.BLOCK_STATES.get().add(state);
states.add(state);
}); });
return states;
} }
} }

View file

@ -30,6 +30,8 @@ import it.unimi.dsi.fastutil.objects.Reference2ObjectMaps;
import org.geysermc.geyser.level.block.property.Property; import org.geysermc.geyser.level.block.property.Property;
import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.BlockRegistries;
import java.util.Locale;
public final class BlockState { public final class BlockState {
private final Block block; private final Block block;
private final int javaId; private final int javaId;
@ -62,12 +64,25 @@ public final class BlockState {
return this.block == block; return this.block == block;
} }
@Override
public String toString() {
if (this.states.isEmpty()) {
return this.block.javaIdentifier().toString();
}
return this.block.javaIdentifier().toString() + "[" + paramsToString() + "]";
}
private String paramsToString() { private String paramsToString() {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
var it = this.states.entrySet().iterator(); var it = this.states.entrySet().iterator();
while (it.hasNext()) { while (it.hasNext()) {
var entry = it.next(); var entry = it.next();
builder.append(entry.getKey()).append("=").append(entry.getValue()); builder.append(entry.getKey().name())
.append("=")
.append(entry.getValue().toString().toLowerCase(Locale.ROOT)); // lowercase covers enums
if (it.hasNext()) {
builder.append(",");
}
} }
return builder.toString(); return builder.toString();
} }

View file

@ -68,7 +68,7 @@ public class FlowerPotBlock extends Block implements BedrockChunkWantsBlockEntit
if (this.flower != Blocks.AIR) { if (this.flower != Blocks.AIR) {
// Get the Bedrock CompoundTag of the block. // 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. // 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()); NbtMap plant = session.getBlockMappings().getFlowerPotBlocks().get(this.flower.javaIdentifier().asString());
if (plant != null) { if (plant != null) {
tagBuilder.putCompound("PlantBlock", plant.toBuilder().build()); tagBuilder.putCompound("PlantBlock", plant.toBuilder().build());
} }

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.level.block.type;
import org.geysermc.geyser.level.block.property.Properties;
import org.geysermc.geyser.level.physics.Direction;
import java.util.List;
public class FurnaceBlock extends Block {
private static BlockState LIT;
private static BlockState UNLIT;
public FurnaceBlock(String javaIdentifier, Builder builder) {
super(javaIdentifier, builder);
}
@Override
protected void processStates(List<BlockState> states) {
LIT = states.stream()
.filter(state -> state.getValue(Properties.HORIZONTAL_FACING) == Direction.NORTH
&& state.getValue(Properties.LIT))
.findFirst().orElseThrow();
UNLIT = states.stream()
.filter(state -> state.getValue(Properties.HORIZONTAL_FACING) == Direction.NORTH
&& !state.getValue(Properties.LIT))
.findFirst().orElseThrow();
}
public static BlockState state(boolean lit) {
return lit ? LIT : UNLIT;
}
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.level.block.type;
import java.util.List;
public class HoneyBlock extends Block {
private static BlockState STATE;
public HoneyBlock(String javaIdentifier, Builder builder) {
super(javaIdentifier, builder);
}
@Override
protected void processStates(List<BlockState> states) {
STATE = states.get(0);
}
public static BlockState state() {
return STATE;
}
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.level.block.type;
import java.util.List;
public class SpawnerBlock extends Block {
private static BlockState STATE;
public SpawnerBlock(String javaIdentifier, Builder builder) {
super(javaIdentifier, builder);
}
@Override
protected void processStates(List<BlockState> states) {
STATE = states.get(0);
}
public static BlockState state() {
return STATE;
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.level.block.type;
import java.util.List;
public class WaterBlock extends Block {
private static BlockState LEVEL_0;
public WaterBlock(String javaIdentifier, Builder builder) {
super(javaIdentifier, builder);
}
@Override
protected void processStates(List<BlockState> states) {
super.processStates(states);
}
}

View file

@ -147,6 +147,7 @@ public class BlockRegistries {
CustomBlockRegistryPopulator.populate(CustomBlockRegistryPopulator.Stage.CUSTOM_REGISTRATION); CustomBlockRegistryPopulator.populate(CustomBlockRegistryPopulator.Stage.CUSTOM_REGISTRATION);
BlockRegistryPopulator.populate(BlockRegistryPopulator.Stage.INIT_BEDROCK); BlockRegistryPopulator.populate(BlockRegistryPopulator.Stage.INIT_BEDROCK);
BlockRegistryPopulator.populate(BlockRegistryPopulator.Stage.POST_INIT); BlockRegistryPopulator.populate(BlockRegistryPopulator.Stage.POST_INIT);
System.out.println("Block registries loaded");
} }
public static void init() { public static void init() {

View file

@ -204,5 +204,6 @@ public final class Registries {
biomesNbt.put(key, value.build()); biomesNbt.put(key, value.build());
} }
BIOMES_NBT.set(biomesNbt.build()); BIOMES_NBT.set(biomesNbt.build());
System.out.println("Registries loaded");
} }
} }

View file

@ -106,7 +106,7 @@ public class CollisionRegistryLoader extends MultiResourceRegistryLoader<String,
} }
private @Nullable BlockCollision instantiateCollision(BlockState state, Map<Class<?>, CollisionInfo> annotationMap, int collisionIndex, List<BoundingBox[]> collisionList) { private @Nullable BlockCollision instantiateCollision(BlockState state, Map<Class<?>, CollisionInfo> annotationMap, int collisionIndex, List<BoundingBox[]> collisionList) {
String blockName = state.block().javaIdentifier().substring("minecraft:".length()); String blockName = state.block().javaIdentifier().value();
for (Map.Entry<Class<?>, CollisionInfo> collisionRemappers : annotationMap.entrySet()) { for (Map.Entry<Class<?>, CollisionInfo> collisionRemappers : annotationMap.entrySet()) {
Class<?> type = collisionRemappers.getKey(); Class<?> type = collisionRemappers.getKey();

View file

@ -50,10 +50,12 @@ import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.block.custom.CustomBlockData; import org.geysermc.geyser.api.block.custom.CustomBlockData;
import org.geysermc.geyser.api.block.custom.CustomBlockState; import org.geysermc.geyser.api.block.custom.CustomBlockState;
import org.geysermc.geyser.api.block.custom.nonvanilla.JavaBlockState; import org.geysermc.geyser.api.block.custom.nonvanilla.JavaBlockState;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.block.type.Block; import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.level.physics.PistonBehavior; import org.geysermc.geyser.level.physics.PistonBehavior;
import org.geysermc.geyser.registry.BlockRegistries; 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.BlockMapping;
import org.geysermc.geyser.registry.type.BlockMappings; import org.geysermc.geyser.registry.type.BlockMappings;
import org.geysermc.geyser.registry.type.GeyserBedrockBlock; import org.geysermc.geyser.registry.type.GeyserBedrockBlock;
@ -391,7 +393,7 @@ public final class BlockRegistryPopulator {
throw new AssertionError("Unable to load Java block mappings", e); throw new AssertionError("Unable to load Java block mappings", e);
} }
JAVA_BLOCKS_SIZE = blocksJson.size(); JAVA_BLOCKS_SIZE = BlockRegistries.BLOCK_STATES.get().size();
if (!BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().isEmpty()) { if (!BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().isEmpty()) {
MIN_CUSTOM_RUNTIME_ID = BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().keySet().stream().min(Comparator.comparing(JavaBlockState::javaId)).orElseThrow().javaId(); MIN_CUSTOM_RUNTIME_ID = BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().keySet().stream().min(Comparator.comparing(JavaBlockState::javaId)).orElseThrow().javaId();
@ -409,12 +411,6 @@ public final class BlockRegistryPopulator {
Deque<String> cleanIdentifiers = new ArrayDeque<>(); Deque<String> cleanIdentifiers = new ArrayDeque<>();
int javaRuntimeId = -1; int javaRuntimeId = -1;
int furnaceRuntimeId = -1;
int furnaceLitRuntimeId = -1;
int honeyBlockRuntimeId = -1;
int slimeBlockRuntimeId = -1;
int spawnerRuntimeId = -1;
int uniqueJavaId = -1;
int waterRuntimeId = -1; int waterRuntimeId = -1;
Iterator<Map.Entry<String, JsonNode>> blocksIterator = blocksJson.fields(); Iterator<Map.Entry<String, JsonNode>> blocksIterator = blocksJson.fields();
while (blocksIterator.hasNext()) { while (blocksIterator.hasNext()) {
@ -422,7 +418,6 @@ public final class BlockRegistryPopulator {
Map.Entry<String, JsonNode> entry = blocksIterator.next(); Map.Entry<String, JsonNode> entry = blocksIterator.next();
String javaId = entry.getKey(); String javaId = entry.getKey();
// TODO fix this, (no block should have a null hardness)
BlockMapping.BlockMappingBuilder builder = BlockMapping.builder(); BlockMapping.BlockMappingBuilder builder = BlockMapping.builder();
JsonNode pickItemNode = entry.getValue().get("pick_item"); JsonNode pickItemNode = entry.getValue().get("pick_item");
@ -443,7 +438,6 @@ public final class BlockRegistryPopulator {
String bedrockIdentifier = entry.getValue().get("bedrock_identifier").asText(); String bedrockIdentifier = entry.getValue().get("bedrock_identifier").asText();
if (!cleanJavaIdentifier.equals(cleanIdentifiers.peekLast())) { if (!cleanJavaIdentifier.equals(cleanIdentifiers.peekLast())) {
uniqueJavaId++;
cleanIdentifiers.add(cleanJavaIdentifier.intern()); cleanIdentifiers.add(cleanJavaIdentifier.intern());
} }
@ -456,50 +450,11 @@ public final class BlockRegistryPopulator {
// 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 // 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 (javaId.startsWith("minecraft:furnace[facing=north")) { if ("minecraft:water[level=0]".equals(javaId)) {
if (javaId.contains("lit=true")) {
furnaceLitRuntimeId = javaRuntimeId;
} else {
furnaceRuntimeId = javaRuntimeId;
}
} else if (javaId.startsWith("minecraft:spawner")) {
spawnerRuntimeId = javaRuntimeId;
} else if ("minecraft:water[level=0]".equals(javaId)) {
waterRuntimeId = javaRuntimeId; waterRuntimeId = javaRuntimeId;
} else if (javaId.equals("minecraft:honey_block")) {
honeyBlockRuntimeId = javaRuntimeId;
} else if (javaId.equals("minecraft:slime_block")) {
slimeBlockRuntimeId = javaRuntimeId;
} }
} }
if (furnaceRuntimeId == -1) {
throw new AssertionError("Unable to find furnace in palette");
}
BlockStateValues.JAVA_FURNACE_ID = furnaceRuntimeId;
if (furnaceLitRuntimeId == -1) {
throw new AssertionError("Unable to find lit furnace in palette");
}
BlockStateValues.JAVA_FURNACE_LIT_ID = furnaceLitRuntimeId;
if (honeyBlockRuntimeId == -1) {
throw new AssertionError("Unable to find honey block in palette");
}
BlockStateValues.JAVA_HONEY_BLOCK_ID = honeyBlockRuntimeId;
if (slimeBlockRuntimeId == -1) {
throw new AssertionError("Unable to find slime block in palette");
}
BlockStateValues.JAVA_SLIME_BLOCK_ID = slimeBlockRuntimeId;
if (spawnerRuntimeId == -1) {
throw new AssertionError("Unable to find spawner in palette");
}
BlockStateValues.JAVA_SPAWNER_ID = spawnerRuntimeId;
if (waterRuntimeId == -1) { if (waterRuntimeId == -1) {
throw new AssertionError("Unable to find Java water in palette"); throw new AssertionError("Unable to find Java water in palette");
} }
@ -534,12 +489,20 @@ public final class BlockRegistryPopulator {
builder.setBlockEntity(); builder.setBlockEntity();
} }
String cleanJavaIdentifier = BlockUtils.getCleanIdentifier(javaBlockState.identifier()); String cleanJavaIdentifier = BlockUtils.getCleanIdentifier(javaBlockState.identifier());
Block block = new Block(cleanJavaIdentifier, builder); String pickItem = javaBlockState.pickItem();
Block block = new Block(cleanJavaIdentifier, builder) {
@Override
public Item asItem() {
if (this.item == null) {
return Registries.JAVA_ITEM_IDENTIFIERS.get(pickItem);
}
return this.item;
}
};
String bedrockIdentifier = customBlockState.block().identifier(); String bedrockIdentifier = customBlockState.block().identifier();
if (!cleanJavaIdentifier.equals(cleanIdentifiers.peekLast())) { if (!cleanJavaIdentifier.equals(cleanIdentifiers.peekLast())) {
uniqueJavaId++;
cleanIdentifiers.add(cleanJavaIdentifier.intern()); cleanIdentifiers.add(cleanJavaIdentifier.intern());
} }

View file

@ -28,7 +28,6 @@ package org.geysermc.geyser.registry.type;
import lombok.Builder; import lombok.Builder;
import lombok.Value; import lombok.Value;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.util.BlockUtils;
@Builder @Builder
@Value @Value
@ -37,42 +36,8 @@ public class BlockMapping {
String javaIdentifier; String javaIdentifier;
boolean canBreakWithHand;
@Nullable String pickItem; @Nullable String pickItem;
boolean isBlockEntity; boolean isBlockEntity;
boolean isNonVanilla; boolean isNonVanilla;
/**
* @return the identifier without the additional block states
*/
public String getCleanJavaIdentifier() {
return BlockUtils.getCleanIdentifier(javaIdentifier);
}
/**
* @return the corresponding Java identifier for this item
*/
public String getItemIdentifier() {
if (pickItem != null && !pickItem.equals("minecraft:air")) {
// Spawners can have air as their pick item which we are not interested in.
return pickItem;
}
return getCleanJavaIdentifier();
}
/**
* Get the item a Java client would receive when pressing
* the Pick Block key on a specific Java block state.
*
* @return The Java identifier of the item
*/
public String getPickItem() {
if (pickItem != null) {
return pickItem;
}
return getCleanJavaIdentifier();
}
} }

View file

@ -97,6 +97,7 @@ import org.geysermc.geyser.inventory.PlayerInventory;
import org.geysermc.geyser.inventory.recipe.GeyserRecipe; import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserStonecutterData; import org.geysermc.geyser.inventory.recipe.GeyserStonecutterData;
import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.BlockItem;
import org.geysermc.geyser.level.JavaDimension; import org.geysermc.geyser.level.JavaDimension;
import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.level.physics.CollisionManager; import org.geysermc.geyser.level.physics.CollisionManager;
@ -349,7 +350,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
private Vector3i lastBlockPlacePosition; private Vector3i lastBlockPlacePosition;
@Setter @Setter
private String lastBlockPlacedId; private BlockItem lastBlockPlaced;
@Setter @Setter
private boolean interacting; private boolean interacting;

View file

@ -30,6 +30,7 @@ import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.holder.BlockInventoryHolder; import org.geysermc.geyser.inventory.holder.BlockInventoryHolder;
import org.geysermc.geyser.inventory.holder.InventoryHolder; import org.geysermc.geyser.inventory.holder.InventoryHolder;
import org.geysermc.geyser.inventory.updater.InventoryUpdater; import org.geysermc.geyser.inventory.updater.InventoryUpdater;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
/** /**
@ -44,10 +45,10 @@ public abstract class AbstractBlockInventoryTranslator extends BaseInventoryTran
* @param javaBlockIdentifier a Java block identifier that is used as a temporary block * @param javaBlockIdentifier a Java block identifier that is used as a temporary block
* @param containerType the container type of this inventory * @param containerType the container type of this inventory
* @param updater updater * @param updater updater
* @param additionalValidBlocks any other block identifiers that can safely use this inventory without a fake block * @param additionalValidBlocks any other blocks that can safely use this inventory without a fake block
*/ */
public AbstractBlockInventoryTranslator(int size, String javaBlockIdentifier, ContainerType containerType, InventoryUpdater updater, public AbstractBlockInventoryTranslator(int size, String javaBlockIdentifier, ContainerType containerType, InventoryUpdater updater,
String... additionalValidBlocks) { Block... additionalValidBlocks) {
super(size); super(size);
this.holder = new BlockInventoryHolder(javaBlockIdentifier, containerType, additionalValidBlocks); this.holder = new BlockInventoryHolder(javaBlockIdentifier, containerType, additionalValidBlocks);
this.updater = updater; this.updater = updater;

View file

@ -37,6 +37,7 @@ import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.PlayerInventory; import org.geysermc.geyser.inventory.PlayerInventory;
import org.geysermc.geyser.inventory.updater.AnvilInventoryUpdater; import org.geysermc.geyser.inventory.updater.AnvilInventoryUpdater;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType; import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
@ -45,7 +46,7 @@ import java.util.Objects;
public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator { public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator {
public AnvilInventoryTranslator() { public AnvilInventoryTranslator() {
super(3, "minecraft:anvil[facing=north]", org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType.ANVIL, AnvilInventoryUpdater.INSTANCE, super(3, "minecraft:anvil[facing=north]", org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType.ANVIL, AnvilInventoryUpdater.INSTANCE,
"minecraft:chipped_anvil", "minecraft:damaged_anvil"); Blocks.CHIPPED_ANVIL, Blocks.DAMAGED_ANVIL);
} }
@Override @Override

View file

@ -32,6 +32,7 @@ import org.geysermc.geyser.inventory.Generic3X3Container;
import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.PlayerInventory; import org.geysermc.geyser.inventory.PlayerInventory;
import org.geysermc.geyser.inventory.updater.ContainerInventoryUpdater; import org.geysermc.geyser.inventory.updater.ContainerInventoryUpdater;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType; import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
@ -41,7 +42,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
public class Generic3X3InventoryTranslator extends AbstractBlockInventoryTranslator { public class Generic3X3InventoryTranslator extends AbstractBlockInventoryTranslator {
public Generic3X3InventoryTranslator() { public Generic3X3InventoryTranslator() {
super(9, "minecraft:dispenser[facing=north,triggered=false]", org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType.DISPENSER, ContainerInventoryUpdater.INSTANCE, super(9, "minecraft:dispenser[facing=north,triggered=false]", org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType.DISPENSER, ContainerInventoryUpdater.INSTANCE,
"minecraft:dropper"); Blocks.DROPPER);
} }
@Override @Override

View file

@ -52,14 +52,14 @@ public class ShulkerInventoryTranslator extends AbstractBlockInventoryTranslator
} }
@Override @Override
protected void setCustomName(GeyserSession session, Vector3i position, Inventory inventory, int javaBlockState) { protected void setCustomName(GeyserSession session, Vector3i position, Inventory inventory, BlockState javaBlockState) {
NbtMapBuilder tag = NbtMap.builder() NbtMapBuilder tag = NbtMap.builder()
.putInt("x", position.getX()) .putInt("x", position.getX())
.putInt("y", position.getY()) .putInt("y", position.getY())
.putInt("z", position.getZ()) .putInt("z", position.getZ())
.putString("CustomName", inventory.getTitle()); .putString("CustomName", inventory.getTitle());
// Don't reset facing property // Don't reset facing property
shulkerBoxTranslator.translateTag(session, tag, null, BlockState.of(javaBlockState)); shulkerBoxTranslator.translateTag(session, tag, null, javaBlockState);
BlockEntityDataPacket dataPacket = new BlockEntityDataPacket(); BlockEntityDataPacket dataPacket = new BlockEntityDataPacket();
dataPacket.setData(tag.build()); dataPacket.setData(tag.build());

View file

@ -29,15 +29,17 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.holder.BlockInventoryHolder; import org.geysermc.geyser.inventory.holder.BlockInventoryHolder;
import org.geysermc.geyser.inventory.holder.InventoryHolder; import org.geysermc.geyser.inventory.holder.InventoryHolder;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
public class SingleChestInventoryTranslator extends ChestInventoryTranslator { public class SingleChestInventoryTranslator extends ChestInventoryTranslator {
private final InventoryHolder holder; private final InventoryHolder holder;
// TODO add barrel???
public SingleChestInventoryTranslator(int size) { public SingleChestInventoryTranslator(int size) {
super(size, 27); super(size, 27);
this.holder = new BlockInventoryHolder("minecraft:chest[facing=north,type=single,waterlogged=false]", ContainerType.CONTAINER, this.holder = new BlockInventoryHolder("minecraft:chest[facing=north,type=single,waterlogged=false]", ContainerType.CONTAINER,
"minecraft:ender_chest", "minecraft:trapped_chest") { Blocks.ENDER_CHEST, Blocks.TRAPPED_CHEST) {
@Override @Override
protected boolean isValidBlock(String[] javaBlockString) { protected boolean isValidBlock(String[] javaBlockString) {
if (javaBlockString[0].equals("minecraft:ender_chest")) { if (javaBlockString[0].equals("minecraft:ender_chest")) {

View file

@ -30,6 +30,7 @@ import it.unimi.dsi.fastutil.objects.*;
import org.geysermc.geyser.level.block.Blocks; import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.level.block.type.Block; import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.level.block.type.BlockState; import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.level.block.type.HoneyBlock;
import org.geysermc.geyser.level.block.type.PistonBlock; import org.geysermc.geyser.level.block.type.PistonBlock;
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.PistonValueType; import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.PistonValueType;
import org.cloudburstmc.math.vector.Vector3d; import org.cloudburstmc.math.vector.Vector3d;
@ -98,7 +99,7 @@ public class PistonBlockEntity {
static { static {
// Create a ~1 x ~0.5 x ~1 bounding box above the honey block // Create a ~1 x ~0.5 x ~1 bounding box above the honey block
BlockCollision blockCollision = BlockRegistries.COLLISIONS.get(BlockStateValues.JAVA_HONEY_BLOCK_ID); BlockCollision blockCollision = BlockRegistries.COLLISIONS.get(HoneyBlock.state().javaId());
if (blockCollision == null) { if (blockCollision == null) {
throw new RuntimeException("Failed to find honey block collision"); throw new RuntimeException("Failed to find honey block collision");
} }
@ -622,11 +623,11 @@ public class PistonBlockEntity {
} }
placedFinalBlocks = true; placedFinalBlocks = true;
Vector3i movement = getMovement(); Vector3i movement = getMovement();
attachedBlocks.forEach((blockPos, javaId) -> { attachedBlocks.forEach((blockPos, state) -> {
blockPos = blockPos.add(movement); blockPos = blockPos.add(movement);
// Don't place blocks that collide with the player // Don't place blocks that collide with the player
if (!SOLID_BOUNDING_BOX.checkIntersection(blockPos.toDouble(), session.getCollisionManager().getPlayerBoundingBox())) { if (!SOLID_BOUNDING_BOX.checkIntersection(blockPos.toDouble(), session.getCollisionManager().getPlayerBoundingBox())) {
ChunkUtils.updateBlock(session, javaId, blockPos); ChunkUtils.updateBlock(session, state, blockPos);
} }
}); });
if (action == PistonValueType.PUSHING) { if (action == PistonValueType.PUSHING) {

View file

@ -29,11 +29,10 @@ import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.packet.BlockPickRequestPacket; import org.cloudburstmc.protocol.bedrock.packet.BlockPickRequestPacket;
import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.entity.type.ItemFrameEntity;
import org.geysermc.geyser.level.block.BlockStateValues; 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.Block;
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.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.protocol.Translator;
@ -46,10 +45,10 @@ public class BedrockBlockPickRequestTranslator extends PacketTranslator<BlockPic
@Override @Override
public void translate(GeyserSession session, BlockPickRequestPacket packet) { public void translate(GeyserSession session, BlockPickRequestPacket packet) {
Vector3i vector = packet.getBlockPosition(); Vector3i vector = packet.getBlockPosition();
int blockToPick = session.getGeyser().getWorldManager().getBlockAt(session, vector.getX(), vector.getY(), vector.getZ()); Block blockToPick = session.getGeyser().getWorldManager().blockAt(session, vector.getX(), vector.getY(), vector.getZ()).block();
// Block is air - chunk caching is probably off // Block is air - chunk caching is probably off
if (blockToPick == Block.JAVA_AIR_ID) { if (blockToPick == Blocks.AIR) {
// Check for an item frame since the client thinks that's a block when it's an entity in Java // 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()); ItemFrameEntity entity = ItemFrameEntity.getItemFrameEntity(session, packet.getBlockPosition());
if (entity != null) { if (entity != null) {
@ -59,35 +58,31 @@ public class BedrockBlockPickRequestTranslator extends PacketTranslator<BlockPic
InventoryUtils.findOrCreateItem(session, entity.getHeldItem()); InventoryUtils.findOrCreateItem(session, entity.getHeldItem());
} else { } else {
// Grab the frame as the item // Grab the frame as the item
InventoryUtils.findOrCreateItem(session, entity.getDefinition() == EntityDefinitions.GLOW_ITEM_FRAME ? "minecraft:glow_item_frame" : "minecraft:item_frame"); InventoryUtils.findOrCreateItem(session, entity.getDefinition() == EntityDefinitions.GLOW_ITEM_FRAME ? Items.GLOW_ITEM_FRAME : Items.ITEM_FRAME);
} }
} }
return; return;
} }
BlockMapping blockMapping = BlockRegistries.JAVA_BLOCKS.getOrDefault(blockToPick, BlockMapping.DEFAULT); boolean addExtraData = packet.isAddUserData() && blockToPick.hasBlockEntity(); // Holding down CTRL
boolean addExtraData = packet.isAddUserData() && blockMapping.isBlockEntity(); // Holding down CTRL if (blockToPick instanceof BannerBlock || addExtraData) { //TODO
if (BlockStateValues.getBannerColor(blockToPick) != -1 || addExtraData) { //TODO
session.getGeyser().getWorldManager().getPickItemComponents(session, vector.getX(), vector.getY(), vector.getZ(), addExtraData) session.getGeyser().getWorldManager().getPickItemComponents(session, vector.getX(), vector.getY(), vector.getZ(), addExtraData)
.whenComplete((components, ex) -> session.ensureInEventLoop(() -> { .whenComplete((components, ex) -> session.ensureInEventLoop(() -> {
if (components == null) { if (components == null) {
pickItem(session, blockMapping); pickItem(session, blockToPick);
return; return;
} }
// I don't really like this... I'd rather get an ID from the block mapping I think ItemStack itemStack = new ItemStack(blockToPick.asItem().javaId(), 1, components);
ItemMapping mapping = session.getItemMappings().getMapping(blockMapping.getPickItem());
ItemStack itemStack = new ItemStack(mapping.getJavaItem().javaId(), 1, components);
InventoryUtils.findOrCreateItem(session, itemStack); InventoryUtils.findOrCreateItem(session, itemStack);
})); }));
return; return;
} }
pickItem(session, blockMapping); pickItem(session, blockToPick);
} }
private void pickItem(GeyserSession session, BlockMapping blockToPick) { private void pickItem(GeyserSession session, Block block) {
InventoryUtils.findOrCreateItem(session, blockToPick.getPickItem()); InventoryUtils.findOrCreateItem(session, block.asItem());
} }
} }

View file

@ -355,9 +355,9 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
} }
} }
} }
if (item instanceof BlockItem) { if (item instanceof BlockItem blockItem) {
session.setLastBlockPlacePosition(blockPos); session.setLastBlockPlacePosition(blockPos);
session.setLastBlockPlacedId(item.javaIdentifier()); session.setLastBlockPlaced(blockItem);
} }
session.setInteracting(true); session.setInteracting(true);
} }
@ -420,7 +420,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
int blockState = session.getGameMode() == GameMode.CREATIVE ? int blockState = session.getGameMode() == GameMode.CREATIVE ?
session.getGeyser().getWorldManager().getBlockAt(session, packet.getBlockPosition()) : session.getBreakingBlock(); session.getGeyser().getWorldManager().getBlockAt(session, packet.getBlockPosition()) : session.getBreakingBlock();
session.setLastBlockPlacedId(null); session.setLastBlockPlaced(null);
session.setLastBlockPlacePosition(null); session.setLastBlockPlacePosition(null);
// Same deal with vanilla block placing as above. // Same deal with vanilla block placing as above.

View file

@ -25,6 +25,8 @@
package org.geysermc.geyser.translator.protocol.java.level; 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.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.level.ClientboundBlockUpdatePacket;
import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.SoundEvent; import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
@ -66,14 +68,14 @@ public class JavaBlockUpdateTranslator extends PacketTranslator<ClientboundBlock
// We need to check if the identifier is the same, else a packet with the sound of what the // We need to check if the identifier is the same, else a packet with the sound of what the
// player has in their hand is played, despite if the block is being placed or not // player has in their hand is played, despite if the block is being placed or not
boolean contains = false; boolean contains = false;
String identifier = BlockRegistries.JAVA_BLOCKS.get(packet.getEntry().getBlock()).getItemIdentifier(); Item item = BlockState.of(packet.getEntry().getBlock()).block().asItem();
if (identifier.equals(session.getLastBlockPlacedId())) { if (item == session.getLastBlockPlaced()) {
contains = true; contains = true;
} }
if (!contains) { if (!contains) {
session.setLastBlockPlacePosition(null); session.setLastBlockPlacePosition(null);
session.setLastBlockPlacedId(null); session.setLastBlockPlaced(null);
return false; return false;
} }
@ -86,7 +88,7 @@ public class JavaBlockUpdateTranslator extends PacketTranslator<ClientboundBlock
placeBlockSoundPacket.setIdentifier(":"); placeBlockSoundPacket.setIdentifier(":");
session.sendUpstreamPacket(placeBlockSoundPacket); session.sendUpstreamPacket(placeBlockSoundPacket);
session.setLastBlockPlacePosition(null); session.setLastBlockPlacePosition(null);
session.setLastBlockPlacedId(null); session.setLastBlockPlaced(null);
return true; return true;
} }

View file

@ -166,11 +166,13 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
GeyserChunkSection section = new GeyserChunkSection(session.getBlockMappings().getBedrockAir().getRuntimeId(), subChunkIndex); GeyserChunkSection section = new GeyserChunkSection(session.getBlockMappings().getBedrockAir().getRuntimeId(), subChunkIndex);
for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) { for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) {
int javaId = javaData.get(yzx); int javaId = javaData.get(yzx);
BlockState state = BlockState.of(javaId);
int bedrockId = session.getBlockMappings().getBedrockBlockId(javaId); int bedrockId = session.getBlockMappings().getBedrockBlockId(javaId);
int xzy = indexYZXtoXZY(yzx); int xzy = indexYZXtoXZY(yzx);
section.getBlockStorageArray()[0].setFullBlock(xzy, bedrockId); section.getBlockStorageArray()[0].setFullBlock(xzy, bedrockId);
if (BlockRegistries.WATERLOGGED.get().get(javaId)) { Boolean waterlogged = state.getValue(Properties.WATERLOGGED); // TODO performance check
if (waterlogged == Boolean.TRUE) {
section.getBlockStorageArray()[1].setFullBlock(xzy, session.getBlockMappings().getBedrockWater().getRuntimeId()); section.getBlockStorageArray()[1].setFullBlock(xzy, session.getBlockMappings().getBedrockWater().getRuntimeId());
} }
@ -192,7 +194,6 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
} }
} }
BlockState state = BlockState.of(javaId);
// Check if block is piston or flower to see if we'll need to create additional block entities, as they're only block entities in Bedrock // Check if block is piston or flower to see if we'll need to create additional block entities, as they're only block entities in Bedrock
if (state.block() instanceof BedrockChunkWantsBlockEntityTag blockEntity) { if (state.block() instanceof BedrockChunkWantsBlockEntityTag blockEntity) {
bedrockBlockEntities.add(blockEntity.createTag(session, bedrockBlockEntities.add(blockEntity.createTag(session,

View file

@ -42,6 +42,7 @@ import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe; import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe; import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe;
import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.level.BedrockDimension;
import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMapping;
@ -300,6 +301,11 @@ public class InventoryUtils {
} }
} }
// Please remove!!!
public static void findOrCreateItem(GeyserSession session, String itemName) {
findOrCreateItem(session, Registries.JAVA_ITEM_IDENTIFIERS.getOrDefault(itemName, Items.AIR));
}
/** /**
* Attempt to find the specified item name in the session's inventory. * Attempt to find the specified item name in the session's inventory.
* If it is found and in the hotbar, set the user's held item to that slot. * If it is found and in the hotbar, set the user's held item to that slot.
@ -309,13 +315,13 @@ public class InventoryUtils {
* <p> * <p>
* This attempts to mimic Java Edition behavior as best as it can. * This attempts to mimic Java Edition behavior as best as it can.
* @param session the Bedrock client's session * @param session the Bedrock client's session
* @param itemName the Java identifier of the item to search/select * @param item the Java item to search/select for
*/ */
public static void findOrCreateItem(GeyserSession session, String itemName) { public static void findOrCreateItem(GeyserSession session, Item item) {
// Get the inventory to choose a slot to pick // Get the inventory to choose a slot to pick
PlayerInventory inventory = session.getPlayerInventory(); PlayerInventory inventory = session.getPlayerInventory();
if (itemName.equals("minecraft:air")) { if (item == Items.AIR) {
return; return;
} }
@ -326,7 +332,7 @@ public class InventoryUtils {
continue; continue;
} }
// If this isn't the item we're looking for // If this isn't the item we're looking for
if (!geyserItem.asItem().javaIdentifier().equals(itemName)) { if (!geyserItem.asItem().equals(item)) {
continue; continue;
} }
@ -342,7 +348,7 @@ public class InventoryUtils {
continue; continue;
} }
// If this isn't the item we're looking for // If this isn't the item we're looking for
if (!geyserItem.asItem().javaIdentifier().equals(itemName)) { if (!geyserItem.asItem().equals(item)) {
continue; continue;
} }
@ -355,17 +361,13 @@ public class InventoryUtils {
if (session.getGameMode() == GameMode.CREATIVE) { if (session.getGameMode() == GameMode.CREATIVE) {
int slot = findEmptyHotbarSlot(inventory); int slot = findEmptyHotbarSlot(inventory);
ItemMapping mapping = session.getItemMappings().getMapping(itemName); // TODO ItemMapping mapping = session.getItemMappings().getMapping(item);
if (mapping != null) {
ServerboundSetCreativeModeSlotPacket actionPacket = new ServerboundSetCreativeModeSlotPacket((short)slot, ServerboundSetCreativeModeSlotPacket actionPacket = new ServerboundSetCreativeModeSlotPacket((short)slot,
new ItemStack(mapping.getJavaItem().javaId())); new ItemStack(mapping.getJavaItem().javaId()));
if ((slot - 36) != inventory.getHeldItemSlot()) { if ((slot - 36) != inventory.getHeldItemSlot()) {
setHotbarItem(session, slot); setHotbarItem(session, slot);
} }
session.sendDownstreamGamePacket(actionPacket); session.sendDownstreamGamePacket(actionPacket);
} else {
session.getGeyser().getLogger().debug("Cannot find item for block " + itemName);
}
} }
} }

View file

@ -96,7 +96,7 @@ public class StatisticsUtils {
if (entry.getKey() instanceof BreakBlockStatistic statistic) { if (entry.getKey() instanceof BreakBlockStatistic statistic) {
Block block = BlockRegistries.JAVA_BLOCKS_TO_RENAME.get(statistic.getId()); Block block = BlockRegistries.JAVA_BLOCKS_TO_RENAME.get(statistic.getId());
if (block != null) { if (block != null) {
String identifier = block.javaIdentifier().replace("minecraft:", "block.minecraft."); String identifier = "block.minecraft." + block.javaIdentifier().value();
content.add(identifier + ": " + entry.getIntValue()); content.add(identifier + ": " + entry.getIntValue());
} }
} }