From 9f5a3561807bebeb366a14776d0c9cab1fd3d8e5 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 25 Feb 2021 12:54:30 -0500 Subject: [PATCH 01/14] Armor stand fixes (#1270) Armor stands now show armor if invisible. This allows both names and armor to show on an armor stand, and should allow for custom models that use armor stands to show, to an extent. --- .../org/geysermc/connector/entity/Entity.java | 10 +- .../entity/living/ArmorStandEntity.java | 288 ++++++++++++++++-- 2 files changed, 274 insertions(+), 24 deletions(-) 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/living/ArmorStandEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java index e0f4d9a11..5543c3d5d 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,245 @@ 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 || entityMetadata.getId() == 3) { + updateSecondEntityStatus(false); } else if (entityMetadata.getId() == 14 && entityMetadata.getType() == MetadataType.BYTE) { byte xd = (byte) entityMetadata.getValue(); // isSmall - if ((xd & 0x01) == 0x01) { - isSmall = true; + boolean newIsSmall = (xd & 0x01) == 0x01; + if ((newIsSmall != isSmall) && positionRequiresOffset) { + // Fix new inconsistency with offset + this.position = fixOffsetForSize(position, newIsSmall); + positionUpdateRequired = true; + } + isSmall = newIsSmall; + if (isSmall) { - if (metadata.getFloat(EntityData.SCALE) != 0.55f && metadata.getFloat(EntityData.SCALE) != 0.0f) { + float scale = metadata.getFloat(EntityData.SCALE); + if (scale != 0.55f && scale != 0.0f) { metadata.put(EntityData.SCALE, 0.55f); } - if (metadata.get(EntityData.BOUNDING_BOX_WIDTH) != null && metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.5f)) { + if (metadata.getFloat(EntityData.BOUNDING_BOX_WIDTH) == 0.5f) { metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.25f); metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.9875f); } - } else if (metadata.get(EntityData.BOUNDING_BOX_WIDTH) != null && metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.25f)) { + } else if (metadata.getFloat(EntityData.BOUNDING_BOX_WIDTH) == 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))) { + boolean oldIsMarker = isMarker; + isMarker = (xd & 0x10) == 0x10; + if (isMarker) { metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.0f); metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.0f); - isMarker = true; + } + if (oldIsMarker != isMarker) { + 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. + 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); + } + } + + /** + * @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); } } From 325b8ab4d42e660e0d63f316ca245fe73f17f775 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 25 Feb 2021 13:12:25 -0500 Subject: [PATCH 02/14] Fix rowing from Java to Bedrock (#1943) Rowing apparently broke in possibly the 1.16 update and nobody noticed until now. --- .../geysermc/connector/entity/BoatEntity.java | 77 +++++++++++++------ 1 file changed, 54 insertions(+), 23 deletions(-) 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); + } } From ae3f50a79c4d5fe6e276bc940d80f43f7647e808 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 25 Feb 2021 13:13:24 -0500 Subject: [PATCH 03/14] Firework star item translation (#1968) Firework stars, unsurprisingly, share some code with fireworks. This commit adds a new FireworkBaseTranslator abstract class that both firework items extend from, in order to use the explosion translation code. Firework stars also show color. --- .../nbt/FireworkBaseTranslator.java | 124 +++++++++++++ .../nbt/FireworkRocketTranslator.java | 92 ++++++++++ .../nbt/FireworkStarTranslator.java | 96 ++++++++++ .../translators/nbt/FireworkTranslator.java | 164 ------------------ 4 files changed, 312 insertions(+), 164 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkBaseTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkRocketTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkStarTranslator.java delete mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java 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()); - } -} From e09f33c61482532c4503b136c0821f084aa0f0ae Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 27 Feb 2021 12:19:30 -0500 Subject: [PATCH 04/14] Clean up and fix more armor stand inconsistencies (#1988) --- .../entity/living/ArmorStandEntity.java | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) 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 5543c3d5d..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 @@ -123,43 +123,38 @@ public class ArmorStandEntity extends LivingEntity { isInvisible = (xd & 0x20) == 0x20; updateSecondEntityStatus(false); } - } else if (entityMetadata.getId() == 2 || entityMetadata.getId() == 3) { + } else if (entityMetadata.getId() == 2) { updateSecondEntityStatus(false); } else if (entityMetadata.getId() == 14 && entityMetadata.getType() == MetadataType.BYTE) { byte xd = (byte) entityMetadata.getValue(); // isSmall boolean newIsSmall = (xd & 0x01) == 0x01; - if ((newIsSmall != isSmall) && positionRequiresOffset) { - // Fix new inconsistency with offset - this.position = fixOffsetForSize(position, newIsSmall); - positionUpdateRequired = true; - } - isSmall = newIsSmall; - if (isSmall) { - - float scale = metadata.getFloat(EntityData.SCALE); - if (scale != 0.55f && scale != 0.0f) { - metadata.put(EntityData.SCALE, 0.55f); + if (newIsSmall != isSmall) { + if (positionRequiresOffset) { + // Fix new inconsistency with offset + this.position = fixOffsetForSize(position, newIsSmall); + positionUpdateRequired = true; } - if (metadata.getFloat(EntityData.BOUNDING_BOX_WIDTH) == 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.getFloat(EntityData.BOUNDING_BOX_WIDTH) == 0.25f) { - metadata.put(EntityData.BOUNDING_BOX_WIDTH, entityType.getWidth()); - metadata.put(EntityData.BOUNDING_BOX_HEIGHT, entityType.getHeight()); } // setMarker boolean oldIsMarker = isMarker; isMarker = (xd & 0x10) == 0x10; - if (isMarker) { - metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.0f); - metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.0f); - } 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); } } @@ -226,6 +221,7 @@ public class ArmorStandEntity extends LivingEntity { 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; @@ -306,6 +302,15 @@ public class ArmorStandEntity extends LivingEntity { } } + /** + * 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. */ From 88d4903fc6a62ed2cd5cc4ea014742fb97b9b2a9 Mon Sep 17 00:00:00 2001 From: SupremeMortal <6178101+SupremeMortal@users.noreply.github.com> Date: Sun, 28 Feb 2021 16:07:26 +0000 Subject: [PATCH 05/14] Update Protocol lib and fastutil (#1990) * Update protocol lib and fastutil Move away from jitpack since we store each unique snapshot build in the opencollab repo. Also, trove hasn't existed in the network repo for quite some time so the exclusion isn't needed anymore. * Store fastutil version in a property * Store adventure version in a property --- connector/pom.xml | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index 8b1bfe510..33ad39db3 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -12,6 +12,8 @@ 4.1.59.Final + 8.5.2 + 4.5.0 @@ -28,15 +30,11 @@ compile - com.github.CloudburstMC.Protocol + com.nukkitx.protocol bedrock-v422 - 294e7e5 + 2.6.2-20210228.150048-4 compile - - net.sf.trove4j - trove - com.nukkitx.network raknet @@ -44,9 +42,9 @@ - com.github.CloudburstMC.Network + com.nukkitx.network raknet - a94d2dd + 1.6.26-20210217.205834-2 compile @@ -58,61 +56,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 @@ -214,25 +212,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 From e4e9758950db1a5ce9258b7aaaca6f30efc18b3c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 2 Mar 2021 19:02:34 -0500 Subject: [PATCH 06/14] Fix logging into a server with Fabric Networking API (#1995) * Fix logging into a server with Fabric Networking API Turns out we prevented the response to LoginPluginRequestPacket. Fabric API 0.28 and later will not let a client join without this response. * Switch back to Jitpack just for Protocol to prevent weird errors * Re-add sub protocol check * Simplify check --- connector/pom.xml | 4 ++-- .../geysermc/connector/network/session/GeyserSession.java | 5 +++-- ...Translator.java => JavaLoginPluginRequestTranslator.java} | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) rename connector/src/main/java/org/geysermc/connector/network/translators/java/{JavaLoginPluginMessageTranslator.java => JavaLoginPluginRequestTranslator.java} (92%) diff --git a/connector/pom.xml b/connector/pom.xml index 33ad39db3..1ee651a2a 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -30,9 +30,9 @@ compile - com.nukkitx.protocol + com.github.CloudburstMC.Protocol bedrock-v422 - 2.6.2-20210228.150048-4 + 42da92f compile 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 ac872d909..885a910c1 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; @@ -69,10 +70,10 @@ import lombok.Setter; import org.geysermc.common.window.CustomFormWindow; import org.geysermc.common.window.FormWindow; import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.entity.Tickable; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.Tickable; import org.geysermc.connector.entity.player.SessionPlayerEntity; import org.geysermc.connector.entity.player.SkullPlayerEntity; import org.geysermc.connector.inventory.PlayerInventory; @@ -956,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/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) ); From e0e435fdc5d4906ab18e0e18f4259be8ff03413b Mon Sep 17 00:00:00 2001 From: rtm516 Date: Wed, 3 Mar 2021 23:00:29 +0000 Subject: [PATCH 07/14] Fix capes being scaled wrong and invisible skins (#1940) * Fix capes being scaled wrong and invisible skins * Flush old image objects * Remove alpha workaround and fix more scaling issues * Remove unnecessary scale * Reduce diff Co-authored-by: Camotoy <20743703+Camotoy@users.noreply.github.com> --- .../org/geysermc/connector/skin/SkinProvider.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) 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 c4d4bc486..61c28addb 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java @@ -413,14 +413,23 @@ public class SkinProvider { // if the requested image is a cape if (provider != null) { if (image.getWidth() > 64) { - image = scale(image, 64, 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 { // 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); From f926b83b33f416d2645febfecd131a8cff1788bc Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 4 Mar 2021 11:32:56 -0500 Subject: [PATCH 08/14] Fix capes of a *smaller* size throwing an error (#1998) --- .../java/org/geysermc/connector/skin/SkinProvider.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 61c28addb..745a23ebd 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java @@ -412,7 +412,7 @@ public class SkinProvider { // if the requested image is a cape if (provider != null) { - if (image.getWidth() > 64) { + 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(); @@ -420,6 +420,14 @@ public class SkinProvider { 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. From 457d7a9fb5409adb21e961fd39d67582452c69ff Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 6 Mar 2021 13:18:54 -0500 Subject: [PATCH 09/14] Remove MD-5 repository (#2007) --- bootstrap/pom.xml | 4 ---- 1 file changed, 4 deletions(-) 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/ From f7130d2fe16aa8d660538f9206a780c408e9ab2d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 6 Mar 2021 13:35:17 -0500 Subject: [PATCH 10/14] Update languages submodule (#2002) --- connector/src/main/resources/languages | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From e4cff743efc777503132e3c938907c27afd1449e Mon Sep 17 00:00:00 2001 From: SupremeMortal <6178101+SupremeMortal@users.noreply.github.com> Date: Sat, 6 Mar 2021 18:40:37 +0000 Subject: [PATCH 11/14] Use authenticated resolver for jenkins builds (#2008) Allows us to cache dependencies for faster resolution whilst building. --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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', From efc7e43e02c5f6882fc6d8659ad8e1f8eb46c657 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Mon, 8 Mar 2021 22:00:44 +0000 Subject: [PATCH 12/14] Fix settings not displaying due to bedrock bug by delaying 1s (#2010) * Fix settings not displaying due to bedrock bug by delaying 1s * Update BedrockServerSettingsRequestTranslator.java --- .../BedrockServerSettingsRequestTranslator.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java index 997bba8aa..3f3226280 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java @@ -32,6 +32,8 @@ import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.utils.SettingsUtils; +import java.util.concurrent.TimeUnit; + @Translator(packet = ServerSettingsRequestPacket.class) public class BedrockServerSettingsRequestTranslator extends PacketTranslator { @@ -39,9 +41,12 @@ public class BedrockServerSettingsRequestTranslator extends PacketTranslator { + ServerSettingsResponsePacket serverSettingsResponsePacket = new ServerSettingsResponsePacket(); + serverSettingsResponsePacket.setFormData(session.getSettingsForm().getJSONData()); + serverSettingsResponsePacket.setFormId(SettingsUtils.SETTINGS_FORM_ID); + session.sendUpstreamPacket(serverSettingsResponsePacket); + }, 1, TimeUnit.SECONDS); } } From 9ae7c1de252102967a13d9a445dc2fcc151c33d8 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 9 Mar 2021 12:51:48 -0500 Subject: [PATCH 13/14] 1.16.210 support (#2019) This commit implements 1.16.210 support while still keeping 1.16.100 and 1.16.210 compatibility. Co-authored-by: AJ Ferguson Co-authored-by: rtm516 --- .../world/GeyserSpigotBlockPlaceListener.java | 4 +- connector/pom.xml | 2 +- .../entity/CommandBlockMinecartEntity.java | 5 +- .../entity/DefaultBlockMinecartEntity.java | 16 +- .../connector/entity/FallingBlockEntity.java | 9 +- .../entity/FurnaceMinecartEntity.java | 6 +- .../connector/entity/ItemFrameEntity.java | 31 ++- .../connector/entity/MinecartEntity.java | 3 +- .../entity/SpawnerMinecartEntity.java | 5 +- .../entity/living/monster/EndermanEntity.java | 3 +- .../connector/network/BedrockProtocol.java | 8 +- .../network/UpstreamPacketHandler.java | 9 +- .../network/session/GeyserSession.java | 7 + ...BedrockInventoryTransactionTranslator.java | 10 +- .../player/BedrockActionTranslator.java | 2 +- .../inventory/BlockInventoryTranslator.java | 3 +- .../DoubleChestInventoryTranslator.java | 14 +- .../SingleChestInventoryTranslator.java | 2 +- .../holder/BlockInventoryHolder.java | 7 +- .../translators/item/ItemTranslator.java | 10 +- .../java/world/JavaBlockChangeTranslator.java | 2 +- .../java/world/JavaChunkDataTranslator.java | 4 +- .../world/JavaPlayBuiltinSoundTranslator.java | 2 +- .../java/world/JavaPlayEffectTranslator.java | 4 +- .../world/JavaSpawnParticleTranslator.java | 19 +- .../block/GrassPathInteractionHandler.java | 2 +- .../sound/block/HoeInteractionHandler.java | 2 +- .../world/block/BlockStateValues.java | 12 - .../world/block/BlockTranslator.java | 262 +++++++++++------- .../world/block/BlockTranslator1_16_100.java | 59 ++++ .../world/block/BlockTranslator1_16_210.java | 43 +++ .../block/entity/BedrockOnlyBlockEntity.java | 4 +- .../FlowerPotBlockEntityTranslator.java | 12 +- .../translators/world/chunk/BlockStorage.java | 9 +- .../translators/world/chunk/ChunkSection.java | 4 +- .../world/chunk/EmptyChunkProvider.java | 58 ++++ .../geysermc/connector/utils/BlockUtils.java | 2 +- .../geysermc/connector/utils/ChunkUtils.java | 50 +--- .../bedrock/blockpalette.1_16_100.nbt | Bin 0 -> 104176 bytes .../bedrock/blockpalette.1_16_210.nbt | Bin 0 -> 34796 bytes .../main/resources/bedrock/blockpalette.nbt | Bin 1160789 -> 0 bytes connector/src/main/resources/mappings | 2 +- 42 files changed, 454 insertions(+), 254 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_100.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_210.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/EmptyChunkProvider.java create mode 100644 connector/src/main/resources/bedrock/blockpalette.1_16_100.nbt create mode 100644 connector/src/main/resources/bedrock/blockpalette.1_16_210.nbt delete mode 100644 connector/src/main/resources/bedrock/blockpalette.nbt 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 1ee651a2a..fb253117c 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -31,7 +31,7 @@ com.github.CloudburstMC.Protocol - bedrock-v422 + bedrock-v428 42da92f compile 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/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/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 7ebfaeda5..829ae23ef 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.*; import java.io.FileInputStream; @@ -68,6 +71,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 885a910c1..adba703a7 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 @@ -88,6 +88,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.SkinManager; import org.geysermc.connector.utils.*; import org.geysermc.floodgate.util.BedrockData; @@ -138,6 +139,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<>()); 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 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/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/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 0000000000000000000000000000000000000000..4513be031b02c9a0b67871b36f14bb136ff30e04 GIT binary patch literal 104176 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 C)))!^ literal 0 HcmV?d00001 diff --git a/connector/src/main/resources/bedrock/blockpalette.nbt b/connector/src/main/resources/bedrock/blockpalette.nbt deleted file mode 100644 index b92a06260912dfd8e741a6c8f559af277d6c9775..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1160789 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 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 From f0e6e55565c4b31599b80fff93b547a9b59697b0 Mon Sep 17 00:00:00 2001 From: YHDiamond <47502993+YHDiamond@users.noreply.github.com> Date: Tue, 9 Mar 2021 13:15:52 -0500 Subject: [PATCH 14/14] Update README.md (#2020) Co-authored-by: yehudahrrs <47502993+yehudahrrs@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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.