diff --git a/README.md b/README.md index bc267f06f..a51c61f9f 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,8 @@ The following things cannot be fixed without changes to Bedrock. As of now, they - Custom heads in inventories - Clickable links in chat - Glowing effect -- Custom armor stand poses + +Do note that some things require the [GeyserOptionalPack](https://github.com/GeyserMC/Geyser/wiki/GeyserOptionalPack) in order to function, such as custom armor stand poses. ## Compiling 1. Clone the repo to your computer diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 03bf7538c..d2caac216 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -188,7 +188,7 @@ public class GeyserConnector { defaultAuthType = AuthType.getByName(config.getRemote().getAuthType()); - CooldownUtils.setShowCooldown(config.getShowCooldown()); + CooldownUtils.setDefaultShowCooldown(config.getShowCooldown()); DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether SkullBlockEntityTranslator.ALLOW_CUSTOM_SKULLS = config.isAllowCustomSkulls(); diff --git a/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java b/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java index 70dbdf959..25e8d37a1 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java @@ -36,6 +36,9 @@ public class AbstractArrowEntity extends Entity { public AbstractArrowEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); + // Set the correct texture if using the resource pack + metadata.getFlags().setFlag(EntityFlag.BRIBED, entityType == EntityType.SPECTRAL_ARROW); + setMotion(motion); } 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 e10ad0afd..79711b0cb 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java @@ -35,6 +35,7 @@ import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; +import lombok.Getter; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemEntry; @@ -69,6 +70,11 @@ public class ItemFrameEntity extends Entity { * Cached item frame's Bedrock compound tag. */ private NbtMap cachedTag; + /** + * The item currently in the item frame. Used for block picking. + */ + @Getter + private ItemStack heldItem = null; public ItemFrameEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, HangingDirection direction) { super(entityId, geyserId, entityType, position, motion, rotation); @@ -87,7 +93,8 @@ public class ItemFrameEntity extends Entity { bedrockRuntimeId = session.getBlockTranslator().getItemFrame(blockBuilder.build()); bedrockPosition = Vector3i.from(position.getFloorX(), position.getFloorY(), position.getFloorZ()); - session.getItemFrameCache().put(bedrockPosition, entityId); + session.getItemFrameCache().put(bedrockPosition, this); + // Delay is required, or else loading in frames on chunk load is sketchy at best session.getConnector().getGeneralThreadPool().schedule(() -> { updateBlock(session); @@ -99,13 +106,14 @@ public class ItemFrameEntity extends Entity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 7 && entityMetadata.getValue() != null) { - ItemData itemData = ItemTranslator.translateToBedrock(session, (ItemStack) entityMetadata.getValue()); + this.heldItem = (ItemStack) entityMetadata.getValue(); + ItemData itemData = ItemTranslator.translateToBedrock(session, heldItem); ItemEntry itemEntry = ItemRegistry.getItem((ItemStack) entityMetadata.getValue()); NbtMapBuilder builder = NbtMap.builder(); builder.putByte("Count", (byte) itemData.getCount()); if (itemData.getTag() != null) { - builder.put("tag", itemData.getTag().toBuilder().build()); + builder.put("tag", itemData.getTag()); } builder.putShort("Damage", (short) itemData.getDamage()); builder.putString("Name", itemEntry.getBedrockIdentifier()); @@ -146,7 +154,9 @@ public class ItemFrameEntity extends Entity { updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); session.sendUpstreamPacket(updateBlockPacket); - session.getItemFrameCache().remove(position, entityId); + + session.getItemFrameCache().remove(bedrockPosition, this); + valid = false; return true; } @@ -192,16 +202,7 @@ public class ItemFrameEntity extends Entity { * @param session GeyserSession. * @return Java entity ID or -1 if not found. */ - public static long getItemFrameEntityId(GeyserSession session, Vector3i position) { - return session.getItemFrameCache().getOrDefault(position, -1); - } - - /** - * Force-remove from the position-to-ID map so it doesn't cause conflicts. - * @param session GeyserSession. - * @param position position of the removed item frame. - */ - public static void removePosition(GeyserSession session, Vector3i position) { - session.getItemFrameCache().remove(position); + public static ItemFrameEntity getItemFrameEntity(GeyserSession session, Vector3i position) { + return session.getItemFrameCache().get(position); } } 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 49b12a3e1..b66b049eb 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java @@ -79,4 +79,10 @@ public class MinecartEntity extends Entity { public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { super.moveAbsolute(session, position.add(0d, this.entityType.getOffset(), 0d), rotation, isOnGround, teleported); } + + @Override + public Vector3f getBedrockRotation() { + // Note: minecart rotation on rails does not care about the actual rotation value + return Vector3f.from(0, rotation.getX(), 0); + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/attribute/Attribute.java b/connector/src/main/java/org/geysermc/connector/entity/attribute/Attribute.java index 2a2d47ba9..9cb803898 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/attribute/Attribute.java +++ b/connector/src/main/java/org/geysermc/connector/entity/attribute/Attribute.java @@ -28,10 +28,12 @@ package org.geysermc.connector.entity.attribute; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; +import lombok.ToString; @Getter @Setter @AllArgsConstructor +@ToString public class Attribute { private AttributeType type; diff --git a/connector/src/main/java/org/geysermc/connector/entity/attribute/AttributeType.java b/connector/src/main/java/org/geysermc/connector/entity/attribute/AttributeType.java index ccd0bcb5b..6877bb7c6 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/attribute/AttributeType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/attribute/AttributeType.java @@ -57,12 +57,12 @@ public enum AttributeType { HUNGER(null, "minecraft:player.hunger", 0f, 20f, 20f), SATURATION(null, "minecraft:player.saturation", 0f, 20f, 20f); - private String javaIdentifier; - private String bedrockIdentifier; + private final String javaIdentifier; + private final String bedrockIdentifier; - private float minimum; - private float maximum; - private float defaultValue; + private final float minimum; + private final float maximum; + private final float defaultValue; public Attribute getAttribute(float value) { return getAttribute(value, maximum); 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 3d1005510..13f8a4c1d 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 @@ -27,6 +27,7 @@ package org.geysermc.connector.entity.living; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Rotation; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; @@ -157,6 +158,72 @@ public class ArmorStandEntity extends LivingEntity { updateSecondEntityStatus(false); } + + // The following values don't do anything on normal Bedrock. + // But if given a resource pack, then we can use these values to control armor stand visual properties + metadata.getFlags().setFlag(EntityFlag.ANGRY, (xd & 0x04) != 0x04); // Has arms + metadata.getFlags().setFlag(EntityFlag.ADMIRING, (xd & 0x08) == 0x08); // Has no baseplate + } else { + EntityData dataLeech = null; + EntityFlag negativeXToggle = null; + EntityFlag negativeYToggle = null; + EntityFlag negativeZToggle = null; + switch (entityMetadata.getId()) { + case 15: // Head + dataLeech = EntityData.MARK_VARIANT; + negativeXToggle = EntityFlag.INTERESTED; + negativeYToggle = EntityFlag.CHARGED; + negativeZToggle = EntityFlag.POWERED; + break; + case 16: // Body + dataLeech = EntityData.VARIANT; + negativeXToggle = EntityFlag.IN_LOVE; + negativeYToggle = EntityFlag.CELEBRATING; + negativeZToggle = EntityFlag.CELEBRATING_SPECIAL; + break; + case 17: // Left arm + dataLeech = EntityData.TRADE_TIER; + negativeXToggle = EntityFlag.CHARGING; + negativeYToggle = EntityFlag.CRITICAL; + negativeZToggle = EntityFlag.DANCING; + break; + case 18: // Right arm + dataLeech = EntityData.MAX_TRADE_TIER; + negativeXToggle = EntityFlag.ELDER; + negativeYToggle = EntityFlag.EMOTING; + negativeZToggle = EntityFlag.IDLING; + break; + case 19: // Left leg + dataLeech = EntityData.SKIN_ID; + negativeXToggle = EntityFlag.IS_ILLAGER_CAPTAIN; + negativeYToggle = EntityFlag.IS_IN_UI; + negativeZToggle = EntityFlag.LINGERING; + break; + case 20: // Right leg + dataLeech = EntityData.HURT_DIRECTION; + negativeXToggle = EntityFlag.IS_PREGNANT; + negativeYToggle = EntityFlag.SHEARED; + negativeZToggle = EntityFlag.STALKING; + break; + } + if (dataLeech != null) { + // Indicate that rotation should be checked + metadata.getFlags().setFlag(EntityFlag.BRIBED, true); + + Rotation rotation = (Rotation) entityMetadata.getValue(); + int rotationX = getRotation(rotation.getPitch()); + int rotationY = getRotation(rotation.getYaw()); + int rotationZ = getRotation(rotation.getRoll()); + // The top bit acts like binary and determines if each rotation goes above 100 + // We don't do this for the negative values out of concerns of the number being too big + int topBit = (Math.abs(rotationX) >= 100 ? 4 : 0) + (Math.abs(rotationY) >= 100 ? 2 : 0) + (Math.abs(rotationZ) >= 100 ? 1 : 0); + int value = (topBit * 1000000) + ((Math.abs(rotationX) % 100) * 10000) + ((Math.abs(rotationY) % 100) * 100) + (Math.abs(rotationZ) % 100); + metadata.put(dataLeech, value); + // Set the entity flags if a value is negative + metadata.getFlags().setFlag(negativeXToggle, rotationX < 0); + metadata.getFlags().setFlag(negativeYToggle, rotationY < 0); + metadata.getFlags().setFlag(negativeZToggle, rotationZ < 0); + } } if (secondEntity != null) { secondEntity.updateBedrockMetadata(entityMetadata, session); @@ -302,6 +369,17 @@ public class ArmorStandEntity extends LivingEntity { } } + private int getRotation(float rotation) { + rotation = rotation % 360f; + if (rotation < -180f) { + rotation += 360f; + } else if (rotation >= 180f) { + // 181 -> -179 + rotation = -(180 - (rotation - 180)); + } + return (int) rotation; + } + /** * If this armor stand is not a marker, set its bounding box size and scale. */ diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/IronGolemEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/IronGolemEntity.java new file mode 100644 index 000000000..5fca355f5 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/IronGolemEntity.java @@ -0,0 +1,80 @@ +/* + * 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.entity.living; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.AttributeData; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; +import org.geysermc.connector.entity.attribute.AttributeType; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.AttributeUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class IronGolemEntity extends GolemEntity { + + public IronGolemEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + // Indicate that we should show cracks through a resource pack + metadata.getFlags().setFlag(EntityFlag.BRIBED, true); + // Required, or else the overlay is black + metadata.put(EntityData.COLOR_2, (byte) 0); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + super.updateBedrockMetadata(entityMetadata, session); + if (entityMetadata.getId() == 8) { + // Required so the resource pack sees the entity health + attributes.put(AttributeType.HEALTH, AttributeType.HEALTH.getAttribute(metadata.getFloat(EntityData.HEALTH), 100f)); + updateBedrockAttributes(session); + } + } + + @Override + public void updateBedrockAttributes(GeyserSession session) { + if (!valid) return; + + List attributes = new ArrayList<>(); + for (Map.Entry entry : this.attributes.entrySet()) { + if (!entry.getValue().getType().isBedrockAttribute()) + continue; + + attributes.add(AttributeUtils.getBedrockAttribute(entry.getValue())); + } + + UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket(); + updateAttributesPacket.setRuntimeEntityId(geyserId); + updateAttributesPacket.setAttributes(attributes); + session.sendUpstreamPacket(updateAttributesPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/AnimalEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/AnimalEntity.java index 075690662..efded1f6f 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/AnimalEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/AnimalEntity.java @@ -28,6 +28,8 @@ package org.geysermc.connector.entity.living.animal; import com.nukkitx.math.vector.Vector3f; import org.geysermc.connector.entity.living.AgeableEntity; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; public class AnimalEntity extends AgeableEntity { @@ -40,7 +42,7 @@ public class AnimalEntity extends AgeableEntity { * wheat. * @return true if this is a valid item to breed with for this animal. */ - public boolean canEat(String javaIdentifierStripped) { + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { // This is what it defaults to. OK. return javaIdentifierStripped.equals("wheat"); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java index d076b8a6f..b2d5faaad 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java @@ -26,7 +26,6 @@ package org.geysermc.connector.entity.living.animal; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.google.common.collect.ImmutableSet; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; @@ -34,16 +33,9 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; - -import java.util.Set; +import org.geysermc.connector.network.translators.item.ItemEntry; public class BeeEntity extends AnimalEntity { - /** - * A list of all flowers. Used for feeding bees. - */ - private static final Set FLOWERS = ImmutableSet.of("dandelion", "poppy", "blue_orchid", "allium", "azure_bluet", - "red_tulip", "pink_tulip", "white_tulip", "orange_tulip", "cornflower", "lily_of_the_valley", "wither_rose", - "sunflower", "lilac", "rose_bush", "peony"); public BeeEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); @@ -74,7 +66,7 @@ public class BeeEntity extends AnimalEntity { } @Override - public boolean canEat(String javaIdentifierStripped) { - return FLOWERS.contains(javaIdentifierStripped); + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { + return session.getTagCache().isFlower(itemEntry); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/ChickenEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/ChickenEntity.java index 66e8f9ac8..b5f0395d4 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/ChickenEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/ChickenEntity.java @@ -27,6 +27,8 @@ package org.geysermc.connector.entity.living.animal; import com.nukkitx.math.vector.Vector3f; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; public class ChickenEntity extends AnimalEntity { @@ -35,7 +37,7 @@ public class ChickenEntity extends AnimalEntity { } @Override - public boolean canEat(String javaIdentifierStripped) { + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { return javaIdentifierStripped.contains("seeds"); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java index bfa376272..ff71e87f0 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java @@ -31,6 +31,7 @@ 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.item.ItemEntry; public class FoxEntity extends AnimalEntity { @@ -54,7 +55,7 @@ public class FoxEntity extends AnimalEntity { } @Override - public boolean canEat(String javaIdentifierStripped) { + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { return javaIdentifierStripped.equals("sweet_berries"); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java index b915c07de..9fbb17725 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java @@ -30,6 +30,7 @@ import com.nukkitx.math.vector.Vector3f; 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.item.ItemEntry; import org.geysermc.connector.utils.DimensionUtils; public class HoglinEntity extends AnimalEntity { @@ -49,7 +50,7 @@ public class HoglinEntity extends AnimalEntity { } @Override - public boolean canEat(String javaIdentifierStripped) { + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { return javaIdentifierStripped.equals("crimson_fungus"); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/OcelotEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/OcelotEntity.java index 7c7d74770..1cf541c8c 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/OcelotEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/OcelotEntity.java @@ -30,6 +30,7 @@ import com.nukkitx.math.vector.Vector3f; 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.item.ItemEntry; public class OcelotEntity extends AnimalEntity { @@ -46,7 +47,7 @@ public class OcelotEntity extends AnimalEntity { } @Override - public boolean canEat(String javaIdentifierStripped) { + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { return javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon"); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java index 9431d66a0..2f5ced080 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java @@ -33,6 +33,7 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; public class PandaEntity extends AnimalEntity { @@ -80,7 +81,7 @@ public class PandaEntity extends AnimalEntity { } @Override - public boolean canEat(String javaIdentifierStripped) { + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { return javaIdentifierStripped.equals("bamboo"); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java index 551e1c345..bbb1aed20 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java @@ -30,6 +30,7 @@ import com.nukkitx.math.vector.Vector3f; 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.item.ItemEntry; public class PigEntity extends AnimalEntity { @@ -47,7 +48,7 @@ public class PigEntity extends AnimalEntity { } @Override - public boolean canEat(String javaIdentifierStripped) { + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { return javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("potato") || javaIdentifierStripped.equals("beetroot"); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PolarBearEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PolarBearEntity.java index 7b5d42f35..f33635aeb 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PolarBearEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PolarBearEntity.java @@ -30,6 +30,7 @@ import com.nukkitx.math.vector.Vector3f; 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.item.ItemEntry; public class PolarBearEntity extends AnimalEntity { @@ -46,7 +47,7 @@ public class PolarBearEntity extends AnimalEntity { } @Override - public boolean canEat(String javaIdentifierStripped) { + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { return false; } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java index a789b48e4..9a4691cc0 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java @@ -31,6 +31,7 @@ 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.item.ItemEntry; public class RabbitEntity extends AnimalEntity { @@ -52,16 +53,19 @@ public class RabbitEntity extends AnimalEntity { int variant = (int) entityMetadata.getValue(); // Change the killer bunny to display as white since it only exists on Java Edition - if (variant == 99) { + boolean isKillerBunny = variant == 99; + if (isKillerBunny) { variant = 1; } + // Allow the resource pack to adjust to the killer bunny + metadata.getFlags().setFlag(EntityFlag.BRIBED, isKillerBunny); metadata.put(EntityData.VARIANT, variant); } } @Override - public boolean canEat(String javaIdentifierStripped) { + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { return javaIdentifierStripped.equals("dandelion") || javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("golden_carrot"); } } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java index 2a99a5a03..a90a044bc 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/StriderEntity.java @@ -31,6 +31,7 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; public class StriderEntity extends AnimalEntity { @@ -87,7 +88,7 @@ public class StriderEntity extends AnimalEntity { } @Override - public boolean canEat(String javaIdentifierStripped) { + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { return javaIdentifierStripped.equals("warped_fungus"); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java index 89df815d7..536f40755 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java @@ -30,6 +30,7 @@ import com.nukkitx.math.vector.Vector3f; 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.item.ItemEntry; public class TurtleEntity extends AnimalEntity { @@ -48,7 +49,7 @@ public class TurtleEntity extends AnimalEntity { } @Override - public boolean canEat(String javaIdentifierStripped) { + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { return javaIdentifierStripped.equals("seagrass"); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java index d2f89e101..324c8229f 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java @@ -37,6 +37,7 @@ import org.geysermc.connector.entity.attribute.AttributeType; import org.geysermc.connector.entity.living.animal.AnimalEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; import java.util.Set; @@ -112,7 +113,7 @@ public class AbstractHorseEntity extends AnimalEntity { } @Override - public boolean canEat(String javaIdentifierStripped) { + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { return DONKEY_AND_HORSE_FOODS.contains(javaIdentifierStripped); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java index 0b21c771e..8ab1df1a8 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java @@ -32,6 +32,7 @@ import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.MobArmorEquipmentPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; public class LlamaEntity extends ChestedHorseEntity { @@ -77,7 +78,7 @@ public class LlamaEntity extends ChestedHorseEntity { } @Override - public boolean canEat(String javaIdentifierStripped) { + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { return javaIdentifierStripped.equals("wheat") || javaIdentifierStripped.equals("hay_block"); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java index 7866d25cf..2d56d0c18 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java @@ -31,6 +31,7 @@ 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.item.ItemEntry; public class CatEntity extends TameableEntity { @@ -85,7 +86,7 @@ public class CatEntity extends TameableEntity { } @Override - public boolean canEat(String javaIdentifierStripped) { + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { return javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon"); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/ParrotEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/ParrotEntity.java index 50ec9ed04..45327c785 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/ParrotEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/ParrotEntity.java @@ -30,6 +30,7 @@ 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.item.ItemEntry; public class ParrotEntity extends TameableEntity { @@ -47,7 +48,7 @@ public class ParrotEntity extends TameableEntity { } @Override - public boolean canEat(String javaIdentifierStripped) { + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { return javaIdentifierStripped.contains("seeds") || javaIdentifierStripped.equals("cookie"); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java index 91350ef54..bb7916937 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java @@ -32,6 +32,7 @@ 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.item.ItemEntry; import java.util.Set; @@ -87,7 +88,7 @@ public class WolfEntity extends TameableEntity { } @Override - public boolean canEat(String javaIdentifierStripped) { + public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { // Cannot be a baby to eat these foods return WOLF_FOODS.contains(javaIdentifierStripped) && !metadata.getFlags().getFlag(EntityFlag.BABY); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java index e6e509b11..44fa2c49d 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java @@ -61,7 +61,7 @@ public class PiglinEntity extends BasePiglinEntity { @Override public void updateOffHand(GeyserSession session) { // Check if the Piglin is holding Gold and set the ADMIRING flag accordingly so its pose updates - boolean changed = metadata.getFlags().setFlag(EntityFlag.ADMIRING, offHand.getId() == ItemRegistry.GOLD.getBedrockId()); + boolean changed = metadata.getFlags().setFlag(EntityFlag.ADMIRING, session.getTagCache().shouldPiglinAdmire(ItemRegistry.getItem(this.offHand))); if (changed) { super.updateBedrockMetadata(session); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java index f31dde69c..b99f66ac8 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java @@ -31,6 +31,7 @@ import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.connector.entity.living.GolemEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -39,6 +40,8 @@ public class ShulkerEntity extends GolemEntity { public ShulkerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); + // Indicate that invisibility should be fixed through the resource pack + metadata.getFlags().setFlag(EntityFlag.BRIBED, true); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/SpellcasterIllagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/SpellcasterIllagerEntity.java index d43bd8fb7..6d4500a1c 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/SpellcasterIllagerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/SpellcasterIllagerEntity.java @@ -39,6 +39,8 @@ public class SpellcasterIllagerEntity extends AbstractIllagerEntity { public SpellcasterIllagerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); + // OptionalPack usage + metadata.getFlags().setFlag(EntityFlag.BRIBED, this.entityType == EntityType.ILLUSIONER); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java index b8be69ab3..f8eeef307 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java @@ -82,6 +82,9 @@ public class PlayerEntity extends LivingEntity { profile = gameProfile; uuid = gameProfile.getId(); username = gameProfile.getName(); + + // For the OptionalPack, set all bits as invisible by default as this matches Java Edition behavior + metadata.put(EntityData.MARK_VARIANT, 0xff); } @Override @@ -280,6 +283,14 @@ public class PlayerEntity extends LivingEntity { session.sendUpstreamPacket(attributesPacket); } + if (entityMetadata.getId() == 16) { + // OptionalPack usage for toggling skin bits + // In Java Edition, a bit being set means that part should be enabled + // However, to ensure that the pack still works on other servers, we invert the bit so all values by default + // are true (0). + metadata.put(EntityData.MARK_VARIANT, ~((byte) entityMetadata.getValue()) & 0xff); + } + // Parrot occupying shoulder if (entityMetadata.getId() == 18 || entityMetadata.getId() == 19) { CompoundTag tag = (CompoundTag) entityMetadata.getValue(); diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index a1fac77f6..0e9c2fe9b 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java @@ -55,7 +55,7 @@ public enum EntityType { SQUID(SquidEntity.class, 17, 0.8f), RABBIT(RabbitEntity.class, 18, 0.5f, 0.4f), BAT(BatEntity.class, 19, 0.9f, 0.5f), - IRON_GOLEM(GolemEntity.class, 20, 2.7f, 1.4f), + IRON_GOLEM(IronGolemEntity.class, 20, 2.7f, 1.4f), SNOW_GOLEM(SnowGolemEntity.class, 21, 1.9f, 0.7f), OCELOT(OcelotEntity.class, 22, 0.35f, 0.3f), HORSE(HorseEntity.class, 23, 1.6f, 1.3965f), @@ -147,7 +147,7 @@ public enum EntityType { EVOKER(SpellcasterIllagerEntity.class, 104, 1.95f, 0.6f, 0.6f, 0f, "minecraft:evocation_illager"), VEX(VexEntity.class, 105, 0.8f, 0.4f), ICE_BOMB(Entity.class, 106, 0f), - BALLOON(Entity.class, 107, 0f), //TODO + BALLOON(Entity.class, 107, 0f), PUFFERFISH(PufferFishEntity.class, 108, 0.7f, 0.7f), SALMON(AbstractFishEntity.class, 109, 0.5f, 0.7f), DROWNED(ZombieEntity.class, 110, 1.95f, 0.6f), @@ -169,9 +169,9 @@ public enum EntityType { ITEM_FRAME(ItemFrameEntity.class, 0, 0, 0), /** - * Not an entity in Bedrock, so we replace it with a Pillager + * Not an entity in Bedrock, so we replace it with an evoker */ - ILLUSIONER(AbstractIllagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:pillager"), + ILLUSIONER(SpellcasterIllagerEntity.class, 104, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:evocation_illager"), /** * Not an entity in Bedrock, but used for the Ender Dragon's multiple hitboxes 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 39d0f05c9..400796392 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 @@ -66,10 +66,7 @@ import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2LongMap; -import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectIterator; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import it.unimi.dsi.fastutil.objects.*; import lombok.AccessLevel; import lombok.Getter; import lombok.NonNull; @@ -81,6 +78,7 @@ import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.configuration.EmoteOffhandWorkaroundOption; import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.ItemFrameEntity; import org.geysermc.connector.entity.Tickable; import org.geysermc.connector.entity.attribute.Attribute; import org.geysermc.connector.entity.attribute.AttributeType; @@ -146,6 +144,8 @@ public class GeyserSession implements CommandSender { private ChunkCache chunkCache; private EntityCache entityCache; private EntityEffectCache effectCache; + private final PreferencesCache preferencesCache; + private final TagCache tagCache; private WorldCache worldCache; private WindowCache windowCache; private final Int2ObjectMap teleportMap = new Int2ObjectOpenHashMap<>(); @@ -188,10 +188,10 @@ public class GeyserSession implements CommandSender { private final Long2ObjectMap storedMaps = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); /** - * A map of Vector3i positions to Java entity IDs. + * A map of Vector3i positions to Java entities. * Used for translating Bedrock block actions to Java entity actions. */ - private final Object2LongMap itemFrameCache = new Object2LongOpenHashMap<>(); + private final Map itemFrameCache = new Object2ObjectOpenHashMap<>(); /** * Stores a list of all lectern locations and their block entity tags. @@ -452,6 +452,8 @@ public class GeyserSession implements CommandSender { this.chunkCache = new ChunkCache(this); this.entityCache = new EntityCache(this); this.effectCache = new EntityEffectCache(); + this.preferencesCache = new PreferencesCache(this); + this.tagCache = new TagCache(); this.worldCache = new WorldCache(this); this.windowCache = new WindowCache(this); @@ -1225,7 +1227,7 @@ public class GeyserSession implements CommandSender { public void setReducedDebugInfo(boolean value) { reducedDebugInfo = value; // Set the showCoordinates data. This is done because updateShowCoordinates() uses this gamerule as a variable. - getWorldCache().updateShowCoordinates(); + preferencesCache.updateShowCoordinates(); } /** diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/PreferencesCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/PreferencesCache.java new file mode 100644 index 000000000..d477066c2 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/PreferencesCache.java @@ -0,0 +1,71 @@ +/* + * 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.session.cache; + +import lombok.Getter; +import lombok.Setter; +import org.geysermc.connector.configuration.GeyserConfiguration; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.CooldownUtils; + +@Getter +public class PreferencesCache { + private final GeyserSession session; + + /** + * True if the client prefers being shown their coordinates, regardless if they're being shown or not. + * This will be true everytime the client joins the server because neither the client nor server store the preference permanently. + */ + @Setter + private boolean prefersShowCoordinates = true; + /** + * If the client's preference will be ignored, this will return false. + */ + private boolean allowShowCoordinates; + + /** + * Which CooldownType the client prefers. Initially set to {@link CooldownUtils#getDefaultShowCooldown()}. + */ + @Setter + private CooldownUtils.CooldownType cooldownPreference = CooldownUtils.getDefaultShowCooldown(); + + public PreferencesCache(GeyserSession session) { + this.session = session; + } + + /** + * Tell the client to hide or show the coordinates. + * + * If {@link #prefersShowCoordinates} is true, coordinates will be shown, unless either of the following conditions apply:
+ *
+ * {@link GeyserSession#reducedDebugInfo} is enabled + * {@link GeyserConfiguration#isShowCoordinates()} is disabled + */ + public void updateShowCoordinates() { + allowShowCoordinates = !session.isReducedDebugInfo() && session.getConnector().getConfig().isShowCoordinates(); + session.sendGameRule("showcoordinates", allowShowCoordinates && prefersShowCoordinates); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/TagCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/TagCache.java new file mode 100644 index 000000000..c46e37b74 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/TagCache.java @@ -0,0 +1,74 @@ +/* + * 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.session.cache; + +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerDeclareTagsPacket; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.IntLists; +import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.registry.type.BlockMapping; + +import java.util.Map; + +/** + * Manages information sent from the {@link ServerDeclareTagsPacket}. If that packet is not sent, all lists here + * will remain empty, matching Java Edition behavior. + */ +public class TagCache { + /* Blocks */ + private IntList wool = IntLists.emptyList(); + /* Items */ + private IntList flowers = IntLists.emptyList(); + private IntList piglinLoved = IntLists.emptyList(); + + public void loadPacket(ServerDeclareTagsPacket packet) { + Map blockTags = packet.getBlockTags(); + this.wool = IntList.of(blockTags.get("minecraft:wool")); + + Map itemTags = packet.getItemTags(); + this.flowers = IntList.of(itemTags.get("minecraft:flowers")); + this.piglinLoved = IntList.of(itemTags.get("minecraft:piglin_loved")); + } + + public void clear() { + this.wool = IntLists.emptyList(); + + this.flowers = IntLists.emptyList(); + this.piglinLoved = IntLists.emptyList(); + } + + public boolean isFlower(ItemEntry itemEntry) { + return flowers.contains(itemEntry.getJavaId()); + } + + public boolean shouldPiglinAdmire(ItemEntry itemEntry) { + return piglinLoved.contains(itemEntry.getJavaId()); + } + + public boolean isWool(BlockMapping blockMapping) { + return wool.contains(blockMapping.getJavaBlockId()); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java index 84678c211..4a2939621 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java @@ -28,7 +28,6 @@ package org.geysermc.connector.network.session.cache; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; import lombok.Getter; import lombok.Setter; -import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.scoreboard.Objective; import org.geysermc.connector.scoreboard.Scoreboard; @@ -40,13 +39,6 @@ public class WorldCache { @Setter private Difficulty difficulty = Difficulty.EASY; - /** - * True if the client prefers being shown their coordinates, regardless if they're being shown or not. - * This will be true everytime the client joins the server because neither the client nor server store the preference permanently. - */ - @Setter - private boolean prefersShowCoordinates = true; - private Scoreboard scoreboard; private final ScoreboardUpdater scoreboardUpdater; @@ -71,17 +63,4 @@ public class WorldCache { int pps = scoreboardUpdater.getPacketsPerSecond(); return Math.max(pps, pendingPps); } - - /** - * Tell the client to hide or show the coordinates. - * - * If {@link #prefersShowCoordinates} is true, coordinates will be shown, unless either of the following conditions apply:
- *
- * {@link GeyserSession#reducedDebugInfo} is enabled - * {@link GeyserConfiguration#isShowCoordinates()} is disabled - */ - public void updateShowCoordinates() { - boolean allowShowCoordinates = !session.isReducedDebugInfo() && session.getConnector().getConfig().isShowCoordinates(); - session.sendGameRule("showcoordinates", allowShowCoordinates && prefersShowCoordinates); - } } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java index 9becfb36f..ba74c7769 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java @@ -27,6 +27,7 @@ package org.geysermc.connector.network.translators.bedrock; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.packet.BlockPickRequestPacket; +import org.geysermc.connector.entity.ItemFrameEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -43,9 +44,21 @@ public class BedrockBlockPickRequestTranslator extends PacketTranslator 0) { + InventoryActionData actionData = packet.getActions().get(0); + LegacySetItemSlotData slotData = packet.getLegacySlots().get(0); + if (slotData.getContainerId() == 6 && actionData.getToItem().getId() != 0) { + // The player is trying to swap out an armor piece that already has an item in it + // Java Edition does not allow this; let's revert it + session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory()); + } + } + + // Handled when sneaking if (session.getPlayerInventory().getItemInHand().getJavaId() == ItemRegistry.SHIELD.getJavaId()) { break; } @@ -279,9 +289,10 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator type : collisionTypes) { CollisionRemapper annotation = annotationMap.get(type); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index 10c14074f..0e1a28d54 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -108,10 +108,6 @@ public class ItemRegistry { * Egg item entry, used in JavaEntityStatusTranslator.java */ public static ItemEntry EGG; - /** - * Gold item entry, used in PiglinEntity.java - */ - public static ItemEntry GOLD; /** * Shield item entry, used in Entity.java and LivingEntity.java */ @@ -294,7 +290,7 @@ public class ItemRegistry { // However, in order for some visuals and crafting to work, we need to send the first matching block state // as indexed by Bedrock's block palette // There are exceptions! But, ideally, the block ID override should take care of those. - String javaBlockIdentifier = BlockTranslator.getJavaIdBlockMap().inverse().get(blockRuntimeIdNode.intValue()).split("\\[")[0]; + String javaBlockIdentifier = BlockTranslator.getBlockMapping(blockRuntimeIdNode.intValue()).getCleanJavaIdentifier(); NbtMapBuilder requiredBlockStatesBuilder = NbtMap.builder(); String correctBedrockIdentifier = blockTranslator.getAllBedrockBlockStates().get(aValidBedrockBlockId).getString("name"); boolean firstPass = true; @@ -437,9 +433,6 @@ public class ItemRegistry { case "minecraft:egg": EGG = itemEntry; break; - case "minecraft:gold_ingot": - GOLD = itemEntry; - break; case "minecraft:shield": SHIELD = itemEntry; break; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareTagsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareTagsTranslator.java new file mode 100644 index 000000000..368693bf2 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareTagsTranslator.java @@ -0,0 +1,40 @@ +/* + * 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.java; + +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerDeclareTagsPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +@Translator(packet = ServerDeclareTagsPacket.class) +public class JavaDeclareTagsTranslator extends PacketTranslator { + + @Override + public void translate(ServerDeclareTagsPacket packet, GeyserSession session) { + session.getTagCache().loadPacket(packet); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java index 74093eecc..422083ca7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java @@ -63,6 +63,8 @@ public class JavaJoinGameTranslator extends PacketTranslator { @Override public void translate(ServerEntityAnimationPacket packet, GeyserSession session) { - Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); + Entity entity; if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { entity = session.getPlayerEntity(); + } else { + entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); } if (entity == null) return; @@ -51,11 +56,30 @@ public class JavaEntityAnimationTranslator extends PacketTranslator { + private final Random random = new Random(); @Override public void translate(ServerSpawnParticlePacket packet, GeyserSession session) { - LevelEventPacket particle = new LevelEventPacket(); - switch (packet.getParticle().getType()) { - case BLOCK: - particle.setType(LevelEventType.PARTICLE_DESTROY_BLOCK_NO_SOUND); - particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - particle.setData(session.getBlockTranslator().getBedrockBlockId(((BlockParticleData) packet.getParticle().getData()).getBlockState())); - session.sendUpstreamPacket(particle); - break; - case FALLING_DUST: - //In fact, FallingDustParticle should have data like DustParticle, - //but in MCProtocol, its data is BlockState(1). - particle.setType(LevelEventType.PARTICLE_FALLING_DUST); - particle.setData(session.getBlockTranslator().getBedrockBlockId(((FallingDustParticleData)packet.getParticle().getData()).getBlockState())); - particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - session.sendUpstreamPacket(particle); - break; - case ITEM: - ItemStack javaItem = ((ItemParticleData)packet.getParticle().getData()).getItemStack(); - ItemData bedrockItem = ItemTranslator.translateToBedrock(session, javaItem); - int id = bedrockItem.getId(); - int damage = bedrockItem.getDamage(); - particle.setType(LevelEventType.PARTICLE_ITEM_BREAK); - particle.setData(id << 16 | damage); - particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - session.sendUpstreamPacket(particle); - break; - case DUST: - DustParticleData data = (DustParticleData)packet.getParticle().getData(); - int r = (int) (data.getRed()*255); - int g = (int) (data.getGreen()*255); - int b = (int) (data.getBlue()*255); - particle.setType(LevelEventType.PARTICLE_FALLING_DUST); - particle.setData(((0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff)); - particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - session.sendUpstreamPacket(particle); - break; - default: - LevelEventType typeParticle = EffectRegistry.getParticleLevelEventType(packet.getParticle().getType()); - if (typeParticle != null) { - particle.setType(typeParticle); - particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - session.sendUpstreamPacket(particle); - } else { - String stringParticle = EffectRegistry.getParticleString(packet.getParticle().getType()); - if (stringParticle != null) { - SpawnParticleEffectPacket stringPacket = new SpawnParticleEffectPacket(); - stringPacket.setIdentifier(stringParticle); - stringPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension())); - stringPacket.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - session.sendUpstreamPacket(stringPacket); - } + Function particleCreateFunction = createParticle(session, packet.getParticle()); + if (particleCreateFunction != null) { + if (packet.getAmount() == 0) { + // 0 means don't apply the offset + Vector3f position = Vector3f.from(packet.getX(), packet.getY(), packet.getZ()); + session.sendUpstreamPacket(particleCreateFunction.apply(position)); + } else { + for (int i = 0; i < packet.getAmount(); i++) { + double offsetX = this.random.nextGaussian() * (double) packet.getOffsetX(); + double offsetY = this.random.nextGaussian() * (double) packet.getOffsetY(); + double offsetZ = this.random.nextGaussian() * (double) packet.getOffsetZ(); + Vector3f position = Vector3f.from(packet.getX() + offsetX, packet.getY() + offsetY, packet.getZ() + offsetZ); + + session.sendUpstreamPacket(particleCreateFunction.apply(position)); } - break; + } + } else { + // Null is only returned when no particle of this type is found + session.getConnector().getLogger().debug("Unhandled particle packet: " + packet); } } + /** + * @param session the Bedrock client session. + * @param particle the Java particle to translate to a Bedrock equivalent. + * @return a function to create a packet with a specified particle, in the event we need to spawn multiple particles + * with different offsets. + */ + private Function createParticle(GeyserSession session, Particle particle) { + switch (particle.getType()) { + case BLOCK: { + int blockState = session.getBlockTranslator().getBedrockBlockId(((BlockParticleData) particle.getData()).getBlockState()); + return (position) -> { + LevelEventPacket packet = new LevelEventPacket(); + packet.setType(LevelEventType.PARTICLE_CRACK_BLOCK); + packet.setPosition(position); + packet.setData(blockState); + return packet; + }; + } + case FALLING_DUST: { + int blockState = session.getBlockTranslator().getBedrockBlockId(((FallingDustParticleData) particle.getData()).getBlockState()); + return (position) -> { + LevelEventPacket packet = new LevelEventPacket(); + // In fact, FallingDustParticle should have data like DustParticle, + // but in MCProtocol, its data is BlockState(1). + packet.setType(LevelEventType.PARTICLE_FALLING_DUST); + packet.setData(blockState); + packet.setPosition(position); + return packet; + }; + } + case ITEM: { + ItemStack javaItem = ((ItemParticleData) particle.getData()).getItemStack(); + ItemData bedrockItem = ItemTranslator.translateToBedrock(session, javaItem); + int data = bedrockItem.getId() << 16 | bedrockItem.getDamage(); + return (position) -> { + LevelEventPacket packet = new LevelEventPacket(); + packet.setType(LevelEventType.PARTICLE_ITEM_BREAK); + packet.setData(data); + packet.setPosition(position); + return packet; + }; + } + case DUST: { + DustParticleData data = (DustParticleData) particle.getData(); + int r = (int) (data.getRed() * 255); + int g = (int) (data.getGreen() * 255); + int b = (int) (data.getBlue() * 255); + int rgbData = ((0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff); + return (position) -> { + LevelEventPacket packet = new LevelEventPacket(); + packet.setType(LevelEventType.PARTICLE_FALLING_DUST); + packet.setData(rgbData); + packet.setPosition(position); + return packet; + }; + } + default: + LevelEventType typeParticle = EffectRegistry.getParticleLevelEventType(particle.getType()); + if (typeParticle != null) { + return (position) -> { + LevelEventPacket packet = new LevelEventPacket(); + packet.setType(typeParticle); + packet.setPosition(position); + return packet; + }; + } else { + String stringParticle = EffectRegistry.getParticleString(particle.getType()); + if (stringParticle != null) { + int dimensionId = DimensionUtils.javaToBedrock(session.getDimension()); + return (position) -> { + SpawnParticleEffectPacket stringPacket = new SpawnParticleEffectPacket(); + stringPacket.setIdentifier(stringParticle); + stringPacket.setDimensionId(dimensionId); + stringPacket.setPosition(position); + return stringPacket; + }; + } else { + return null; + } + } + } + } } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/entity/FeedBabySoundInteractionHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/entity/FeedBabySoundInteractionHandler.java index f9a885c8c..7482ad565 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/sound/entity/FeedBabySoundInteractionHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/entity/FeedBabySoundInteractionHandler.java @@ -45,7 +45,8 @@ public class FeedBabySoundInteractionHandler implements EntitySoundInteractionHa if (entity instanceof AnimalEntity && !(entity instanceof CatEntity || entity instanceof OcelotEntity)) { String handIdentifier = session.getPlayerInventory().getItemInHand().getItemEntry().getJavaIdentifier(); boolean isBaby = entity.getMetadata().getFlags().getFlag(EntityFlag.BABY); - if (isBaby && ((AnimalEntity) entity).canEat(handIdentifier.replace("minecraft:", ""))) { + if (isBaby && ((AnimalEntity) entity).canEat(session, handIdentifier.replace("minecraft:", ""), + session.getPlayerInventory().getItemInHand().getItemEntry())) { // Play the "feed child" effect EntityEventPacket feedEvent = new EntityEventPacket(); feedEvent.setRuntimeEntityId(entity.getGeyserId()); 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 3bba7a478..3693d34cc 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 @@ -38,6 +38,7 @@ 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.registry.type.BlockMapping; import org.geysermc.connector.utils.FileUtils; import java.io.DataInputStream; @@ -74,14 +75,7 @@ public abstract class BlockTranslator { private final Object2IntMap itemFrames = new Object2IntOpenHashMap<>(); private final Map flowerPotBlocks = new HashMap<>(); - public static final Int2DoubleMap JAVA_RUNTIME_ID_TO_HARDNESS = new Int2DoubleOpenHashMap(); - public static final Int2BooleanMap JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND = new Int2BooleanOpenHashMap(); - public static final Int2ObjectMap JAVA_RUNTIME_ID_TO_TOOL_TYPE = new Int2ObjectOpenHashMap<>(); - - // The index of the collision data in collision.json - public static final Int2IntMap JAVA_RUNTIME_ID_TO_COLLISION_INDEX = new Int2IntOpenHashMap(); - - private static final Int2ObjectMap JAVA_RUNTIME_ID_TO_PICK_ITEM = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap JAVA_RUNTIME_ID_TO_BLOCK_MAPPING = new Int2ObjectOpenHashMap<>(); /** * Java numeric ID to java unique identifier, used for block names in the statistics screen @@ -96,11 +90,7 @@ public abstract class BlockTranslator { private final EmptyChunkProvider emptyChunkProvider; - /** - * A list of all Java runtime wool IDs, for use with block breaking math and shears - */ - public static final IntSet JAVA_RUNTIME_WOOL_IDS = new IntOpenHashSet(); - public static final int JAVA_RUNTIME_COBWEB_ID; + public static final int JAVA_COBWEB_BLOCK_ID; public static final int JAVA_RUNTIME_FURNACE_ID; public static final int JAVA_RUNTIME_FURNACE_LIT_ID; @@ -127,7 +117,7 @@ public abstract class BlockTranslator { } int javaRuntimeId = -1; - int cobwebRuntimeId = -1; + int cobwebBlockId = -1; int furnaceRuntimeId = -1; int furnaceLitRuntimeId = -1; int spawnerRuntimeId = -1; @@ -139,31 +129,35 @@ public abstract class BlockTranslator { Map.Entry entry = blocksIterator.next(); String javaId = entry.getKey(); + BlockMapping.BlockMappingBuilder builder = BlockMapping.builder(); // TODO fix this, (no block should have a null hardness) JsonNode hardnessNode = entry.getValue().get("block_hardness"); if (hardnessNode != null) { - JAVA_RUNTIME_ID_TO_HARDNESS.put(javaRuntimeId, hardnessNode.doubleValue()); + builder.hardness(hardnessNode.doubleValue()); } - try { - JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND.put(javaRuntimeId, entry.getValue().get("can_break_with_hand").booleanValue()); - } catch (Exception e) { - JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND.put(javaRuntimeId, false); + JsonNode canBreakWithHandNode = entry.getValue().get("can_break_with_hand"); + if (canBreakWithHandNode != null) { + builder.canBreakWithHand(canBreakWithHandNode.booleanValue()); + } else { + builder.canBreakWithHand(false); } JsonNode toolTypeNode = entry.getValue().get("tool_type"); if (toolTypeNode != null) { - JAVA_RUNTIME_ID_TO_TOOL_TYPE.put(javaRuntimeId, toolTypeNode.textValue()); + builder.toolType(toolTypeNode.textValue()); + } else { + builder.toolType(""); } JsonNode collisionIndexNode = entry.getValue().get("collision_index"); if (hardnessNode != null) { - JAVA_RUNTIME_ID_TO_COLLISION_INDEX.put(javaRuntimeId, collisionIndexNode.intValue()); + builder.collisionIndex(collisionIndexNode.intValue()); } JsonNode pickItemNode = entry.getValue().get("pick_item"); if (pickItemNode != null) { - JAVA_RUNTIME_ID_TO_PICK_ITEM.put(javaRuntimeId, pickItemNode.textValue()); + builder.pickItem(pickItemNode.textValue()); } JAVA_ID_BLOCK_MAP.put(javaId, javaRuntimeId); @@ -183,11 +177,14 @@ public abstract class BlockTranslator { JAVA_TO_BEDROCK_IDENTIFIERS.put(cleanJavaIdentifier, bedrockIdentifier); } - if (javaId.contains("wool")) { - JAVA_RUNTIME_WOOL_IDS.add(javaRuntimeId); + builder.javaBlockId(uniqueJavaId); - } else if (javaId.contains("cobweb")) { - cobwebRuntimeId = javaRuntimeId; + builder.javaIdentifier(javaId); + + JAVA_RUNTIME_ID_TO_BLOCK_MAPPING.put(javaRuntimeId, builder.build()); + + if (javaId.contains("cobweb")) { + cobwebBlockId = uniqueJavaId; } else if (javaId.startsWith("minecraft:furnace[facing=north")) { if (javaId.contains("lit=true")) { @@ -204,10 +201,10 @@ public abstract class BlockTranslator { } } - if (cobwebRuntimeId == -1) { + if (cobwebBlockId == -1) { throw new AssertionError("Unable to find cobwebs in palette"); } - JAVA_RUNTIME_COBWEB_ID = cobwebRuntimeId; + JAVA_COBWEB_BLOCK_ID = cobwebBlockId; if (furnaceRuntimeId == -1) { throw new AssertionError("Unable to find furnace in palette"); @@ -228,6 +225,8 @@ public abstract class BlockTranslator { throw new AssertionError("Unable to find Java water in palette"); } JAVA_WATER_ID = waterRuntimeId; + + BlockMapping.AIR = JAVA_RUNTIME_ID_TO_BLOCK_MAPPING.get(JAVA_AIR_ID); BlockTranslator1_16_210.init(); BLOCKS_JSON = null; // We no longer require this so let it garbage collect away @@ -459,18 +458,11 @@ public abstract class BlockTranslator { } /** - * Get the item a Java client would receive when pressing - * the Pick Block key on a specific Java block state. - * - * @param javaId The Java runtime id of the block - * @return The Java identifier of the item + * @param javaRuntimeId the Java runtime ID of the block to search for. + * @return the corresponding block mapping for this runtime ID. */ - public static String getPickItem(int javaId) { - String itemIdentifier = JAVA_RUNTIME_ID_TO_PICK_ITEM.get(javaId); - if (itemIdentifier == null) { - return JAVA_ID_BLOCK_MAP.inverse().get(javaId).split("\\[")[0]; - } - return itemIdentifier; + public static BlockMapping getBlockMapping(int javaRuntimeId) { + return JAVA_RUNTIME_ID_TO_BLOCK_MAPPING.getOrDefault(javaRuntimeId, BlockMapping.AIR); } /** diff --git a/connector/src/main/java/org/geysermc/connector/registry/type/BlockMapping.java b/connector/src/main/java/org/geysermc/connector/registry/type/BlockMapping.java new file mode 100644 index 000000000..f3eaddd91 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/registry/type/BlockMapping.java @@ -0,0 +1,87 @@ +/* + * 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.registry.type; + +import lombok.Builder; +import lombok.Value; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +@Builder +@Value +public class BlockMapping { + public static BlockMapping AIR; + + String javaIdentifier; + /** + * The block ID shared between all different block states of this block. + * NOT the runtime ID! + */ + int javaBlockId; + + double hardness; + boolean canBreakWithHand; + @Nonnull String toolType; + /** + * The index of this collision in collision.json + */ + int collisionIndex; + @Nullable String pickItem; + + /** + * @return the identifier without the additional block states + */ + public String getCleanJavaIdentifier() { + return javaIdentifier.split("\\[")[0]; + } + + /** + * @return the corresponding Java identifier for this item + */ + public String getItemIdentifier() { + if (pickItem != null && !pickItem.equals("minecraft:air")) { + // Spawners can have air as their pick item which we are not interested in. + return pickItem; + } + + return getCleanJavaIdentifier(); + } + + /** + * Get the item a Java client would receive when pressing + * the Pick Block key on a specific Java block state. + * + * @return The Java identifier of the item + */ + public String getPickItem() { + if (pickItem != null) { + return pickItem; + } + + return getCleanJavaIdentifier(); + } +} 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 997e4aee4..01c85ab43 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java @@ -33,6 +33,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ToolItemEntry; import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.connector.registry.type.BlockMapping; public class BlockUtils { /** @@ -108,11 +109,11 @@ public class BlockUtils { return 1.0 / speed; } - public static double getBreakTime(double blockHardness, int blockId, ItemEntry item, CompoundTag nbtData, GeyserSession session) { - boolean isWoolBlock = BlockTranslator.JAVA_RUNTIME_WOOL_IDS.contains(blockId); - boolean isCobweb = blockId == BlockTranslator.JAVA_RUNTIME_COBWEB_ID; - String blockToolType = BlockTranslator.JAVA_RUNTIME_ID_TO_TOOL_TYPE.getOrDefault(blockId, ""); - boolean canHarvestWithHand = BlockTranslator.JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND.get(blockId); + public static double getBreakTime(GeyserSession session, BlockMapping blockMapping, ItemEntry item, CompoundTag nbtData, boolean isSessionPlayer) { + boolean isWoolBlock = session.getTagCache().isWool(blockMapping); + boolean isCobweb = blockMapping.getJavaBlockId() == BlockTranslator.JAVA_COBWEB_BLOCK_ID; + String blockToolType = blockMapping.getToolType(); + boolean canHarvestWithHand = blockMapping.isCanBreakWithHand(); String toolType = ""; String toolTier = ""; boolean correctTool = false; @@ -126,8 +127,11 @@ public class BlockUtils { int hasteLevel = 0; int miningFatigueLevel = 0; - if (session == null) { - return calculateBreakTime(blockHardness, toolTier, canHarvestWithHand, correctTool, toolType, isWoolBlock, isCobweb, toolEfficiencyLevel, hasteLevel, miningFatigueLevel, false, false, false); + if (!isSessionPlayer) { + // Another entity is currently mining; we have all the information we know + return calculateBreakTime(blockMapping.getHardness(), toolTier, canHarvestWithHand, correctTool, toolType, isWoolBlock, + isCobweb, toolEfficiencyLevel, hasteLevel, miningFatigueLevel, false, + false, false); } hasteLevel = session.getEffectCache().getEffectLevel(Effect.FASTER_DIG); @@ -140,7 +144,9 @@ public class BlockUtils { boolean outOfWaterButNotOnGround = (!isInWater) && (!session.getPlayerEntity().isOnGround()); boolean insideWaterNotOnGround = isInWater && !session.getPlayerEntity().isOnGround(); - return calculateBreakTime(blockHardness, toolTier, canHarvestWithHand, correctTool, toolType, isWoolBlock, isCobweb, toolEfficiencyLevel, hasteLevel, miningFatigueLevel, insideOfWaterWithoutAquaAffinity, outOfWaterButNotOnGround, insideWaterNotOnGround); + return calculateBreakTime(blockMapping.getHardness(), toolTier, canHarvestWithHand, correctTool, toolType, isWoolBlock, + isCobweb, toolEfficiencyLevel, hasteLevel, miningFatigueLevel, insideOfWaterWithoutAquaAffinity, + outOfWaterButNotOnGround, insideWaterNotOnGround); } /** 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 09d446374..7838adde2 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -47,7 +47,6 @@ import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import lombok.Data; import lombok.experimental.UtilityClass; import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.ItemFrameEntity; import org.geysermc.connector.entity.player.SkullPlayerEntity; import org.geysermc.connector.network.session.GeyserSession; @@ -295,21 +294,13 @@ public class ChunkUtils { */ public static void updateBlock(GeyserSession session, int blockState, Vector3i position) { // Checks for item frames so they aren't tripped up and removed - long frameEntityId = ItemFrameEntity.getItemFrameEntityId(session, position); - if (frameEntityId != -1) { - // TODO: Very occasionally the item frame doesn't sync up when destroyed - Entity entity = session.getEntityCache().getEntityByJavaId(frameEntityId); - if (blockState == JAVA_AIR_ID && entity != null) { // Item frame is still present and no block overrides that; refresh it - ((ItemFrameEntity) entity).updateBlock(session); + ItemFrameEntity itemFrameEntity = ItemFrameEntity.getItemFrameEntity(session, position); + if (itemFrameEntity != null) { + if (blockState == JAVA_AIR_ID) { // Item frame is still present and no block overrides that; refresh it + itemFrameEntity.updateBlock(session); return; } - - // Otherwise the item frame is gone - if (entity != null) { - session.getEntityCache().removeEntity(entity, false); - } else { - ItemFrameEntity.removePosition(session, position); - } + // Otherwise, let's still store our reference to the item frame, but let the new block take precedence for now } SkullPlayerEntity skull = session.getSkullCache().get(position); diff --git a/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java b/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java index 0b5c2bdd3..43675ebfd 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java @@ -28,6 +28,7 @@ package org.geysermc.connector.utils; import com.nukkitx.protocol.bedrock.packet.SetTitlePacket; import lombok.Getter; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.session.cache.PreferencesCache; import java.util.concurrent.TimeUnit; @@ -36,18 +37,25 @@ import java.util.concurrent.TimeUnit; * Much of the work here is from the wonderful folks from ViaRewind: https://github.com/ViaVersion/ViaRewind */ public class CooldownUtils { - private static CooldownType SHOW_COOLDOWN; + private static CooldownType DEFAULT_SHOW_COOLDOWN; - public static void setShowCooldown(String showCooldown) { - SHOW_COOLDOWN = CooldownType.getByName(showCooldown); + public static void setDefaultShowCooldown(String showCooldown) { + DEFAULT_SHOW_COOLDOWN = CooldownType.getByName(showCooldown); + } + + public static CooldownType getDefaultShowCooldown() { + return DEFAULT_SHOW_COOLDOWN; } /** - * Starts sending the fake cooldown to the Bedrock client. + * Starts sending the fake cooldown to the Bedrock client. If the cooldown is not disabled, the sent type is the cooldownPreference in {@link PreferencesCache} * @param session GeyserSession */ public static void sendCooldown(GeyserSession session) { - if (SHOW_COOLDOWN == CooldownType.DISABLED) return; + if (DEFAULT_SHOW_COOLDOWN == CooldownType.DISABLED) return; + CooldownType sessionPreference = session.getPreferencesCache().getCooldownPreference(); + if (sessionPreference == CooldownType.DISABLED) return; + if (session.getAttackSpeed() == 0.0 || session.getAttackSpeed() > 20) return; // 0.0 usually happens on login and causes issues with visuals; anything above 20 means a plugin like OldCombatMechanics is being used // Needs to be sent or no subtitle packet is recognized by the client SetTitlePacket titlePacket = new SetTitlePacket(); @@ -56,19 +64,20 @@ public class CooldownUtils { session.sendUpstreamPacket(titlePacket); session.setLastHitTime(System.currentTimeMillis()); long lastHitTime = session.getLastHitTime(); // Used later to prevent multiple scheduled cooldown threads - computeCooldown(session, lastHitTime); + computeCooldown(session, sessionPreference, lastHitTime); } /** * Keeps updating the cooldown until the bar is complete. * @param session GeyserSession + * @param sessionPreference The type of cooldown the client prefers * @param lastHitTime The time of the last hit. Used to gauge how long the cooldown is taking. */ - private static void computeCooldown(GeyserSession session, long lastHitTime) { + private static void computeCooldown(GeyserSession session, CooldownType sessionPreference, long lastHitTime) { if (session.isClosed()) return; // Don't run scheduled tasks if the client left if (lastHitTime != session.getLastHitTime()) return; // Means another cooldown has started so there's no need to continue this one SetTitlePacket titlePacket = new SetTitlePacket(); - if (SHOW_COOLDOWN == CooldownType.ACTIONBAR) { + if (sessionPreference == CooldownType.ACTIONBAR) { titlePacket.setType(SetTitlePacket.Type.ACTIONBAR); } else { titlePacket.setType(SetTitlePacket.Type.SUBTITLE); @@ -79,10 +88,10 @@ public class CooldownUtils { titlePacket.setStayTime(2); session.sendUpstreamPacket(titlePacket); if (hasCooldown(session)) { - session.getConnector().getGeneralThreadPool().schedule(() -> computeCooldown(session, lastHitTime), 50, TimeUnit.MILLISECONDS); // Updated per tick. 1000 divided by 20 ticks equals 50 + session.getConnector().getGeneralThreadPool().schedule(() -> computeCooldown(session, sessionPreference, lastHitTime), 50, TimeUnit.MILLISECONDS); // Updated per tick. 1000 divided by 20 ticks equals 50 } else { SetTitlePacket removeTitlePacket = new SetTitlePacket(); - if (SHOW_COOLDOWN == CooldownType.ACTIONBAR) { + if (sessionPreference == CooldownType.ACTIONBAR) { removeTitlePacket.setType(SetTitlePacket.Type.ACTIONBAR); } else { removeTitlePacket.setType(SetTitlePacket.Type.SUBTITLE); @@ -133,7 +142,7 @@ public class CooldownUtils { public static final CooldownType[] VALUES = values(); /** - * Convert the CooldownType string (from config) to the enum, TITLE on fail + * Convert the CooldownType string (from config) to the enum, DISABLED on fail * * @param name CooldownType string * diff --git a/connector/src/main/java/org/geysermc/connector/utils/InteractiveTagManager.java b/connector/src/main/java/org/geysermc/connector/utils/InteractiveTagManager.java index ff7ad11cf..1d6cf60d9 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/InteractiveTagManager.java +++ b/connector/src/main/java/org/geysermc/connector/utils/InteractiveTagManager.java @@ -82,7 +82,7 @@ public class InteractiveTagManager { // Holding a leash and the mob is leashable for sure // (Plugins can change this behavior so that's something to look into in the far far future) interactiveTag = InteractiveTag.LEASH; - } else if (interactEntity instanceof AnimalEntity && ((AnimalEntity) interactEntity).canEat(javaIdentifierStripped)) { + } else if (interactEntity instanceof AnimalEntity && ((AnimalEntity) interactEntity).canEat(session, javaIdentifierStripped, itemEntry)) { // This animal can be fed interactiveTag = InteractiveTag.FEED; } else { diff --git a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java index d4759acaa..d9480d466 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java @@ -170,6 +170,60 @@ public class InventoryUtils { .tag(root.build()).build(); } + /** + * See {@link #findOrCreateItem(GeyserSession, String)}. This is for finding a specified {@link ItemStack}. + * + * @param session the Bedrock client's session + * @param itemStack the item to try to find a match for. NBT will also be accounted for. + */ + public static void findOrCreateItem(GeyserSession session, ItemStack itemStack) { + PlayerInventory inventory = session.getPlayerInventory(); + + if (itemStack == null || itemStack.getId() == 0) { + return; + } + + // Check hotbar for item + for (int i = 36; i < 45; i++) { + GeyserItemStack geyserItem = inventory.getItem(i); + if (geyserItem.isEmpty()) { + continue; + } + // If this is the item we're looking for + if (geyserItem.getJavaId() == itemStack.getId() && Objects.equals(geyserItem.getNbt(), itemStack.getNbt())) { + setHotbarItem(session, i); + // Don't check inventory if item was in hotbar + return; + } + } + + // Check inventory for item + for (int i = 9; i < 36; i++) { + GeyserItemStack geyserItem = inventory.getItem(i); + if (geyserItem.isEmpty()) { + continue; + } + // If this is the item we're looking for + if (geyserItem.getJavaId() == itemStack.getId() && Objects.equals(geyserItem.getNbt(), itemStack.getNbt())) { + ClientMoveItemToHotbarPacket packetToSend = new ClientMoveItemToHotbarPacket(i); // https://wiki.vg/Protocol#Pick_Item + session.sendDownstreamPacket(packetToSend); + return; + } + } + + // If we still have not found the item, and we're in creative, ask for the item from the server. + if (session.getGameMode() == GameMode.CREATIVE) { + int slot = findEmptyHotbarSlot(inventory); + + ClientCreativeInventoryActionPacket actionPacket = new ClientCreativeInventoryActionPacket(slot, + itemStack); + if ((slot - 36) != inventory.getHeldItemSlot()) { + setHotbarItem(session, slot); + } + session.sendDownstreamPacket(actionPacket); + } + } + /** * Attempt to find the specified item name in the session's inventory. * If it is found and in the hotbar, set the user's held item to that slot. @@ -223,15 +277,7 @@ public class InventoryUtils { // If we still have not found the item, and we're in creative, ask for the item from the server. if (session.getGameMode() == GameMode.CREATIVE) { - int slot = inventory.getHeldItemSlot() + 36; - if (!inventory.getItemInHand().isEmpty()) { // Otherwise we should just use the current slot - for (int i = 36; i < 45; i++) { - if (inventory.getItem(i).isEmpty()) { - slot = i; - break; - } - } - } + int slot = findEmptyHotbarSlot(inventory); ItemEntry entry = ItemRegistry.getItemEntry(itemName); if (entry != null) { @@ -247,6 +293,22 @@ public class InventoryUtils { } } + /** + * @return the first empty slot found in this inventory, or else the player's currently held slot. + */ + private static int findEmptyHotbarSlot(PlayerInventory inventory) { + int slot = inventory.getHeldItemSlot() + 36; + if (!inventory.getItemInHand().isEmpty()) { // Otherwise we should just use the current slot + for (int i = 36; i < 45; i++) { + if (inventory.getItem(i).isEmpty()) { + slot = i; + break; + } + } + } + return slot; + } + /** * Changes the held item slot to the specified slot * @param session GeyserSession diff --git a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java index 1d06c8a0f..d0cfce862 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java @@ -57,11 +57,24 @@ public class SettingsUtils { CustomFormBuilder builder = new CustomFormBuilder(LanguageUtils.getPlayerLocaleString("geyser.settings.title.main", language)); builder.setIcon(new FormImage(FormImage.FormImageType.PATH, "textures/ui/settings_glyph_color_2x.png")); - // Client can only see its coordinates if reducedDebugInfo is disabled and coordinates are enabled in geyser config. - if (!session.isReducedDebugInfo() && session.getConnector().getConfig().isShowCoordinates()) { + // Only show the client title if any of the client settings are available + if (session.getPreferencesCache().isAllowShowCoordinates() || CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED) { builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.client", language))); - builder.addComponent(new ToggleComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.option.coordinates", language), session.getWorldCache().isPrefersShowCoordinates())); + // Client can only see its coordinates if reducedDebugInfo is disabled and coordinates are enabled in geyser config. + if (session.getPreferencesCache().isAllowShowCoordinates()) { + builder.addComponent(new ToggleComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.option.coordinates", language), session.getPreferencesCache().isPrefersShowCoordinates())); + } + + if (CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED) { + DropdownComponent cooldownDropdown = new DropdownComponent(); + cooldownDropdown.setText(LocaleUtils.getLocaleString("options.attackIndicator", language)); + cooldownDropdown.setOptions(new ArrayList<>()); + cooldownDropdown.addOption(LocaleUtils.getLocaleString("options.attack.crosshair", language), session.getPreferencesCache().getCooldownPreference() == CooldownUtils.CooldownType.TITLE); + cooldownDropdown.addOption(LocaleUtils.getLocaleString("options.attack.hotbar", language), session.getPreferencesCache().getCooldownPreference() == CooldownUtils.CooldownType.ACTIONBAR); + cooldownDropdown.addOption(LocaleUtils.getLocaleString("options.off", language), session.getPreferencesCache().getCooldownPreference() == CooldownUtils.CooldownType.DISABLED); + builder.addComponent(cooldownDropdown); + } } @@ -121,13 +134,21 @@ public class SettingsUtils { } int offset = 0; - // Client can only see its coordinates if reducedDebugInfo is disabled and coordinates are enabled in geyser config. - if (!session.isReducedDebugInfo() && session.getConnector().getConfig().isShowCoordinates()) { + if (session.getPreferencesCache().isAllowShowCoordinates() || CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED) { offset++; // Client settings title - session.getWorldCache().setPrefersShowCoordinates(settingsResponse.getToggleResponses().get(offset)); - session.getWorldCache().updateShowCoordinates(); - offset++; + // Client can only see its coordinates if reducedDebugInfo is disabled and coordinates are enabled in geyser config. + if (session.getPreferencesCache().isAllowShowCoordinates()) { + session.getPreferencesCache().setPrefersShowCoordinates(settingsResponse.getToggleResponses().get(offset)); + session.getPreferencesCache().updateShowCoordinates(); + offset++; + } + + if (CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED) { + CooldownUtils.CooldownType cooldownType = CooldownUtils.CooldownType.VALUES[settingsResponse.getDropdownResponses().get(offset).getElementID()]; + session.getPreferencesCache().setCooldownPreference(cooldownType); + offset++; + } } if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) { diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index 96e7ed66c..9b08df518 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 96e7ed66ccdafea0cc991b8004566d448e8f6e6a +Subproject commit 9b08df51898fd71ee24e7accdfbe56f164b5c539 diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index c846b8200..53e13b7a0 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit c846b8200eb8ebb37207666f7eddb83f2b636c37 +Subproject commit 53e13b7a0d2ea14df71ed0c9582d29a9b4fb4453