diff --git a/Jenkinsfile b/Jenkinsfile index 6564bd1f9..09e88e86e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -35,8 +35,8 @@ pipeline { rtMavenResolver( id: "maven-resolver", serverId: "opencollab-artifactory", - releaseRepo: "release", - snapshotRepo: "snapshot" + releaseRepo: "maven-deploy-release", + snapshotRepo: "maven-deploy-snapshot" ) rtMavenRun( pom: 'pom.xml', diff --git a/README.md b/README.md index 343fca94a..b17eede96 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have now joined us here! -### Currently supporting Minecraft Bedrock v1.16.100 - v1.16.201 and Minecraft Java v1.16.4 - v1.16.5. +### Currently supporting Minecraft Bedrock v1.16.100 - v1.16.210 and Minecraft Java v1.16.4 - v1.16.5. ## Setting Up Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser. diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index a6a2a86de..4b61a65c3 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -16,10 +16,6 @@ spigot-public https://hub.spigotmc.org/nexus/content/repositories/public/ - - bukkit-public - https://repo.md-5.net/content/repositories/public/ - sponge-repo https://repo.spongepowered.org/repository/maven-public/ diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java index 56fa7581b..51e68a263 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java @@ -53,11 +53,11 @@ public class GeyserSpigotBlockPlaceListener implements Listener { placeBlockSoundPacket.setPosition(Vector3f.from(event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ())); placeBlockSoundPacket.setBabySound(false); if (worldManager.isLegacy()) { - placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(worldManager.getBlockAt(session, + placeBlockSoundPacket.setExtraData(session.getBlockTranslator().getBedrockBlockId(worldManager.getBlockAt(session, event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ()))); } else { String javaBlockId = event.getBlockPlaced().getBlockData().getAsString(); - placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaIdBlockMap().getOrDefault(javaBlockId, BlockTranslator.JAVA_AIR_ID))); + placeBlockSoundPacket.setExtraData(session.getBlockTranslator().getBedrockBlockId(BlockTranslator.getJavaIdBlockMap().getOrDefault(javaBlockId, BlockTranslator.JAVA_AIR_ID))); } placeBlockSoundPacket.setIdentifier(":"); session.sendUpstreamPacket(placeBlockSoundPacket); diff --git a/connector/pom.xml b/connector/pom.xml index 107b4144b..5a462c5c5 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -12,6 +12,8 @@ 4.1.59.Final + 8.5.2 + 4.5.0 @@ -40,14 +42,10 @@ com.github.CloudburstMC.Protocol - bedrock-v422 - 294e7e5 + bedrock-v428 + 42da92f compile - - net.sf.trove4j - trove - com.nukkitx.network raknet @@ -55,9 +53,9 @@ - com.github.CloudburstMC.Network + com.nukkitx.network raknet - a94d2dd + 1.6.26-20210217.205834-2 compile @@ -69,61 +67,61 @@ com.nukkitx.fastutil fastutil-int-int-maps - 8.3.1 + ${fastutil.version} compile com.nukkitx.fastutil fastutil-int-float-maps - 8.3.1 + ${fastutil.version} compile com.nukkitx.fastutil fastutil-long-long-maps - 8.3.1 + ${fastutil.version} compile com.nukkitx.fastutil fastutil-object-long-maps - 8.3.1 + ${fastutil.version} compile com.nukkitx.fastutil fastutil-int-byte-maps - 8.3.1 + ${fastutil.version} compile com.nukkitx.fastutil fastutil-int-double-maps - 8.3.1 + ${fastutil.version} compile com.nukkitx.fastutil fastutil-int-boolean-maps - 8.3.1 + ${fastutil.version} compile com.nukkitx.fastutil fastutil-object-int-maps - 8.3.1 + ${fastutil.version} compile com.nukkitx.fastutil fastutil-object-byte-maps - 8.3.1 + ${fastutil.version} compile com.nukkitx.fastutil fastutil-object-object-maps - 8.3.1 + ${fastutil.version} compile @@ -225,25 +223,25 @@ net.kyori adventure-api - 4.5.0 + ${adventure.version} compile net.kyori adventure-text-serializer-gson - 4.5.0 + ${adventure.version} compile net.kyori adventure-text-serializer-legacy - 4.5.0 + ${adventure.version} compile net.kyori adventure-text-serializer-gson-legacy-impl - 4.5.0 + ${adventure.version} compile diff --git a/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java index ed5272b89..d07ecc965 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java @@ -28,6 +28,7 @@ package org.geysermc.connector.entity; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.packet.AnimatePacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -105,27 +106,38 @@ public class BoatEntity extends Entity { metadata.put(EntityData.VARIANT, entityMetadata.getValue()); } else if (entityMetadata.getId() == 11) { isPaddlingLeft = (boolean) entityMetadata.getValue(); - if (!isPaddlingLeft) { - metadata.put(EntityData.ROW_TIME_LEFT, 0f); - } - else { + if (isPaddlingLeft) { // Java sends simply "true" and "false" (is_paddling_left), Bedrock keeps sending packets as you're rowing // This is an asynchronous method that emulates Bedrock rowing until "false" is sent. paddleTimeLeft = 0f; - session.getConnector().getGeneralThreadPool().execute(() -> - updateLeftPaddle(session, entityMetadata) - ); + if (!this.passengers.isEmpty()) { + // Get the entity by the first stored passenger and convey motion in this manner + Entity entity = session.getEntityCache().getEntityByJavaId(this.passengers.iterator().nextLong()); + if (entity != null) { + session.getConnector().getGeneralThreadPool().execute(() -> + updateLeftPaddle(session, entity) + ); + } + } + } else { + // Indicate that the row position should be reset + metadata.put(EntityData.ROW_TIME_LEFT, 0.0f); } } else if (entityMetadata.getId() == 12) { isPaddlingRight = (boolean) entityMetadata.getValue(); - if (!isPaddlingRight) { - metadata.put(EntityData.ROW_TIME_RIGHT, 0f); - } else { + if (isPaddlingRight) { paddleTimeRight = 0f; - session.getConnector().getGeneralThreadPool().execute(() -> - updateRightPaddle(session, entityMetadata) - ); + if (!this.passengers.isEmpty()) { + Entity entity = session.getEntityCache().getEntityByJavaId(this.passengers.iterator().nextLong()); + if (entity != null) { + session.getConnector().getGeneralThreadPool().execute(() -> + updateRightPaddle(session, entity) + ); + } + } + } else { + metadata.put(EntityData.ROW_TIME_RIGHT, 0.0f); } } else if (entityMetadata.getId() == 13) { // Possibly - I don't think this does anything? @@ -135,27 +147,46 @@ public class BoatEntity extends Entity { super.updateBedrockMetadata(entityMetadata, session); } - public void updateLeftPaddle(GeyserSession session, EntityMetadata entityMetadata) { + @Override + public void updateBedrockMetadata(GeyserSession session) { + super.updateBedrockMetadata(session); + + // As these indicate to reset rowing, remove them until it is time to send them out again. + metadata.remove(EntityData.ROW_TIME_LEFT); + metadata.remove(EntityData.ROW_TIME_RIGHT); + } + + private void updateLeftPaddle(GeyserSession session, Entity rower) { if (isPaddlingLeft) { paddleTimeLeft += ROWING_SPEED; - metadata.put(EntityData.ROW_TIME_LEFT, paddleTimeLeft); - super.updateBedrockMetadata(entityMetadata, session); + sendAnimationPacket(session, rower, AnimatePacket.Action.ROW_LEFT, paddleTimeLeft); + session.getConnector().getGeneralThreadPool().schedule(() -> - updateLeftPaddle(session, entityMetadata), + updateLeftPaddle(session, rower), 100, TimeUnit.MILLISECONDS ); - }} + } + } - public void updateRightPaddle(GeyserSession session, EntityMetadata entityMetadata) { + private void updateRightPaddle(GeyserSession session, Entity rower) { if (isPaddlingRight) { paddleTimeRight += ROWING_SPEED; - metadata.put(EntityData.ROW_TIME_RIGHT, paddleTimeRight); - super.updateBedrockMetadata(entityMetadata, session); + sendAnimationPacket(session, rower, AnimatePacket.Action.ROW_RIGHT, paddleTimeRight); + session.getConnector().getGeneralThreadPool().schedule(() -> - updateRightPaddle(session, entityMetadata), + updateRightPaddle(session, rower), 100, TimeUnit.MILLISECONDS ); - }} + } + } + + private void sendAnimationPacket(GeyserSession session, Entity rower, AnimatePacket.Action action, float rowTime) { + AnimatePacket packet = new AnimatePacket(); + packet.setRuntimeEntityId(rower.getGeyserId()); + packet.setAction(action); + packet.setRowingTime(rowTime); + session.sendUpstreamPacket(packet); + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java index 6ae65643c..52183c431 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java @@ -31,7 +31,6 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData; import net.kyori.adventure.text.Component; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.chat.MessageTranslator; public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity { @@ -60,8 +59,8 @@ public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity { * By default, the command block shown is purple on Bedrock, which does not match Java Edition's orange. */ @Override - public void updateDefaultBlockMetadata() { - metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.BEDROCK_RUNTIME_COMMAND_BLOCK_ID); + public void updateDefaultBlockMetadata(GeyserSession session) { + metadata.put(EntityData.DISPLAY_ITEM, session.getBlockTranslator().getBedrockRuntimeCommandBlockId()); metadata.put(EntityData.DISPLAY_OFFSET, 6); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/DefaultBlockMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/DefaultBlockMinecartEntity.java index 8ab368e70..805105c64 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/DefaultBlockMinecartEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/DefaultBlockMinecartEntity.java @@ -30,7 +30,6 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; /** * This class is used as a base for minecarts with a default block to display like furnaces and spawners @@ -44,10 +43,15 @@ public class DefaultBlockMinecartEntity extends MinecartEntity { public DefaultBlockMinecartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); - updateDefaultBlockMetadata(); metadata.put(EntityData.CUSTOM_DISPLAY, (byte) 1); } + @Override + public void spawnEntity(GeyserSession session) { + updateDefaultBlockMetadata(session); + super.spawnEntity(session); + } + @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { @@ -56,7 +60,7 @@ public class DefaultBlockMinecartEntity extends MinecartEntity { customBlock = (int) entityMetadata.getValue(); if (showCustomBlock) { - metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId(customBlock)); + metadata.put(EntityData.DISPLAY_ITEM, session.getBlockTranslator().getBedrockBlockId(customBlock)); } } @@ -73,16 +77,16 @@ public class DefaultBlockMinecartEntity extends MinecartEntity { if (entityMetadata.getId() == 12) { if ((boolean) entityMetadata.getValue()) { showCustomBlock = true; - metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId(customBlock)); + metadata.put(EntityData.DISPLAY_ITEM, session.getBlockTranslator().getBedrockBlockId(customBlock)); metadata.put(EntityData.DISPLAY_OFFSET, customBlockOffset); } else { showCustomBlock = false; - updateDefaultBlockMetadata(); + updateDefaultBlockMetadata(session); } } super.updateBedrockMetadata(entityMetadata, session); } - public void updateDefaultBlockMetadata() { } + public void updateDefaultBlockMetadata(GeyserSession session) { } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index d41578db4..3ba3c9730 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -273,13 +273,9 @@ public class Entity { metadata.getFlags().setFlag(EntityFlag.SWIMMING, ((xd & 0x10) == 0x10) && metadata.getFlags().getFlag(EntityFlag.SPRINTING)); // Otherwise swimming is enabled on older servers metadata.getFlags().setFlag(EntityFlag.GLIDING, (xd & 0x80) == 0x80); - if ((xd & 0x20) == 0x20) { - // Armour stands are handled in their own class - if (!this.is(ArmorStandEntity.class)) { - metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true); - } - } else { - metadata.getFlags().setFlag(EntityFlag.INVISIBLE, false); + // Armour stands are handled in their own class + if (!this.is(ArmorStandEntity.class)) { + metadata.getFlags().setFlag(EntityFlag.INVISIBLE, (xd & 0x20) == 0x20); } // Shield code diff --git a/connector/src/main/java/org/geysermc/connector/entity/FallingBlockEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FallingBlockEntity.java index 76ca0567e..bd0fe9b80 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FallingBlockEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FallingBlockEntity.java @@ -31,14 +31,19 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; public class FallingBlockEntity extends Entity { + private final int javaId; public FallingBlockEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, int javaId) { super(entityId, geyserId, entityType, position, motion, rotation); + this.javaId = javaId; + } - this.metadata.put(EntityData.VARIANT, BlockTranslator.getBedrockBlockId(javaId)); + @Override + public void spawnEntity(GeyserSession session) { + this.metadata.put(EntityData.VARIANT, session.getBlockTranslator().getBedrockBlockId(javaId)); + super.spawnEntity(session); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.java index e3af51be6..fdf24f176 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.java @@ -44,15 +44,15 @@ public class FurnaceMinecartEntity extends DefaultBlockMinecartEntity { public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 13 && !showCustomBlock) { hasFuel = (boolean) entityMetadata.getValue(); - updateDefaultBlockMetadata(); + updateDefaultBlockMetadata(session); } super.updateBedrockMetadata(entityMetadata, session); } @Override - public void updateDefaultBlockMetadata() { - metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId(hasFuel ? BlockTranslator.JAVA_RUNTIME_FURNACE_LIT_ID : BlockTranslator.JAVA_RUNTIME_FURNACE_ID)); + public void updateDefaultBlockMetadata(GeyserSession session) { + metadata.put(EntityData.DISPLAY_ITEM, session.getBlockTranslator().getBedrockBlockId(hasFuel ? BlockTranslator.JAVA_RUNTIME_FURNACE_LIT_ID : BlockTranslator.JAVA_RUNTIME_FURNACE_ID)); metadata.put(EntityData.DISPLAY_OFFSET, 6); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java index 4f0a224e2..a898ea389 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java @@ -40,7 +40,6 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; import java.util.concurrent.TimeUnit; @@ -49,15 +48,19 @@ import java.util.concurrent.TimeUnit; */ public class ItemFrameEntity extends Entity { + /** + * Used to construct the block entity tag on spawning. + */ + private final HangingDirection direction; /** * Used for getting the Bedrock block position. * Blocks deal with integers whereas entities deal with floats. */ - private final Vector3i bedrockPosition; + private Vector3i bedrockPosition; /** * Specific block 'state' we are emulating in Bedrock. */ - private final int bedrockRuntimeId; + private int bedrockRuntimeId; /** * Rotation of item in frame. */ @@ -69,19 +72,21 @@ public class ItemFrameEntity extends Entity { public ItemFrameEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, HangingDirection direction) { super(entityId, geyserId, entityType, position, motion, rotation); - NbtMapBuilder blockBuilder = NbtMap.builder() - .putString("name", "minecraft:frame") - .putInt("version", BlockTranslator.getBlockStateVersion()); - blockBuilder.put("states", NbtMap.builder() - .putInt("facing_direction", direction.ordinal()) - .putByte("item_frame_map_bit", (byte) 0) - .build()); - bedrockRuntimeId = BlockTranslator.getItemFrame(blockBuilder.build()); - bedrockPosition = Vector3i.from(position.getFloorX(), position.getFloorY(), position.getFloorZ()); + this.direction = direction; } @Override public void spawnEntity(GeyserSession session) { + NbtMapBuilder blockBuilder = NbtMap.builder() + .putString("name", "minecraft:frame") + .putInt("version", session.getBlockTranslator().getBlockStateVersion()); + blockBuilder.put("states", NbtMap.builder() + .putInt("facing_direction", direction.ordinal()) + .putByte("item_frame_map_bit", (byte) 0) + .build()); + bedrockRuntimeId = session.getBlockTranslator().getItemFrame(blockBuilder.build()); + bedrockPosition = Vector3i.from(position.getFloorX(), position.getFloorY(), position.getFloorZ()); + session.getItemFrameCache().put(bedrockPosition, entityId); // Delay is required, or else loading in frames on chunk load is sketchy at best session.getConnector().getGeneralThreadPool().schedule(() -> { @@ -136,7 +141,7 @@ public class ItemFrameEntity extends Entity { UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); updateBlockPacket.setDataLayer(0); updateBlockPacket.setBlockPosition(bedrockPosition); - updateBlockPacket.setRuntimeId(BlockTranslator.BEDROCK_AIR_ID); + updateBlockPacket.setRuntimeId(session.getBlockTranslator().getBedrockAirId()); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); diff --git a/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java index ed5f28d17..49b12a3e1 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java @@ -30,7 +30,6 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; public class MinecartEntity extends Entity { @@ -58,7 +57,7 @@ public class MinecartEntity extends Entity { if (!(this instanceof DefaultBlockMinecartEntity)) { // Handled in the DefaultBlockMinecartEntity class // Custom block if (entityMetadata.getId() == 10) { - metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId((int) entityMetadata.getValue())); + metadata.put(EntityData.DISPLAY_ITEM, session.getBlockTranslator().getBedrockBlockId((int) entityMetadata.getValue())); } // Custom block offset diff --git a/connector/src/main/java/org/geysermc/connector/entity/SpawnerMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/SpawnerMinecartEntity.java index 143e36373..2f7af73eb 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/SpawnerMinecartEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/SpawnerMinecartEntity.java @@ -28,6 +28,7 @@ package org.geysermc.connector.entity; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; public class SpawnerMinecartEntity extends DefaultBlockMinecartEntity { @@ -37,8 +38,8 @@ public class SpawnerMinecartEntity extends DefaultBlockMinecartEntity { } @Override - public void updateDefaultBlockMetadata() { - metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId(BlockTranslator.JAVA_RUNTIME_SPAWNER_ID)); + public void updateDefaultBlockMetadata(GeyserSession session) { + metadata.put(EntityData.DISPLAY_ITEM, session.getBlockTranslator().getBedrockBlockId(BlockTranslator.JAVA_RUNTIME_SPAWNER_ID)); metadata.put(EntityData.DISPLAY_OFFSET, 6); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java index e0f4d9a11..3d1005510 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java @@ -29,6 +29,9 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; import lombok.Getter; import org.geysermc.connector.entity.LivingEntity; import org.geysermc.connector.entity.type.EntityType; @@ -42,15 +45,68 @@ public class ArmorStandEntity extends LivingEntity { private boolean isInvisible = false; private boolean isSmall = false; + /** + * On Java Edition, armor stands always show their name. Invisibility hides the name on Bedrock. + * By having a second entity, we can allow an invisible entity with the name tag. + * (This lets armor on armor stands still show) + */ + private ArmorStandEntity secondEntity = null; + /** + * Whether this is the primary armor stand that holds the armor and not the name tag. + */ + private boolean primaryEntity = true; + /** + * Whether the entity's position must be updated to included the offset. + * + * This should be true when the Java server marks the armor stand as invisible, but we shrink the entity + * to allow the nametag to appear. Basically: + * - Is visible: this is irrelevant (false) + * - Has armor, no name: false + * - Has armor, has name: false, with a second entity + * - No armor, no name: false + * - No armor, yes name: true + */ + private boolean positionRequiresOffset = false; + /** + * Whether we should update the position of this armor stand after metadata updates. + */ + private boolean positionUpdateRequired = false; + private GeyserSession session; + public ArmorStandEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } + @Override + public void spawnEntity(GeyserSession session) { + this.session = session; + this.rotation = Vector3f.from(rotation.getX(), rotation.getX(), rotation.getX()); + super.spawnEntity(session); + } + + @Override + public boolean despawnEntity(GeyserSession session) { + if (secondEntity != null) { + secondEntity.despawnEntity(session); + } + return super.despawnEntity(session); + } + + @Override + public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) { + if (secondEntity != null) { + secondEntity.moveRelative(session, relX, relY, relZ, rotation, isOnGround); + } + super.moveRelative(session, relX, relY, relZ, rotation, isOnGround); + } + @Override public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { - // Fake the height to be above where it is so the nametag appears in the right location for invisible non-marker armour stands - if (!isMarker && isInvisible && passengers.isEmpty()) { - position = position.add(0d, entityType.getHeight() * (isSmall ? 0.55d : 1d), 0d); + if (secondEntity != null) { + secondEntity.moveAbsolute(session, applyOffsetToPosition(position), rotation, isOnGround, teleported); + } else if (positionRequiresOffset) { + // Fake the height to be above where it is so the nametag appears in the right location for invisible non-marker armour stands + position = applyOffsetToPosition(position); } super.moveAbsolute(session, position, Vector3f.from(rotation.getX(), rotation.getX(), rotation.getX()), isOnGround, teleported); @@ -58,47 +114,250 @@ public class ArmorStandEntity extends LivingEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + super.updateBedrockMetadata(entityMetadata, session); if (entityMetadata.getId() == 0 && entityMetadata.getType() == MetadataType.BYTE) { byte xd = (byte) entityMetadata.getValue(); // Check if the armour stand is invisible and store accordingly - if ((xd & 0x20) == 0x20) { - metadata.put(EntityData.SCALE, 0.0f); - isInvisible = true; + if (primaryEntity) { + isInvisible = (xd & 0x20) == 0x20; + updateSecondEntityStatus(false); } + } else if (entityMetadata.getId() == 2) { + updateSecondEntityStatus(false); } else if (entityMetadata.getId() == 14 && entityMetadata.getType() == MetadataType.BYTE) { byte xd = (byte) entityMetadata.getValue(); // isSmall - if ((xd & 0x01) == 0x01) { - isSmall = true; - - if (metadata.getFloat(EntityData.SCALE) != 0.55f && metadata.getFloat(EntityData.SCALE) != 0.0f) { - metadata.put(EntityData.SCALE, 0.55f); + boolean newIsSmall = (xd & 0x01) == 0x01; + if (newIsSmall != isSmall) { + if (positionRequiresOffset) { + // Fix new inconsistency with offset + this.position = fixOffsetForSize(position, newIsSmall); + positionUpdateRequired = true; } - if (metadata.get(EntityData.BOUNDING_BOX_WIDTH) != null && metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.5f)) { - metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.25f); - metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.9875f); + isSmall = newIsSmall; + if (!isMarker) { + toggleSmallStatus(); } - } else if (metadata.get(EntityData.BOUNDING_BOX_WIDTH) != null && metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.25f)) { - metadata.put(EntityData.BOUNDING_BOX_WIDTH, entityType.getWidth()); - metadata.put(EntityData.BOUNDING_BOX_HEIGHT, entityType.getHeight()); } // setMarker - if ((xd & 0x10) == 0x10 && (metadata.get(EntityData.BOUNDING_BOX_WIDTH) == null || !metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.0f))) { - metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.0f); - metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.0f); - isMarker = true; + boolean oldIsMarker = isMarker; + isMarker = (xd & 0x10) == 0x10; + if (oldIsMarker != isMarker) { + if (isMarker) { + metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.0f); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.0f); + metadata.put(EntityData.SCALE, 0f); + } else { + toggleSmallStatus(); + } + + updateSecondEntityStatus(false); } } - super.updateBedrockMetadata(entityMetadata, session); + if (secondEntity != null) { + secondEntity.updateBedrockMetadata(entityMetadata, session); + } } @Override - public void spawnEntity(GeyserSession session) { - this.rotation = Vector3f.from(rotation.getX(), rotation.getX(), rotation.getX()); - super.spawnEntity(session); + public void updateBedrockMetadata(GeyserSession session) { + if (secondEntity != null) { + secondEntity.updateBedrockMetadata(session); + } + super.updateBedrockMetadata(session); + if (positionUpdateRequired) { + positionUpdateRequired = false; + updatePosition(); + } + } + + @Override + public void setHelmet(ItemData helmet) { + super.setHelmet(helmet); + updateSecondEntityStatus(true); + } + + @Override + public void setChestplate(ItemData chestplate) { + super.setChestplate(chestplate); + updateSecondEntityStatus(true); + } + + @Override + public void setLeggings(ItemData leggings) { + super.setLeggings(leggings); + updateSecondEntityStatus(true); + } + + @Override + public void setBoots(ItemData boots) { + super.setBoots(boots); + updateSecondEntityStatus(true); + } + + @Override + public void setHand(ItemData hand) { + super.setHand(hand); + updateSecondEntityStatus(true); + } + + @Override + public void setOffHand(ItemData offHand) { + super.setOffHand(offHand); + updateSecondEntityStatus(true); + } + + /** + * Determine if we need to load or unload the second entity. + * + * @param sendMetadata whether to send a metadata update after a change. + */ + private void updateSecondEntityStatus(boolean sendMetadata) { + // A secondary entity always has to have the offset applied, so it remains invisible and the nametag shows. + if (!primaryEntity) return; + if (!isInvisible || isMarker) { + // It is either impossible to show armor, or the armor stand isn't invisible. We good. + metadata.getFlags().setFlag(EntityFlag.INVISIBLE, false); + updateOffsetRequirement(false); + if (positionUpdateRequired) { + positionUpdateRequired = false; + updatePosition(); + } + + if (secondEntity != null) { + secondEntity.despawnEntity(session); + secondEntity = null; + } + return; + } + //boolean isNametagEmpty = metadata.getString(EntityData.NAMETAG).isEmpty() || metadata.getByte(EntityData.NAMETAG_ALWAYS_SHOW, (byte) -1) == (byte) 0; - may not be necessary? + boolean isNametagEmpty = metadata.getString(EntityData.NAMETAG).isEmpty(); + if (!isNametagEmpty && (!helmet.equals(ItemData.AIR) || !chestplate.equals(ItemData.AIR) || !leggings.equals(ItemData.AIR) + || !boots.equals(ItemData.AIR) || !hand.equals(ItemData.AIR) || !offHand.equals(ItemData.AIR))) { + // If the second entity exists, no need to recreate it. + // We can't stuff this check above or else it'll fall into another else case and delete the second entity + if (secondEntity != null) return; + + // Create the second entity. It doesn't need to worry about the items, but it does need to worry about + // the metadata as it will hold the name tag. + secondEntity = new ArmorStandEntity(0, session.getEntityCache().getNextEntityId().incrementAndGet(), + EntityType.ARMOR_STAND, position, motion, rotation); + secondEntity.primaryEntity = false; + if (!this.positionRequiresOffset) { + // Ensure the offset is applied for the 0 scale + secondEntity.position = secondEntity.applyOffsetToPosition(secondEntity.position); + } + // Copy metadata + secondEntity.isSmall = isSmall; + secondEntity.getMetadata().putAll(metadata); + // Copy the flags so they aren't the same object in memory + secondEntity.getMetadata().putFlags(metadata.getFlags().copy()); + // Guarantee this copy is NOT invisible + secondEntity.getMetadata().getFlags().setFlag(EntityFlag.INVISIBLE, false); + // Scale to 0 to show nametag + secondEntity.getMetadata().put(EntityData.SCALE, 0.0f); + // No bounding box as we don't want to interact with this entity + secondEntity.getMetadata().put(EntityData.BOUNDING_BOX_WIDTH, 0.0f); + secondEntity.getMetadata().put(EntityData.BOUNDING_BOX_HEIGHT, 0.0f); + secondEntity.spawnEntity(session); + + // Reset scale of the proper armor stand + this.metadata.put(EntityData.SCALE, isSmall ? 0.55f : 1f); + // Set the proper armor stand to invisible to show armor + this.metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true); + // Update the position of the armor stand + updateOffsetRequirement(false); + } else if (isNametagEmpty) { + // We can just make an invisible entity + // Reset scale of the proper armor stand + metadata.put(EntityData.SCALE, isSmall ? 0.55f : 1f); + // Set the proper armor stand to invisible to show armor + metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true); + // Update offset + updateOffsetRequirement(false); + + if (secondEntity != null) { + secondEntity.despawnEntity(session); + secondEntity = null; + } + } else { + // Nametag is not empty and there is no armor + // We don't need to make a new entity + metadata.getFlags().setFlag(EntityFlag.INVISIBLE, false); + metadata.put(EntityData.SCALE, 0.0f); + // As the above is applied, we need an offset + updateOffsetRequirement(true); + + if (secondEntity != null) { + secondEntity.despawnEntity(session); + secondEntity = null; + } + } + if (sendMetadata) { + this.updateBedrockMetadata(session); + } + } + + /** + * If this armor stand is not a marker, set its bounding box size and scale. + */ + private void toggleSmallStatus() { + metadata.put(EntityData.BOUNDING_BOX_WIDTH, isSmall ? 0.25f : entityType.getWidth()); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, isSmall ? 0.9875f : entityType.getHeight()); + metadata.put(EntityData.SCALE, isSmall ? 0.55f : 1f); + } + + /** + * @return the selected position with the position offset applied. + */ + private Vector3f applyOffsetToPosition(Vector3f position) { + return position.add(0d, entityType.getHeight() * (isSmall ? 0.55d : 1d), 0d); + } + + /** + * @return an adjusted offset for the new small status. + */ + private Vector3f fixOffsetForSize(Vector3f position, boolean isNowSmall) { + position = removeOffsetFromPosition(position); + return position.add(0d, entityType.getHeight() * (isNowSmall ? 0.55d : 1d), 0d); + } + + /** + * @return the selected position with the position offset removed. + */ + private Vector3f removeOffsetFromPosition(Vector3f position) { + return position.sub(0d, entityType.getHeight() * (isSmall ? 0.55d : 1d), 0d); + } + + /** + * Set the offset to a new value; if it changed, update the position, too. + */ + private void updateOffsetRequirement(boolean newValue) { + if (newValue != positionRequiresOffset) { + this.positionRequiresOffset = newValue; + if (positionRequiresOffset) { + this.position = applyOffsetToPosition(position); + } else { + this.position = removeOffsetFromPosition(position); + } + positionUpdateRequired = true; + } + } + + /** + * Updates position without calling movement code. + */ + private void updatePosition() { + MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket(); + moveEntityPacket.setRuntimeEntityId(geyserId); + moveEntityPacket.setPosition(position); + moveEntityPacket.setRotation(Vector3f.from(rotation.getX(), rotation.getX(), rotation.getX())); + moveEntityPacket.setOnGround(onGround); + moveEntityPacket.setTeleported(false); + session.sendUpstreamPacket(moveEntityPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java index 3151ae474..0d265b56e 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java @@ -33,7 +33,6 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.LevelSoundEvent2Packet; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; public class EndermanEntity extends MonsterEntity { @@ -45,7 +44,7 @@ public class EndermanEntity extends MonsterEntity { public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { // Held block if (entityMetadata.getId() == 15) { - metadata.put(EntityData.CARRIED_BLOCK, BlockTranslator.getBedrockBlockId((int) entityMetadata.getValue())); + metadata.put(EntityData.CARRIED_BLOCK, session.getBlockTranslator().getBedrockBlockId((int) entityMetadata.getValue())); } // "Is screaming" - controls sound if (entityMetadata.getId() == 16) { diff --git a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java index d24cea328..3b5af7f99 100644 --- a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java +++ b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java @@ -28,6 +28,7 @@ package org.geysermc.connector.network; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; import com.nukkitx.protocol.bedrock.v419.Bedrock_v419; import com.nukkitx.protocol.bedrock.v422.Bedrock_v422; +import com.nukkitx.protocol.bedrock.v428.Bedrock_v428; import java.util.ArrayList; import java.util.List; @@ -40,9 +41,7 @@ public class BedrockProtocol { * Default Bedrock codec that should act as a fallback. Should represent the latest available * release of the game that Geyser supports. */ - public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v422.V422_CODEC.toBuilder() - .minecraftVersion("1.16.201") - .build(); + public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v428.V428_CODEC; /** * A list of all supported Bedrock versions that can join Geyser */ @@ -52,9 +51,10 @@ public class BedrockProtocol { SUPPORTED_BEDROCK_CODECS.add(Bedrock_v419.V419_CODEC.toBuilder() .minecraftVersion("1.16.100/1.16.101") // We change this as 1.16.100.60 is a beta .build()); - SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() + SUPPORTED_BEDROCK_CODECS.add(Bedrock_v422.V422_CODEC.toBuilder() .minecraftVersion("1.16.200/1.16.201") .build()); + SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); } /** diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index d22645467..fa2670be1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -26,15 +26,18 @@ package org.geysermc.connector.network; import com.nukkitx.protocol.bedrock.BedrockPacket; -import com.nukkitx.protocol.bedrock.data.ResourcePackType; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; +import com.nukkitx.protocol.bedrock.data.ResourcePackType; import com.nukkitx.protocol.bedrock.packet.*; +import com.nukkitx.protocol.bedrock.v428.Bedrock_v428; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.cache.AdvancementsCache; import org.geysermc.connector.network.translators.PacketTranslatorRegistry; +import org.geysermc.connector.network.translators.world.block.BlockTranslator1_16_100; +import org.geysermc.connector.network.translators.world.block.BlockTranslator1_16_210; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.LoginEncryptionUtils; import org.geysermc.connector.utils.MathUtils; @@ -72,6 +75,10 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { session.getUpstream().getSession().setPacketCodec(packetCodec); + // Set the block translation based off of version + session.setBlockTranslator(packetCodec.getProtocolVersion() >= Bedrock_v428.V428_CODEC.getProtocolVersion() + ? BlockTranslator1_16_210.INSTANCE : BlockTranslator1_16_100.INSTANCE); + LoginEncryptionUtils.encryptPlayerConnection(connector, session, loginPacket); PlayStatusPacket playStatus = new PlayStatusPacket(); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 01b9452be..589214652 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -42,6 +42,7 @@ import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionRotationPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket; +import com.github.steveice10.mc.protocol.packet.login.client.LoginPluginResponsePacket; import com.github.steveice10.mc.protocol.packet.login.server.LoginSuccessPacket; import com.github.steveice10.packetlib.BuiltinFlags; import com.github.steveice10.packetlib.Client; @@ -85,6 +86,7 @@ import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.network.translators.collision.CollisionManager; import org.geysermc.connector.network.translators.inventory.EnchantmentInventoryTranslator; import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.skin.FloodgateSkinUploader; import org.geysermc.connector.skin.SkinManager; import org.geysermc.connector.utils.*; @@ -136,6 +138,12 @@ public class GeyserSession implements CommandSender { */ private final CollisionManager collisionManager; + /** + * Stores the block translations for this specific version. + */ + @Setter + private BlockTranslator blockTranslator; + private final Map skullCache = new ConcurrentHashMap<>(); private final Long2ObjectMap storedMaps = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); @@ -949,7 +957,7 @@ public class GeyserSession implements CommandSender { * @param packet the java edition packet from MCProtocolLib */ public void sendDownstreamPacket(Packet packet) { - if (downstream != null && downstream.getSession() != null && protocol.getSubProtocol().equals(SubProtocol.GAME)) { + if (downstream != null && downstream.getSession() != null && (protocol.getSubProtocol().equals(SubProtocol.GAME) || packet.getClass() == LoginPluginResponsePacket.class)) { downstream.getSession().send(packet); } else { connector.getLogger().debug("Tried to send downstream packet " + packet.getClass().getSimpleName() + " before connected to the server"); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index 8263507b2..9b8b5b668 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -108,7 +108,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator= 2 && session.getGameMode() == GameMode.CREATIVE) { // Otherwise insufficient permissions - int blockState = BlockTranslator.getJavaBlockState(packet.getBlockRuntimeId()); + int blockState = session.getBlockTranslator().getJavaBlockState(packet.getBlockRuntimeId()); String blockName = BlockTranslator.getJavaIdBlockMap().inverse().getOrDefault(blockState, ""); // In the future this can be used for structure blocks too, however not all elements // are available in each GUI @@ -253,7 +253,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator { @Override @@ -40,9 +42,12 @@ public class BedrockServerSettingsRequestTranslator extends PacketTranslator { + ServerSettingsResponsePacket serverSettingsResponsePacket = new ServerSettingsResponsePacket(); + serverSettingsResponsePacket.setFormData(window.getJsonData()); + serverSettingsResponsePacket.setFormId(windowId); + session.sendUpstreamPacket(serverSettingsResponsePacket); + }, 1, TimeUnit.SECONDS); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java index c248b57a5..e837a036a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java @@ -180,7 +180,7 @@ public class BedrockActionTranslator extends PacketTranslator 0) { canModifyBedrock = new String[canModifyJava.size()]; for (int i = 0; i < canModifyBedrock.length; i++) { @@ -185,7 +185,7 @@ public abstract class ItemTranslator { if (!block.startsWith("minecraft:")) block = "minecraft:" + block; // Get the Bedrock identifier of the item and replace it. // This will unfortunately be limited - for example, beds and banners will be translated weirdly - canModifyBedrock[i] = BlockTranslator.getBedrockBlockIdentifier(block).replace("minecraft:", ""); + canModifyBedrock[i] = session.getBlockTranslator().getBedrockBlockIdentifier(block).replace("minecraft:", ""); } } return canModifyBedrock; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkBaseTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkBaseTranslator.java new file mode 100644 index 000000000..dff40ea75 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkBaseTranslator.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2019-2021 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.connector.network.translators.item.translators.nbt; + +import com.github.steveice10.opennbt.tag.builtin.ByteArrayTag; +import com.github.steveice10.opennbt.tag.builtin.ByteTag; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.IntArrayTag; +import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; +import org.geysermc.connector.utils.FireworkColor; +import org.geysermc.connector.utils.MathUtils; + +/** + * Stores common code for firework rockets and firework stars. + */ +public abstract class FireworkBaseTranslator extends NbtItemStackTranslator { + + protected CompoundTag translateExplosionToBedrock(CompoundTag explosion, String newName) { + CompoundTag newExplosionData = new CompoundTag(newName); + + if (explosion.get("Type") != null) { + newExplosionData.put(new ByteTag("FireworkType", MathUtils.convertByte(explosion.get("Type").getValue()))); + } + + if (explosion.get("Colors") != null) { + int[] oldColors = (int[]) explosion.get("Colors").getValue(); + byte[] colors = new byte[oldColors.length]; + + int i = 0; + for (int color : oldColors) { + colors[i++] = FireworkColor.fromJavaID(color).getBedrockID(); + } + + newExplosionData.put(new ByteArrayTag("FireworkColor", colors)); + } + + if (explosion.get("FadeColors") != null) { + int[] oldColors = (int[]) explosion.get("FadeColors").getValue(); + byte[] colors = new byte[oldColors.length]; + + int i = 0; + for (int color : oldColors) { + colors[i++] = FireworkColor.fromJavaID(color).getBedrockID(); + } + + newExplosionData.put(new ByteArrayTag("FireworkFade", colors)); + } + + if (explosion.get("Trail") != null) { + newExplosionData.put(new ByteTag("FireworkTrail", MathUtils.convertByte(explosion.get("Trail").getValue()))); + } + + if (explosion.get("Flicker") != null) { + newExplosionData.put(new ByteTag("FireworkFlicker", MathUtils.convertByte(explosion.get("Flicker").getValue()))); + } + + return newExplosionData; + } + + protected CompoundTag translateExplosionToJava(CompoundTag explosion, String newName) { + CompoundTag newExplosionData = new CompoundTag(newName); + + if (explosion.get("FireworkType") != null) { + newExplosionData.put(new ByteTag("Type", MathUtils.convertByte(explosion.get("FireworkType").getValue()))); + } + + if (explosion.get("FireworkColor") != null) { + byte[] oldColors = (byte[]) explosion.get("FireworkColor").getValue(); + int[] colors = new int[oldColors.length]; + + int i = 0; + for (byte color : oldColors) { + colors[i++] = FireworkColor.fromBedrockID(color).getJavaID(); + } + + newExplosionData.put(new IntArrayTag("Colors", colors)); + } + + if (explosion.get("FireworkFade") != null) { + byte[] oldColors = (byte[]) explosion.get("FireworkFade").getValue(); + int[] colors = new int[oldColors.length]; + + int i = 0; + for (byte color : oldColors) { + colors[i++] = FireworkColor.fromBedrockID(color).getJavaID(); + } + + newExplosionData.put(new IntArrayTag("FadeColors", colors)); + } + + if (explosion.get("FireworkTrail") != null) { + newExplosionData.put(new ByteTag("Trail", MathUtils.convertByte(explosion.get("FireworkTrail").getValue()))); + } + + if (explosion.get("FireworkFlicker") != null) { + newExplosionData.put(new ByteTag("Flicker", MathUtils.convertByte(explosion.get("FireworkFlicker").getValue()))); + } + + return newExplosionData; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkRocketTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkRocketTranslator.java new file mode 100644 index 000000000..f294315c8 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkRocketTranslator.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2019-2021 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.connector.network.translators.item.translators.nbt; + +import com.github.steveice10.opennbt.tag.builtin.ByteTag; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.utils.MathUtils; + +@ItemRemapper +public class FireworkRocketTranslator extends FireworkBaseTranslator { + + @Override + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { + CompoundTag fireworks = itemTag.get("Fireworks"); + if (fireworks == null) { + return; + } + + if (fireworks.get("Flight") != null) { + fireworks.put(new ByteTag("Flight", MathUtils.convertByte(fireworks.get("Flight").getValue()))); + } + + ListTag explosions = fireworks.get("Explosions"); + if (explosions == null) { + return; + } + for (Tag effect : explosions.getValue()) { + CompoundTag effectData = (CompoundTag) effect; + CompoundTag newEffectData = translateExplosionToBedrock(effectData, ""); + + explosions.remove(effectData); + explosions.add(newEffectData); + } + } + + @Override + public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { + CompoundTag fireworks = itemTag.get("Fireworks"); + if (fireworks == null) { + return; + } + + if (fireworks.contains("Flight")) { + fireworks.put(new ByteTag("Flight", MathUtils.convertByte(fireworks.get("Flight").getValue()))); + } + + ListTag explosions = fireworks.get("Explosions"); + if (explosions == null) { + return; + } + for (Tag effect : explosions.getValue()) { + CompoundTag effectData = (CompoundTag) effect; + CompoundTag newEffectData = translateExplosionToJava(effectData, ""); + + explosions.remove(effect); + explosions.add(newEffectData); + } + } + + @Override + public boolean acceptItem(ItemEntry itemEntry) { + return "minecraft:firework_rocket".equals(itemEntry.getJavaIdentifier()); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkStarTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkStarTranslator.java new file mode 100644 index 000000000..686887b45 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkStarTranslator.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2019-2021 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.connector.network.translators.item.translators.nbt; + +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.IntArrayTag; +import com.github.steveice10.opennbt.tag.builtin.IntTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.item.ItemEntry; + +@ItemRemapper +public class FireworkStarTranslator extends FireworkBaseTranslator { + + @Override + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { + Tag explosion = itemTag.get("Explosion"); + if (explosion instanceof CompoundTag) { + CompoundTag newExplosion = translateExplosionToBedrock((CompoundTag) explosion, "FireworksItem"); + itemTag.remove("Explosion"); + itemTag.put(newExplosion); + Tag color = ((CompoundTag) explosion).get("Colors"); + if (color instanceof IntArrayTag) { + // Determine the custom color, if any. + // Mostly replicates Java's own rendering code, as Java determines the final firework star color client-side + // while Bedrock determines it server-side. + int[] colors = ((IntArrayTag) color).getValue(); + if (colors.length == 0) { + return; + } + int finalColor; + if (colors.length == 1) { + finalColor = colors[0]; + } else { + int r = 0; + int g = 0; + int b = 0; + + for (int fireworkColor : colors) { + r += (fireworkColor & (255 << 16)) >> 16; + g += (fireworkColor & (255 << 8)) >> 8; + b += fireworkColor & 255; + } + + r /= colors.length; + g /= colors.length; + b /= colors.length; + finalColor = r << 16 | g << 8 | b; + } + + itemTag.put(new IntTag("customColor", finalColor)); + } + } + } + + @Override + public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { + Tag explosion = itemTag.get("FireworksItem"); + if (explosion instanceof CompoundTag) { + CompoundTag newExplosion = translateExplosionToJava((CompoundTag) explosion, "Explosion"); + itemTag.remove("FireworksItem"); + itemTag.put(newExplosion); + } + // Remove custom color, if any, since this only exists on Bedrock + itemTag.remove("customColor"); + } + + @Override + public boolean acceptItem(ItemEntry itemEntry) { + return "minecraft:firework_star".equals(itemEntry.getJavaIdentifier()); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java deleted file mode 100644 index 8c5b74f13..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (c) 2019-2021 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.connector.network.translators.item.translators.nbt; - -import com.github.steveice10.opennbt.tag.builtin.*; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.ItemRemapper; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; -import org.geysermc.connector.utils.FireworkColor; -import org.geysermc.connector.utils.MathUtils; - -@ItemRemapper -public class FireworkTranslator extends NbtItemStackTranslator { - - @Override - public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { - if (!itemTag.contains("Fireworks")) { - return; - } - - CompoundTag fireworks = itemTag.get("Fireworks"); - if (fireworks.get("Flight") != null) { - fireworks.put(new ByteTag("Flight", MathUtils.convertByte(fireworks.get("Flight").getValue()))); - } - - ListTag explosions = fireworks.get("Explosions"); - if (explosions == null) { - return; - } - for (Tag effect : explosions.getValue()) { - CompoundTag effectData = (CompoundTag) effect; - - CompoundTag newEffectData = new CompoundTag(""); - - if (effectData.get("Type") != null) { - newEffectData.put(new ByteTag("FireworkType", MathUtils.convertByte(effectData.get("Type").getValue()))); - } - - if (effectData.get("Colors") != null) { - int[] oldColors = (int[]) effectData.get("Colors").getValue(); - byte[] colors = new byte[oldColors.length]; - - int i = 0; - for (int color : oldColors) { - colors[i++] = FireworkColor.fromJavaID(color).getBedrockID(); - } - - newEffectData.put(new ByteArrayTag("FireworkColor", colors)); - } - - if (effectData.get("FadeColors") != null) { - int[] oldColors = (int[]) effectData.get("FadeColors").getValue(); - byte[] colors = new byte[oldColors.length]; - - int i = 0; - for (int color : oldColors) { - colors[i++] = FireworkColor.fromJavaID(color).getBedrockID(); - } - - newEffectData.put(new ByteArrayTag("FireworkFade", colors)); - } - - if (effectData.get("Trail") != null) { - newEffectData.put(new ByteTag("FireworkTrail", MathUtils.convertByte(effectData.get("Trail").getValue()))); - } - - if (effectData.get("Flicker") != null) { - newEffectData.put(new ByteTag("FireworkFlicker", MathUtils.convertByte(effectData.get("Flicker").getValue()))); - } - - explosions.remove(effect); - explosions.add(newEffectData); - } - } - - @Override - public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { - if (!itemTag.contains("Fireworks")) { - return; - } - CompoundTag fireworks = itemTag.get("Fireworks"); - if (fireworks.contains("Flight")) { - fireworks.put(new ByteTag("Flight", MathUtils.convertByte(fireworks.get("Flight").getValue()))); - } - - if (!itemTag.contains("Explosions")) { - return; - } - ListTag explosions = fireworks.get("Explosions"); - for (Tag effect : explosions.getValue()) { - CompoundTag effectData = (CompoundTag) effect; - - CompoundTag newEffectData = new CompoundTag(""); - - if (effectData.get("FireworkType") != null) { - newEffectData.put(new ByteTag("Type", MathUtils.convertByte(effectData.get("FireworkType").getValue()))); - } - - if (effectData.get("FireworkColor") != null) { - byte[] oldColors = (byte[]) effectData.get("FireworkColor").getValue(); - int[] colors = new int[oldColors.length]; - - int i = 0; - for (byte color : oldColors) { - colors[i++] = FireworkColor.fromBedrockID(color).getJavaID(); - } - - newEffectData.put(new IntArrayTag("Colors", colors)); - } - - if (effectData.get("FireworkFade") != null) { - byte[] oldColors = (byte[]) effectData.get("FireworkFade").getValue(); - int[] colors = new int[oldColors.length]; - - int i = 0; - for (byte color : oldColors) { - colors[i++] = FireworkColor.fromBedrockID(color).getJavaID(); - } - - newEffectData.put(new IntArrayTag("FadeColors", colors)); - } - - if (effectData.get("FireworkTrail") != null) { - newEffectData.put(new ByteTag("Trail", MathUtils.convertByte(effectData.get("FireworkTrail").getValue()))); - } - - if (effectData.get("FireworkFlicker") != null) { - newEffectData.put(new ByteTag("Flicker", MathUtils.convertByte(effectData.get("FireworkFlicker").getValue()))); - } - - explosions.remove(effect); - explosions.add(newEffectData); - } - } - - @Override - public boolean acceptItem(ItemEntry itemEntry) { - return "minecraft:firework_rocket".equals(itemEntry.getJavaIdentifier()); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginPluginMessageTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginPluginRequestTranslator.java similarity index 92% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginPluginMessageTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginPluginRequestTranslator.java index 74583b797..9680188be 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginPluginMessageTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginPluginRequestTranslator.java @@ -33,10 +33,11 @@ import com.github.steveice10.mc.protocol.packet.login.client.LoginPluginResponse import com.github.steveice10.mc.protocol.packet.login.server.LoginPluginRequestPacket; @Translator(packet = LoginPluginRequestPacket.class) -public class JavaLoginPluginMessageTranslator extends PacketTranslator { +public class JavaLoginPluginRequestTranslator extends PacketTranslator { @Override public void translate(LoginPluginRequestPacket packet, GeyserSession session) { // A vanilla client doesn't know any PluginMessage in the Login state, so we don't know any either. + // Note: Fabric Networking API v1 will not let the client log in without sending this session.sendDownstreamPacket( new LoginPluginResponsePacket(packet.getMessageId(), null) ); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java index d74165b14..371446b7e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java @@ -84,7 +84,7 @@ public class JavaBlockChangeTranslator extends PacketTranslator DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>(); - private static final Map FLOWER_POT_BLOCKS = new HashMap<>(); private static final Int2IntMap NOTEBLOCK_PITCHES = new Int2IntOpenHashMap(); private static final Int2BooleanMap IS_STICKY_PISTON = new Int2BooleanOpenHashMap(); private static final Int2BooleanMap PISTON_VALUES = new Int2BooleanOpenHashMap(); @@ -196,15 +193,6 @@ public class BlockStateValues { return FLOWER_POT_VALUES; } - /** - * Get the map of contained flower pot plants to Bedrock CompoundTag - * - * @return Map of flower pot blocks. - */ - public static Map getFlowerPotBlocks() { - return FLOWER_POT_BLOCKS; - } - /** * 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. diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java index 1d7c86a53..64bbae210 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java @@ -34,16 +34,20 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import lombok.Getter; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.translators.world.chunk.ChunkSection; +import org.geysermc.connector.network.translators.world.chunk.EmptyChunkProvider; import org.geysermc.connector.utils.FileUtils; -import org.reflections.Reflections; import java.io.DataInputStream; import java.io.InputStream; +import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import java.util.zip.GZIPInputStream; -public class BlockTranslator { +public abstract class BlockTranslator { /** * The Java block runtime ID of air */ @@ -51,11 +55,11 @@ public class BlockTranslator { /** * The Bedrock block runtime ID of air */ - public static final int BEDROCK_AIR_ID; - public static final int BEDROCK_WATER_ID; + private final int bedrockAirId; + private final int bedrockWaterId; - private static final Int2IntMap JAVA_TO_BEDROCK_BLOCK_MAP = new Int2IntOpenHashMap(); - private static final Int2IntMap BEDROCK_TO_JAVA_BLOCK_MAP = new Int2IntOpenHashMap(); + private final Int2IntMap javaToBedrockBlockMap = new Int2IntOpenHashMap(); + private final Int2IntMap bedrockToJavaBlockMap = new Int2IntOpenHashMap(); /** * Stores a list of differences in block identifiers. * Items will not be added to this list if the key and value is the same. @@ -63,7 +67,8 @@ public class BlockTranslator { private static final Object2ObjectMap JAVA_TO_BEDROCK_IDENTIFIERS = new Object2ObjectOpenHashMap<>(); private static final BiMap JAVA_ID_BLOCK_MAP = HashBiMap.create(); private static final IntSet WATERLOGGED = new IntOpenHashSet(); - private static final Object2IntMap ITEM_FRAMES = new Object2IntOpenHashMap<>(); + private final Object2IntMap itemFrames = new Object2IntOpenHashMap<>(); + private final Map flowerPotBlocks = new HashMap<>(); // Bedrock carpet ID, used in LlamaEntity.java for decoration public static final int CARPET = 171; @@ -85,7 +90,10 @@ public class BlockTranslator { /** * Runtime command block ID, used for fixing command block minecart appearances */ - public static final int BEDROCK_RUNTIME_COMMAND_BLOCK_ID; + @Getter + private final int bedrockRuntimeCommandBlockId; + + private final EmptyChunkProvider emptyChunkProvider; /** * A list of all Java runtime wool IDs, for use with block breaking math and shears @@ -98,63 +106,30 @@ public class BlockTranslator { public static final int JAVA_RUNTIME_SPAWNER_ID; - private static final int BLOCK_STATE_VERSION = 17825808; + /** + * Stores the raw blocks JSON until it is no longer needed. + */ + public static JsonNode BLOCKS_JSON; static { - /* Load block palette */ - InputStream stream = FileUtils.getResource("bedrock/blockpalette.nbt"); - - NbtList blocksTag; - try (NBTInputStream nbtInputStream = new NBTInputStream(new DataInputStream(stream))) { - NbtMap blockPalette = (NbtMap) nbtInputStream.readTag(); - blocksTag = (NbtList) blockPalette.getList("blocks", NbtType.COMPOUND); - } catch (Exception e) { - throw new AssertionError("Unable to get blocks from runtime block states", e); - } - - // New since 1.16.100 - find the block runtime ID by the order given to us in the block palette, - // as we no longer send a block palette - Object2IntMap blockStateOrderedMap = new Object2IntOpenHashMap<>(blocksTag.size()); - - for (int i = 0; i < blocksTag.size(); i++) { - NbtMap tag = blocksTag.get(i); - NbtMap blockTag = tag.getCompound("block"); - if (blockStateOrderedMap.containsKey(blockTag)) { - throw new AssertionError("Duplicate block states in Bedrock palette"); - } - blockStateOrderedMap.put(blockTag, i); - } - - stream = FileUtils.getResource("mappings/blocks.json"); - JsonNode blocks; + InputStream stream = FileUtils.getResource("mappings/blocks.json"); try { - blocks = GeyserConnector.JSON_MAPPER.readTree(stream); + BLOCKS_JSON = GeyserConnector.JSON_MAPPER.readTree(stream); } catch (Exception e) { throw new AssertionError("Unable to load Java block mappings", e); } - Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") - : new Reflections("org.geysermc.connector.network.translators.world.block.entity"); - - int waterRuntimeId = -1; int javaRuntimeId = -1; - int airRuntimeId = -1; int cobwebRuntimeId = -1; - int commandBlockRuntimeId = -1; int furnaceRuntimeId = -1; int furnaceLitRuntimeId = -1; int spawnerRuntimeId = -1; int uniqueJavaId = -1; - Iterator> blocksIterator = blocks.fields(); + Iterator> blocksIterator = BLOCKS_JSON.fields(); while (blocksIterator.hasNext()) { javaRuntimeId++; Map.Entry entry = blocksIterator.next(); String javaId = entry.getKey(); - NbtMap blockTag = buildBedrockState(entry.getValue()); - int bedrockRuntimeId = blockStateOrderedMap.getOrDefault(blockTag, -1); - if (bedrockRuntimeId == -1) { - throw new RuntimeException("Unable to find " + javaId + " Bedrock runtime ID!"); - } // TODO fix this, (no block should have a null hardness) JsonNode hardnessNode = entry.getValue().get("block_hardness"); @@ -196,42 +171,17 @@ public class BlockTranslator { String bedrockIdentifier = entry.getValue().get("bedrock_identifier").asText(); + // Keeping this here since this is currently unchanged between versions if (!cleanJavaIdentifier.equals(bedrockIdentifier)) { JAVA_TO_BEDROCK_IDENTIFIERS.put(cleanJavaIdentifier, bedrockIdentifier); } - // Get the tag needed for non-empty flower pots - if (entry.getValue().get("pottable") != null) { - BlockStateValues.getFlowerPotBlocks().put(cleanJavaIdentifier, buildBedrockState(entry.getValue())); - } - - if ("minecraft:water[level=0]".equals(javaId)) { - waterRuntimeId = bedrockRuntimeId; - } - boolean waterlogged = entry.getKey().contains("waterlogged=true") - || javaId.contains("minecraft:bubble_column") || javaId.contains("minecraft:kelp") || javaId.contains("seagrass"); - - if (waterlogged) { - BEDROCK_TO_JAVA_BLOCK_MAP.putIfAbsent(bedrockRuntimeId | 1 << 31, javaRuntimeId); - WATERLOGGED.add(javaRuntimeId); - } else { - BEDROCK_TO_JAVA_BLOCK_MAP.putIfAbsent(bedrockRuntimeId, javaRuntimeId); - } - - JAVA_TO_BEDROCK_BLOCK_MAP.put(javaRuntimeId, bedrockRuntimeId); - - if (bedrockIdentifier.equals("minecraft:air")) { - airRuntimeId = bedrockRuntimeId; - - } else if (javaId.contains("wool")) { + if (javaId.contains("wool")) { JAVA_RUNTIME_WOOL_IDS.add(javaRuntimeId); } else if (javaId.contains("cobweb")) { cobwebRuntimeId = javaRuntimeId; - } else if (javaId.equals("minecraft:command_block[conditional=false,facing=north]")) { - commandBlockRuntimeId = bedrockRuntimeId; - } else if (javaId.startsWith("minecraft:furnace[facing=north")) { if (javaId.contains("lit=true")) { furnaceLitRuntimeId = javaRuntimeId; @@ -249,11 +199,6 @@ public class BlockTranslator { } JAVA_RUNTIME_COBWEB_ID = cobwebRuntimeId; - if (commandBlockRuntimeId == -1) { - throw new AssertionError("Unable to find command block in palette"); - } - BEDROCK_RUNTIME_COMMAND_BLOCK_ID = commandBlockRuntimeId; - if (furnaceRuntimeId == -1) { throw new AssertionError("Unable to find furnace in palette"); } @@ -269,35 +214,117 @@ public class BlockTranslator { } JAVA_RUNTIME_SPAWNER_ID = spawnerRuntimeId; + BlockTranslator1_16_100.init(); + BlockTranslator1_16_210.init(); + BLOCKS_JSON = null; // We no longer require this so let it garbage collect away + } + + public BlockTranslator(String paletteFile) { + /* Load block palette */ + InputStream stream = FileUtils.getResource(paletteFile); + + NbtList blocksTag; + try (NBTInputStream nbtInputStream = new NBTInputStream(new DataInputStream(new GZIPInputStream(stream)))) { + NbtMap blockPalette = (NbtMap) nbtInputStream.readTag(); + blocksTag = (NbtList) blockPalette.getList("blocks", NbtType.COMPOUND); + } catch (Exception e) { + throw new AssertionError("Unable to get blocks from runtime block states", e); + } + + // New since 1.16.100 - find the block runtime ID by the order given to us in the block palette, + // as we no longer send a block palette + Object2IntMap blockStateOrderedMap = new Object2IntOpenHashMap<>(blocksTag.size()); + + for (int i = 0; i < blocksTag.size(); i++) { + NbtMap tag = blocksTag.get(i); + if (blockStateOrderedMap.containsKey(tag)) { + throw new AssertionError("Duplicate block states in Bedrock palette: " + tag); + } + blockStateOrderedMap.put(tag, i); + } + + int airRuntimeId = -1; + int commandBlockRuntimeId = -1; + int javaRuntimeId = -1; + int waterRuntimeId = -1; + Iterator> blocksIterator = BLOCKS_JSON.fields(); + while (blocksIterator.hasNext()) { + javaRuntimeId++; + Map.Entry entry = blocksIterator.next(); + String javaId = entry.getKey(); + + NbtMap blockTag = buildBedrockState(entry.getValue()); + int bedrockRuntimeId = blockStateOrderedMap.getOrDefault(blockTag, -1); + if (bedrockRuntimeId == -1) { + throw new RuntimeException("Unable to find " + javaId + " Bedrock runtime ID! Built compound tag: \n" + blockTag); + } + + switch (javaId) { + case "minecraft:air": + airRuntimeId = bedrockRuntimeId; + break; + case "minecraft:water[level=0]": + waterRuntimeId = bedrockRuntimeId; + break; + case "minecraft:command_block[conditional=false,facing=north]": + commandBlockRuntimeId = bedrockRuntimeId; + break; + } + + boolean waterlogged = entry.getKey().contains("waterlogged=true") + || javaId.contains("minecraft:bubble_column") || javaId.contains("minecraft:kelp") || javaId.contains("seagrass"); + + if (waterlogged) { + bedrockToJavaBlockMap.putIfAbsent(bedrockRuntimeId | 1 << 31, javaRuntimeId); + WATERLOGGED.add(javaRuntimeId); + } else { + bedrockToJavaBlockMap.putIfAbsent(bedrockRuntimeId, javaRuntimeId); + } + + String cleanJavaIdentifier = entry.getKey().split("\\[")[0]; + + // Get the tag needed for non-empty flower pots + if (entry.getValue().get("pottable") != null) { + flowerPotBlocks.put(cleanJavaIdentifier, buildBedrockState(entry.getValue())); + } + + javaToBedrockBlockMap.put(javaRuntimeId, bedrockRuntimeId); + } + + if (commandBlockRuntimeId == -1) { + throw new AssertionError("Unable to find command block in palette"); + } + bedrockRuntimeCommandBlockId = commandBlockRuntimeId; + if (waterRuntimeId == -1) { throw new AssertionError("Unable to find water in palette"); } - BEDROCK_WATER_ID = waterRuntimeId; + bedrockWaterId = waterRuntimeId; if (airRuntimeId == -1) { throw new AssertionError("Unable to find air in palette"); } - BEDROCK_AIR_ID = airRuntimeId; + bedrockAirId = airRuntimeId; // Loop around again to find all item frame runtime IDs for (Object2IntMap.Entry entry : blockStateOrderedMap.object2IntEntrySet()) { if (entry.getKey().getString("name").equals("minecraft:frame")) { - ITEM_FRAMES.put(entry.getKey(), entry.getIntValue()); + itemFrames.put(entry.getKey(), entry.getIntValue()); } } - } - private BlockTranslator() { + this.emptyChunkProvider = new EmptyChunkProvider(bedrockAirId); } public static void init() { // no-op } - private static NbtMap buildBedrockState(JsonNode node) { + private NbtMap buildBedrockState(JsonNode node) { NbtMapBuilder tagBuilder = NbtMap.builder(); - tagBuilder.putString("name", node.get("bedrock_identifier").textValue()) - .putInt("version", BlockTranslator.BLOCK_STATE_VERSION); + String bedrockIdentifier = node.get("bedrock_identifier").textValue(); + tagBuilder.putString("name", bedrockIdentifier) + .putInt("version", getBlockStateVersion()); NbtMapBuilder statesBuilder = NbtMap.builder(); @@ -320,36 +347,67 @@ public class BlockTranslator { } } } - tagBuilder.put("states", statesBuilder.build()); + tagBuilder.put("states", adjustBlockStateForVersion(bedrockIdentifier, statesBuilder).build()); return tagBuilder.build(); } - public static int getBedrockBlockId(int state) { - return JAVA_TO_BEDROCK_BLOCK_MAP.get(state); + /** + * @return an adjusted state list, if necessary, that converts Geyser's new mapping to Bedrock's older version + * of the mapping. + */ + protected NbtMapBuilder adjustBlockStateForVersion(String bedrockIdentifier, NbtMapBuilder statesBuilder) { + return statesBuilder; } - public static int getJavaBlockState(int bedrockId) { - return BEDROCK_TO_JAVA_BLOCK_MAP.get(bedrockId); + public int getBedrockBlockId(int state) { + return javaToBedrockBlockMap.get(state); + } + + public int getJavaBlockState(int bedrockId) { + return bedrockToJavaBlockMap.get(bedrockId); } /** * @param javaIdentifier the Java identifier of the block to search for * @return the Bedrock identifier if different, or else the Java identifier */ - public static String getBedrockBlockIdentifier(String javaIdentifier) { + public String getBedrockBlockIdentifier(String javaIdentifier) { return JAVA_TO_BEDROCK_IDENTIFIERS.getOrDefault(javaIdentifier, javaIdentifier); } - public static int getItemFrame(NbtMap tag) { - return ITEM_FRAMES.getOrDefault(tag, -1); + public int getItemFrame(NbtMap tag) { + return itemFrames.getOrDefault(tag, -1); } - public static boolean isItemFrame(int bedrockBlockRuntimeId) { - return ITEM_FRAMES.values().contains(bedrockBlockRuntimeId); + public boolean isItemFrame(int bedrockBlockRuntimeId) { + return itemFrames.values().contains(bedrockBlockRuntimeId); } - public static int getBlockStateVersion() { - return BLOCK_STATE_VERSION; + /** + * Get the map of contained flower pot plants to Bedrock CompoundTag + * + * @return Map of flower pot blocks. + */ + public Map getFlowerPotBlocks() { + return flowerPotBlocks; + } + + public int getBedrockAirId() { + return bedrockAirId; + } + + public int getBedrockWaterId() { + return bedrockWaterId; + } + + public abstract int getBlockStateVersion(); + + public byte[] getEmptyChunkData() { + return emptyChunkProvider.getEmptyLevelChunkData(); + } + + public ChunkSection getEmptyChunkSection() { + return emptyChunkProvider.getEmptySection(); } /** @@ -368,10 +426,6 @@ public class BlockTranslator { return JAVA_ID_BLOCK_MAP; } - public static int getJavaWaterloggedState(int bedrockId) { - return BEDROCK_TO_JAVA_BLOCK_MAP.get(1 << 31 | bedrockId); - } - /** * Get the item a Java client would receive when pressing * the Pick Block key on a specific Java block state. diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_100.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_100.java new file mode 100644 index 000000000..e10a503ea --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_100.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019-2021 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.connector.network.translators.world.block; + +import com.google.common.collect.ImmutableSet; +import com.nukkitx.nbt.NbtMapBuilder; + +import java.util.Set; + +public class BlockTranslator1_16_100 extends BlockTranslator { + private static final Set CORRECTED_STATES = ImmutableSet.of("minecraft:stripped_warped_stem", + "minecraft:stripped_warped_hyphae", "minecraft:stripped_crimson_stem", "minecraft:stripped_crimson_hyphae"); + + public static final BlockTranslator1_16_100 INSTANCE = new BlockTranslator1_16_100(); + + public BlockTranslator1_16_100() { + super("bedrock/blockpalette.1_16_100.nbt"); + } + + @Override + public int getBlockStateVersion() { + return 17825808; + } + + @Override + protected NbtMapBuilder adjustBlockStateForVersion(String bedrockIdentifier, NbtMapBuilder statesBuilder) { + if (CORRECTED_STATES.contains(bedrockIdentifier)) { + statesBuilder.putInt("deprecated", 0); + } + return super.adjustBlockStateForVersion(bedrockIdentifier, statesBuilder); + } + + public static void init() { + // no-op + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_210.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_210.java new file mode 100644 index 000000000..58861cb9c --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_210.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019-2021 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.connector.network.translators.world.block; + +public class BlockTranslator1_16_210 extends BlockTranslator { + public static final BlockTranslator1_16_210 INSTANCE = new BlockTranslator1_16_210(); + + public BlockTranslator1_16_210() { + super("bedrock/blockpalette.1_16_210.nbt"); + } + + @Override + public int getBlockStateVersion() { + return 17879555; + } + + public static void init() { + // no-op + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java index e05fcc67b..2417f1e6e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java @@ -47,9 +47,9 @@ public interface BedrockOnlyBlockEntity { * @param blockState Java BlockState of block. * @return Bedrock tag, or null if not a Bedrock-only Block Entity */ - static NbtMap getTag(Vector3i position, int blockState) { + static NbtMap getTag(GeyserSession session, Vector3i position, int blockState) { if (FlowerPotBlockEntityTranslator.isFlowerBlock(blockState)) { - return FlowerPotBlockEntityTranslator.getTag(blockState, position); + return FlowerPotBlockEntityTranslator.getTag(session, blockState, position); } else if (PistonBlockEntityTranslator.isBlock(blockState)) { return PistonBlockEntityTranslator.getTag(blockState, position); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java index 9eebe37d7..062fd4922 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java @@ -31,7 +31,6 @@ import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.BlockEntityUtils; public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity, RequiresBlockState { @@ -50,7 +49,7 @@ public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity, R * @param position Bedrock position of flower pot. * @return Bedrock tag of flower pot. */ - public static NbtMap getTag(int blockState, Vector3i position) { + public static NbtMap getTag(GeyserSession session, int blockState, Vector3i position) { NbtMapBuilder tagBuilder = NbtMap.builder() .putInt("x", position.getX()) .putInt("y", position.getY()) @@ -62,7 +61,7 @@ public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity, R if (name != null) { // Get the Bedrock CompoundTag of the block. // This is where we need to store the *Java* name because Bedrock has six minecraft:sapling blocks with different block states. - NbtMap plant = BlockStateValues.getFlowerPotBlocks().get(name); + NbtMap plant = session.getBlockTranslator().getFlowerPotBlocks().get(name); if (plant != null) { tagBuilder.put("PlantBlock", plant.toBuilder().build()); } @@ -77,15 +76,16 @@ public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity, R @Override public void updateBlock(GeyserSession session, int blockState, Vector3i position) { - BlockEntityUtils.updateBlockEntity(session, getTag(blockState, position), position); + NbtMap tag = getTag(session, blockState, position); + BlockEntityUtils.updateBlockEntity(session, tag, position); UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); updateBlockPacket.setDataLayer(0); - updateBlockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(blockState)); + updateBlockPacket.setRuntimeId(session.getBlockTranslator().getBedrockBlockId(blockState)); updateBlockPacket.setBlockPosition(position); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); session.sendUpstreamPacket(updateBlockPacket); - BlockEntityUtils.updateBlockEntity(session, getTag(blockState, position), position); + BlockEntityUtils.updateBlockEntity(session, tag, position); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java index 7a5086241..672fa1a35 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java @@ -30,7 +30,6 @@ import io.netty.buffer.ByteBuf; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; import lombok.Getter; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArray; import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArrayVersion; @@ -44,14 +43,14 @@ public class BlockStorage { private final IntList palette; private BitArray bitArray; - public BlockStorage() { - this(BitArrayVersion.V2); + public BlockStorage(int airBlockId) { + this(airBlockId, BitArrayVersion.V2); } - public BlockStorage(BitArrayVersion version) { + public BlockStorage(int airBlockId, BitArrayVersion version) { this.bitArray = version.createArray(SIZE); this.palette = new IntArrayList(16); - this.palette.add(BlockTranslator.BEDROCK_AIR_ID); // Air is at the start of every palette and controls what the default block is in second-layer non-air block spaces. + this.palette.add(airBlockId); // Air is at the start of every palette and controls what the default block is in second-layer non-air block spaces. } public BlockStorage(BitArray bitArray, IntList palette) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkSection.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkSection.java index 2709e3e23..53528d654 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkSection.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkSection.java @@ -34,8 +34,8 @@ public class ChunkSection { private final BlockStorage[] storage; - public ChunkSection() { - this(new BlockStorage[]{new BlockStorage(), new BlockStorage()}); + public ChunkSection(int airBlockId) { + this(new BlockStorage[]{new BlockStorage(airBlockId), new BlockStorage(airBlockId)}); } public ChunkSection(BlockStorage[] storage) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/EmptyChunkProvider.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/EmptyChunkProvider.java new file mode 100644 index 000000000..1bc7d684f --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/EmptyChunkProvider.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019-2021 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.connector.network.translators.world.chunk; + +import com.nukkitx.nbt.NBTOutputStream; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtUtils; +import lombok.Getter; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class EmptyChunkProvider { + @Getter + private final byte[] emptyLevelChunkData; + @Getter + private final ChunkSection emptySection; + + public EmptyChunkProvider(int airId) { + BlockStorage emptyStorage = new BlockStorage(airId); + emptySection = new ChunkSection(new BlockStorage[]{emptyStorage}); + + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + outputStream.write(new byte[258]); // Biomes + Border Size + Extra Data Size + + try (NBTOutputStream stream = NbtUtils.createNetworkWriter(outputStream)) { + stream.writeTag(NbtMap.EMPTY); + } + + emptyLevelChunkData = outputStream.toByteArray(); + } catch (IOException e) { + throw new AssertionError("Unable to generate empty level chunk data"); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java b/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java index 96e390e75..6574c9350 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java @@ -412,15 +412,32 @@ public class SkinProvider { // if the requested image is a cape if (provider != null) { - if (image.getWidth() > 64) { - image = scale(image, 64, 32); + if (image.getWidth() > 64 || image.getHeight() > 32) { + // Prevent weirdly-scaled capes from being cut off + BufferedImage newImage = new BufferedImage(128, 64, BufferedImage.TYPE_INT_ARGB); + Graphics g = newImage.createGraphics(); + g.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null); + g.dispose(); + image.flush(); + image = scale(newImage, 64, 32); + } else if (image.getWidth() < 64 || image.getHeight() < 32) { + // Bedrock doesn't like smaller-sized capes, either. + BufferedImage newImage = new BufferedImage(64, 32, BufferedImage.TYPE_INT_ARGB); + Graphics g = newImage.createGraphics(); + g.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null); + g.dispose(); + image.flush(); + image = newImage; } } else { // Very rarely, skins can be larger than Minecraft's default. // Bedrock will not render anything above a width of 128. if (image.getWidth() > 128) { - image = scale(image, 128, image.getHeight() / (image.getWidth() / 128)); + // On Height: Scale by the amount we divided width by, or simply cut down to 128 + image = scale(image, 128, image.getHeight() >= 256 ? (image.getHeight() / (image.getWidth() / 128)) : 128); } + + // TODO remove alpha channel } byte[] data = bufferedImageToImageData(image); diff --git a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java index c859b9f65..07fe9744d 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java @@ -132,7 +132,7 @@ public class BlockUtils { miningFatigueLevel = session.getEffectCache().getEffectLevel(Effect.SLOWER_DIG); boolean isInWater = session.getConnector().getConfig().isCacheChunks() - && BlockTranslator.getBedrockBlockId(session.getConnector().getWorldManager().getBlockAt(session, session.getPlayerEntity().getPosition().toInt())) == BlockTranslator.BEDROCK_WATER_ID; + && session.getBlockTranslator().getBedrockBlockId(session.getConnector().getWorldManager().getBlockAt(session, session.getPlayerEntity().getPosition().toInt())) == session.getBlockTranslator().getBedrockWaterId(); boolean insideOfWaterWithoutAquaAffinity = isInWater && ItemUtils.getEnchantmentLevel(Optional.ofNullable(session.getInventory().getItem(5)).map(ItemStack::getNbt).orElse(null), "minecraft:aqua_affinity") < 1; diff --git a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java index e5e3f36d7..65c15eb21 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -36,9 +36,7 @@ import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.math.vector.Vector2i; import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.nbt.NBTOutputStream; import com.nukkitx.nbt.NbtMap; -import com.nukkitx.nbt.NbtUtils; import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; import com.nukkitx.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; @@ -64,13 +62,11 @@ import org.geysermc.connector.network.translators.world.chunk.ChunkSection; import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArray; import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArrayVersion; -import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.util.ArrayList; import java.util.BitSet; import java.util.List; -import static org.geysermc.connector.network.translators.world.block.BlockTranslator.*; +import static org.geysermc.connector.network.translators.world.block.BlockTranslator.JAVA_AIR_ID; @UtilityClass public class ChunkUtils { @@ -80,26 +76,6 @@ public class ChunkUtils { */ public static final Object2IntMap CACHED_BLOCK_ENTITIES = new Object2IntOpenHashMap<>(); - private static final NbtMap EMPTY_TAG = NbtMap.builder().build(); - public static final byte[] EMPTY_LEVEL_CHUNK_DATA; - - public static final BlockStorage EMPTY_STORAGE = new BlockStorage(); - public static final ChunkSection EMPTY_SECTION = new ChunkSection(new BlockStorage[]{ EMPTY_STORAGE }); - - static { - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - outputStream.write(new byte[258]); // Biomes + Border Size + Extra Data Size - - try (NBTOutputStream stream = NbtUtils.createNetworkWriter(outputStream)) { - stream.writeTag(EMPTY_TAG); - } - - EMPTY_LEVEL_CHUNK_DATA = outputStream.toByteArray(); - } catch (IOException e) { - throw new AssertionError("Unable to generate empty level chunk data"); - } - } - private static int indexYZXtoXZY(int yzx) { return (yzx >> 8) | (yzx & 0x0F0) | ((yzx & 0x00F) << 8); } @@ -161,20 +137,20 @@ public class ChunkUtils { if (javaPalette instanceof GlobalPalette) { // As this is the global palette, simply iterate through the whole chunk section once - ChunkSection section = new ChunkSection(); + ChunkSection section = new ChunkSection(session.getBlockTranslator().getBedrockAirId()); for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) { int javaId = javaData.get(yzx); - int bedrockId = BlockTranslator.getBedrockBlockId(javaId); + int bedrockId = session.getBlockTranslator().getBedrockBlockId(javaId); int xzy = indexYZXtoXZY(yzx); section.getBlockStorageArray()[0].setFullBlock(xzy, bedrockId); if (BlockTranslator.isWaterlogged(javaId)) { - section.getBlockStorageArray()[1].setFullBlock(xzy, BEDROCK_WATER_ID); + section.getBlockStorageArray()[1].setFullBlock(xzy, session.getBlockTranslator().getBedrockWaterId()); } // Check if block is piston or flower to see if we'll need to create additional block entities, as they're only block entities in Bedrock if (BlockStateValues.getFlowerPotValues().containsKey(javaId) || BlockStateValues.getPistonValues().containsKey(javaId)) { - bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag( + bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag(session, Vector3i.from((column.getX() << 4) + (yzx & 0xF), (sectionY << 4) + ((yzx >> 8) & 0xF), (column.getZ() << 4) + ((yzx >> 4) & 0xF)), javaId )); @@ -191,7 +167,7 @@ public class ChunkUtils { // Iterate through palette and convert state IDs to Bedrock, doing some additional checks as we go for (int i = 0; i < javaPalette.size(); i++) { int javaId = javaPalette.idToState(i); - bedrockPalette.add(BlockTranslator.getBedrockBlockId(javaId)); + bedrockPalette.add(session.getBlockTranslator().getBedrockBlockId(javaId)); if (BlockTranslator.isWaterlogged(javaId)) { waterloggedPaletteIds.set(i); @@ -210,7 +186,7 @@ public class ChunkUtils { for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) { int paletteId = javaData.get(yzx); if (pistonOrFlowerPaletteIds.get(paletteId)) { - bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag( + bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag(session, Vector3i.from((column.getX() << 4) + (yzx & 0xF), (sectionY << 4) + ((yzx >> 8) & 0xF), (column.getZ() << 4) + ((yzx >> 4) & 0xF)), javaPalette.idToState(paletteId) )); @@ -247,8 +223,8 @@ public class ChunkUtils { // V1 palette IntList layer1Palette = new IntArrayList(2); - layer1Palette.add(BEDROCK_AIR_ID); // Air - see BlockStorage's constructor for more information - layer1Palette.add(BEDROCK_WATER_ID); + layer1Palette.add(session.getBlockTranslator().getBedrockAirId()); // Air - see BlockStorage's constructor for more information + layer1Palette.add(session.getBlockTranslator().getBedrockWaterId()); layers = new BlockStorage[]{ layer0, new BlockStorage(BitArrayVersion.V1.createArray(BlockStorage.SIZE, layer1Data), layer1Palette) }; } @@ -368,7 +344,7 @@ public class ChunkUtils { skull.despawnEntity(session, position); } - int blockId = BlockTranslator.getBedrockBlockId(blockState); + int blockId = session.getBlockTranslator().getBedrockBlockId(blockState); UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); updateBlockPacket.setDataLayer(0); @@ -382,9 +358,9 @@ public class ChunkUtils { waterPacket.setDataLayer(1); waterPacket.setBlockPosition(position); if (BlockTranslator.isWaterlogged(blockState)) { - waterPacket.setRuntimeId(BEDROCK_WATER_ID); + waterPacket.setRuntimeId(session.getBlockTranslator().getBedrockWaterId()); } else { - waterPacket.setRuntimeId(BEDROCK_AIR_ID); + waterPacket.setRuntimeId(session.getBlockTranslator().getBedrockAirId()); } session.sendUpstreamPacket(waterPacket); @@ -417,7 +393,7 @@ public class ChunkUtils { data.setChunkX(chunkX + x); data.setChunkZ(chunkZ + z); data.setSubChunksLength(0); - data.setData(EMPTY_LEVEL_CHUNK_DATA); + data.setData(session.getBlockTranslator().getEmptyChunkData()); data.setCachingEnabled(false); session.sendUpstreamPacket(data); diff --git a/connector/src/main/resources/bedrock/blockpalette.1_16_100.nbt b/connector/src/main/resources/bedrock/blockpalette.1_16_100.nbt new file mode 100644 index 000000000..4513be031 Binary files /dev/null and b/connector/src/main/resources/bedrock/blockpalette.1_16_100.nbt differ diff --git a/connector/src/main/resources/bedrock/blockpalette.1_16_210.nbt b/connector/src/main/resources/bedrock/blockpalette.1_16_210.nbt new file mode 100644 index 000000000..178a370ed Binary files /dev/null and b/connector/src/main/resources/bedrock/blockpalette.1_16_210.nbt differ diff --git a/connector/src/main/resources/bedrock/blockpalette.nbt b/connector/src/main/resources/bedrock/blockpalette.nbt deleted file mode 100644 index b92a06260..000000000 Binary files a/connector/src/main/resources/bedrock/blockpalette.nbt and /dev/null differ diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index bffb5617c..3d3b60de7 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit bffb5617c1ecdacc10031c6ec36988a5f04cb5c6 +Subproject commit 3d3b60de724f3f552f351c5f400269fde7598b67 diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index bf0610450..ef994d8fe 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit bf0610450ce94507a18286e94af2965550ff9eaa +Subproject commit ef994d8fea421524cbc11f565a3f5ec59fc05741