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);
for (int i = 0; i < BlockRegistries.JAVA_BLOCKS.get().length; i++) {
String cleanIdentifier = BlockRegistries.JAVA_BLOCKS.get(i).getCleanJavaIdentifier();
String newIdentifier = BlockRegistries.BLOCK_STATES.get(i).block().javaIdentifier();
if (!cleanIdentifier.equals(newIdentifier)) {
System.out.println("Check block " + BlockRegistries.BLOCK_STATES.get(i).block().javaIdentifier());
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;
}
}

View file

@ -25,14 +25,14 @@
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.protocol.bedrock.data.entity.EntityDataTypes;
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.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;
@ -51,7 +51,7 @@ public class FurnaceMinecartEntity extends DefaultBlockMinecartEntity {
@Override
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);
}

View file

@ -28,7 +28,7 @@ package org.geysermc.geyser.entity.type;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
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 java.util.UUID;
@ -41,7 +41,7 @@ public class SpawnerMinecartEntity extends DefaultBlockMinecartEntity {
@Override
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);
}
}

View file

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

View file

@ -36,8 +36,9 @@ import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.inventory.Inventory;
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.type.BlockMapping;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.util.BlockUtils;
@ -59,12 +60,14 @@ public class BlockInventoryHolder extends InventoryHolder {
private final ContainerType containerType;
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.containerType = containerType;
if (validBlocks != null) {
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));
this.validBlocks = Set.copyOf(validBlocksTemp);
} else {
@ -80,14 +83,15 @@ public class BlockInventoryHolder extends InventoryHolder {
if (checkInteractionPosition(session)) {
// 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
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("\\[");
BlockState state = session.getGeyser().getWorldManager().blockAt(session, session.getLastInteractionBlockPosition());
if (!BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get().containsKey(state.javaId())) {
// TODO TODO TODO
String[] javaBlockString = state.toString().split("\\[");
if (isValidBlock(javaBlockString)) {
// We can safely use this block
inventory.setHolderPosition(session.getLastInteractionBlockPosition());
((Container) inventory).setUsingRealBlock(true, javaBlockString[0]);
setCustomName(session, session.getLastInteractionBlockPosition(), inventory, javaBlockId);
setCustomName(session, session.getLastInteractionBlockPosition(), inventory, state);
return true;
}
@ -107,7 +111,7 @@ public class BlockInventoryHolder extends InventoryHolder {
session.sendUpstreamPacket(blockPacket);
inventory.setHolderPosition(position);
setCustomName(session, position, inventory, defaultJavaBlockState);
setCustomName(session, position, inventory, BlockState.of(defaultJavaBlockState));
return true;
}
@ -129,7 +133,7 @@ public class BlockInventoryHolder extends InventoryHolder {
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()
.putInt("x", position.getX())
.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.geysermc.geyser.inventory.item.BannerPattern;
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.session.GeyserSession;
import org.geysermc.geyser.session.cache.registry.JavaRegistry;
@ -199,8 +200,8 @@ public class BannerItem extends BlockItem {
return null;
}
public BannerItem(String javaIdentifier, Builder builder) {
super(javaIdentifier, builder);
public BannerItem(Builder builder, Block block, Block... otherBlocks) {
super(builder, block, otherBlocks);
}
@Override

View file

@ -25,11 +25,26 @@
package org.geysermc.geyser.item.type;
/**
* TODO needed?
*/
import org.geysermc.geyser.level.block.type.Block;
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);
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.cloudburstmc.nbt.NbtType;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
@ -38,8 +39,8 @@ import java.util.List;
public class DecoratedPotItem extends BlockItem {
public DecoratedPotItem(String javaIdentifier, Builder builder) {
super(javaIdentifier, builder);
public DecoratedPotItem(Builder builder, Block block, Block... otherBlocks) {
super(builder, block, otherBlocks);
}
@Override

View file

@ -35,6 +35,7 @@ import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.inventory.item.Enchantment;
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.ItemMappings;
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 java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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");
private static final Map<Block, Item> BLOCK_TO_ITEM = new HashMap<>();
private final String javaIdentifier;
private int javaId = -1;
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) {
// TODO verify
// 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() {
return new Builder();
}

View file

@ -27,6 +27,7 @@ package org.geysermc.geyser.item.type;
import com.github.steveice10.mc.auth.data.GameProfile;
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.text.ChatColor;
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.DataComponents;
public class PlayerHeadItem extends Item {
public PlayerHeadItem(String javaIdentifier, Builder builder) {
super(javaIdentifier, builder);
public class PlayerHeadItem extends BlockItem {
public PlayerHeadItem(Builder builder, Block block, Block... otherBlocks) {
super(builder, block, otherBlocks);
}
@Override

View file

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

View file

@ -249,25 +249,6 @@ public final class BlockStateValues {
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.
*/
@ -282,26 +263,6 @@ public final class BlockStateValues {
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) {
return STICKY_PISTONS.contains(blockState);
}
@ -435,16 +396,6 @@ public final class BlockStateValues {
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
* 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)
.booleanState(WATERLOGGED)));
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)));
public static final Block LAVA = register(new Block("lava", builder().destroyTime(100.0f).pushReaction(PistonBehavior.DESTROY)
.intState(LEVEL, 0, 15)));
@ -379,7 +379,7 @@ public final class Blocks {
.booleanState(UP)
.booleanState(WEST)));
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)
.enumState(HORIZONTAL_FACING, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST)
.enumState(HALF, "top", "bottom")
@ -403,7 +403,7 @@ public final class Blocks {
.intState(AGE_7, 0, 7)));
public static final Block FARMLAND = register(new Block("farmland", builder().destroyTime(0.6f)
.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)
.booleanState(LIT)));
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_2)));
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)));
public static final Block LAVA_CAULDRON = register(new Block("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 LAVA_CAULDRON = register(new CauldronBlock("lava_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)));
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)
@ -2179,7 +2179,7 @@ public final class Blocks {
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)
.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 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)));

