diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/TropicalFishEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/TropicalFishEntity.java index 7a5906dd9..d16eb2ece 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/TropicalFishEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/TropicalFishEntity.java @@ -26,14 +26,27 @@ package org.geysermc.connector.entity.living.animal; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.google.common.collect.ImmutableList; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import it.unimi.dsi.fastutil.ints.IntList; import org.geysermc.connector.entity.living.AbstractFishEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import java.util.List; + public class TropicalFishEntity extends AbstractFishEntity { + /** + * A list of variant numbers that are given special names + * The index of the variant in this list is used as part of the locale key + */ + private static final IntList PREDEFINED_VARIANTS = IntList.of(117506305, 117899265, 185008129, 117441793, 118161664, 65536, 50726144, 67764993, 234882305, 67110144, 117441025, 16778497, 101253888, 50660352, 918529, 235340288, 918273, 67108865, 917504, 459008, 67699456, 67371009); + + private static final List VARIANT_NAMES = ImmutableList.of("kob", "sunstreak", "snooper", "dasher", "brinely", "spotty", "flopper", "stripey", "glitter", "blockfish", "betty", "clayfish"); + private static final List COLOR_NAMES = ImmutableList.of("white", "orange", "magenta", "light_blue", "yellow", "lime", "pink", "gray", "light_gray", "cyan", "purple", "blue", "brown", "green", "red", "black"); + public TropicalFishEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } @@ -43,11 +56,48 @@ public class TropicalFishEntity extends AbstractFishEntity { if (entityMetadata.getId() == 17) { int varNumber = (int) entityMetadata.getValue(); - metadata.put(EntityData.VARIANT, varNumber & 0xFF); // Shape 0-1 - metadata.put(EntityData.MARK_VARIANT, (varNumber >> 8) & 0xFF); // Pattern 0-5 - metadata.put(EntityData.COLOR, (byte) ((varNumber >> 16) & 0xFF)); // Base color 0-15 - metadata.put(EntityData.COLOR_2, (byte) ((varNumber >> 24) & 0xFF)); // Pattern color 0-15 + metadata.put(EntityData.VARIANT, getShape(varNumber)); // Shape 0-1 + metadata.put(EntityData.MARK_VARIANT, getPattern(varNumber)); // Pattern 0-5 + metadata.put(EntityData.COLOR, getBaseColor(varNumber)); // Base color 0-15 + metadata.put(EntityData.COLOR_2, getPatternColor(varNumber)); // Pattern color 0-15 } super.updateBedrockMetadata(entityMetadata, session); } + + public static int getShape(int variant) { + return Math.min(variant & 0xFF, 1); + } + + public static int getPattern(int variant) { + return Math.min((variant >> 8) & 0xFF, 5); + } + + public static byte getBaseColor(int variant) { + byte color = (byte) ((variant >> 16) & 0xFF); + if (!(0 <= color && color <= 15)) { + return 0; + } + return color; + } + + public static byte getPatternColor(int variant) { + byte color = (byte) ((variant >> 24) & 0xFF); + if (!(0 <= color && color <= 15)) { + return 0; + } + return color; + } + + public static String getVariantName(int variant) { + int id = 6 * getShape(variant) + getPattern(variant); + return VARIANT_NAMES.get(id); + } + + public static String getColorName(byte colorId) { + return COLOR_NAMES.get(colorId); + } + + public static int getPredefinedId(int variant) { + return PREDEFINED_VARIANTS.indexOf(variant); + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index 589fa49d0..d519f2682 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -38,7 +38,6 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlaye import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.LevelEventType; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlags; import com.nukkitx.protocol.bedrock.data.inventory.*; import com.nukkitx.protocol.bedrock.packet.*; import org.geysermc.connector.entity.CommandBlockMinecartEntity; @@ -149,7 +148,6 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator { - ClientPlayerUseItemPacket itemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND); - session.sendDownstreamPacket(itemPacket); - }, 5, TimeUnit.MILLISECONDS)); + // Don't send ClientPlayerUseItemPacket for powder snow buckets + if (packet.getItemInHand().getId() != session.getItemMappings().getStoredItems().powderSnowBucket().getBedrockId()) { + // Special check for crafting tables since clients don't send BLOCK_INTERACT when interacting + int blockState = session.getConnector().getWorldManager().getBlockAt(session, packet.getBlockPosition()); + if (session.isSneaking() || blockState != BlockRegistries.JAVA_IDENTIFIERS.get("minecraft:crafting_table")) { + // Delay the interaction in case the client doesn't intend to actually use the bucket + // See BedrockActionTranslator.java + session.setBucketScheduledFuture(session.getConnector().getGeneralThreadPool().schedule(() -> { + ClientPlayerUseItemPacket itemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND); + session.sendDownstreamPacket(itemPacket); + }, 5, TimeUnit.MILLISECONDS)); + } + } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/StoredItemMappings.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/StoredItemMappings.java index 80a50f831..7f8456d6a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/StoredItemMappings.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/StoredItemMappings.java @@ -46,6 +46,7 @@ public class StoredItemMappings { private final ItemMapping fishingRod; private final ItemMapping lodestoneCompass; private final ItemMapping milkBucket; + private final ItemMapping powderSnowBucket; private final ItemMapping egg; private final ItemMapping shield; private final ItemMapping wheat; @@ -60,6 +61,7 @@ public class StoredItemMappings { this.fishingRod = load(itemMappings, "fishing_rod"); this.lodestoneCompass = load(itemMappings, "lodestone_compass"); this.milkBucket = load(itemMappings, "milk_bucket"); + this.powderSnowBucket = load(itemMappings, "powder_snow_bucket"); this.egg = load(itemMappings, "egg"); this.shield = load(itemMappings, "shield"); this.wheat = load(itemMappings, "wheat"); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/TropicalFishBucketTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/TropicalFishBucketTranslator.java new file mode 100644 index 000000000..2cbaea910 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/TropicalFishBucketTranslator.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.item.translators.nbt; + +import com.github.steveice10.opennbt.tag.builtin.*; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.TextDecoration; +import org.geysermc.connector.entity.living.animal.TropicalFishEntity; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.chat.MessageTranslator; +import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; +import org.geysermc.connector.registry.type.ItemMapping; +import org.geysermc.connector.utils.LocaleUtils; + +import java.util.ArrayList; +import java.util.List; + +@ItemRemapper +public class TropicalFishBucketTranslator extends NbtItemStackTranslator { + + private static final Style LORE_STYLE = Style.style(NamedTextColor.GRAY, TextDecoration.ITALIC); + + @Override + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) { + // Prevent name from appearing as "Bucket of" + itemTag.put(new ByteTag("AppendCustomName", (byte) 1)); + itemTag.put(new StringTag("CustomName", LocaleUtils.getLocaleString("entity.minecraft.tropical_fish", session.getLocale()))); + // Add Java's client side lore tag + Tag bucketVariantTag = itemTag.get("BucketVariantTag"); + if (bucketVariantTag instanceof IntTag) { + CompoundTag displayTag = itemTag.get("display"); + if (displayTag == null) { + displayTag = new CompoundTag("display"); + itemTag.put(displayTag); + } + + List lore = new ArrayList<>(); + + int varNumber = ((IntTag) bucketVariantTag).getValue(); + int predefinedVariantId = TropicalFishEntity.getPredefinedId(varNumber); + if (predefinedVariantId != -1) { + Component tooltip = Component.translatable("entity.minecraft.tropical_fish.predefined." + predefinedVariantId, LORE_STYLE); + lore.add(0, new StringTag("", MessageTranslator.convertMessage(tooltip, session.getLocale()))); + } else { + Component typeTooltip = Component.translatable("entity.minecraft.tropical_fish.type." + TropicalFishEntity.getVariantName(varNumber), LORE_STYLE); + lore.add(0, new StringTag("", MessageTranslator.convertMessage(typeTooltip, session.getLocale()))); + + byte baseColor = TropicalFishEntity.getBaseColor(varNumber); + byte patternColor = TropicalFishEntity.getPatternColor(varNumber); + Component colorTooltip = Component.translatable("color.minecraft." + TropicalFishEntity.getColorName(baseColor), LORE_STYLE); + if (baseColor != patternColor) { + colorTooltip = colorTooltip.append(Component.text(", ", LORE_STYLE)) + .append(Component.translatable("color.minecraft." + TropicalFishEntity.getColorName(patternColor), LORE_STYLE)); + } + lore.add(1, new StringTag("", MessageTranslator.convertMessage(colorTooltip, session.getLocale()))); + } + + ListTag loreTag = displayTag.get("Lore"); + if (loreTag != null) { + lore.addAll(loreTag.getValue()); + } + displayTag.put(new ListTag("Lore", lore)); + } + } + + @Override + public boolean acceptItem(ItemMapping mapping) { + return mapping.getJavaIdentifier().equals("minecraft:tropical_fish_bucket"); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/BucketSoundInteractionHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/BucketSoundInteractionHandler.java index d8689f51c..fe93361f0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/BucketSoundInteractionHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/BucketSoundInteractionHandler.java @@ -32,12 +32,14 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.sound.BlockSoundInteractionHandler; import org.geysermc.connector.network.translators.sound.SoundHandler; -@SoundHandler(items = "bucket") +@SoundHandler(items = "bucket", ignoreSneakingWhileHolding = true) public class BucketSoundInteractionHandler implements BlockSoundInteractionHandler { @Override public void handleInteraction(GeyserSession session, Vector3f position, String identifier) { - if (session.getBucketScheduledFuture() == null) return; // No bucket was really interacted with + if (session.getBucketScheduledFuture() == null) { + return; // No bucket was really interacted with + } String handItemIdentifier = session.getPlayerInventory().getItemInHand().getMapping(session).getJavaIdentifier(); LevelSoundEventPacket soundEventPacket = new LevelSoundEventPacket(); soundEventPacket.setPosition(position); @@ -52,22 +54,31 @@ public class BucketSoundInteractionHandler implements BlockSoundInteractionHandl soundEvent = SoundEvent.BUCKET_FILL_WATER; } else if (identifier.contains("lava[")) { soundEvent = SoundEvent.BUCKET_FILL_LAVA; + } else if (identifier.contains("powder_snow")) { + soundEvent = SoundEvent.BUCKET_FILL_POWDER_SNOW; } break; case "minecraft:lava_bucket": soundEvent = SoundEvent.BUCKET_EMPTY_LAVA; break; - case "minecraft:fish_bucket": + case "minecraft:axolotl_bucket": + case "minecraft:cod_bucket": + case "minecraft:salmon_bucket": + case "minecraft:pufferfish_bucket": + case "minecraft:tropical_fish_bucket": soundEvent = SoundEvent.BUCKET_EMPTY_FISH; break; case "minecraft:water_bucket": soundEvent = SoundEvent.BUCKET_EMPTY_WATER; break; + case "minecraft:powder_snow_bucket": + soundEvent = SoundEvent.BUCKET_EMPTY_POWDER_SNOW; + break; } if (soundEvent != null) { soundEventPacket.setSound(soundEvent); session.sendUpstreamPacket(soundEventPacket); + session.setBucketScheduledFuture(null); } - session.setBucketScheduledFuture(null); } } diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index f109d34a3..2efdb453e 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit f109d34a343da0ade6132661839b893859680d91 +Subproject commit 2efdb453e4e76992d63824b5c8b551bebec67b71