View file

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

View file

@ -25,21 +25,24 @@
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.IntList;
import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap;
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.cloudburstmc.math.vector.Vector3i;
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.Identifier;
import org.intellij.lang.annotations.Subst;
import java.util.*;
import java.util.stream.Stream;
@ -47,20 +50,21 @@ import java.util.stream.Stream;
public class Block {
public static final int JAVA_AIR_ID = 0;
private final String javaIdentifier;
private final Key javaIdentifier;
private final boolean requiresCorrectToolForDrops;
private final boolean hasBlockEntity;
private final float destroyTime;
private final @NonNull PistonBehavior pushReaction;
protected Item item = null;
private int javaId = -1;
public Block(String javaIdentifier, Builder builder) {
this.javaIdentifier = Identifier.formalize(javaIdentifier).intern();
public Block(@Subst("empty") String javaIdentifier, Builder builder) {
this.javaIdentifier = Key.key(javaIdentifier);
this.requiresCorrectToolForDrops = builder.requiresCorrectToolForDrops;
this.hasBlockEntity = builder.hasBlockEntity;
this.destroyTime = builder.destroyTime;
this.pushReaction = builder.pushReaction;
builder.build(this);
processStates(builder.build(this));
}
public void updateBlock(GeyserSession session, BlockState state, Vector3i position) {
@ -114,7 +118,8 @@ public class Block {
UpdateBlockPacket waterPacket = new UpdateBlockPacket();
waterPacket.setDataLayer(1);
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());
} else {
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;
}
@ -163,7 +182,7 @@ public class Block {
@Override
public String toString() {
return "Item{" +
return "Block{" +
"javaIdentifier='" + javaIdentifier + '\'' +
", javaId=" + javaId +
'}';
@ -229,14 +248,27 @@ public class Block {
return this;
}
private void build(Block block) {
private List<BlockState> build(Block block) {
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 {
// 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.
Stream<List<Pair<Property<?>, Comparable<?>>>> stream = Stream.of(Collections.emptyList());
for (var state : this.states.entrySet()) {
Stream<List<Comparable<?>>> stream = Stream.of(Collections.emptyList());
for (var values : this.states.values()) {
// 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.
// 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.
// We're about to create a new stream, each with a new list,
// for every previous property
state.getValue().stream().map(value -> {
values.stream().map(value -> {
var newProperties = new ArrayList<>(aPreviousPropertiesList);
newProperties.add(Pair.of(state.getKey(), value));
newProperties.add(value);
return newProperties;
}));
}
List<BlockState> states = new ArrayList<>();
// 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],
// We'll see [up=true,down=true], [up=false,down=true], [up=true,down=false], [up=false,down=false]
stream.forEach(properties -> {
Reference2ObjectMap<Property<?>, Comparable<?>> propertyMap = new Reference2ObjectArrayMap<>(properties.size());
for (int i = 0; i < properties.size(); i++) {
Pair<Property<?>, Comparable<?>> property = properties.get(i);
propertyMap.put(property.key(), property.value());
}
BlockRegistries.BLOCK_STATES.get().add(new BlockState(block, BlockRegistries.BLOCK_STATES.get().size(), propertyMap));
List<List<Comparable<?>>> result = stream.toList();
// Ensure each block state shares the same key array. Creating a keySet here shouldn't be an issue since
// this states map should be removed after build.
Property<?>[] keys = this.states.keySet().toArray(new Property<?>[0]);
result.forEach(properties -> {
Comparable<?>[] values = properties.toArray(new Comparable<?>[0]);
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.registry.BlockRegistries;
import java.util.Locale;
public final class BlockState {
private final Block block;
private final int javaId;
@ -62,12 +64,25 @@ public final class BlockState {
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() {
StringBuilder builder = new StringBuilder();
var it = this.states.entrySet().iterator();
while (it.hasNext()) {
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();
}

View file

@ -68,7 +68,7 @@ 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());
NbtMap plant = session.getBlockMappings().getFlowerPotBlocks().get(this.flower.javaIdentifier().asString());
if (plant != null) {
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);
BlockRegistryPopulator.populate(BlockRegistryPopulator.Stage.INIT_BEDROCK);
BlockRegistryPopulator.populate(BlockRegistryPopulator.Stage.POST_INIT);
System.out.println("Block registries loaded");
}
public static void init() {

View file

@ -204,5 +204,6 @@ public final class Registries {
biomesNbt.put(key, value.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) {
String blockName = state.block().javaIdentifier().substring("minecraft:".length());
String blockName = state.block().javaIdentifier().value();
for (Map.Entry<Class<?>, CollisionInfo> collisionRemappers : annotationMap.entrySet()) {
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.CustomBlockState;
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.type.Block;
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;
@ -391,7 +393,7 @@ public final class BlockRegistryPopulator {
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()) {
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<>();
int javaRuntimeId = -1;
int furnaceRuntimeId = -1;
int furnaceLitRuntimeId = -1;
int honeyBlockRuntimeId = -1;
int slimeBlockRuntimeId = -1;
int spawnerRuntimeId = -1;
int uniqueJavaId = -1;
int waterRuntimeId = -1;
Iterator<Map.Entry<String, JsonNode>> blocksIterator = blocksJson.fields();
while (blocksIterator.hasNext()) {
@ -422,7 +418,6 @@ public final class BlockRegistryPopulator {
Map.Entry<String, JsonNode> entry = blocksIterator.next();
String javaId = entry.getKey();
// TODO fix this, (no block should have a null hardness)
BlockMapping.BlockMappingBuilder builder = BlockMapping.builder();
JsonNode pickItemNode = entry.getValue().get("pick_item");
@ -443,7 +438,6 @@ public final class BlockRegistryPopulator {
String bedrockIdentifier = entry.getValue().get("bedrock_identifier").asText();
if (!cleanJavaIdentifier.equals(cleanIdentifiers.peekLast())) {
uniqueJavaId++;
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
BlockRegistries.JAVA_TO_BEDROCK_IDENTIFIERS.register(cleanJavaIdentifier.intern(), bedrockIdentifier.intern());
if (javaId.startsWith("minecraft:furnace[facing=north")) {
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)) {
if ("minecraft:water[level=0]".equals(javaId)) {
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) {
throw new AssertionError("Unable to find Java water in palette");
}
@ -534,12 +489,20 @@ public final class BlockRegistryPopulator {
builder.setBlockEntity();
}
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();
if (!cleanJavaIdentifier.equals(cleanIdentifiers.peekLast())) {
uniqueJavaId++;
cleanIdentifiers.add(cleanJavaIdentifier.intern());
}

View file

@ -28,7 +28,6 @@ package org.geysermc.geyser.registry.type;
import lombok.Builder;
import lombok.Value;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.util.BlockUtils;
@Builder
@Value
@ -37,42 +36,8 @@ public class BlockMapping {
String javaIdentifier;
boolean canBreakWithHand;
@Nullable String pickItem;
boolean isBlockEntity;
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.GeyserStonecutterData;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.BlockItem;
import org.geysermc.geyser.level.JavaDimension;
import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.level.physics.CollisionManager;
@ -349,7 +350,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
private Vector3i lastBlockPlacePosition;
@Setter
private String lastBlockPlacedId;
private BlockItem lastBlockPlaced;
@Setter
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.InventoryHolder;
import org.geysermc.geyser.inventory.updater.InventoryUpdater;
import org.geysermc.geyser.level.block.type.Block;
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 containerType the container type of this inventory
* @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,
String... additionalValidBlocks) {
Block... additionalValidBlocks) {
super(size);
this.holder = new BlockInventoryHolder(javaBlockIdentifier, containerType, additionalValidBlocks);
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.PlayerInventory;
import org.geysermc.geyser.inventory.updater.AnvilInventoryUpdater;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
@ -45,7 +46,7 @@ import java.util.Objects;
public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator {
public AnvilInventoryTranslator() {
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

View file

@ -32,6 +32,7 @@ import org.geysermc.geyser.inventory.Generic3X3Container;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.PlayerInventory;
import org.geysermc.geyser.inventory.updater.ContainerInventoryUpdater;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.session.GeyserSession;
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 Generic3X3InventoryTranslator() {
super(9, "minecraft:dispenser[facing=north,triggered=false]", org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType.DISPENSER, ContainerInventoryUpdater.INSTANCE,
"minecraft:dropper");
Blocks.DROPPER);
}
@Override

View file

@ -52,14 +52,14 @@ public class ShulkerInventoryTranslator extends AbstractBlockInventoryTranslator
}
@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()
.putInt("x", position.getX())
.putInt("y", position.getY())
.putInt("z", position.getZ())
.putString("CustomName", inventory.getTitle());
// Don't reset facing property
shulkerBoxTranslator.translateTag(session, tag, null, BlockState.of(javaBlockState));
shulkerBoxTranslator.translateTag(session, tag, null, javaBlockState);
BlockEntityDataPacket dataPacket = new BlockEntityDataPacket();
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.holder.BlockInventoryHolder;
import org.geysermc.geyser.inventory.holder.InventoryHolder;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.session.GeyserSession;
public class SingleChestInventoryTranslator extends ChestInventoryTranslator {
private final InventoryHolder holder;
// TODO add barrel???
public SingleChestInventoryTranslator(int size) {
super(size, 27);
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
protected boolean isValidBlock(String[] javaBlockString) {
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.type.Block;
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.mcprotocollib.protocol.data.game.level.block.value.PistonValueType;
import org.cloudburstmc.math.vector.Vector3d;
@ -98,7 +99,7 @@ public class PistonBlockEntity {
static {
// 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) {
throw new RuntimeException("Failed to find honey block collision");
}
@ -622,11 +623,11 @@ public class PistonBlockEntity {
}
placedFinalBlocks = true;
Vector3i movement = getMovement();
attachedBlocks.forEach((blockPos, javaId) -> {
attachedBlocks.forEach((blockPos, state) -> {
blockPos = blockPos.add(movement);
// Don't place blocks that collide with the player
if (!SOLID_BOUNDING_BOX.checkIntersection(blockPos.toDouble(), session.getCollisionManager().getPlayerBoundingBox())) {
ChunkUtils.updateBlock(session, javaId, blockPos);
ChunkUtils.updateBlock(session, state, blockPos);
}
});
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.geysermc.geyser.entity.EntityDefinitions;
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.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.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
@ -46,10 +45,10 @@ public class BedrockBlockPickRequestTranslator extends PacketTranslator<BlockPic
@Override
public void translate(GeyserSession session, BlockPickRequestPacket packet) {
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
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
ItemFrameEntity entity = ItemFrameEntity.getItemFrameEntity(session, packet.getBlockPosition());
if (entity != null) {
@ -59,35 +58,31 @@ public class BedrockBlockPickRequestTranslator extends PacketTranslator<BlockPic
InventoryUtils.findOrCreateItem(session, entity.getHeldItem());
} else {
// 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;
}
BlockMapping blockMapping = BlockRegistries.JAVA_BLOCKS.getOrDefault(blockToPick, BlockMapping.DEFAULT);
boolean addExtraData = packet.isAddUserData() && blockMapping.isBlockEntity(); // Holding down CTRL
if (BlockStateValues.getBannerColor(blockToPick) != -1 || addExtraData) { //TODO
boolean addExtraData = packet.isAddUserData() && blockToPick.hasBlockEntity(); // Holding down CTRL
if (blockToPick instanceof BannerBlock || addExtraData) { //TODO
session.getGeyser().getWorldManager().getPickItemComponents(session, vector.getX(), vector.getY(), vector.getZ(), addExtraData)
.whenComplete((components, ex) -> session.ensureInEventLoop(() -> {
if (components == null) {
pickItem(session, blockMapping);
pickItem(session, blockToPick);
return;
}
// I don't really like this... I'd rather get an ID from the block mapping I think
ItemMapping mapping = session.getItemMappings().getMapping(blockMapping.getPickItem());
ItemStack itemStack = new ItemStack(mapping.getJavaItem().javaId(), 1, components);
ItemStack itemStack = new ItemStack(blockToPick.asItem().javaId(), 1, components);
InventoryUtils.findOrCreateItem(session, itemStack);
}));
return;
}
pickItem(session, blockMapping);
pickItem(session, blockToPick);
}
private void pickItem(GeyserSession session, BlockMapping blockToPick) {
InventoryUtils.findOrCreateItem(session, blockToPick.getPickItem());
private void pickItem(GeyserSession session, Block block) {
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.setLastBlockPlacedId(item.javaIdentifier());
session.setLastBlockPlaced(blockItem);
}
session.setInteracting(true);
}
@ -420,7 +420,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
int blockState = session.getGameMode() == GameMode.CREATIVE ?
session.getGeyser().getWorldManager().getBlockAt(session, packet.getBlockPosition()) : session.getBreakingBlock();
session.setLastBlockPlacedId(null);
session.setLastBlockPlaced(null);
session.setLastBlockPlacePosition(null);
// Same deal with vanilla block placing as above.

View file

@ -25,6 +25,8 @@
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;
@ -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
// player has in their hand is played, despite if the block is being placed or not
boolean contains = false;
String identifier = BlockRegistries.JAVA_BLOCKS.get(packet.getEntry().getBlock()).getItemIdentifier();
if (identifier.equals(session.getLastBlockPlacedId())) {
Item item = BlockState.of(packet.getEntry().getBlock()).block().asItem();
if (item == session.getLastBlockPlaced()) {
contains = true;
}
if (!contains) {
session.setLastBlockPlacePosition(null);
session.setLastBlockPlacedId(null);
session.setLastBlockPlaced(null);
return false;
}
@ -86,7 +88,7 @@ public class JavaBlockUpdateTranslator extends PacketTranslator<ClientboundBlock
placeBlockSoundPacket.setIdentifier(":");
session.sendUpstreamPacket(placeBlockSoundPacket);
session.setLastBlockPlacePosition(null);
session.setLastBlockPlacedId(null);
session.setLastBlockPlaced(null);
return true;
}

View file

@ -166,11 +166,13 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
GeyserChunkSection section = new GeyserChunkSection(session.getBlockMappings().getBedrockAir().getRuntimeId(), subChunkIndex);
for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) {
int javaId = javaData.get(yzx);
BlockState state = BlockState.of(javaId);
int bedrockId = session.getBlockMappings().getBedrockBlockId(javaId);
int xzy = indexYZXtoXZY(yzx);
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());
}
@ -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
if (state.block() instanceof BedrockChunkWantsBlockEntityTag blockEntity) {
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.GeyserShapelessRecipe;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.level.BedrockDimension;
import org.geysermc.geyser.registry.Registries;
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.
* 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>
* This attempts to mimic Java Edition behavior as best as it can.
* @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
PlayerInventory inventory = session.getPlayerInventory();
if (itemName.equals("minecraft:air")) {
if (item == Items.AIR) {
return;
}
@ -326,7 +332,7 @@ public class InventoryUtils {
continue;
}
// If this isn't the item we're looking for
if (!geyserItem.asItem().javaIdentifier().equals(itemName)) {
if (!geyserItem.asItem().equals(item)) {
continue;
}
@ -342,7 +348,7 @@ public class InventoryUtils {
continue;
}
// If this isn't the item we're looking for
if (!geyserItem.asItem().javaIdentifier().equals(itemName)) {
if (!geyserItem.asItem().equals(item)) {
continue;
}
@ -355,17 +361,13 @@ public class InventoryUtils {
if (session.getGameMode() == GameMode.CREATIVE) {
int slot = findEmptyHotbarSlot(inventory);
ItemMapping mapping = session.getItemMappings().getMapping(itemName); // TODO
if (mapping != null) {
ItemMapping mapping = session.getItemMappings().getMapping(item);
ServerboundSetCreativeModeSlotPacket actionPacket = new ServerboundSetCreativeModeSlotPacket((short)slot,
new ItemStack(mapping.getJavaItem().javaId()));
if ((slot - 36) != inventory.getHeldItemSlot()) {
setHotbarItem(session, slot);
}
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) {
Block block = BlockRegistries.JAVA_BLOCKS_TO_RENAME.get(statistic.getId());
if (block != null) {
String identifier = block.javaIdentifier().replace("minecraft:", "block.minecraft.");
String identifier = "block.minecraft." + block.javaIdentifier().value();
content.add(identifier + ": " + entry.getIntValue());
}
}