mirror of
https://github.com/GeyserMC/Geyser.git
synced 2025-04-17 19:12:14 +02:00
Merge remote-tracking branch 'upstream/master' into custom-item-api-v2
# Conflicts: # core/src/main/java/org/geysermc/geyser/session/cache/RegistryCache.java # core/src/main/java/org/geysermc/geyser/session/cache/registry/JavaRegistry.java # core/src/main/java/org/geysermc/geyser/session/cache/registry/RegistryEntryData.java # core/src/main/java/org/geysermc/geyser/session/cache/registry/SimpleJavaRegistry.java # core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java # gradle/libs.versions.toml
This commit is contained in:
commit
306c3eac34
183 changed files with 13250 additions and 21097 deletions
README.md
bootstrap
mod
fabric/src/main/resources
neoforge/src/main/resources/META-INF
src/main/java/org/geysermc/geyser/platform/mod
standalone/src/main/java/org/geysermc/geyser/platform/standalone
velocity/src/main/java/org/geysermc/geyser/platform/velocity
build-logic/src/main/kotlin
core/src/main/java/org/geysermc/geyser
GeyserLogger.java
command/defaults
entity
EntityDefinitions.java
type
inventory
AnvilContainer.javaBeaconContainer.javaCartographyContainer.javaContainer.javaCrafterContainer.javaEnchantingContainer.javaGeneric3X3Container.javaInventory.javaLecternContainer.javaMerchantContainer.javaPlayerInventory.javaStonecutterContainer.java
click
holder
item
recipe
updater
item
Items.javaTooltipOptions.java
hashing
ComponentHasher.javaDataComponentHashers.javaMapBuilder.javaMapHasher.javaMinecraftHashEncoder.javaMinecraftHasher.javaRegistryHasher.java
data
type
ArmorItem.javaAxolotlBucketItem.javaBannerItem.javaCompassItem.javaCrossbowItem.javaDecoratedPotItem.javaDyeableArmorItem.javaEnchantedBookItem.javaFireworkRocketItem.javaFireworkStarItem.javaFishingRodItem.javaGoatHornItem.javaItem.javaMapItem.javaPlayerHeadItem.javaShieldItem.javaShulkerBoxItem.javaTropicalFishBucketItem.javaWolfArmorItem.javaWritableBookItem.javaWrittenBookItem.java
level
|
@ -15,7 +15,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
|
|||
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
|
||||
|
||||
## Supported Versions
|
||||
Geyser is currently supporting Minecraft Bedrock 1.21.40 - 1.21.70 and Minecraft Java 1.21.4. For more information, please see [here](https://geysermc.org/wiki/geyser/supported-versions/).
|
||||
Geyser is currently supporting Minecraft Bedrock 1.21.50 - 1.21.71 and Minecraft Java 1.21.5. For more information, please see [here](https://geysermc.org/wiki/geyser/supported-versions/).
|
||||
|
||||
## Setting Up
|
||||
Take a look [here](https://geysermc.org/wiki/geyser/setup/) for how to set up Geyser.
|
||||
|
|
|
@ -25,6 +25,6 @@
|
|||
"depends": {
|
||||
"fabricloader": ">=0.16.7",
|
||||
"fabric-api": "*",
|
||||
"minecraft": ">=1.21.4"
|
||||
"minecraft": ">=1.21.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,12 +16,12 @@ config = "geyser_neoforge.mixins.json"
|
|||
[[dependencies.geyser_neoforge]]
|
||||
modId="neoforge"
|
||||
type="required"
|
||||
versionRange="[21.0.0-beta,)"
|
||||
versionRange="[21.5.0-beta,)"
|
||||
ordering="NONE"
|
||||
side="BOTH"
|
||||
[[dependencies.geyser_neoforge]]
|
||||
modId="minecraft"
|
||||
type="required"
|
||||
versionRange="[1.21,)"
|
||||
versionRange="[1.21.5,)"
|
||||
ordering="NONE"
|
||||
side="BOTH"
|
||||
side="BOTH"
|
||||
|
|
|
@ -87,7 +87,7 @@ public class GeyserModLogger implements GeyserLogger {
|
|||
@Override
|
||||
public void debug(String message, Object... arguments) {
|
||||
if (debug) {
|
||||
logger.info(message, arguments);
|
||||
logger.info(String.format(message, arguments));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,15 +25,13 @@
|
|||
|
||||
package org.geysermc.geyser.platform.mod.mixin.server;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.BlockItem;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.SoundType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
|
||||
|
@ -44,33 +42,29 @@ import org.spongepowered.asm.mixin.Mixin;
|
|||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
|
||||
|
||||
@Mixin(BlockItem.class)
|
||||
public class BlockPlaceMixin {
|
||||
|
||||
@Inject(method = "place", locals = LocalCapture.CAPTURE_FAILSOFT, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;playSound(Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/core/BlockPos;Lnet/minecraft/sounds/SoundEvent;Lnet/minecraft/sounds/SoundSource;FF)V"))
|
||||
private void geyser$hijackPlaySound(BlockPlaceContext blockPlaceContext, CallbackInfoReturnable<InteractionResult> cir, BlockPlaceContext blockPlaceContext2, BlockState blockState, BlockPos blockPos, Level level, Player player, ItemStack itemStack, BlockState blockState2, SoundType soundType) {
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Inject(method = "place", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;playSound(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/core/BlockPos;Lnet/minecraft/sounds/SoundEvent;Lnet/minecraft/sounds/SoundSource;FF)V"))
|
||||
private void geyser$hijackPlaySound(BlockPlaceContext blockPlaceContext, CallbackInfoReturnable<InteractionResult> callbackInfoReturnable,
|
||||
@Local BlockPos pos, @Local Player player, @Local(ordinal = 1) BlockState placedState) {
|
||||
GeyserSession session = GeyserImpl.getInstance().connectionByUuid(player.getUUID());
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3f position = Vector3f.from(
|
||||
blockPos.getX(),
|
||||
blockPos.getY(),
|
||||
blockPos.getZ()
|
||||
pos.getX(),
|
||||
pos.getY(),
|
||||
pos.getZ()
|
||||
);
|
||||
|
||||
LevelSoundEventPacket placeBlockSoundPacket = new LevelSoundEventPacket();
|
||||
placeBlockSoundPacket.setSound(SoundEvent.PLACE);
|
||||
placeBlockSoundPacket.setPosition(position);
|
||||
placeBlockSoundPacket.setBabySound(false);
|
||||
placeBlockSoundPacket.setExtraData(session.getBlockMappings().getBedrockBlockId(Block.BLOCK_STATE_REGISTRY.getId(blockState2)));
|
||||
placeBlockSoundPacket.setExtraData(session.getBlockMappings().getBedrockBlockId(Block.BLOCK_STATE_REGISTRY.getId(placedState)));
|
||||
placeBlockSoundPacket.setIdentifier(":");
|
||||
session.sendUpstreamPacket(placeBlockSoundPacket);
|
||||
session.setLastBlockPlacePosition(null);
|
||||
|
|
|
@ -117,7 +117,8 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements Gey
|
|||
|
||||
@Override
|
||||
public void debug(String message, Object... arguments) {
|
||||
log.debug(ChatColor.GRAY + message, arguments);
|
||||
// We can't use the debug call that would format for us as we're using Java's string formatting
|
||||
log.debug(ChatColor.GRAY + String.format(message, arguments));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -77,7 +77,7 @@ public class GeyserVelocityLogger implements GeyserLogger {
|
|||
@Override
|
||||
public void debug(String message, Object... arguments) {
|
||||
if (debug) {
|
||||
logger.info(message, arguments);
|
||||
logger.info(String.format(message, arguments));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,5 +69,4 @@ repositories {
|
|||
maven("https://jitpack.io") {
|
||||
content { includeGroupByRegex("com\\.github\\..*") }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import net.kyori.adventure.text.Component;
|
|||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
|
@ -119,6 +120,15 @@ public interface GeyserLogger extends GeyserCommandSource {
|
|||
*/
|
||||
void setDebug(boolean debug);
|
||||
|
||||
/**
|
||||
* A method to debug information specific to a session.
|
||||
*/
|
||||
default void debug(GeyserSession session, String message, Object... arguments) {
|
||||
if (isDebug()) {
|
||||
debug("(" + session.bedrockUsername() + ") " + message, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If debug is enabled for this logger
|
||||
*/
|
||||
|
|
|
@ -51,6 +51,6 @@ public class AdvancedTooltipsCommand extends GeyserCommand {
|
|||
+ MinecraftLocale.getLocaleString("debug.prefix", session.locale())
|
||||
+ " " + ChatColor.RESET
|
||||
+ MinecraftLocale.getLocaleString("debug.advanced_tooltips." + onOrOff, session.locale()));
|
||||
session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory());
|
||||
session.getPlayerInventory().updateInventory();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ import org.geysermc.geyser.entity.type.BoatEntity;
|
|||
import org.geysermc.geyser.entity.type.ChestBoatEntity;
|
||||
import org.geysermc.geyser.entity.type.CommandBlockMinecartEntity;
|
||||
import org.geysermc.geyser.entity.type.DisplayBaseEntity;
|
||||
import org.geysermc.geyser.entity.type.ThrowableEggEntity;
|
||||
import org.geysermc.geyser.entity.type.EnderCrystalEntity;
|
||||
import org.geysermc.geyser.entity.type.EnderEyeEntity;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
|
@ -80,8 +81,8 @@ import org.geysermc.geyser.entity.type.living.TadpoleEntity;
|
|||
import org.geysermc.geyser.entity.type.living.animal.ArmadilloEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.AxolotlEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.BeeEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.ChickenEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.CowEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.farm.ChickenEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.farm.CowEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.FoxEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.FrogEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.GoatEntity;
|
||||
|
@ -89,7 +90,7 @@ import org.geysermc.geyser.entity.type.living.animal.HoglinEntity;
|
|||
import org.geysermc.geyser.entity.type.living.animal.MooshroomEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.OcelotEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.PandaEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.PigEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.farm.PigEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.PolarBearEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.PufferFishEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.RabbitEntity;
|
||||
|
@ -189,7 +190,7 @@ public final class EntityDefinitions {
|
|||
public static final EntityDefinition<ChestedHorseEntity> DONKEY;
|
||||
public static final EntityDefinition<FireballEntity> DRAGON_FIREBALL;
|
||||
public static final EntityDefinition<ZombieEntity> DROWNED;
|
||||
public static final EntityDefinition<ThrowableItemEntity> EGG;
|
||||
public static final EntityDefinition<ThrowableEggEntity> EGG;
|
||||
public static final EntityDefinition<ElderGuardianEntity> ELDER_GUARDIAN;
|
||||
public static final EntityDefinition<EndermanEntity> ENDERMAN;
|
||||
public static final EntityDefinition<MonsterEntity> ENDERMITE;
|
||||
|
@ -250,7 +251,8 @@ public final class EntityDefinitions {
|
|||
public static final EntityDefinition<PillagerEntity> PILLAGER;
|
||||
public static final EntityDefinition<PlayerEntity> PLAYER;
|
||||
public static final EntityDefinition<PolarBearEntity> POLAR_BEAR;
|
||||
public static final EntityDefinition<ThrownPotionEntity> POTION;
|
||||
public static final EntityDefinition<ThrownPotionEntity> SPLASH_POTION;
|
||||
public static final EntityDefinition<ThrownPotionEntity> LINGERING_POTION;
|
||||
public static final EntityDefinition<PufferFishEntity> PUFFERFISH;
|
||||
public static final EntityDefinition<RabbitEntity> RABBIT;
|
||||
public static final EntityDefinition<RavagerEntity> RAVAGER;
|
||||
|
@ -312,7 +314,7 @@ public final class EntityDefinitions {
|
|||
EntityDefinition<Entity> entityBase = EntityDefinition.builder(Entity::new)
|
||||
.addTranslator(MetadataTypes.BYTE, Entity::setFlags)
|
||||
.addTranslator(MetadataTypes.INT, Entity::setAir) // Air/bubbles
|
||||
.addTranslator(MetadataTypes.OPTIONAL_CHAT, Entity::setDisplayName)
|
||||
.addTranslator(MetadataTypes.OPTIONAL_COMPONENT, Entity::setDisplayName)
|
||||
.addTranslator(MetadataTypes.BOOLEAN, Entity::setDisplayNameVisible)
|
||||
.addTranslator(MetadataTypes.BOOLEAN, Entity::setSilent)
|
||||
.addTranslator(MetadataTypes.BOOLEAN, Entity::setGravity)
|
||||
|
@ -337,12 +339,13 @@ public final class EntityDefinitions {
|
|||
.type(EntityType.END_CRYSTAL)
|
||||
.heightAndWidth(2.0f)
|
||||
.identifier("minecraft:ender_crystal")
|
||||
.addTranslator(MetadataTypes.OPTIONAL_POSITION, EnderCrystalEntity::setBlockTarget)
|
||||
.addTranslator(MetadataTypes.OPTIONAL_BLOCK_POS, EnderCrystalEntity::setBlockTarget)
|
||||
.addTranslator(MetadataTypes.BOOLEAN,
|
||||
(enderCrystalEntity, entityMetadata) -> enderCrystalEntity.setFlag(EntityFlag.SHOW_BOTTOM, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue())) // There is a base located on the ender crystal
|
||||
.build();
|
||||
EXPERIENCE_ORB = EntityDefinition.inherited(ExpOrbEntity::new, entityBase)
|
||||
.type(EntityType.EXPERIENCE_ORB)
|
||||
.addTranslator(null) // int determining xb orb texture
|
||||
.identifier("minecraft:xp_orb")
|
||||
.build();
|
||||
EVOKER_FANGS = EntityDefinition.inherited(EvokerFangsEntity::new, entityBase)
|
||||
|
@ -365,8 +368,8 @@ public final class EntityDefinitions {
|
|||
.type(EntityType.FIREWORK_ROCKET)
|
||||
.heightAndWidth(0.25f)
|
||||
.identifier("minecraft:fireworks_rocket")
|
||||
.addTranslator(MetadataTypes.ITEM, FireworkEntity::setFireworkItem)
|
||||
.addTranslator(MetadataTypes.OPTIONAL_VARINT, FireworkEntity::setPlayerGliding)
|
||||
.addTranslator(MetadataTypes.ITEM_STACK, FireworkEntity::setFireworkItem)
|
||||
.addTranslator(MetadataTypes.OPTIONAL_UNSIGNED_INT, FireworkEntity::setPlayerGliding)
|
||||
.addTranslator(null) // Shot at angle
|
||||
.build();
|
||||
FISHING_BOBBER = EntityDefinition.<FishingHookEntity>inherited(null, entityBase)
|
||||
|
@ -379,7 +382,7 @@ public final class EntityDefinitions {
|
|||
.type(EntityType.ITEM)
|
||||
.heightAndWidth(0.25f)
|
||||
.offset(0.125f)
|
||||
.addTranslator(MetadataTypes.ITEM, ItemEntity::setItem)
|
||||
.addTranslator(MetadataTypes.ITEM_STACK, ItemEntity::setItem)
|
||||
.build();
|
||||
LEASH_KNOT = EntityDefinition.inherited(LeashKnotEntity::new, entityBase)
|
||||
.type(EntityType.LEASH_KNOT)
|
||||
|
@ -428,7 +431,7 @@ public final class EntityDefinitions {
|
|||
.type(EntityType.TEXT_DISPLAY)
|
||||
.identifier("minecraft:armor_stand")
|
||||
.offset(-0.5f)
|
||||
.addTranslator(MetadataTypes.CHAT, TextDisplayEntity::setText)
|
||||
.addTranslator(MetadataTypes.COMPONENT, TextDisplayEntity::setText)
|
||||
.addTranslator(null) // Line width
|
||||
.addTranslator(null) // Background color
|
||||
.addTranslator(null) // Text opacity
|
||||
|
@ -457,9 +460,9 @@ public final class EntityDefinitions {
|
|||
.build();
|
||||
|
||||
EntityDefinition<ThrowableItemEntity> throwableItemBase = EntityDefinition.inherited(ThrowableItemEntity::new, entityBase)
|
||||
.addTranslator(MetadataTypes.ITEM, ThrowableItemEntity::setItem)
|
||||
.addTranslator(MetadataTypes.ITEM_STACK, ThrowableItemEntity::setItem)
|
||||
.build();
|
||||
EGG = EntityDefinition.inherited(ThrowableItemEntity::new, throwableItemBase)
|
||||
EGG = EntityDefinition.inherited(ThrowableEggEntity::new, throwableItemBase)
|
||||
.type(EntityType.EGG)
|
||||
.heightAndWidth(0.25f)
|
||||
.properties(VanillaEntityProperties.CLIMATE_VARIANT)
|
||||
|
@ -473,11 +476,16 @@ public final class EntityDefinitions {
|
|||
.heightAndWidth(0.25f)
|
||||
.identifier("minecraft:xp_bottle")
|
||||
.build();
|
||||
POTION = EntityDefinition.inherited(ThrownPotionEntity::new, throwableItemBase)
|
||||
.type(EntityType.POTION)
|
||||
SPLASH_POTION = EntityDefinition.inherited(ThrownPotionEntity::new, throwableItemBase)
|
||||
.type(EntityType.SPLASH_POTION)
|
||||
.heightAndWidth(0.25f)
|
||||
.identifier("minecraft:splash_potion")
|
||||
.build();
|
||||
LINGERING_POTION = EntityDefinition.inherited(ThrownPotionEntity::new, throwableItemBase)
|
||||
.type(EntityType.LINGERING_POTION)
|
||||
.heightAndWidth(0.25f)
|
||||
.identifier("minecraft:splash_potion")
|
||||
.build();
|
||||
SNOWBALL = EntityDefinition.inherited(ThrowableItemEntity::new, throwableItemBase)
|
||||
.type(EntityType.SNOWBALL)
|
||||
.heightAndWidth(0.25f)
|
||||
|
@ -520,7 +528,7 @@ public final class EntityDefinitions {
|
|||
// Item frames are handled differently as they are blocks, not items, in Bedrock
|
||||
ITEM_FRAME = EntityDefinition.<ItemFrameEntity>inherited(null, entityBase)
|
||||
.type(EntityType.ITEM_FRAME)
|
||||
.addTranslator(MetadataTypes.ITEM, ItemFrameEntity::setItemInFrame)
|
||||
.addTranslator(MetadataTypes.ITEM_STACK, ItemFrameEntity::setItemInFrame)
|
||||
.addTranslator(MetadataTypes.INT, ItemFrameEntity::setItemRotation)
|
||||
.build();
|
||||
GLOW_ITEM_FRAME = EntityDefinition.inherited(ITEM_FRAME.factory(), ITEM_FRAME)
|
||||
|
@ -536,9 +544,8 @@ public final class EntityDefinitions {
|
|||
.addTranslator(MetadataTypes.FLOAT, (minecartEntity, entityMetadata) ->
|
||||
// Power in Java, hurt ticks in Bedrock
|
||||
minecartEntity.getDirtyMetadata().put(EntityDataTypes.HURT_TICKS, Math.min((int) ((FloatEntityMetadata) entityMetadata).getPrimitiveValue(), 15)))
|
||||
.addTranslator(MetadataTypes.INT, MinecartEntity::setCustomBlock)
|
||||
.addTranslator(MetadataTypes.OPTIONAL_BLOCK_STATE, MinecartEntity::setCustomBlock)
|
||||
.addTranslator(MetadataTypes.INT, MinecartEntity::setCustomBlockOffset)
|
||||
.addTranslator(MetadataTypes.BOOLEAN, MinecartEntity::setShowCustomBlock)
|
||||
.build();
|
||||
CHEST_MINECART = EntityDefinition.inherited(MINECART.factory(), MINECART)
|
||||
.type(EntityType.CHEST_MINECART)
|
||||
|
@ -546,7 +553,7 @@ public final class EntityDefinitions {
|
|||
COMMAND_BLOCK_MINECART = EntityDefinition.inherited(CommandBlockMinecartEntity::new, MINECART)
|
||||
.type(EntityType.COMMAND_BLOCK_MINECART)
|
||||
.addTranslator(MetadataTypes.STRING, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityDataTypes.COMMAND_BLOCK_NAME, entityMetadata.getValue()))
|
||||
.addTranslator(MetadataTypes.CHAT, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityDataTypes.COMMAND_BLOCK_LAST_OUTPUT, MessageTranslator.convertMessage(entityMetadata.getValue())))
|
||||
.addTranslator(MetadataTypes.COMPONENT, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityDataTypes.COMMAND_BLOCK_LAST_OUTPUT, MessageTranslator.convertMessage(entityMetadata.getValue())))
|
||||
.build();
|
||||
FURNACE_MINECART = EntityDefinition.inherited(FurnaceMinecartEntity::new, MINECART)
|
||||
.type(EntityType.FURNACE_MINECART)
|
||||
|
@ -622,19 +629,19 @@ public final class EntityDefinitions {
|
|||
(livingEntity, entityMetadata) -> livingEntity.getDirtyMetadata().put(EntityDataTypes.EFFECT_AMBIENCE, (byte) (((BooleanEntityMetadata) entityMetadata).getPrimitiveValue() ? 1 : 0)))
|
||||
.addTranslator(null) // Arrow count
|
||||
.addTranslator(null) // Stinger count
|
||||
.addTranslator(MetadataTypes.OPTIONAL_POSITION, LivingEntity::setBedPosition)
|
||||
.addTranslator(MetadataTypes.OPTIONAL_BLOCK_POS, LivingEntity::setBedPosition)
|
||||
.build();
|
||||
|
||||
ARMOR_STAND = EntityDefinition.inherited(ArmorStandEntity::new, livingEntityBase)
|
||||
.type(EntityType.ARMOR_STAND)
|
||||
.height(1.975f).width(0.5f)
|
||||
.addTranslator(MetadataTypes.BYTE, ArmorStandEntity::setArmorStandFlags)
|
||||
.addTranslator(MetadataTypes.ROTATION, ArmorStandEntity::setHeadRotation)
|
||||
.addTranslator(MetadataTypes.ROTATION, ArmorStandEntity::setBodyRotation)
|
||||
.addTranslator(MetadataTypes.ROTATION, ArmorStandEntity::setLeftArmRotation)
|
||||
.addTranslator(MetadataTypes.ROTATION, ArmorStandEntity::setRightArmRotation)
|
||||
.addTranslator(MetadataTypes.ROTATION, ArmorStandEntity::setLeftLegRotation)
|
||||
.addTranslator(MetadataTypes.ROTATION, ArmorStandEntity::setRightLegRotation)
|
||||
.addTranslator(MetadataTypes.ROTATIONS, ArmorStandEntity::setHeadRotation)
|
||||
.addTranslator(MetadataTypes.ROTATIONS, ArmorStandEntity::setBodyRotation)
|
||||
.addTranslator(MetadataTypes.ROTATIONS, ArmorStandEntity::setLeftArmRotation)
|
||||
.addTranslator(MetadataTypes.ROTATIONS, ArmorStandEntity::setRightArmRotation)
|
||||
.addTranslator(MetadataTypes.ROTATIONS, ArmorStandEntity::setLeftLegRotation)
|
||||
.addTranslator(MetadataTypes.ROTATIONS, ArmorStandEntity::setRightLegRotation)
|
||||
.build();
|
||||
PLAYER = EntityDefinition.<PlayerEntity>inherited(null, livingEntityBase)
|
||||
.type(EntityType.PLAYER)
|
||||
|
@ -644,8 +651,8 @@ public final class EntityDefinitions {
|
|||
.addTranslator(null) // Player score
|
||||
.addTranslator(MetadataTypes.BYTE, PlayerEntity::setSkinVisibility)
|
||||
.addTranslator(null) // Player main hand
|
||||
.addTranslator(MetadataTypes.NBT_TAG, PlayerEntity::setLeftParrot)
|
||||
.addTranslator(MetadataTypes.NBT_TAG, PlayerEntity::setRightParrot)
|
||||
.addTranslator(MetadataTypes.COMPOUND_TAG, PlayerEntity::setLeftParrot)
|
||||
.addTranslator(MetadataTypes.COMPOUND_TAG, PlayerEntity::setRightParrot)
|
||||
.build();
|
||||
|
||||
EntityDefinition<MobEntity> mobEntityBase = EntityDefinition.inherited(MobEntity::new, livingEntityBase)
|
||||
|
@ -685,7 +692,7 @@ public final class EntityDefinitions {
|
|||
.addTranslator(MetadataTypes.BOOLEAN, CreakingEntity::setCanMove)
|
||||
.addTranslator(MetadataTypes.BOOLEAN, CreakingEntity::setActive)
|
||||
.addTranslator(MetadataTypes.BOOLEAN, CreakingEntity::setIsTearingDown)
|
||||
.addTranslator(MetadataTypes.OPTIONAL_POSITION, CreakingEntity::setHomePos)
|
||||
.addTranslator(MetadataTypes.OPTIONAL_BLOCK_POS, CreakingEntity::setHomePos)
|
||||
.properties(VanillaEntityProperties.CREAKING)
|
||||
.build();
|
||||
CREEPER = EntityDefinition.inherited(CreeperEntity::new, mobEntityBase)
|
||||
|
@ -960,11 +967,13 @@ public final class EntityDefinitions {
|
|||
.type(EntityType.CHICKEN)
|
||||
.height(0.7f).width(0.4f)
|
||||
.properties(VanillaEntityProperties.CLIMATE_VARIANT)
|
||||
.addTranslator(MetadataTypes.CHICKEN_VARIANT, ChickenEntity::setVariant)
|
||||
.build();
|
||||
COW = EntityDefinition.inherited(CowEntity::new, ageableEntityBase)
|
||||
.type(EntityType.COW)
|
||||
.height(1.4f).width(0.9f)
|
||||
.properties(VanillaEntityProperties.CLIMATE_VARIANT)
|
||||
.addTranslator(MetadataTypes.COW_VARIANT, CowEntity::setVariant)
|
||||
.build();
|
||||
FOX = EntityDefinition.inherited(FoxEntity::new, ageableEntityBase)
|
||||
.type(EntityType.FOX)
|
||||
|
@ -977,8 +986,8 @@ public final class EntityDefinitions {
|
|||
FROG = EntityDefinition.inherited(FrogEntity::new, ageableEntityBase)
|
||||
.type(EntityType.FROG)
|
||||
.heightAndWidth(0.5f)
|
||||
.addTranslator(MetadataTypes.FROG_VARIANT, FrogEntity::setFrogVariant)
|
||||
.addTranslator(MetadataTypes.OPTIONAL_VARINT, FrogEntity::setTongueTarget)
|
||||
.addTranslator(MetadataTypes.FROG_VARIANT, FrogEntity::setVariant)
|
||||
.addTranslator(MetadataTypes.OPTIONAL_UNSIGNED_INT, FrogEntity::setTongueTarget)
|
||||
.build();
|
||||
HOGLIN = EntityDefinition.inherited(HoglinEntity::new, ageableEntityBase)
|
||||
.type(EntityType.HOGLIN)
|
||||
|
@ -995,7 +1004,7 @@ public final class EntityDefinitions {
|
|||
MOOSHROOM = EntityDefinition.inherited(MooshroomEntity::new, ageableEntityBase)
|
||||
.type(EntityType.MOOSHROOM)
|
||||
.height(1.4f).width(0.9f)
|
||||
.addTranslator(MetadataTypes.STRING, MooshroomEntity::setVariant)
|
||||
.addTranslator(MetadataTypes.INT, MooshroomEntity::setMooshroomVariant)
|
||||
.build();
|
||||
OCELOT = EntityDefinition.inherited(OcelotEntity::new, ageableEntityBase)
|
||||
.type(EntityType.OCELOT)
|
||||
|
@ -1016,8 +1025,8 @@ public final class EntityDefinitions {
|
|||
.type(EntityType.PIG)
|
||||
.heightAndWidth(0.9f)
|
||||
.properties(VanillaEntityProperties.CLIMATE_VARIANT)
|
||||
.addTranslator(MetadataTypes.BOOLEAN, (pigEntity, entityMetadata) -> pigEntity.setFlag(EntityFlag.SADDLED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
|
||||
.addTranslator(MetadataTypes.INT, PigEntity::setBoost)
|
||||
.addTranslator(MetadataTypes.PIG_VARIANT, PigEntity::setVariant)
|
||||
.build();
|
||||
POLAR_BEAR = EntityDefinition.inherited(PolarBearEntity::new, ageableEntityBase)
|
||||
.type(EntityType.POLAR_BEAR)
|
||||
|
@ -1045,7 +1054,6 @@ public final class EntityDefinitions {
|
|||
.height(1.7f).width(0.9f)
|
||||
.addTranslator(MetadataTypes.INT, StriderEntity::setBoost)
|
||||
.addTranslator(MetadataTypes.BOOLEAN, StriderEntity::setCold)
|
||||
.addTranslator(MetadataTypes.BOOLEAN, StriderEntity::setSaddled)
|
||||
.build();
|
||||
TURTLE = EntityDefinition.inherited(TurtleEntity::new, ageableEntityBase)
|
||||
.type(EntityType.TURTLE)
|
||||
|
@ -1144,12 +1152,12 @@ public final class EntityDefinitions {
|
|||
|
||||
EntityDefinition<TameableEntity> tameableEntityBase = EntityDefinition.<TameableEntity>inherited(null, ageableEntityBase) // No factory, is abstract
|
||||
.addTranslator(MetadataTypes.BYTE, TameableEntity::setTameableFlags)
|
||||
.addTranslator(MetadataTypes.OPTIONAL_UUID, TameableEntity::setOwner)
|
||||
.addTranslator(MetadataTypes.OPTIONAL_LIVING_ENTITY_REFERENCE, TameableEntity::setOwner)
|
||||
.build();
|
||||
CAT = EntityDefinition.inherited(CatEntity::new, tameableEntityBase)
|
||||
.type(EntityType.CAT)
|
||||
.height(0.35f).width(0.3f)
|
||||
.addTranslator(MetadataTypes.CAT_VARIANT, CatEntity::setCatVariant)
|
||||
.addTranslator(MetadataTypes.CAT_VARIANT, CatEntity::setVariant)
|
||||
.addTranslator(MetadataTypes.BOOLEAN, CatEntity::setResting)
|
||||
.addTranslator(null) // "resting state one" //TODO
|
||||
.addTranslator(MetadataTypes.INT, CatEntity::setCollarColor)
|
||||
|
@ -1167,7 +1175,7 @@ public final class EntityDefinitions {
|
|||
.addTranslator(MetadataTypes.BOOLEAN, (wolfEntity, entityMetadata) -> wolfEntity.setFlag(EntityFlag.INTERESTED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
|
||||
.addTranslator(MetadataTypes.INT, WolfEntity::setCollarColor)
|
||||
.addTranslator(MetadataTypes.INT, WolfEntity::setWolfAngerTime)
|
||||
.addTranslator(MetadataTypes.WOLF_VARIANT, WolfEntity::setWolfVariant)
|
||||
.addTranslator(MetadataTypes.WOLF_VARIANT, WolfEntity::setVariant)
|
||||
.build();
|
||||
|
||||
// As of 1.18 these don't track entity data at all
|
||||
|
|
|
@ -35,7 +35,7 @@ import org.geysermc.geyser.session.GeyserSession;
|
|||
import org.geysermc.geyser.util.MathUtils;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.particle.EntityEffectParticleData;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.particle.ColorParticleData;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.particle.Particle;
|
||||
|
||||
import java.util.UUID;
|
||||
|
@ -72,7 +72,7 @@ public class AreaEffectCloudEntity extends Entity {
|
|||
Registries.PARTICLES.map(particle.getType(), p -> p.levelEventType() instanceof ParticleType particleType ? particleType : null).ifPresent(type ->
|
||||
dirtyMetadata.put(EntityDataTypes.AREA_EFFECT_CLOUD_PARTICLE, type));
|
||||
|
||||
if (particle.getData() instanceof EntityEffectParticleData effectParticleData) {
|
||||
if (particle.getData() instanceof ColorParticleData effectParticleData) {
|
||||
dirtyMetadata.put(EntityDataTypes.EFFECT_COLOR, effectParticleData.getColor());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,11 +77,6 @@ public class BoatEntity extends Entity implements Leashable, Tickable {
|
|||
super(session, entityId, geyserId, uuid, definition, position.add(0d, definition.offset(), 0d), motion, yaw + 90, 0, yaw + 90);
|
||||
this.variant = variant;
|
||||
|
||||
// TODO remove once 1.21.40 is dropped
|
||||
if (variant == BoatVariant.PALE_OAK && GameProtocol.isPreWinterDrop(session)) {
|
||||
variant = BoatVariant.BIRCH;
|
||||
}
|
||||
|
||||
dirtyMetadata.put(EntityDataTypes.VARIANT, variant.ordinal());
|
||||
|
||||
// Required to be able to move on land 1.16.200+ or apply gravity not in the water 1.16.100+
|
||||
|
|
|
@ -25,12 +25,11 @@
|
|||
|
||||
package org.geysermc.geyser.entity.type;
|
||||
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
|
@ -58,9 +57,13 @@ public class DefaultBlockMinecartEntity extends MinecartEntity {
|
|||
@Override
|
||||
public void setCustomBlock(IntEntityMetadata entityMetadata) {
|
||||
customBlock = entityMetadata.getPrimitiveValue();
|
||||
showCustomBlock = entityMetadata.getPrimitiveValue() != 0;
|
||||
|
||||
if (showCustomBlock) {
|
||||
dirtyMetadata.put(EntityDataTypes.DISPLAY_BLOCK_STATE, session.getBlockMappings().getBedrockBlock(customBlock));
|
||||
dirtyMetadata.put(EntityDataTypes.DISPLAY_OFFSET, customBlockOffset);
|
||||
} else {
|
||||
updateDefaultBlockMetadata();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,18 +76,6 @@ public class DefaultBlockMinecartEntity extends MinecartEntity {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShowCustomBlock(BooleanEntityMetadata entityMetadata) {
|
||||
if (entityMetadata.getPrimitiveValue()) {
|
||||
showCustomBlock = true;
|
||||
dirtyMetadata.put(EntityDataTypes.DISPLAY_BLOCK_STATE, session.getBlockMappings().getBedrockBlock(customBlock));
|
||||
dirtyMetadata.put(EntityDataTypes.DISPLAY_OFFSET, customBlockOffset);
|
||||
} else {
|
||||
showCustomBlock = false;
|
||||
updateDefaultBlockMetadata();
|
||||
}
|
||||
}
|
||||
|
||||
public void updateDefaultBlockMetadata() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -435,7 +435,7 @@ public class Entity implements GeyserEntity {
|
|||
}
|
||||
|
||||
public String teamIdentifier() {
|
||||
// experience orbs are the only known entities that do not send an uuid (even though they do have one),
|
||||
// experience orbs were the only known entities that do not send an uuid pre 1.21.5 (even though they do have one),
|
||||
// but to be safe in the future it's done in the entity class itself instead of the entity specific one.
|
||||
// All entities without an uuid cannot show up in the scoreboard!
|
||||
return uuid != null ? uuid.toString() : null;
|
||||
|
|
|
@ -28,7 +28,6 @@ package org.geysermc.geyser.entity.type;
|
|||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
import java.util.UUID;
|
||||
|
@ -36,12 +35,7 @@ import java.util.UUID;
|
|||
public class ExpOrbEntity extends Entity {
|
||||
|
||||
public ExpOrbEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> entityDefinition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
this(session, 1, entityId, geyserId, position);
|
||||
}
|
||||
|
||||
public ExpOrbEntity(GeyserSession session, int amount, int entityId, long geyserId, Vector3f position) {
|
||||
super(session, entityId, geyserId, null, EntityDefinitions.EXPERIENCE_ORB, position, Vector3f.ZERO, 0, 0, 0);
|
||||
|
||||
this.dirtyMetadata.put(EntityDataTypes.TRADE_EXPERIENCE, amount);
|
||||
super(session, entityId, geyserId, uuid, entityDefinition, position, motion, yaw, pitch, headYaw);
|
||||
this.dirtyMetadata.put(EntityDataTypes.TRADE_EXPERIENCE, 1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.cloudburstmc.protocol.bedrock.packet.SetEntityMotionPacket;
|
|||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.item.TooltipOptions;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
|
||||
|
@ -59,7 +60,8 @@ public class FireworkEntity extends Entity {
|
|||
// TODO this looked the same, so I'm going to assume it is and (keep below comment if true)
|
||||
// Translate using item methods to get firework NBT for Bedrock
|
||||
BedrockItemBuilder builder = new BedrockItemBuilder();
|
||||
Items.FIREWORK_ROCKET.translateComponentsToBedrock(session, components, builder);
|
||||
TooltipOptions tooltip = TooltipOptions.fromComponents(components);
|
||||
Items.FIREWORK_ROCKET.translateComponentsToBedrock(session, components, tooltip, builder);
|
||||
|
||||
dirtyMetadata.put(EntityDataTypes.DISPLAY_FIREWORK, builder.build());
|
||||
}
|
||||
|
|
|
@ -44,6 +44,8 @@ import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
|||
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.scoreboard.Team;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
@ -51,6 +53,7 @@ import org.geysermc.geyser.translator.item.ItemTranslator;
|
|||
import org.geysermc.geyser.util.AttributeUtils;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.MathUtils;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.EquipmentSlot;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.Attribute;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.AttributeType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
|
||||
|
@ -62,7 +65,9 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.Object
|
|||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.particle.EntityEffectParticleData;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.Equippable;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.particle.ColorParticleData;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.particle.Particle;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.particle.ParticleType;
|
||||
|
||||
|
@ -80,6 +85,7 @@ public class LivingEntity extends Entity {
|
|||
protected ItemData leggings = ItemData.AIR;
|
||||
protected ItemData boots = ItemData.AIR;
|
||||
protected ItemData body = ItemData.AIR;
|
||||
protected ItemData saddle = ItemData.AIR;
|
||||
protected ItemData hand = ItemData.AIR;
|
||||
protected ItemData offhand = ItemData.AIR;
|
||||
|
||||
|
@ -118,10 +124,6 @@ public class LivingEntity extends Entity {
|
|||
this.chestplate = ItemTranslator.translateToBedrock(session, stack);
|
||||
}
|
||||
|
||||
public void setBody(ItemStack stack) {
|
||||
this.body = ItemTranslator.translateToBedrock(session, stack);
|
||||
}
|
||||
|
||||
public void setLeggings(ItemStack stack) {
|
||||
this.leggings = ItemTranslator.translateToBedrock(session, stack);
|
||||
}
|
||||
|
@ -130,6 +132,26 @@ public class LivingEntity extends Entity {
|
|||
this.boots = ItemTranslator.translateToBedrock(session, stack);
|
||||
}
|
||||
|
||||
public void setBody(ItemStack stack) {
|
||||
this.body = ItemTranslator.translateToBedrock(session, stack);
|
||||
}
|
||||
|
||||
public void setSaddle(@Nullable ItemStack stack) {
|
||||
this.saddle = ItemTranslator.translateToBedrock(session, stack);
|
||||
|
||||
boolean saddled = false;
|
||||
if (stack != null) {
|
||||
Item item = Registries.JAVA_ITEMS.get(stack.getId());
|
||||
if (item != null) {
|
||||
DataComponents components = item.gatherComponents(stack.getDataComponentsPatch());
|
||||
Equippable equippable = components.get(DataComponentTypes.EQUIPPABLE);
|
||||
saddled = equippable != null && equippable.slot() == EquipmentSlot.SADDLE;
|
||||
}
|
||||
}
|
||||
|
||||
updateSaddled(saddled);
|
||||
}
|
||||
|
||||
public void setHand(ItemStack stack) {
|
||||
this.hand = ItemTranslator.translateToBedrock(session, stack);
|
||||
}
|
||||
|
@ -138,6 +160,17 @@ public class LivingEntity extends Entity {
|
|||
this.offhand = ItemTranslator.translateToBedrock(session, stack);
|
||||
}
|
||||
|
||||
protected void updateSaddled(boolean saddled) {
|
||||
setFlag(EntityFlag.SADDLED, saddled);
|
||||
updateBedrockMetadata();
|
||||
|
||||
// Update the interactive tag, if necessary
|
||||
Entity mouseoverEntity = session.getMouseoverEntity();
|
||||
if (mouseoverEntity != null && mouseoverEntity.getEntityId() == entityId) {
|
||||
mouseoverEntity.updateInteractiveTag();
|
||||
}
|
||||
}
|
||||
|
||||
public void switchHands() {
|
||||
ItemData offhand = this.offhand;
|
||||
this.offhand = this.hand;
|
||||
|
@ -202,7 +235,7 @@ public class LivingEntity extends Entity {
|
|||
continue;
|
||||
}
|
||||
|
||||
int color = ((EntityEffectParticleData) particle.getData()).getColor();
|
||||
int color = ((ColorParticleData) particle.getData()).getColor();
|
||||
r += ((color >> 16) & 0xFF) / 255f;
|
||||
g += ((color >> 8) & 0xFF) / 255f;
|
||||
b += ((color) & 0xFF) / 255f;
|
||||
|
|
|
@ -37,7 +37,6 @@ import org.geysermc.geyser.util.InteractionResult;
|
|||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
import org.geysermc.geyser.util.MathUtils;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.MinecartStep;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundMoveMinecartPacket;
|
||||
|
@ -65,6 +64,9 @@ public class MinecartEntity extends Entity implements Tickable {
|
|||
}
|
||||
|
||||
public void setCustomBlock(IntEntityMetadata entityMetadata) {
|
||||
// Optional block state -> "0" is air, aka none
|
||||
// Sets whether the custom block should be enabled
|
||||
dirtyMetadata.put(EntityDataTypes.CUSTOM_DISPLAY, (byte) (entityMetadata.getPrimitiveValue() != 0 ? 1 : 0));
|
||||
dirtyMetadata.put(EntityDataTypes.DISPLAY_BLOCK_STATE, session.getBlockMappings().getBedrockBlock(entityMetadata.getPrimitiveValue()));
|
||||
}
|
||||
|
||||
|
@ -72,12 +74,6 @@ public class MinecartEntity extends Entity implements Tickable {
|
|||
dirtyMetadata.put(EntityDataTypes.DISPLAY_OFFSET, entityMetadata.getPrimitiveValue());
|
||||
}
|
||||
|
||||
public void setShowCustomBlock(BooleanEntityMetadata entityMetadata) {
|
||||
// If the custom block should be enabled
|
||||
// Needs a byte based off of Java's boolean
|
||||
dirtyMetadata.put(EntityDataTypes.CUSTOM_DISPLAY, (byte) (entityMetadata.getPrimitiveValue() ? 1 : 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
if (!session.isUsingExperimentalMinecartLogic()) {
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.cloudburstmc.protocol.bedrock.packet.AddPaintingPacket;
|
|||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.level.PaintingType;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.Holder;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.PaintingVariant;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
|
||||
|
@ -55,10 +56,7 @@ public class PaintingEntity extends Entity {
|
|||
if (!entityMetadata.getValue().isId()) {
|
||||
return;
|
||||
}
|
||||
PaintingType type = session.getRegistryCache().paintings().byId(entityMetadata.getValue().id());
|
||||
if (type == null) {
|
||||
return;
|
||||
}
|
||||
PaintingType type = session.getRegistryCache().registry(JavaRegistries.PAINTING_VARIANT).byId(entityMetadata.getValue().id());
|
||||
AddPaintingPacket addPaintingPacket = new AddPaintingPacket();
|
||||
addPaintingPacket.setUniqueEntityId(geyserId);
|
||||
addPaintingPacket.setRuntimeEntityId(geyserId);
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.entity.type;
|
||||
|
||||
import net.kyori.adventure.key.Key;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.AddEntityPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.properties.VanillaEntityProperties;
|
||||
import org.geysermc.geyser.entity.type.living.animal.farm.TemperatureVariantAnimal;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.Holder;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ThrowableEggEntity extends ThrowableItemEntity {
|
||||
public ThrowableEggEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAdditionalSpawnData(AddEntityPacket addEntityPacket) {
|
||||
propertyManager.add(VanillaEntityProperties.CLIMATE_VARIANT_ID, "temperate");
|
||||
propertyManager.applyIntProperties(addEntityPacket.getProperties().getIntProperties());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(EntityMetadata<ItemStack, ?> entityMetadata) {
|
||||
GeyserItemStack stack = GeyserItemStack.from(entityMetadata.getValue());
|
||||
propertyManager.add(VanillaEntityProperties.CLIMATE_VARIANT_ID, getVariantOrFallback(session, stack));
|
||||
updateBedrockEntityProperties();
|
||||
}
|
||||
|
||||
private static String getVariantOrFallback(GeyserSession session, GeyserItemStack stack) {
|
||||
Holder<Key> holder = stack.getComponent(DataComponentTypes.CHICKEN_VARIANT);
|
||||
if (holder != null) {
|
||||
Key chickenVariant = holder.getOrCompute(id -> JavaRegistries.CHICKEN_VARIANT.keyFromNetworkId(session, id));
|
||||
for (var variant : TemperatureVariantAnimal.BuiltInVariant.values()) {
|
||||
if (chickenVariant.asMinimalString().equalsIgnoreCase(variant.name())) {
|
||||
return chickenVariant.asMinimalString().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TemperatureVariantAnimal.BuiltInVariant.TEMPERATE.name().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
|
@ -120,7 +120,7 @@ public class ThrowableEntity extends Entity implements Tickable {
|
|||
protected float getGravity() {
|
||||
if (getFlag(EntityFlag.HAS_GRAVITY)) {
|
||||
switch (definition.entityType()) {
|
||||
case POTION:
|
||||
case LINGERING_POTION, SPLASH_POTION:
|
||||
return 0.05f;
|
||||
case EXPERIENCE_BOTTLE:
|
||||
return 0.07f;
|
||||
|
@ -146,7 +146,7 @@ public class ThrowableEntity extends Entity implements Tickable {
|
|||
return 0.8f;
|
||||
} else {
|
||||
switch (definition.entityType()) {
|
||||
case POTION:
|
||||
case LINGERING_POTION, SPLASH_POTION:
|
||||
case EXPERIENCE_BOTTLE:
|
||||
case SNOWBALL:
|
||||
case EGG:
|
||||
|
|
|
@ -28,13 +28,10 @@ package org.geysermc.geyser.entity.type;
|
|||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.AddEntityPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.entity.properties.VanillaEntityProperties;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
|
||||
|
||||
import java.util.UUID;
|
||||
|
@ -56,14 +53,6 @@ public class ThrowableItemEntity extends ThrowableEntity {
|
|||
age = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAdditionalSpawnData(AddEntityPacket addEntityPacket) {
|
||||
if (definition.entityType() == EntityType.EGG) {
|
||||
propertyManager.add(VanillaEntityProperties.CLIMATE_VARIANT_ID, "temperate");
|
||||
propertyManager.applyIntProperties(addEntityPacket.getProperties().getIntProperties());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeMetadata() {
|
||||
super.initializeMetadata();
|
||||
|
|
|
@ -31,10 +31,9 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
|||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.item.Potion;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
|
||||
|
@ -73,7 +72,7 @@ public class ThrownPotionEntity extends ThrowableItemEntity {
|
|||
}
|
||||
}
|
||||
|
||||
boolean isLingering = Registries.JAVA_ITEMS.get().get(itemStack.getId()) == Items.LINGERING_POTION;
|
||||
boolean isLingering = definition.entityType() == EntityType.LINGERING_POTION;
|
||||
setFlag(EntityFlag.LINGERING, isLingering);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,16 +33,17 @@ import org.geysermc.geyser.entity.EntityDefinition;
|
|||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistryKey;
|
||||
import org.geysermc.geyser.session.cache.tags.ItemTag;
|
||||
import org.geysermc.geyser.session.cache.tags.Tag;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
|
||||
|
||||
import java.util.OptionalInt;
|
||||
import java.util.UUID;
|
||||
|
||||
public class FrogEntity extends AnimalEntity {
|
||||
public class FrogEntity extends AnimalEntity implements VariantIntHolder {
|
||||
public FrogEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
@ -56,13 +57,14 @@ public class FrogEntity extends AnimalEntity {
|
|||
super.setPose(pose);
|
||||
}
|
||||
|
||||
public void setFrogVariant(IntEntityMetadata entityMetadata) {
|
||||
int variant = entityMetadata.getPrimitiveValue();
|
||||
dirtyMetadata.put(EntityDataTypes.VARIANT, switch (variant) {
|
||||
case 1 -> 2; // White
|
||||
case 2 -> 1; // Green
|
||||
default -> variant;
|
||||
});
|
||||
@Override
|
||||
public JavaRegistryKey<BuiltInVariant> variantRegistry() {
|
||||
return JavaRegistries.FROG_VARIANT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBedrockVariantId(int bedrockId) {
|
||||
dirtyMetadata.put(EntityDataTypes.VARIANT, bedrockId);
|
||||
}
|
||||
|
||||
public void setTongueTarget(ObjectEntityMetadata<OptionalInt> entityMetadata) {
|
||||
|
@ -82,4 +84,12 @@ public class FrogEntity extends AnimalEntity {
|
|||
protected Tag<Item> getFoodTag() {
|
||||
return ItemTag.FROG_FOOD;
|
||||
}
|
||||
|
||||
// Ordered by bedrock id
|
||||
// TODO: are these ordered correctly?
|
||||
public enum BuiltInVariant implements BuiltIn {
|
||||
TEMPERATE,
|
||||
COLD,
|
||||
WARM
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,13 +30,14 @@ import org.cloudburstmc.math.vector.Vector3f;
|
|||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.AddEntityPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.type.living.animal.farm.CowEntity;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.tags.ItemTag;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
||||
|
||||
import java.util.UUID;
|
||||
|
@ -48,9 +49,9 @@ public class MooshroomEntity extends CowEntity {
|
|||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
public void setVariant(ObjectEntityMetadata<String> entityMetadata) {
|
||||
isBrown = entityMetadata.getValue().equals("brown");
|
||||
dirtyMetadata.put(EntityDataTypes.VARIANT, isBrown ? 1 : 0);
|
||||
public void setMooshroomVariant(IntEntityMetadata metadata) {
|
||||
isBrown = metadata.getPrimitiveValue() == 1;
|
||||
dirtyMetadata.put(EntityDataTypes.VARIANT, metadata.getPrimitiveValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -69,10 +69,6 @@ public class StriderEntity extends AnimalEntity implements Tickable, ClientVehic
|
|||
isCold = entityMetadata.getPrimitiveValue();
|
||||
}
|
||||
|
||||
public void setSaddled(BooleanEntityMetadata entityMetadata) {
|
||||
setFlag(EntityFlag.SADDLED, entityMetadata.getPrimitiveValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBedrockMetadata() {
|
||||
// Make sure they are not shaking when riding another entity
|
||||
|
|
|
@ -61,6 +61,10 @@ public class TropicalFishEntity extends AbstractFishEntity {
|
|||
dirtyMetadata.put(EntityDataTypes.COLOR_2, getPatternColor(varNumber)); // Pattern color 0-15
|
||||
}
|
||||
|
||||
public static int getPackedVariant(int pattern, int baseColor, int patternColor) {
|
||||
return pattern & 65535 | (baseColor & 0xFF) << 16 | (patternColor & 0xFF) << 24;
|
||||
}
|
||||
|
||||
public static int getShape(int variant) {
|
||||
return Math.min(variant & 0xFF, 1);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.entity.type.living.animal;
|
||||
|
||||
import net.kyori.adventure.key.Key;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.RegistryCache;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistryKey;
|
||||
import org.geysermc.geyser.util.MinecraftKey;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Interface to help set up data-driven entity variants for mobs.
|
||||
*
|
||||
* <p>Data-driven variants are sent as an int ID of their variant registry by Java, but can be a metadata ID or entity property on bedrock.
|
||||
* This interface helps translate data-driven variants to built-in bedrock ones.</p>
|
||||
*
|
||||
* <p>Implementations usually have to implement {@link VariantHolder#variantRegistry()} and {@link VariantHolder#setBedrockVariant(BuiltIn)}, and should also
|
||||
* have an enum with built-in variants on bedrock (implementing {@link BuiltIn}).</p>
|
||||
*
|
||||
* @param <BedrockVariant> the enum of Bedrock variants.
|
||||
*/
|
||||
public interface VariantHolder<BedrockVariant extends VariantHolder.BuiltIn> {
|
||||
|
||||
default void setVariant(IntEntityMetadata variant) {
|
||||
setVariantFromJavaId(variant.getPrimitiveValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the variant of the entity.
|
||||
*/
|
||||
default void setVariantFromJavaId(int variant) {
|
||||
setBedrockVariant(variantRegistry().fromNetworkId(getSession(), variant));
|
||||
}
|
||||
|
||||
GeyserSession getSession();
|
||||
|
||||
/**
|
||||
* The registry in {@link org.geysermc.geyser.session.cache.registry.JavaRegistries} for this mob's variants. The registry can utilise the {@link VariantHolder#reader(Class, Enum)} method
|
||||
* to create a reader to be used in {@link org.geysermc.geyser.session.cache.RegistryCache}.
|
||||
*/
|
||||
JavaRegistryKey<? extends BedrockVariant> variantRegistry();
|
||||
|
||||
/**
|
||||
* Should set the variant for bedrock.
|
||||
*/
|
||||
void setBedrockVariant(BedrockVariant bedrockVariant);
|
||||
|
||||
/**
|
||||
* Creates a registry reader for this mob's variants.
|
||||
*
|
||||
* <p>This reader simply matches the identifiers of registry entries with built-in variants. If no built-in variant matches, the fallback/default is returned.</p>
|
||||
*/
|
||||
static <BuiltInVariant extends Enum<? extends BuiltIn>> RegistryCache.RegistryReader<BuiltInVariant> reader(Class<BuiltInVariant> clazz, BuiltInVariant fallback) {
|
||||
BuiltInVariant[] variants = clazz.getEnumConstants();
|
||||
if (variants == null) {
|
||||
throw new IllegalArgumentException("Class is not an enum");
|
||||
}
|
||||
return context -> {
|
||||
for (BuiltInVariant variant : variants) {
|
||||
if (((BuiltIn) variant).javaIdentifier().equals(context.id())) {
|
||||
return variant;
|
||||
}
|
||||
}
|
||||
return fallback;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be implemented on an enum within the entity class. The enum lists vanilla variants that can appear on bedrock.
|
||||
*
|
||||
* <p>The enum constants should be named the same as their Java identifiers.</p>
|
||||
*/
|
||||
interface BuiltIn {
|
||||
|
||||
String name();
|
||||
|
||||
default Key javaIdentifier() {
|
||||
return MinecraftKey.key(name().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
* Copyright (c) 2025 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
|
||||
|
@ -23,27 +23,32 @@
|
|||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.translator.protocol.java.entity.spawn;
|
||||
package org.geysermc.geyser.entity.type.living.animal;
|
||||
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.spawn.ClientboundAddExperienceOrbPacket;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.type.ExpOrbEntity;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
|
||||
@Translator(packet = ClientboundAddExperienceOrbPacket.class)
|
||||
public class JavaAddExperienceOrbTranslator extends PacketTranslator<ClientboundAddExperienceOrbPacket> {
|
||||
/**
|
||||
* Extension to {@link VariantHolder} to make it easier to implement on mobs that use bedrock's metadata system to set their variants, which are quite common.
|
||||
*
|
||||
* @see VariantHolder
|
||||
*/
|
||||
public interface VariantIntHolder extends VariantHolder<VariantIntHolder.BuiltIn> {
|
||||
|
||||
@Override
|
||||
public void translate(GeyserSession session, ClientboundAddExperienceOrbPacket packet) {
|
||||
Vector3f position = Vector3f.from(packet.getX(), packet.getY(), packet.getZ());
|
||||
default void setBedrockVariant(BuiltIn variant) {
|
||||
setBedrockVariantId(variant.ordinal());
|
||||
}
|
||||
|
||||
Entity entity = new ExpOrbEntity(
|
||||
session, packet.getExp(), packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), position
|
||||
);
|
||||
/**
|
||||
* Should set the variant on bedrock's metadata. The bedrock ID has already been checked and is always valid.
|
||||
*/
|
||||
void setBedrockVariantId(int bedrockId);
|
||||
|
||||
session.getEntityCache().spawnEntity(entity);
|
||||
/**
|
||||
* The enum constants should be ordered in the order of their bedrock network ID.
|
||||
*
|
||||
* @see org.geysermc.geyser.entity.type.living.animal.VariantHolder.BuiltIn
|
||||
*/
|
||||
interface BuiltIn extends VariantHolder.BuiltIn {
|
||||
|
||||
int ordinal();
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
* Copyright (c) 2019-2025 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
|
||||
|
@ -23,35 +23,34 @@
|
|||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.entity.type.living.animal;
|
||||
package org.geysermc.geyser.entity.type.living.animal.farm;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.AddEntityPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.properties.VanillaEntityProperties;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistryKey;
|
||||
import org.geysermc.geyser.session.cache.tags.ItemTag;
|
||||
import org.geysermc.geyser.session.cache.tags.Tag;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class ChickenEntity extends AnimalEntity {
|
||||
public class ChickenEntity extends TemperatureVariantAnimal {
|
||||
|
||||
public ChickenEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAdditionalSpawnData(AddEntityPacket addEntityPacket) {
|
||||
propertyManager.add(VanillaEntityProperties.CLIMATE_VARIANT_ID, "temperate");
|
||||
propertyManager.applyIntProperties(addEntityPacket.getProperties().getIntProperties());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected Tag<Item> getFoodTag() {
|
||||
return ItemTag.CHICKEN_FOOD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaRegistryKey<BuiltInVariant> variantRegistry() {
|
||||
return JavaRegistries.CHICKEN_VARIANT;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
* Copyright (c) 2019-2025 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
|
||||
|
@ -23,20 +23,20 @@
|
|||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.entity.type.living.animal;
|
||||
package org.geysermc.geyser.entity.type.living.animal.farm;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.AddEntityPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.properties.VanillaEntityProperties;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistryKey;
|
||||
import org.geysermc.geyser.session.cache.tags.ItemTag;
|
||||
import org.geysermc.geyser.session.cache.tags.Tag;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
|
@ -45,17 +45,12 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
|||
|
||||
import java.util.UUID;
|
||||
|
||||
public class CowEntity extends AnimalEntity {
|
||||
public class CowEntity extends TemperatureVariantAnimal {
|
||||
|
||||
public CowEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAdditionalSpawnData(AddEntityPacket addEntityPacket) {
|
||||
propertyManager.add(VanillaEntityProperties.CLIMATE_VARIANT_ID, "temperate");
|
||||
propertyManager.applyIntProperties(addEntityPacket.getProperties().getIntProperties());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected InteractiveTag testMobInteraction(@NonNull Hand hand, @NonNull GeyserItemStack itemInHand) {
|
||||
|
@ -82,4 +77,9 @@ public class CowEntity extends AnimalEntity {
|
|||
protected Tag<Item> getFoodTag() {
|
||||
return ItemTag.COW_FOOD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaRegistryKey<BuiltInVariant> variantRegistry() {
|
||||
return JavaRegistries.COW_VARIANT;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
* Copyright (c) 2019-2025 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
|
||||
|
@ -23,7 +23,7 @@
|
|||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.entity.type.living.animal;
|
||||
package org.geysermc.geyser.entity.type.living.animal.farm;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
@ -31,9 +31,7 @@ import org.cloudburstmc.math.vector.Vector2f;
|
|||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.AddEntityPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.properties.VanillaEntityProperties;
|
||||
import org.geysermc.geyser.entity.type.Tickable;
|
||||
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
||||
import org.geysermc.geyser.entity.vehicle.BoostableVehicleComponent;
|
||||
|
@ -43,6 +41,8 @@ import org.geysermc.geyser.inventory.GeyserItemStack;
|
|||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistryKey;
|
||||
import org.geysermc.geyser.session.cache.tags.ItemTag;
|
||||
import org.geysermc.geyser.session.cache.tags.Tag;
|
||||
import org.geysermc.geyser.util.EntityUtils;
|
||||
|
@ -53,19 +53,13 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
|||
|
||||
import java.util.UUID;
|
||||
|
||||
public class PigEntity extends AnimalEntity implements Tickable, ClientVehicle {
|
||||
public class PigEntity extends TemperatureVariantAnimal implements Tickable, ClientVehicle {
|
||||
private final BoostableVehicleComponent<PigEntity> vehicleComponent = new BoostableVehicleComponent<>(this, 1.0f);
|
||||
|
||||
public PigEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAdditionalSpawnData(AddEntityPacket addEntityPacket) {
|
||||
propertyManager.add(VanillaEntityProperties.CLIMATE_VARIANT_ID, "temperate");
|
||||
propertyManager.applyIntProperties(addEntityPacket.getProperties().getIntProperties());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected Tag<Item> getFoodTag() {
|
||||
|
@ -155,4 +149,9 @@ public class PigEntity extends AnimalEntity implements Tickable, ClientVehicle {
|
|||
public boolean isClientControlled() {
|
||||
return getPlayerPassenger() == session.getPlayerEntity() && session.getPlayerInventory().isHolding(Items.CARROT_ON_A_STICK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaRegistryKey<BuiltInVariant> variantRegistry() {
|
||||
return JavaRegistries.PIG_VARIANT;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.entity.type.living.animal.farm;
|
||||
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.AddEntityPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.properties.VanillaEntityProperties;
|
||||
import org.geysermc.geyser.entity.type.living.animal.AnimalEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.VariantHolder;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.RegistryCache;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class TemperatureVariantAnimal extends AnimalEntity implements VariantHolder<TemperatureVariantAnimal.BuiltInVariant> {
|
||||
|
||||
public static final RegistryCache.RegistryReader<BuiltInVariant> VARIANT_READER = VariantHolder.reader(BuiltInVariant.class, BuiltInVariant.TEMPERATE);
|
||||
|
||||
public TemperatureVariantAnimal(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition,
|
||||
Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAdditionalSpawnData(AddEntityPacket addEntityPacket) {
|
||||
propertyManager.add(VanillaEntityProperties.CLIMATE_VARIANT_ID, "temperate");
|
||||
propertyManager.applyIntProperties(addEntityPacket.getProperties().getIntProperties());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBedrockVariant(BuiltInVariant variant) {
|
||||
propertyManager.add(VanillaEntityProperties.CLIMATE_VARIANT_ID, variant.name().toLowerCase(Locale.ROOT));
|
||||
updateBedrockEntityProperties();
|
||||
}
|
||||
|
||||
public enum BuiltInVariant implements BuiltIn {
|
||||
COLD,
|
||||
TEMPERATE,
|
||||
WARM
|
||||
}
|
||||
}
|
|
@ -79,12 +79,17 @@ public class AbstractHorseEntity extends AnimalEntity {
|
|||
session.sendUpstreamPacket(attributesPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSaddled(boolean saddled) {
|
||||
// Shows the jump meter
|
||||
setFlag(EntityFlag.CAN_POWER_JUMP, saddled);
|
||||
super.updateSaddled(saddled);
|
||||
}
|
||||
|
||||
public void setHorseFlags(ByteEntityMetadata entityMetadata) {
|
||||
byte xd = entityMetadata.getPrimitiveValue();
|
||||
boolean tamed = (xd & 0x02) == 0x02;
|
||||
boolean saddled = (xd & 0x04) == 0x04;
|
||||
setFlag(EntityFlag.TAMED, tamed);
|
||||
setFlag(EntityFlag.SADDLED, saddled);
|
||||
setFlag(EntityFlag.EATING, (xd & 0x10) == 0x10);
|
||||
setFlag(EntityFlag.STANDING, (xd & 0x20) == 0x20);
|
||||
|
||||
|
@ -114,9 +119,6 @@ public class AbstractHorseEntity extends AnimalEntity {
|
|||
|
||||
// Set container type if tamed
|
||||
dirtyMetadata.put(EntityDataTypes.CONTAINER_TYPE, tamed ? (byte) ContainerType.HORSE.getId() : (byte) 0);
|
||||
|
||||
// Shows the jump meter
|
||||
setFlag(EntityFlag.CAN_POWER_JUMP, saddled);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -31,9 +31,12 @@ import org.cloudburstmc.math.vector.Vector3f;
|
|||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.type.living.animal.VariantIntHolder;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistryKey;
|
||||
import org.geysermc.geyser.session.cache.tags.ItemTag;
|
||||
import org.geysermc.geyser.session.cache.tags.Tag;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
|
@ -45,7 +48,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
|||
|
||||
import java.util.UUID;
|
||||
|
||||
public class CatEntity extends TameableEntity {
|
||||
public class CatEntity extends TameableEntity implements VariantIntHolder {
|
||||
|
||||
private byte collarColor = 14; // Red - default
|
||||
|
||||
|
@ -81,17 +84,14 @@ public class CatEntity extends TameableEntity {
|
|||
updateCollarColor();
|
||||
}
|
||||
|
||||
public void setCatVariant(IntEntityMetadata entityMetadata) {
|
||||
// Different colors in Java and Bedrock for some reason
|
||||
int metadataValue = entityMetadata.getPrimitiveValue();
|
||||
int variantColor = switch (metadataValue) {
|
||||
case 0 -> 8;
|
||||
case 8 -> 0;
|
||||
case 9 -> 10;
|
||||
case 10 -> 9;
|
||||
default -> metadataValue;
|
||||
};
|
||||
dirtyMetadata.put(EntityDataTypes.VARIANT, variantColor);
|
||||
@Override
|
||||
public JavaRegistryKey<BuiltInVariant> variantRegistry() {
|
||||
return JavaRegistries.CAT_VARIANT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBedrockVariantId(int bedrockId) {
|
||||
dirtyMetadata.put(EntityDataTypes.VARIANT, bedrockId);
|
||||
}
|
||||
|
||||
public void setResting(BooleanEntityMetadata entityMetadata) {
|
||||
|
@ -138,4 +138,20 @@ public class CatEntity extends TameableEntity {
|
|||
return !canEat(itemInHand) || health >= maxHealth && tamed ? InteractionResult.PASS : InteractionResult.SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
// Ordered by bedrock id
|
||||
// TODO: are these ordered correctly?
|
||||
public enum BuiltInVariant implements BuiltIn {
|
||||
WHITE,
|
||||
BLACK,
|
||||
RED,
|
||||
SIAMESE,
|
||||
BRITISH_SHORTHAIR,
|
||||
CALICO,
|
||||
PERSIAN,
|
||||
RAGDOLL,
|
||||
TABBY,
|
||||
ALL_BLACK,
|
||||
JELLIE
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,22 +33,22 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
|||
import org.cloudburstmc.protocol.bedrock.packet.AddEntityPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.type.living.animal.VariantIntHolder;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.item.enchantment.EnchantmentComponent;
|
||||
import org.geysermc.geyser.item.type.DyeItem;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistryKey;
|
||||
import org.geysermc.geyser.session.cache.tags.ItemTag;
|
||||
import org.geysermc.geyser.session.cache.tags.Tag;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
import org.geysermc.geyser.util.ItemUtils;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.Holder;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.WolfVariant;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
|
||||
|
@ -56,10 +56,9 @@ import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponen
|
|||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.HolderSet;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
public class WolfEntity extends TameableEntity {
|
||||
public class WolfEntity extends TameableEntity implements VariantIntHolder {
|
||||
private byte collarColor = 14; // Red - default
|
||||
private HolderSet repairableItems = null;
|
||||
private boolean isCurseOfBinding = false;
|
||||
|
@ -114,15 +113,14 @@ public class WolfEntity extends TameableEntity {
|
|||
dirtyMetadata.put(EntityDataTypes.COLOR, time != 0 ? (byte) 0 : collarColor);
|
||||
}
|
||||
|
||||
// 1.20.5+
|
||||
public void setWolfVariant(ObjectEntityMetadata<Holder<WolfVariant>> entityMetadata) {
|
||||
entityMetadata.getValue().ifId(id -> {
|
||||
BuiltInWolfVariant wolfVariant = session.getRegistryCache().wolfVariants().byId(id);
|
||||
if (wolfVariant == null) {
|
||||
wolfVariant = BuiltInWolfVariant.PALE;
|
||||
}
|
||||
dirtyMetadata.put(EntityDataTypes.VARIANT, wolfVariant.ordinal());
|
||||
});
|
||||
@Override
|
||||
public JavaRegistryKey<BuiltInVariant> variantRegistry() {
|
||||
return JavaRegistries.WOLF_VARIANT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBedrockVariantId(int bedrockId) {
|
||||
dirtyMetadata.put(EntityDataTypes.VARIANT, bedrockId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -195,7 +193,7 @@ public class WolfEntity extends TameableEntity {
|
|||
}
|
||||
|
||||
// Ordered by bedrock id
|
||||
public enum BuiltInWolfVariant {
|
||||
public enum BuiltInVariant implements BuiltIn {
|
||||
PALE,
|
||||
ASHEN,
|
||||
BLACK,
|
||||
|
@ -204,23 +202,6 @@ public class WolfEntity extends TameableEntity {
|
|||
SNOWY,
|
||||
SPOTTED,
|
||||
STRIPED,
|
||||
WOODS;
|
||||
|
||||
private static final BuiltInWolfVariant[] VALUES = values();
|
||||
|
||||
private final String javaIdentifier;
|
||||
|
||||
BuiltInWolfVariant() {
|
||||
this.javaIdentifier = "minecraft:" + this.name().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
public static @Nullable BuiltInWolfVariant getByJavaIdentifier(String javaIdentifier) {
|
||||
for (BuiltInWolfVariant wolfVariant : VALUES) {
|
||||
if (wolfVariant.javaIdentifier.equals(javaIdentifier)) {
|
||||
return wolfVariant;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
WOODS
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import lombok.Setter;
|
|||
import net.kyori.adventure.text.Component;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
|
||||
|
@ -62,8 +63,8 @@ public class AnvilContainer extends Container {
|
|||
|
||||
private int lastTargetSlot = -1;
|
||||
|
||||
public AnvilContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
super(title, id, size, containerType, playerInventory);
|
||||
public AnvilContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
|
||||
super(session, title, id, size, containerType, playerInventory, translator);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
|
||||
package org.geysermc.geyser.inventory;
|
||||
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
@ -35,7 +37,7 @@ public class BeaconContainer extends Container {
|
|||
private int primaryId;
|
||||
private int secondaryId;
|
||||
|
||||
public BeaconContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
super(title, id, size, containerType, playerInventory);
|
||||
public BeaconContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
|
||||
super(session, title, id, size, containerType, playerInventory, translator);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,10 +25,12 @@
|
|||
|
||||
package org.geysermc.geyser.inventory;
|
||||
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
|
||||
|
||||
public class CartographyContainer extends Container {
|
||||
public CartographyContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
super(title, id, size, containerType, playerInventory);
|
||||
public CartographyContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
|
||||
super(session, title, id, size, containerType, playerInventory, translator);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,8 +46,8 @@ public class Container extends Inventory {
|
|||
*/
|
||||
private boolean isUsingRealBlock = false;
|
||||
|
||||
public Container(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
super(title, id, size, containerType);
|
||||
public Container(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
|
||||
super(session, title, id, size, containerType, translator);
|
||||
this.playerInventory = playerInventory;
|
||||
this.containerSize = this.size + InventoryTranslator.PLAYER_INVENTORY_SIZE;
|
||||
}
|
||||
|
|
|
@ -48,8 +48,8 @@ public class CrafterContainer extends Container {
|
|||
*/
|
||||
private short disabledSlotsMask = 0;
|
||||
|
||||
public CrafterContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
super(title, id, size, containerType, playerInventory);
|
||||
public CrafterContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
|
||||
super(session, title, id, size, containerType, playerInventory, translator);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -25,24 +25,25 @@
|
|||
|
||||
package org.geysermc.geyser.inventory;
|
||||
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.EnchantOptionData;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class EnchantingContainer extends Container {
|
||||
/**
|
||||
* A cache of what Bedrock sees
|
||||
*/
|
||||
@Getter
|
||||
private final EnchantOptionData[] enchantOptions;
|
||||
/**
|
||||
* A mutable cache of what the server sends us
|
||||
*/
|
||||
@Getter
|
||||
private final GeyserEnchantOption[] geyserEnchantOptions;
|
||||
|
||||
public EnchantingContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
super(title, id, size, containerType, playerInventory);
|
||||
public EnchantingContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
|
||||
super(session, title, id, size, containerType, playerInventory, translator);
|
||||
|
||||
enchantOptions = new EnchantOptionData[3];
|
||||
geyserEnchantOptions = new GeyserEnchantOption[3];
|
||||
|
|
|
@ -30,19 +30,20 @@ import org.geysermc.geyser.level.block.Blocks;
|
|||
import org.geysermc.geyser.level.block.type.Block;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.Generic3X3InventoryTranslator;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
|
||||
|
||||
@Getter
|
||||
public class Generic3X3Container extends Container {
|
||||
/**
|
||||
* Whether we need to set the container type as {@link org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType#DROPPER}.
|
||||
* <p>
|
||||
* Used at {@link Generic3X3InventoryTranslator#openInventory(GeyserSession, Inventory)}
|
||||
*/
|
||||
@Getter
|
||||
private boolean isDropper = false;
|
||||
|
||||
public Generic3X3Container(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
super(title, id, size, containerType, playerInventory);
|
||||
public Generic3X3Container(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
|
||||
super(session, title, id, size, containerType, playerInventory, translator);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -32,8 +32,10 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
|||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.inventory.click.ClickPlan;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
import org.geysermc.geyser.translator.item.ItemTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
|
||||
|
@ -46,6 +48,10 @@ public abstract class Inventory {
|
|||
@Getter
|
||||
protected final int javaId;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
private int bedrockId;
|
||||
|
||||
/**
|
||||
* The Java inventory state ID from the server. As of Java Edition 1.18.1 this value has one instance per player.
|
||||
* If this is out of sync with the server when a packet containing it is handled, the server will resync items.
|
||||
|
@ -55,7 +61,7 @@ public abstract class Inventory {
|
|||
@Setter
|
||||
private int stateId;
|
||||
/**
|
||||
* See {@link org.geysermc.geyser.inventory.click.ClickPlan#execute(boolean)}; used as a hack
|
||||
* See {@link ClickPlan#execute(boolean)}; used as a hack
|
||||
*/
|
||||
@Getter
|
||||
private int nextStateId = -1;
|
||||
|
@ -75,43 +81,72 @@ public abstract class Inventory {
|
|||
protected final GeyserItemStack[] items;
|
||||
|
||||
/**
|
||||
* The location of the inventory block. Will either be a fake block above the player's head, or the actual block location
|
||||
* The location of the inventory block. Will either be a fake block above the player's head, or the actual block location.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
protected Vector3i holderPosition = Vector3i.ZERO;
|
||||
|
||||
/**
|
||||
* The entity id of the entity holding the inventory.
|
||||
* Either this, or the holder position must be set in order for Bedrock to open inventories.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
protected long holderId = -1;
|
||||
|
||||
/**
|
||||
* Whether this inventory is currently pending.
|
||||
* It can be pending if this inventory was opened while another inventory was still open,
|
||||
* or because opening this inventory takes more time (e.g. virtual inventories).
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
private boolean pending = false;
|
||||
|
||||
/**
|
||||
* Whether this inventory is currently shown to the Bedrock player.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
private boolean displayed = false;
|
||||
|
||||
protected Inventory(int id, int size, ContainerType containerType) {
|
||||
this("Inventory", id, size, containerType);
|
||||
/**
|
||||
* The translator for this inventory. Stored here to avoid de-syncs of the inventory and current translator.
|
||||
*/
|
||||
@Getter
|
||||
private final InventoryTranslator translator;
|
||||
|
||||
@Getter
|
||||
private final GeyserSession session;
|
||||
|
||||
protected Inventory(GeyserSession session, int id, int size, ContainerType containerType, InventoryTranslator translator) {
|
||||
this(session, "Inventory", id, size, containerType, translator);
|
||||
}
|
||||
|
||||
protected Inventory(String title, int javaId, int size, ContainerType containerType) {
|
||||
protected Inventory(GeyserSession session, String title, int javaId, int size, ContainerType containerType, InventoryTranslator translator) {
|
||||
this.title = title;
|
||||
this.javaId = javaId;
|
||||
this.size = size;
|
||||
this.containerType = containerType;
|
||||
this.items = new GeyserItemStack[size];
|
||||
Arrays.fill(items, GeyserItemStack.EMPTY);
|
||||
}
|
||||
this.translator = translator;
|
||||
this.session = session;
|
||||
|
||||
// This is to prevent conflicts with special bedrock inventory IDs.
|
||||
// The vanilla java server only sends an ID between 1 and 100 when opening an inventory,
|
||||
// so this is rarely needed. (certain plugins)
|
||||
// Example: https://github.com/GeyserMC/Geyser/issues/3254
|
||||
public int getBedrockId() {
|
||||
return javaId <= 100 ? javaId : (javaId % 100) + 1;
|
||||
// This is to prevent conflicts with special bedrock inventory IDs.
|
||||
// The vanilla java server only sends an ID between 1 and 100 when opening an inventory,
|
||||
// so this is rarely needed. (certain plugins)
|
||||
// Example: https://github.com/GeyserMC/Geyser/issues/3254
|
||||
this.bedrockId = javaId <= 100 ? javaId : (javaId % 100) + 1;
|
||||
|
||||
// We occasionally need to re-open inventories with a delay in cases where
|
||||
// Java wouldn't - e.g. for virtual chest menus that switch pages.
|
||||
// And, well, we want to avoid reusing Bedrock inventory id's that are currently being used in a closing inventory;
|
||||
// so to be safe we just deviate in that case as well.
|
||||
if ((session.getOpenInventory() != null && session.getOpenInventory().getBedrockId() == bedrockId) || session.isClosingInventory()) {
|
||||
this.bedrockId += 1;
|
||||
}
|
||||
}
|
||||
|
||||
public GeyserItemStack getItem(int slot) {
|
||||
|
@ -179,4 +214,20 @@ public abstract class Inventory {
|
|||
public boolean shouldConfirmContainerClose() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper methods to avoid using the wrong translator to update specific inventories.
|
||||
*/
|
||||
|
||||
public void updateInventory() {
|
||||
this.translator.updateInventory(session, this);
|
||||
}
|
||||
|
||||
public void updateProperty(int rawProperty, int value) {
|
||||
this.translator.updateProperty(session, this, rawProperty, value);
|
||||
}
|
||||
|
||||
public void updateSlot(int slot) {
|
||||
this.translator.updateSlot(session, this, slot);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
|||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.java.inventory.JavaOpenBookTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
|
||||
|
||||
|
@ -45,8 +46,8 @@ public class LecternContainer extends Container {
|
|||
|
||||
private boolean isBookInPlayerInventory = false;
|
||||
|
||||
public LecternContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
super(title, id, size, containerType, playerInventory);
|
||||
public LecternContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
|
||||
super(session, title, id, size, containerType, playerInventory, translator);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -30,28 +30,31 @@ import lombok.Setter;
|
|||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.inventory.VillagerTrade;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.inventory.ClientboundMerchantOffersPacket;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Setter
|
||||
public class MerchantContainer extends Container {
|
||||
@Getter @Setter
|
||||
@Getter
|
||||
private Entity villager;
|
||||
@Setter
|
||||
private VillagerTrade[] villagerTrades;
|
||||
@Getter @Setter
|
||||
private List<VillagerTrade> villagerTrades;
|
||||
@Getter
|
||||
private ClientboundMerchantOffersPacket pendingOffersPacket;
|
||||
@Getter @Setter
|
||||
@Getter
|
||||
private int tradeExperience;
|
||||
|
||||
public MerchantContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
super(title, id, size, containerType, playerInventory);
|
||||
public MerchantContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
|
||||
super(session, title, id, size, containerType, playerInventory, translator);
|
||||
}
|
||||
|
||||
public void onTradeSelected(GeyserSession session, int slot) {
|
||||
if (villagerTrades != null && slot >= 0 && slot < villagerTrades.length) {
|
||||
VillagerTrade trade = villagerTrades[slot];
|
||||
setItem(2, GeyserItemStack.from(trade.getOutput()), session);
|
||||
if (villagerTrades != null && slot >= 0 && slot < villagerTrades.size()) {
|
||||
VillagerTrade trade = villagerTrades.get(slot);
|
||||
setItem(2, GeyserItemStack.from(trade.getResult()), session);
|
||||
|
||||
tradeExperience += trade.getXp();
|
||||
villager.getDirtyMetadata().put(EntityDataTypes.TRADE_EXPERIENCE, tradeExperience);
|
||||
|
|
|
@ -31,24 +31,24 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
|||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
||||
import org.jetbrains.annotations.Range;
|
||||
|
||||
@Getter
|
||||
public class PlayerInventory extends Inventory {
|
||||
/**
|
||||
* Stores the held item slot, starting at index 0.
|
||||
* Add 36 in order to get the network item slot.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
private int heldItemSlot;
|
||||
|
||||
@Getter
|
||||
@NonNull
|
||||
private GeyserItemStack cursor = GeyserItemStack.EMPTY;
|
||||
|
||||
public PlayerInventory() {
|
||||
super(0, 46, null);
|
||||
public PlayerInventory(GeyserSession session) {
|
||||
super(session, 0, 46, null, InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR);
|
||||
heldItemSlot = 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,18 +29,19 @@ import lombok.Getter;
|
|||
import lombok.Setter;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
public class StonecutterContainer extends Container {
|
||||
/**
|
||||
* The button that has currently been pressed Java-side
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
private int stonecutterButton = -1;
|
||||
|
||||
public StonecutterContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
super(title, id, size, containerType, playerInventory);
|
||||
public StonecutterContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
|
||||
super(session, title, id, size, containerType, playerInventory, translator);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -32,6 +32,7 @@ import it.unimi.dsi.fastutil.ints.IntSet;
|
|||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.inventory.Inventory;
|
||||
import org.geysermc.geyser.inventory.SlotType;
|
||||
import org.geysermc.geyser.item.hashing.DataComponentHashers;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.BundleInventoryTranslator;
|
||||
import org.geysermc.geyser.translator.inventory.CraftingInventoryTranslator;
|
||||
|
@ -41,6 +42,7 @@ import org.geysermc.geyser.util.thirdparty.Fraction;
|
|||
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerActionType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.inventory.MoveToHotbarAction;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.HashedStack;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClickPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundSelectBundleItemPacket;
|
||||
|
@ -58,6 +60,7 @@ public final class ClickPlan {
|
|||
* Used for 1.17.1+ proper packet translation - any non-cursor item that is changed in a single transaction gets sent here.
|
||||
*/
|
||||
private Int2ObjectMap<ItemStack> changedItems;
|
||||
private Int2ObjectMap<HashedStack> changedHashedItems;
|
||||
private GeyserItemStack simulatedCursor;
|
||||
private int desiredBundleSlot;
|
||||
private boolean executionBegan;
|
||||
|
@ -74,6 +77,7 @@ public final class ClickPlan {
|
|||
|
||||
this.simulatedItems = new Int2ObjectOpenHashMap<>(inventory.getSize());
|
||||
this.changedItems = null;
|
||||
this.changedHashedItems = null;
|
||||
this.simulatedCursor = session.getPlayerInventory().getCursor().copy();
|
||||
this.executionBegan = false;
|
||||
|
||||
|
@ -118,6 +122,7 @@ public final class ClickPlan {
|
|||
}
|
||||
|
||||
changedItems = new Int2ObjectOpenHashMap<>();
|
||||
changedHashedItems = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
boolean emulatePost1_16Logic = session.isEmulatePost1_16Logic();
|
||||
|
||||
|
@ -157,8 +162,8 @@ public final class ClickPlan {
|
|||
action.slot,
|
||||
action.click.actionType,
|
||||
action.click.action,
|
||||
clickedItemStack,
|
||||
changedItems
|
||||
DataComponentHashers.hashStack(session, clickedItemStack),
|
||||
changedHashedItems
|
||||
);
|
||||
|
||||
session.sendDownstreamGamePacket(clickPacket);
|
||||
|
@ -175,6 +180,7 @@ public final class ClickPlan {
|
|||
//update geyser inventory after simulation to avoid net id desync
|
||||
resetSimulation();
|
||||
changedItems = new Int2ObjectOpenHashMap<>();
|
||||
changedHashedItems = new Int2ObjectOpenHashMap<>();
|
||||
for (ClickAction action : plan) {
|
||||
simulateAction(action);
|
||||
}
|
||||
|
@ -254,6 +260,7 @@ public final class ClickPlan {
|
|||
private void onSlotItemChange(int slot, GeyserItemStack itemStack) {
|
||||
if (changedItems != null) {
|
||||
changedItems.put(slot, itemStack.getItemStack());
|
||||
changedHashedItems.put(slot, DataComponentHashers.hashStack(session, itemStack.getItemStack()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,11 +40,11 @@ import org.geysermc.geyser.level.block.type.Block;
|
|||
import org.geysermc.geyser.level.block.type.BlockState;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
import org.geysermc.geyser.util.InventoryUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
|
@ -77,24 +77,29 @@ public class BlockInventoryHolder extends InventoryHolder {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
|
||||
// Check to see if there is an existing block we can use that the player just selected.
|
||||
// First, verify that the player's position has not changed, so we don't try to select a block wildly out of range.
|
||||
// (This could be a virtual inventory that the player is opening)
|
||||
if (checkInteractionPosition(session)) {
|
||||
// Then, check to see if the interacted block is valid for this inventory by ensuring the block state identifier is valid
|
||||
// and the bedrock block is vanilla
|
||||
BlockState state = session.getGeyser().getWorldManager().blockAt(session, session.getLastInteractionBlockPosition());
|
||||
if (!BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get().containsKey(state.javaId())) {
|
||||
if (isValidBlock(state)) {
|
||||
// We can safely use this block
|
||||
inventory.setHolderPosition(session.getLastInteractionBlockPosition());
|
||||
((Container) inventory).setUsingRealBlock(true, state.block());
|
||||
setCustomName(session, session.getLastInteractionBlockPosition(), inventory, state);
|
||||
public boolean canReuseContainer(GeyserSession session, Container container, Container previous) {
|
||||
// We already ensured that the inventories are using the same type, size, and title
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// TODO this would currently break, so we're not reusing this
|
||||
if (previous.isUsingRealBlock()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we'd be using the same virtual inventory position.
|
||||
Vector3i position = InventoryUtils.findAvailableWorldSpace(session);
|
||||
if (Objects.equals(position, previous.getHolderPosition())) {
|
||||
return true;
|
||||
} else {
|
||||
GeyserImpl.getInstance().getLogger().debug(session, "Not reusing inventory (%s) due to virtual block holder changing (%s -> %s)!",
|
||||
InventoryUtils.debugInventory(container), previous.getHolderPosition(), position);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean prepareInventory(GeyserSession session, Container inventory) {
|
||||
if (canUseRealBlock(session, inventory)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Vector3i position = InventoryUtils.findAvailableWorldSpace(session);
|
||||
|
@ -115,6 +120,29 @@ public class BlockInventoryHolder extends InventoryHolder {
|
|||
return true;
|
||||
}
|
||||
|
||||
protected boolean canUseRealBlock(GeyserSession session, Container inventory) {
|
||||
// Check to see if there is an existing block we can use that the player just selected.
|
||||
// First, verify that the player's position has not changed, so we don't try to select a block wildly out of range.
|
||||
// (This could be a virtual inventory that the player is opening)
|
||||
if (checkInteractionPosition(session)) {
|
||||
// Then, check to see if the interacted block is valid for this inventory by ensuring the block state identifier is valid
|
||||
// and the bedrock block is vanilla
|
||||
BlockState state = session.getGeyser().getWorldManager().blockAt(session, session.getLastInteractionBlockPosition());
|
||||
if (!BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get().containsKey(state.javaId())) {
|
||||
if (isValidBlock(state)) {
|
||||
// We can safely use this block
|
||||
inventory.setHolderPosition(session.getLastInteractionBlockPosition());
|
||||
inventory.setUsingRealBlock(true, state.block());
|
||||
setCustomName(session, session.getLastInteractionBlockPosition(), inventory, state);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will be overwritten in the beacon inventory translator to remove the check, since virtual inventories can't exist.
|
||||
*
|
||||
|
@ -145,58 +173,49 @@ public class BlockInventoryHolder extends InventoryHolder {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void openInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
|
||||
public void openInventory(GeyserSession session, Container container) {
|
||||
ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket();
|
||||
containerOpenPacket.setId((byte) inventory.getBedrockId());
|
||||
containerOpenPacket.setId((byte) container.getBedrockId());
|
||||
containerOpenPacket.setType(containerType);
|
||||
containerOpenPacket.setBlockPosition(inventory.getHolderPosition());
|
||||
containerOpenPacket.setUniqueEntityId(inventory.getHolderId());
|
||||
containerOpenPacket.setBlockPosition(container.getHolderPosition());
|
||||
containerOpenPacket.setUniqueEntityId(container.getHolderId());
|
||||
session.sendUpstreamPacket(containerOpenPacket);
|
||||
|
||||
GeyserImpl.getInstance().getLogger().debug(session, containerOpenPacket.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory, ContainerType type) {
|
||||
if (!(inventory instanceof Container container)) {
|
||||
GeyserImpl.getInstance().getLogger().warning("Tried to close a non-container inventory in a block inventory holder! Please report this error on discord.");
|
||||
GeyserImpl.getInstance().getLogger().warning("Current inventory translator: " + translator.getClass().getSimpleName());
|
||||
GeyserImpl.getInstance().getLogger().warning("Current inventory: " + inventory.getClass().getSimpleName());
|
||||
// Try to save ourselves? maybe?
|
||||
// https://github.com/GeyserMC/Geyser/issues/4141
|
||||
// TODO: improve once this issue is pinned down
|
||||
session.setOpenInventory(null);
|
||||
session.setInventoryTranslator(InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Lecterns are special, and cannot be closed with any of the two methods below.
|
||||
if (container.isUsingRealBlock() && !(container instanceof LecternContainer)) {
|
||||
public void closeInventory(GeyserSession session, Container container, ContainerType type) {
|
||||
if (container.isDisplayed() && !(container instanceof LecternContainer)) {
|
||||
// No need to reset a block since we didn't change any blocks
|
||||
// But send a container close packet because we aren't destroying the original.
|
||||
ContainerClosePacket packet = new ContainerClosePacket();
|
||||
packet.setId((byte) inventory.getBedrockId());
|
||||
packet.setId((byte) container.getBedrockId());
|
||||
packet.setServerInitiated(true);
|
||||
packet.setType(type != null ? type : containerType);
|
||||
session.sendUpstreamPacket(packet);
|
||||
|
||||
// Here comes the ugly part. This is a manual check to filter specific containers
|
||||
// that won't close anymore with "just" a ContainerClosePacket.
|
||||
if (type != null) {
|
||||
return;
|
||||
if (container.isUsingRealBlock()) {
|
||||
// Type being null indicates that the ContainerClosePacket is not effective.
|
||||
// So we yeet away the block!
|
||||
if (type == null) {
|
||||
Vector3i holderPos = container.getHolderPosition();
|
||||
UpdateBlockPacket blockPacket = new UpdateBlockPacket();
|
||||
blockPacket.setDataLayer(0);
|
||||
blockPacket.setBlockPosition(holderPos);
|
||||
blockPacket.setDefinition(session.getBlockMappings().getBedrockAir());
|
||||
blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY);
|
||||
session.sendUpstreamPacket(blockPacket);
|
||||
} else {
|
||||
// We're using a real block and are able to close the block without destroying it,
|
||||
// so we can don't need to reset it below.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy the block. There's no inventory to view => it gets closed!
|
||||
Vector3i holderPos = inventory.getHolderPosition();
|
||||
UpdateBlockPacket blockPacket = new UpdateBlockPacket();
|
||||
blockPacket.setDataLayer(0);
|
||||
blockPacket.setBlockPosition(holderPos);
|
||||
blockPacket.setDefinition(session.getBlockMappings().getBedrockAir());
|
||||
blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY);
|
||||
session.sendUpstreamPacket(blockPacket);
|
||||
}
|
||||
|
||||
// Reset to correct block
|
||||
Vector3i holderPos = inventory.getHolderPosition();
|
||||
Vector3i holderPos = container.getHolderPosition();
|
||||
int realBlock = session.getGeyser().getWorldManager().getBlockAt(session, holderPos.getX(), holderPos.getY(), holderPos.getZ());
|
||||
UpdateBlockPacket blockPacket = new UpdateBlockPacket();
|
||||
blockPacket.setDataLayer(0);
|
||||
|
|
|
@ -26,12 +26,12 @@
|
|||
package org.geysermc.geyser.inventory.holder;
|
||||
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
|
||||
import org.geysermc.geyser.inventory.Inventory;
|
||||
import org.geysermc.geyser.inventory.Container;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
|
||||
public abstract class InventoryHolder {
|
||||
public abstract boolean prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory);
|
||||
public abstract void openInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory);
|
||||
public abstract void closeInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory, ContainerType containerType);
|
||||
public abstract boolean canReuseContainer(GeyserSession session, Container inventory, Container oldInventory);
|
||||
public abstract boolean prepareInventory(GeyserSession session, Container inventory);
|
||||
public abstract void openInventory(GeyserSession session, Container inventory);
|
||||
public abstract void closeInventory(GeyserSession session, Container inventory, ContainerType containerType);
|
||||
}
|
||||
|
|
|
@ -88,13 +88,13 @@ public enum BannerPattern {
|
|||
this.bedrockIdentifier = bedrockIdentifier;
|
||||
}
|
||||
|
||||
public static @Nullable BannerPattern getByJavaIdentifier(Key key) {
|
||||
public static BannerPattern getByJavaIdentifier(Key key) {
|
||||
for (BannerPattern bannerPattern : VALUES) {
|
||||
if (bannerPattern.javaIdentifier.equals(key)) {
|
||||
return bannerPattern;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return BASE; // Default fallback
|
||||
}
|
||||
|
||||
public static @Nullable BannerPattern getByBedrockIdentifier(String bedrockIdentifier) {
|
||||
|
|
|
@ -29,13 +29,13 @@ import net.kyori.adventure.key.Key;
|
|||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistry;
|
||||
import org.geysermc.geyser.session.cache.registry.RegistryEntryContext;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import org.geysermc.geyser.util.MinecraftKey;
|
||||
import org.geysermc.geyser.util.SoundUtils;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.Holder;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.Instrument;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.InstrumentComponent;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.sound.BuiltinSound;
|
||||
|
||||
import java.util.Locale;
|
||||
|
@ -77,7 +77,7 @@ public interface GeyserInstrument {
|
|||
* @return the ID of the Java counterpart for the given Bedrock ID. If an invalid Bedrock ID was given, or there is no counterpart, -1 is returned.
|
||||
*/
|
||||
static int bedrockIdToJava(GeyserSession session, int id) {
|
||||
JavaRegistry<GeyserInstrument> instruments = session.getRegistryCache().instruments();
|
||||
JavaRegistry<GeyserInstrument> instruments = session.getRegistryCache().registry(JavaRegistries.INSTRUMENT);
|
||||
BedrockInstrument bedrockInstrument = BedrockInstrument.getByBedrockId(id);
|
||||
if (bedrockInstrument != null) {
|
||||
for (int i = 0; i < instruments.values().size(); i++) {
|
||||
|
@ -90,34 +90,40 @@ public interface GeyserInstrument {
|
|||
return -1;
|
||||
}
|
||||
|
||||
static GeyserInstrument fromHolder(GeyserSession session, Holder<Instrument> holder) {
|
||||
if (holder.isId()) {
|
||||
return session.getRegistryCache().instruments().byId(holder.id());
|
||||
// TODO test in 1.21.5
|
||||
static GeyserInstrument fromComponent(GeyserSession session, InstrumentComponent component) {
|
||||
if (component.instrumentLocation() != null) {
|
||||
return session.getRegistryCache().registry(JavaRegistries.INSTRUMENT).byKey(component.instrumentLocation());
|
||||
} else if (component.instrumentHolder() != null) {
|
||||
if (component.instrumentHolder().isId()) {
|
||||
return session.getRegistryCache().registry(JavaRegistries.INSTRUMENT).byId(component.instrumentHolder().id());
|
||||
}
|
||||
InstrumentComponent.Instrument custom = component.instrumentHolder().custom();
|
||||
return new Wrapper(custom, session.locale());
|
||||
}
|
||||
Instrument custom = holder.custom();
|
||||
return new Wrapper(custom, session.locale());
|
||||
throw new IllegalStateException("InstrumentComponent must have either a location or a holder");
|
||||
}
|
||||
|
||||
record Wrapper(Instrument instrument, String locale) implements GeyserInstrument {
|
||||
record Wrapper(InstrumentComponent.Instrument instrument, String locale) implements GeyserInstrument {
|
||||
@Override
|
||||
public String soundEvent() {
|
||||
return instrument.getSoundEvent().getName();
|
||||
return instrument.soundEvent().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float range() {
|
||||
return instrument.getRange();
|
||||
return instrument.range();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return MessageTranslator.convertMessageForTooltip(instrument.getDescription(), locale);
|
||||
return MessageTranslator.convertMessageForTooltip(instrument.description(), locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BedrockInstrument bedrockInstrument() {
|
||||
if (instrument.getSoundEvent() instanceof BuiltinSound) {
|
||||
return BedrockInstrument.getByJavaIdentifier(MinecraftKey.key(instrument.getSoundEvent().getName()));
|
||||
if (instrument.soundEvent() instanceof BuiltinSound) {
|
||||
return BedrockInstrument.getByJavaIdentifier(MinecraftKey.key(instrument.soundEvent().getName()));
|
||||
}
|
||||
// Probably custom
|
||||
return null;
|
||||
|
|
|
@ -26,19 +26,30 @@
|
|||
package org.geysermc.geyser.inventory.recipe;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.cloudburstmc.protocol.bedrock.data.TrimMaterial;
|
||||
import org.cloudburstmc.protocol.bedrock.data.TrimPattern;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemDescriptorWithCount;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemTagDescriptor;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.session.cache.registry.RegistryEntryContext;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.Holder;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ArmorTrim;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ProvidesTrimMaterial;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Stores information on trim materials and patterns, including smithing armor hacks for pre-1.20.
|
||||
*/
|
||||
public final class TrimRecipe {
|
||||
private static final Map<ProvidesTrimMaterial, Item> trimMaterialProviders = new HashMap<>();
|
||||
|
||||
// For CraftingDataPacket
|
||||
public static final String ID = "minecraft:smithing_armor_trim";
|
||||
public static final ItemDescriptorWithCount BASE = tagDescriptor("minecraft:trimmable_armors");
|
||||
|
@ -49,22 +60,31 @@ public final class TrimRecipe {
|
|||
String key = context.id().asMinimalString();
|
||||
|
||||
// Color is used when hovering over the item
|
||||
// Find the nearest legacy color from the RGB Java gives us to work with
|
||||
// Also yes this is a COMPLETE hack but it works ok!!!!!
|
||||
String colorTag = context.data().getCompound("description").getString("color");
|
||||
TextColor color = TextColor.fromHexString(colorTag);
|
||||
String legacy = MessageTranslator.convertMessage(Component.space().color(color));
|
||||
// Find the nearest legacy color from the style Java gives us to work with
|
||||
Component description = MessageTranslator.componentFromNbtTag(context.data().get("description"));
|
||||
String legacy = MessageTranslator.convertMessage(Component.space().style(description.style()));
|
||||
|
||||
String itemIdentifier = context.data().getString("ingredient");
|
||||
ItemMapping itemMapping = context.session().getItemMappings().getMapping(itemIdentifier);
|
||||
if (itemMapping == null) {
|
||||
// This should never happen so not sure what to do here.
|
||||
itemMapping = ItemMapping.AIR;
|
||||
int networkId = context.getNetworkId(context.id());
|
||||
ItemMapping trimItem = null;
|
||||
for (ProvidesTrimMaterial provider : materialProviders().keySet()) {
|
||||
Holder<ArmorTrim.TrimMaterial> materialHolder = provider.materialHolder();
|
||||
if (context.id().equals(provider.materialLocation()) || (materialHolder != null && materialHolder.isId() && materialHolder.id() == networkId)) {
|
||||
trimItem = context.session().getItemMappings().getMapping(materialProviders().get(provider));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (trimItem == null) {
|
||||
// This happens for custom trim materials, not sure what to do here.
|
||||
GeyserImpl.getInstance().getLogger().debug("Unable to found trim material item for material " + context.id());
|
||||
trimItem = ItemMapping.AIR;
|
||||
}
|
||||
|
||||
// Just pick out the resulting color code, without RESET in front.
|
||||
return new TrimMaterial(key, legacy.substring(2).trim(), itemMapping.getBedrockIdentifier());
|
||||
return new TrimMaterial(key, legacy.substring(2).trim(), trimItem.getBedrockIdentifier());
|
||||
}
|
||||
|
||||
// TODO this is WRONG. this changed. FIXME in 1.21.5
|
||||
public static TrimPattern readTrimPattern(RegistryEntryContext context) {
|
||||
String key = context.id().asMinimalString();
|
||||
|
||||
|
@ -81,6 +101,19 @@ public final class TrimRecipe {
|
|||
//no-op
|
||||
}
|
||||
|
||||
// Lazy initialise
|
||||
private static Map<ProvidesTrimMaterial, Item> materialProviders() {
|
||||
if (trimMaterialProviders.isEmpty()) {
|
||||
for (Item item : Registries.JAVA_ITEMS.get()) {
|
||||
ProvidesTrimMaterial provider = item.getComponent(DataComponentTypes.PROVIDES_TRIM_MATERIAL);
|
||||
if (provider != null) {
|
||||
trimMaterialProviders.put(provider, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return trimMaterialProviders;
|
||||
}
|
||||
|
||||
private static ItemDescriptorWithCount tagDescriptor(String tag) {
|
||||
return new ItemDescriptorWithCount(new ItemTagDescriptor(tag), 1);
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ import org.geysermc.geyser.inventory.item.BedrockEnchantment;
|
|||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.item.enchantment.Enchantment;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
|
||||
|
@ -374,7 +375,7 @@ public class AnvilInventoryUpdater extends InventoryUpdater {
|
|||
if (enchantmentComponent != null) {
|
||||
Object2IntMap<Enchantment> enchantments = new Object2IntOpenHashMap<>();
|
||||
for (Map.Entry<Integer, Integer> entry : enchantmentComponent.getEnchantments().entrySet()) {
|
||||
Enchantment enchantment = session.getRegistryCache().enchantments().byId(entry.getKey());
|
||||
Enchantment enchantment = session.getRegistryCache().registry(JavaRegistries.ENCHANTMENT).byId(entry.getKey());
|
||||
if (enchantment == null) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Unknown Java enchantment in anvil: " + entry.getKey());
|
||||
continue;
|
||||
|
|
|
@ -270,9 +270,13 @@ public final class Items {
|
|||
public static final Item COBWEB = register(new BlockItem(builder(), Blocks.COBWEB));
|
||||
public static final Item SHORT_GRASS = register(new BlockItem(builder(), Blocks.SHORT_GRASS));
|
||||
public static final Item FERN = register(new BlockItem(builder(), Blocks.FERN));
|
||||
public static final Item BUSH = register(new BlockItem(builder(), Blocks.BUSH));
|
||||
public static final Item AZALEA = register(new BlockItem(builder(), Blocks.AZALEA));
|
||||
public static final Item FLOWERING_AZALEA = register(new BlockItem(builder(), Blocks.FLOWERING_AZALEA));
|
||||
public static final Item DEAD_BUSH = register(new BlockItem(builder(), Blocks.DEAD_BUSH));
|
||||
public static final Item FIREFLY_BUSH = register(new BlockItem(builder(), Blocks.FIREFLY_BUSH));
|
||||
public static final Item SHORT_DRY_GRASS = register(new BlockItem(builder(), Blocks.SHORT_DRY_GRASS));
|
||||
public static final Item TALL_DRY_GRASS = register(new BlockItem(builder(), Blocks.TALL_DRY_GRASS));
|
||||
public static final Item SEAGRASS = register(new BlockItem(builder(), Blocks.SEAGRASS));
|
||||
public static final Item SEA_PICKLE = register(new BlockItem(builder(), Blocks.SEA_PICKLE));
|
||||
public static final Item WHITE_WOOL = register(new BlockItem(builder(), Blocks.WHITE_WOOL));
|
||||
|
@ -321,6 +325,8 @@ public final class Items {
|
|||
public static final Item SUGAR_CANE = register(new BlockItem(builder(), Blocks.SUGAR_CANE));
|
||||
public static final Item KELP = register(new BlockItem(builder(), Blocks.KELP));
|
||||
public static final Item PINK_PETALS = register(new BlockItem(builder(), Blocks.PINK_PETALS));
|
||||
public static final Item WILDFLOWERS = register(new BlockItem(builder(), Blocks.WILDFLOWERS));
|
||||
public static final Item LEAF_LITTER = register(new BlockItem(builder(), Blocks.LEAF_LITTER));
|
||||
public static final Item MOSS_CARPET = register(new BlockItem(builder(), Blocks.MOSS_CARPET));
|
||||
public static final Item MOSS_BLOCK = register(new BlockItem(builder(), Blocks.MOSS_BLOCK));
|
||||
public static final Item PALE_MOSS_CARPET = register(new BlockItem(builder(), Blocks.PALE_MOSS_CARPET));
|
||||
|
@ -389,6 +395,7 @@ public final class Items {
|
|||
public static final Item ICE = register(new BlockItem(builder(), Blocks.ICE));
|
||||
public static final Item SNOW_BLOCK = register(new BlockItem(builder(), Blocks.SNOW_BLOCK));
|
||||
public static final Item CACTUS = register(new BlockItem(builder(), Blocks.CACTUS));
|
||||
public static final Item CACTUS_FLOWER = register(new BlockItem(builder(), Blocks.CACTUS_FLOWER));
|
||||
public static final Item CLAY = register(new BlockItem(builder(), Blocks.CLAY));
|
||||
public static final Item JUKEBOX = register(new BlockItem(builder(), Blocks.JUKEBOX));
|
||||
public static final Item OAK_FENCE = register(new BlockItem(builder(), Blocks.OAK_FENCE));
|
||||
|
@ -891,6 +898,8 @@ public final class Items {
|
|||
public static final Item BAMBOO_CHEST_RAFT = register(new BoatItem("bamboo_chest_raft", builder()));
|
||||
public static final Item STRUCTURE_BLOCK = register(new BlockItem(builder(), Blocks.STRUCTURE_BLOCK));
|
||||
public static final Item JIGSAW = register(new BlockItem(builder(), Blocks.JIGSAW));
|
||||
public static final Item TEST_BLOCK = register(new BlockItem(builder(), Blocks.TEST_BLOCK));
|
||||
public static final Item TEST_INSTANCE_BLOCK = register(new BlockItem(builder(), Blocks.TEST_INSTANCE_BLOCK));
|
||||
public static final Item TURTLE_HELMET = register(new ArmorItem("turtle_helmet", builder()));
|
||||
public static final Item TURTLE_SCUTE = register(new Item("turtle_scute", builder()));
|
||||
public static final Item ARMADILLO_SCUTE = register(new Item("armadillo_scute", builder()));
|
||||
|
@ -1027,6 +1036,8 @@ public final class Items {
|
|||
public static final Item BOOK = register(new Item("book", builder()));
|
||||
public static final Item SLIME_BALL = register(new Item("slime_ball", builder()));
|
||||
public static final Item EGG = register(new Item("egg", builder()));
|
||||
public static final Item BLUE_EGG = register(new Item("blue_egg", builder()));
|
||||
public static final Item BROWN_EGG = register(new Item("brown_egg", builder()));
|
||||
public static final Item COMPASS = register(new CompassItem("compass", builder()));
|
||||
public static final Item RECOVERY_COMPASS = register(new Item("recovery_compass", builder()));
|
||||
public static final Item BUNDLE = register(new Item("bundle", builder()));
|
||||
|
@ -1120,7 +1131,7 @@ public final class Items {
|
|||
public static final Item BLAZE_POWDER = register(new Item("blaze_powder", builder()));
|
||||
public static final Item MAGMA_CREAM = register(new Item("magma_cream", builder()));
|
||||
public static final Item BREWING_STAND = register(new BlockItem(builder(), Blocks.BREWING_STAND));
|
||||
public static final Item CAULDRON = register(new BlockItem(builder(), Blocks.CAULDRON, Blocks.POWDER_SNOW_CAULDRON, Blocks.LAVA_CAULDRON, Blocks.WATER_CAULDRON));
|
||||
public static final Item CAULDRON = register(new BlockItem(builder(), Blocks.CAULDRON, Blocks.POWDER_SNOW_CAULDRON, Blocks.WATER_CAULDRON, Blocks.LAVA_CAULDRON));
|
||||
public static final Item ENDER_EYE = register(new Item("ender_eye", builder()));
|
||||
public static final Item GLISTERING_MELON_SLICE = register(new Item("glistering_melon_slice", builder()));
|
||||
public static final Item ARMADILLO_SPAWN_EGG = register(new SpawnEggItem("armadillo_spawn_egg", builder()));
|
||||
|
@ -1210,7 +1221,7 @@ public final class Items {
|
|||
public static final Item WRITABLE_BOOK = register(new WritableBookItem("writable_book", builder()));
|
||||
public static final Item WRITTEN_BOOK = register(new WrittenBookItem("written_book", builder()));
|
||||
public static final Item BREEZE_ROD = register(new Item("breeze_rod", builder()));
|
||||
public static final Item MACE = register(new Item("mace", builder()));
|
||||
public static final Item MACE = register(new Item("mace", builder().attackDamage(6.0)));
|
||||
public static final Item ITEM_FRAME = register(new Item("item_frame", builder()));
|
||||
public static final Item GLOW_ITEM_FRAME = register(new Item("glow_item_frame", builder()));
|
||||
public static final Item FLOWER_POT = register(new BlockItem(builder(), Blocks.FLOWER_POT));
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item;
|
||||
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.TooltipDisplay;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface TooltipOptions {
|
||||
|
||||
TooltipOptions ALL_SHOWN = component -> true;
|
||||
|
||||
TooltipOptions ALL_HIDDEN = component -> false;
|
||||
|
||||
boolean showInTooltip(DataComponentType<?> component);
|
||||
|
||||
static TooltipOptions fromComponents(DataComponents components) {
|
||||
TooltipDisplay display = components.get(DataComponentTypes.TOOLTIP_DISPLAY);
|
||||
if (display == null) {
|
||||
return ALL_SHOWN;
|
||||
} else if (display.hideTooltip()) {
|
||||
return ALL_HIDDEN;
|
||||
} else if (display.hiddenComponents().isEmpty()) {
|
||||
return ALL_SHOWN;
|
||||
}
|
||||
|
||||
return component -> !display.hiddenComponents().contains(component);
|
||||
}
|
||||
|
||||
static boolean hideTooltip(DataComponents components) {
|
||||
TooltipDisplay display = components.get(DataComponentTypes.TOOLTIP_DISPLAY);
|
||||
return display != null && display.hideTooltip();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.hashing;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.KeybindComponent;
|
||||
import net.kyori.adventure.text.NBTComponent;
|
||||
import net.kyori.adventure.text.ScoreComponent;
|
||||
import net.kyori.adventure.text.SelectorComponent;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.Style;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* This interface contains various {@link MinecraftHasher}s used to encode properties of {@link Component}s. Usually, you'll only need {@link ComponentHasher#COMPONENT}.
|
||||
*/
|
||||
public interface ComponentHasher {
|
||||
|
||||
MinecraftHasher<Component> COMPONENT = MinecraftHasher.lazyInitialize(new Supplier<>() {
|
||||
@Override
|
||||
public MinecraftHasher<Component> get() {
|
||||
return ACTUAL_COMPONENT;
|
||||
}
|
||||
});
|
||||
|
||||
MinecraftHasher<NamedTextColor> NAMED_COLOR = MinecraftHasher.STRING.cast(NamedTextColor::toString);
|
||||
|
||||
MinecraftHasher<TextColor> DIRECT_COLOR = MinecraftHasher.STRING.cast(TextColor::asHexString);
|
||||
|
||||
MinecraftHasher<TextColor> COLOR = (value, encoder) -> {
|
||||
if (value instanceof NamedTextColor named) {
|
||||
return NAMED_COLOR.hash(named, encoder);
|
||||
}
|
||||
return DIRECT_COLOR.hash(value, encoder);
|
||||
};
|
||||
|
||||
MinecraftHasher<TextDecoration.State> DECORATION_STATE = MinecraftHasher.BOOL.cast(state -> switch (state) {
|
||||
case NOT_SET -> null; // Should never happen since we're using .optional() with NOT_SET as default value below
|
||||
case FALSE -> false;
|
||||
case TRUE -> true;
|
||||
});
|
||||
|
||||
MinecraftHasher<ClickEvent.Action> CLICK_EVENT_ACTION = MinecraftHasher.STRING.cast(ClickEvent.Action::toString);
|
||||
|
||||
MinecraftHasher<ClickEvent> CLICK_EVENT = CLICK_EVENT_ACTION.dispatch("action", ClickEvent::action, action -> switch (action) {
|
||||
case OPEN_URL -> builder -> builder.accept("url", MinecraftHasher.STRING, ClickEvent::value);
|
||||
case OPEN_FILE -> builder -> builder.accept("path", MinecraftHasher.STRING, ClickEvent::value);
|
||||
case RUN_COMMAND, SUGGEST_COMMAND -> builder -> builder.accept("command", MinecraftHasher.STRING, ClickEvent::value);
|
||||
case CHANGE_PAGE -> builder -> builder.accept("page", MinecraftHasher.STRING, ClickEvent::value);
|
||||
case COPY_TO_CLIPBOARD -> builder -> builder.accept("value", MinecraftHasher.STRING, ClickEvent::value);
|
||||
});
|
||||
|
||||
MinecraftHasher<HoverEvent.Action<?>> HOVER_EVENT_ACTION = MinecraftHasher.STRING.cast(HoverEvent.Action::toString);
|
||||
|
||||
MinecraftHasher<HoverEvent<?>> HOVER_EVENT = HOVER_EVENT_ACTION.dispatch("action", HoverEvent::action, action -> {
|
||||
if (action == HoverEvent.Action.SHOW_TEXT) {
|
||||
return builder -> builder.accept("value", COMPONENT, event -> (Component) event.value());
|
||||
} else if (action == HoverEvent.Action.SHOW_ITEM) {
|
||||
return builder -> builder
|
||||
.accept("id", MinecraftHasher.KEY, event -> ((HoverEvent.ShowItem) event.value()).item())
|
||||
.accept("count", MinecraftHasher.INT, event -> ((HoverEvent.ShowItem) event.value()).count()); // Data components are probably not possible
|
||||
}
|
||||
return builder -> builder
|
||||
.accept("id", MinecraftHasher.KEY, event -> ((HoverEvent.ShowEntity) event.value()).type())
|
||||
.accept("uuid", MinecraftHasher.UUID, event -> ((HoverEvent.ShowEntity) event.value()).id())
|
||||
.optionalNullable("name", COMPONENT, event -> ((HoverEvent.ShowEntity) event.value()).name());
|
||||
});
|
||||
|
||||
// TODO shadow colours - needs kyori bump
|
||||
MapBuilder<Style> STYLE = builder -> builder
|
||||
.optionalNullable("color", COLOR, Style::color)
|
||||
.optional("bold", DECORATION_STATE, style -> style.decoration(TextDecoration.BOLD), TextDecoration.State.NOT_SET)
|
||||
.optional("italic", DECORATION_STATE, style -> style.decoration(TextDecoration.ITALIC), TextDecoration.State.NOT_SET)
|
||||
.optional("underlined", DECORATION_STATE, style -> style.decoration(TextDecoration.UNDERLINED), TextDecoration.State.NOT_SET)
|
||||
.optional("strikethrough", DECORATION_STATE, style -> style.decoration(TextDecoration.STRIKETHROUGH), TextDecoration.State.NOT_SET)
|
||||
.optional("obfuscated", DECORATION_STATE, style -> style.decoration(TextDecoration.OBFUSCATED), TextDecoration.State.NOT_SET)
|
||||
.optionalNullable("click_event", CLICK_EVENT, Style::clickEvent)
|
||||
.optionalNullable("hover_event", HOVER_EVENT, Style::hoverEvent)
|
||||
.optionalNullable("insertion", MinecraftHasher.STRING, Style::insertion)
|
||||
.optionalNullable("font", MinecraftHasher.KEY, Style::font);
|
||||
|
||||
MinecraftHasher<TextComponent> SIMPLE_TEXT_COMPONENT = MinecraftHasher.STRING.cast(TextComponent::content);
|
||||
|
||||
MinecraftHasher<TextComponent> FULL_TEXT_COMPONENT = component(builder -> builder
|
||||
.accept("text", MinecraftHasher.STRING, TextComponent::content));
|
||||
|
||||
MinecraftHasher<TextComponent> TEXT_COMPONENT = MinecraftHasher.dispatch(component -> {
|
||||
if (component.children().isEmpty() && component.style().isEmpty()) {
|
||||
return SIMPLE_TEXT_COMPONENT;
|
||||
}
|
||||
return FULL_TEXT_COMPONENT;
|
||||
});
|
||||
|
||||
MinecraftHasher<TranslatableComponent> TRANSLATABLE_COMPONENT = component(builder -> builder
|
||||
.accept("translate", MinecraftHasher.STRING, TranslatableComponent::key)
|
||||
.optionalNullable("fallback", MinecraftHasher.STRING, TranslatableComponent::fallback)); // Arguments are probably not possible
|
||||
|
||||
MinecraftHasher<KeybindComponent> KEYBIND_COMPONENT = component(builder -> builder
|
||||
.accept("keybind", MinecraftHasher.STRING, component -> component.keybind()));
|
||||
|
||||
MinecraftHasher<ScoreComponent> SCORE_COMPONENT = component(builder -> builder
|
||||
.accept("name", MinecraftHasher.STRING, ScoreComponent::name)
|
||||
.accept("objective", MinecraftHasher.STRING, ScoreComponent::objective));
|
||||
|
||||
MinecraftHasher<SelectorComponent> SELECTOR_COMPONENT = component(builder -> builder
|
||||
.accept("selector", MinecraftHasher.STRING, SelectorComponent::pattern)
|
||||
.optionalNullable("separator", COMPONENT, SelectorComponent::separator));
|
||||
|
||||
MinecraftHasher<NBTComponent<?, ?>> NBT_COMPONENT = component(builder -> builder
|
||||
.accept("nbt", MinecraftHasher.STRING, NBTComponent::nbtPath)
|
||||
.optional("interpret", MinecraftHasher.BOOL, NBTComponent::interpret, false)
|
||||
.optionalNullable("separator", COMPONENT, NBTComponent::separator)); // TODO source key, needs kyori update?
|
||||
|
||||
MinecraftHasher<Component> ACTUAL_COMPONENT = (component, encoder) -> {
|
||||
if (component instanceof TextComponent text) {
|
||||
return TEXT_COMPONENT.hash(text, encoder);
|
||||
} else if (component instanceof TranslatableComponent translatable) {
|
||||
return TRANSLATABLE_COMPONENT.hash(translatable, encoder);
|
||||
} else if (component instanceof KeybindComponent keybind) {
|
||||
return KEYBIND_COMPONENT.hash(keybind, encoder);
|
||||
} else if (component instanceof ScoreComponent score) {
|
||||
return SCORE_COMPONENT.hash(score, encoder);
|
||||
} else if (component instanceof SelectorComponent selector) {
|
||||
return SELECTOR_COMPONENT.hash(selector, encoder);
|
||||
} else if (component instanceof NBTComponent<?,?> nbt) {
|
||||
return NBT_COMPONENT.hash(nbt, encoder);
|
||||
}
|
||||
throw new IllegalStateException("Unimplemented component hasher: " + component);
|
||||
};
|
||||
|
||||
private static <T extends Component> MinecraftHasher<T> component(MapBuilder<T> componentBuilder) {
|
||||
return MinecraftHasher.mapBuilder(builder -> builder
|
||||
.accept(componentBuilder)
|
||||
.accept(STYLE, Component::style)
|
||||
.optionalList("extra", COMPONENT, Component::children));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,469 @@
|
|||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.hashing;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.nbt.NbtList;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.inventory.item.Potion;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.item.components.Rarity;
|
||||
import org.geysermc.geyser.level.block.Blocks;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
|
||||
import org.geysermc.geyser.util.MinecraftKey;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.Effect;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.EquipmentSlot;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.GlobalPos;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.HashedStack;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.BlockStateProperties;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.BlocksAttacks;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ConsumeEffect;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.CustomModelData;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponent;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.Equippable;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.Fireworks;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.FoodProperties;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.HolderSet;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.IntComponentType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ItemAttributeModifiers;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ItemEnchantments;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.LodestoneTracker;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.MobEffectDetails;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.MobEffectInstance;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.PotionContents;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ToolData;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.TooltipDisplay;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.Unit;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.UseCooldown;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.Weapon;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.WritableBookContent;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.WrittenBookContent;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.sound.BuiltinSound;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public class DataComponentHashers {
|
||||
private static final Map<DataComponentType<?>, MinecraftHasher<?>> hashers = new HashMap<>();
|
||||
|
||||
static {
|
||||
register(DataComponentTypes.CUSTOM_DATA, MinecraftHasher.NBT_MAP);
|
||||
registerInt(DataComponentTypes.MAX_STACK_SIZE);
|
||||
registerInt(DataComponentTypes.MAX_DAMAGE);
|
||||
registerInt(DataComponentTypes.DAMAGE);
|
||||
registerUnit(DataComponentTypes.UNBREAKABLE);
|
||||
|
||||
register(DataComponentTypes.CUSTOM_NAME, ComponentHasher.COMPONENT);
|
||||
register(DataComponentTypes.ITEM_NAME, ComponentHasher.COMPONENT);
|
||||
register(DataComponentTypes.ITEM_MODEL, MinecraftHasher.KEY);
|
||||
register(DataComponentTypes.LORE, ComponentHasher.COMPONENT.list());
|
||||
register(DataComponentTypes.RARITY, MinecraftHasher.RARITY);
|
||||
register(DataComponentTypes.ENCHANTMENTS, RegistryHasher.ITEM_ENCHANTMENTS);
|
||||
|
||||
register(DataComponentTypes.CAN_PLACE_ON, RegistryHasher.ADVENTURE_MODE_PREDICATE);
|
||||
register(DataComponentTypes.CAN_BREAK, RegistryHasher.ADVENTURE_MODE_PREDICATE); // TODO needs tests
|
||||
register(DataComponentTypes.ATTRIBUTE_MODIFIERS, RegistryHasher.ATTRIBUTE_MODIFIER_ENTRY.list().cast(ItemAttributeModifiers::getModifiers)); // TODO needs tests
|
||||
|
||||
registerMap(DataComponentTypes.CUSTOM_MODEL_DATA, builder -> builder
|
||||
.optionalList("floats", MinecraftHasher.FLOAT, CustomModelData::floats)
|
||||
.optionalList("flags", MinecraftHasher.BOOL, CustomModelData::flags)
|
||||
.optionalList("strings", MinecraftHasher.STRING, CustomModelData::strings)
|
||||
.optionalList("colors", MinecraftHasher.INT, CustomModelData::colors));
|
||||
|
||||
registerMap(DataComponentTypes.TOOLTIP_DISPLAY, builder -> builder
|
||||
.optional("hide_tooltip", MinecraftHasher.BOOL, TooltipDisplay::hideTooltip, false)
|
||||
.optionalList("hidden_components", RegistryHasher.DATA_COMPONENT_TYPE, TooltipDisplay::hiddenComponents));
|
||||
|
||||
registerInt(DataComponentTypes.REPAIR_COST);
|
||||
|
||||
register(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, MinecraftHasher.BOOL);
|
||||
registerUnit(DataComponentTypes.INTANGIBLE_PROJECTILE);
|
||||
|
||||
registerMap(DataComponentTypes.FOOD, builder -> builder
|
||||
.accept("nutrition", MinecraftHasher.INT, FoodProperties::getNutrition)
|
||||
.accept("saturation", MinecraftHasher.FLOAT, FoodProperties::getSaturationModifier)
|
||||
.optional("can_always_eat", MinecraftHasher.BOOL, FoodProperties::isCanAlwaysEat, false));
|
||||
registerMap(DataComponentTypes.CONSUMABLE, builder -> builder
|
||||
.optional("consume_seconds", MinecraftHasher.FLOAT, Consumable::consumeSeconds, 1.6F)
|
||||
.optional("animation", RegistryHasher.ITEM_USE_ANIMATION, Consumable::animation, Consumable.ItemUseAnimation.EAT)
|
||||
.optional("sound", RegistryHasher.SOUND_EVENT, Consumable::sound, BuiltinSound.ENTITY_GENERIC_EAT)
|
||||
.optional("has_consume_particles", MinecraftHasher.BOOL, Consumable::hasConsumeParticles, true)
|
||||
.optionalList("on_consume_effects", RegistryHasher.CONSUME_EFFECT, Consumable::onConsumeEffects));
|
||||
|
||||
register(DataComponentTypes.USE_REMAINDER, RegistryHasher.ITEM_STACK);
|
||||
|
||||
registerMap(DataComponentTypes.USE_COOLDOWN, builder -> builder
|
||||
.accept("seconds", MinecraftHasher.FLOAT, UseCooldown::seconds)
|
||||
.optionalNullable("cooldown_group", MinecraftHasher.KEY, UseCooldown::cooldownGroup));
|
||||
registerMap(DataComponentTypes.DAMAGE_RESISTANT, builder -> builder
|
||||
.accept("types", MinecraftHasher.TAG, Function.identity()));
|
||||
registerMap(DataComponentTypes.TOOL, builder -> builder
|
||||
.acceptList("rules", RegistryHasher.TOOL_RULE, ToolData::getRules)
|
||||
.optional("default_mining_speed", MinecraftHasher.FLOAT, ToolData::getDefaultMiningSpeed, 1.0F)
|
||||
.optional("damage_per_block", MinecraftHasher.INT, ToolData::getDamagePerBlock, 1)
|
||||
.optional("can_destroy_blocks_in_creative", MinecraftHasher.BOOL, ToolData::isCanDestroyBlocksInCreative, true));
|
||||
registerMap(DataComponentTypes.WEAPON, builder -> builder
|
||||
.optional("item_damage_per_attack", MinecraftHasher.INT, Weapon::itemDamagePerAttack, 1)
|
||||
.optional("disable_blocking_for_seconds", MinecraftHasher.FLOAT, Weapon::disableBlockingForSeconds, 0.0F));
|
||||
registerMap(DataComponentTypes.ENCHANTABLE, builder -> builder
|
||||
.accept("value", MinecraftHasher.INT, Function.identity()));
|
||||
registerMap(DataComponentTypes.EQUIPPABLE, builder -> builder
|
||||
.accept("slot", MinecraftHasher.EQUIPMENT_SLOT, Equippable::slot)
|
||||
.optional("equip_sound", RegistryHasher.SOUND_EVENT, Equippable::equipSound, BuiltinSound.ITEM_ARMOR_EQUIP_GENERIC)
|
||||
.optionalNullable("asset_id", MinecraftHasher.KEY, Equippable::model)
|
||||
.optionalNullable("camera_overlay", MinecraftHasher.KEY, Equippable::cameraOverlay)
|
||||
.optionalNullable("allowed_entities", RegistryHasher.ENTITY_TYPE.holderSet(), Equippable::allowedEntities)
|
||||
.optional("dispensable", MinecraftHasher.BOOL, Equippable::dispensable, true)
|
||||
.optional("swappable", MinecraftHasher.BOOL, Equippable::swappable, true)
|
||||
.optional("damage_on_hurt", MinecraftHasher.BOOL, Equippable::damageOnHurt, true)
|
||||
.optional("equip_on_interact", MinecraftHasher.BOOL, Equippable::equipOnInteract, false));
|
||||
registerMap(DataComponentTypes.REPAIRABLE, builder -> builder
|
||||
.accept("items", RegistryHasher.ITEM.holderSet(), Function.identity()));
|
||||
|
||||
registerUnit(DataComponentTypes.GLIDER);
|
||||
register(DataComponentTypes.TOOLTIP_STYLE, MinecraftHasher.KEY);
|
||||
|
||||
registerMap(DataComponentTypes.DEATH_PROTECTION, builder -> builder
|
||||
.optionalList("death_effects", RegistryHasher.CONSUME_EFFECT, Function.identity()));
|
||||
registerMap(DataComponentTypes.BLOCKS_ATTACKS, builder -> builder
|
||||
.optional("block_delay_seconds", MinecraftHasher.FLOAT, BlocksAttacks::blockDelaySeconds, 0.0F)
|
||||
.optional("disable_cooldown_scale", MinecraftHasher.FLOAT, BlocksAttacks::disableCooldownScale, 1.0F)
|
||||
.optional("damage_reductions", RegistryHasher.BLOCKS_ATTACKS_DAMAGE_REDUCTION.list(), BlocksAttacks::damageReductions, List.of(new BlocksAttacks.DamageReduction(90.0F, null, 0.0F, 1.0F)))
|
||||
.optional("item_damage", RegistryHasher.BLOCKS_ATTACKS_ITEM_DAMAGE_FUNCTION, BlocksAttacks::itemDamage, new BlocksAttacks.ItemDamageFunction(1.0F, 0.0F, 1.0F))
|
||||
.optionalNullable("bypassed_by", MinecraftHasher.TAG, BlocksAttacks::bypassedBy)
|
||||
.optionalNullable("block_sound", RegistryHasher.SOUND_EVENT, BlocksAttacks::blockSound)
|
||||
.optionalNullable("disabled_sound", RegistryHasher.SOUND_EVENT, BlocksAttacks::disableSound)); // TODO needs tests
|
||||
register(DataComponentTypes.STORED_ENCHANTMENTS, RegistryHasher.ITEM_ENCHANTMENTS);
|
||||
|
||||
registerInt(DataComponentTypes.DYED_COLOR);
|
||||
registerInt(DataComponentTypes.MAP_COLOR);
|
||||
registerInt(DataComponentTypes.MAP_ID);
|
||||
register(DataComponentTypes.MAP_DECORATIONS, MinecraftHasher.NBT_MAP);
|
||||
|
||||
register(DataComponentTypes.CHARGED_PROJECTILES, RegistryHasher.ITEM_STACK.list());
|
||||
register(DataComponentTypes.BUNDLE_CONTENTS, RegistryHasher.ITEM_STACK.list()); // TODO test data component removal
|
||||
|
||||
registerMap(DataComponentTypes.POTION_CONTENTS, builder -> builder
|
||||
.optional("potion", RegistryHasher.POTION, PotionContents::getPotionId, -1)
|
||||
.optional("custom_color", MinecraftHasher.INT, PotionContents::getCustomColor, -1)
|
||||
.optionalList("custom_effects", RegistryHasher.MOB_EFFECT_INSTANCE, PotionContents::getCustomEffects)
|
||||
.optionalNullable("custom_name", MinecraftHasher.STRING, PotionContents::getCustomName));
|
||||
|
||||
register(DataComponentTypes.POTION_DURATION_SCALE, MinecraftHasher.FLOAT);
|
||||
register(DataComponentTypes.SUSPICIOUS_STEW_EFFECTS, RegistryHasher.SUSPICIOUS_STEW_EFFECT.list());
|
||||
|
||||
registerMap(DataComponentTypes.WRITABLE_BOOK_CONTENT, builder -> builder
|
||||
.optionalList("pages", MinecraftHasher.STRING.filterable(), WritableBookContent::getPages));
|
||||
registerMap(DataComponentTypes.WRITTEN_BOOK_CONTENT, builder -> builder
|
||||
.accept("title", MinecraftHasher.STRING.filterable(), WrittenBookContent::getTitle)
|
||||
.accept("author", MinecraftHasher.STRING, WrittenBookContent::getAuthor)
|
||||
.accept("generation", MinecraftHasher.INT, WrittenBookContent::getGeneration)
|
||||
.optionalList("pages", ComponentHasher.COMPONENT.filterable(), WrittenBookContent::getPages)
|
||||
.optional("resolved", MinecraftHasher.BOOL, WrittenBookContent::isResolved, false));
|
||||
|
||||
register(DataComponentTypes.TRIM, RegistryHasher.ARMOR_TRIM);
|
||||
register(DataComponentTypes.DEBUG_STICK_STATE, MinecraftHasher.NBT_MAP);
|
||||
register(DataComponentTypes.ENTITY_DATA, MinecraftHasher.NBT_MAP);
|
||||
register(DataComponentTypes.BUCKET_ENTITY_DATA, MinecraftHasher.NBT_MAP);
|
||||
register(DataComponentTypes.BLOCK_ENTITY_DATA, MinecraftHasher.NBT_MAP);
|
||||
|
||||
register(DataComponentTypes.INSTRUMENT, RegistryHasher.INSTRUMENT_COMPONENT);
|
||||
register(DataComponentTypes.PROVIDES_TRIM_MATERIAL, RegistryHasher.PROVIDES_TRIM_MATERIAL);
|
||||
|
||||
registerInt(DataComponentTypes.OMINOUS_BOTTLE_AMPLIFIER);
|
||||
|
||||
register(DataComponentTypes.JUKEBOX_PLAYABLE, RegistryHasher.JUKEBOX_PLAYABLE);
|
||||
register(DataComponentTypes.PROVIDES_BANNER_PATTERNS, MinecraftHasher.TAG);
|
||||
register(DataComponentTypes.RECIPES, MinecraftHasher.NBT_LIST);
|
||||
|
||||
registerMap(DataComponentTypes.LODESTONE_TRACKER, builder -> builder
|
||||
.optionalNullable("target", MinecraftHasher.GLOBAL_POS, LodestoneTracker::getPos)
|
||||
.optional("tracked", MinecraftHasher.BOOL, LodestoneTracker::isTracked, true));
|
||||
|
||||
register(DataComponentTypes.FIREWORK_EXPLOSION, RegistryHasher.FIREWORK_EXPLOSION);
|
||||
registerMap(DataComponentTypes.FIREWORKS, builder -> builder
|
||||
.optional("flight_duration", MinecraftHasher.BYTE, fireworks -> (byte) fireworks.getFlightDuration(), (byte) 0)
|
||||
.optionalList("explosions", RegistryHasher.FIREWORK_EXPLOSION, Fireworks::getExplosions));
|
||||
|
||||
register(DataComponentTypes.PROFILE, MinecraftHasher.GAME_PROFILE);
|
||||
register(DataComponentTypes.NOTE_BLOCK_SOUND, MinecraftHasher.KEY);
|
||||
register(DataComponentTypes.BANNER_PATTERNS, RegistryHasher.BANNER_PATTERN_LAYER.list());
|
||||
register(DataComponentTypes.BASE_COLOR, MinecraftHasher.DYE_COLOR);
|
||||
register(DataComponentTypes.POT_DECORATIONS, RegistryHasher.ITEM.list());
|
||||
register(DataComponentTypes.CONTAINER, RegistryHasher.ITEM_CONTAINER_CONTENTS);
|
||||
register(DataComponentTypes.BLOCK_STATE, MinecraftHasher.map(MinecraftHasher.STRING, MinecraftHasher.STRING).cast(BlockStateProperties::getProperties));
|
||||
register(DataComponentTypes.BEES, RegistryHasher.BEEHIVE_OCCUPANT.list());
|
||||
|
||||
register(DataComponentTypes.LOCK, MinecraftHasher.NBT_MAP);
|
||||
register(DataComponentTypes.CONTAINER_LOOT, MinecraftHasher.NBT_MAP);
|
||||
register(DataComponentTypes.BREAK_SOUND, RegistryHasher.SOUND_EVENT);
|
||||
|
||||
register(DataComponentTypes.VILLAGER_VARIANT, RegistryHasher.VILLAGER_TYPE);
|
||||
register(DataComponentTypes.WOLF_VARIANT, RegistryHasher.WOLF_VARIANT);
|
||||
register(DataComponentTypes.WOLF_SOUND_VARIANT, RegistryHasher.WOLF_SOUND_VARIANT);
|
||||
register(DataComponentTypes.WOLF_COLLAR, MinecraftHasher.DYE_COLOR);
|
||||
register(DataComponentTypes.FOX_VARIANT, RegistryHasher.FOX_VARIANT);
|
||||
register(DataComponentTypes.SALMON_SIZE, RegistryHasher.SALMON_VARIANT);
|
||||
register(DataComponentTypes.PARROT_VARIANT, RegistryHasher.PARROT_VARIANT);
|
||||
register(DataComponentTypes.TROPICAL_FISH_PATTERN, RegistryHasher.TROPICAL_FISH_PATTERN);
|
||||
register(DataComponentTypes.TROPICAL_FISH_BASE_COLOR, MinecraftHasher.DYE_COLOR);
|
||||
register(DataComponentTypes.TROPICAL_FISH_PATTERN_COLOR, MinecraftHasher.DYE_COLOR);
|
||||
register(DataComponentTypes.MOOSHROOM_VARIANT, RegistryHasher.MOOSHROOM_VARIANT);
|
||||
register(DataComponentTypes.RABBIT_VARIANT, RegistryHasher.RABBIT_VARIANT);
|
||||
register(DataComponentTypes.PIG_VARIANT, RegistryHasher.PIG_VARIANT);
|
||||
register(DataComponentTypes.COW_VARIANT, RegistryHasher.COW_VARIANT);
|
||||
register(DataComponentTypes.CHICKEN_VARIANT, MinecraftHasher.KEY
|
||||
.sessionCast((session, holder) -> holder.getOrCompute(id -> JavaRegistries.CHICKEN_VARIANT.keyFromNetworkId(session, id)))); // Why, Mojang?
|
||||
register(DataComponentTypes.FROG_VARIANT, RegistryHasher.FROG_VARIANT);
|
||||
register(DataComponentTypes.HORSE_VARIANT, RegistryHasher.HORSE_VARIANT);
|
||||
register(DataComponentTypes.PAINTING_VARIANT, RegistryHasher.PAINTING_VARIANT.holder());
|
||||
register(DataComponentTypes.LLAMA_VARIANT, RegistryHasher.LLAMA_VARIANT);
|
||||
register(DataComponentTypes.AXOLOTL_VARIANT, RegistryHasher.AXOLOTL_VARIANT);
|
||||
register(DataComponentTypes.CAT_VARIANT, RegistryHasher.CAT_VARIANT);
|
||||
register(DataComponentTypes.CAT_COLLAR, MinecraftHasher.DYE_COLOR);
|
||||
register(DataComponentTypes.SHEEP_COLOR, MinecraftHasher.DYE_COLOR);
|
||||
register(DataComponentTypes.SHULKER_COLOR, MinecraftHasher.DYE_COLOR);
|
||||
}
|
||||
|
||||
private static void registerUnit(DataComponentType<Unit> component) {
|
||||
register(component, MinecraftHasher.UNIT);
|
||||
}
|
||||
|
||||
private static void registerInt(IntComponentType component) {
|
||||
register(component, MinecraftHasher.INT);
|
||||
}
|
||||
|
||||
private static <T> void registerMap(DataComponentType<T> component, MapBuilder<T> builder) {
|
||||
register(component, MinecraftHasher.mapBuilder(builder));
|
||||
}
|
||||
|
||||
private static <T> void register(DataComponentType<T> component, MinecraftHasher<T> hasher) {
|
||||
if (hashers.containsKey(component)) {
|
||||
throw new IllegalArgumentException("Tried to register a hasher for a component twice");
|
||||
}
|
||||
hashers.put(component, hasher);
|
||||
}
|
||||
|
||||
public static <T> MinecraftHasher<T> hasher(DataComponentType<T> component) {
|
||||
MinecraftHasher<T> hasher = (MinecraftHasher<T>) hashers.get(component);
|
||||
if (hasher == null) {
|
||||
throw new IllegalStateException("Unregistered hasher for component " + component + "!");
|
||||
}
|
||||
return hasher;
|
||||
}
|
||||
|
||||
public static <T> HashCode hash(GeyserSession session, DataComponentType<T> component, T value) {
|
||||
try {
|
||||
return hasher(component).hash(value, new MinecraftHashEncoder(session));
|
||||
} catch (Exception exception) {
|
||||
GeyserImpl.getInstance().getLogger().error("Failed to hash item data component " + component.getKey() + " with value " + value + "!");
|
||||
GeyserImpl.getInstance().getLogger().error("This is a Geyser bug, please report this!");
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
public static HashedStack hashStack(GeyserSession session, ItemStack stack) {
|
||||
if (stack == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
DataComponents patch = stack.getDataComponentsPatch();
|
||||
if (patch == null) {
|
||||
return new HashedStack(stack.getId(), stack.getAmount(), Map.of(), Set.of());
|
||||
}
|
||||
Map<DataComponentType<?>, DataComponent<?, ?>> components = patch.getDataComponents();
|
||||
Map<DataComponentType<?>, Integer> hashedAdditions = new HashMap<>();
|
||||
Set<DataComponentType<?>> removals = new HashSet<>();
|
||||
for (Map.Entry<DataComponentType<?>, DataComponent<?, ?>> component : components.entrySet()) {
|
||||
if (component.getValue().getValue() == null) {
|
||||
removals.add(component.getKey());
|
||||
} else {
|
||||
hashedAdditions.put(component.getKey(), hash(session, (DataComponentType) component.getKey(), component.getValue().getValue()).asInt());
|
||||
}
|
||||
}
|
||||
return new HashedStack(stack.getId(), stack.getAmount(), hashedAdditions, removals);
|
||||
}
|
||||
|
||||
// TODO better testing
|
||||
public static void testHashing(GeyserSession session) {
|
||||
// Hashed values generated by vanilla Java
|
||||
|
||||
NbtMap customData = NbtMap.builder()
|
||||
.putString("hello", "g'day")
|
||||
.putBoolean("nice?", false)
|
||||
.putByte("coolness", (byte) 100)
|
||||
.putCompound("geyser", NbtMap.builder()
|
||||
.putString("is", "very cool")
|
||||
.build())
|
||||
.putList("a list", NbtType.LIST, List.of(new NbtList<>(NbtType.STRING, "in a list")))
|
||||
.build();
|
||||
|
||||
testHash(session, DataComponentTypes.CUSTOM_DATA, customData, -385053299);
|
||||
|
||||
testHash(session, DataComponentTypes.MAX_STACK_SIZE, 64, 733160003);
|
||||
testHash(session, DataComponentTypes.MAX_DAMAGE, 13, -801733367);
|
||||
testHash(session, DataComponentTypes.DAMAGE, 459, 1211405277);
|
||||
testHash(session, DataComponentTypes.UNBREAKABLE, Unit.INSTANCE, -982207288);
|
||||
|
||||
testHash(session, DataComponentTypes.CUSTOM_NAME, Component.text("simple component test!"), 950545066);
|
||||
testHash(session, DataComponentTypes.CUSTOM_NAME, Component.translatable("a.translatable"), 1983484873);
|
||||
testHash(session, DataComponentTypes.CUSTOM_NAME, Component.text("component with *style*")
|
||||
.style(style -> style.color(NamedTextColor.RED).decorate(TextDecoration.ITALIC)), -886479206);
|
||||
testHash(session, DataComponentTypes.CUSTOM_NAME, Component.text("component with more stuff")
|
||||
.children(List.of(Component.translatable("a.translate.string", "fallback!")
|
||||
.style(style -> style.color(TextColor.color(0x446688)).decorate(TextDecoration.BOLD)))), -1591253390);
|
||||
|
||||
testHash(session, DataComponentTypes.ITEM_MODEL, MinecraftKey.key("testing"), -689946239);
|
||||
|
||||
testHash(session, DataComponentTypes.RARITY, Rarity.COMMON.ordinal(), 75150990);
|
||||
testHash(session, DataComponentTypes.RARITY, Rarity.RARE.ordinal(), -1420566726);
|
||||
testHash(session, DataComponentTypes.RARITY, Rarity.EPIC.ordinal(), -292715907);
|
||||
|
||||
testHash(session, DataComponentTypes.ENCHANTMENTS, new ItemEnchantments(Map.of(
|
||||
0, 1
|
||||
)), 0); // TODO identifier lookup
|
||||
|
||||
testHash(session, DataComponentTypes.CUSTOM_MODEL_DATA,
|
||||
new CustomModelData(List.of(5.0F, 3.0F, -1.0F), List.of(false, true, false), List.of("1", "3", "2"), List.of(3424, -123, 345)), 1947635619);
|
||||
|
||||
testHash(session, DataComponentTypes.CUSTOM_MODEL_DATA,
|
||||
new CustomModelData(List.of(5.03F, 3.0F, -1.11F), List.of(true, true, false), List.of("2", "5", "7"), List.of()), -512419908);
|
||||
|
||||
testHash(session, DataComponentTypes.TOOLTIP_DISPLAY, new TooltipDisplay(false, List.of(DataComponentTypes.CONSUMABLE, DataComponentTypes.DAMAGE)), -816418453);
|
||||
testHash(session, DataComponentTypes.TOOLTIP_DISPLAY, new TooltipDisplay(true, List.of()), 14016722);
|
||||
testHash(session, DataComponentTypes.TOOLTIP_DISPLAY, new TooltipDisplay(false, List.of()), -982207288);
|
||||
|
||||
testHash(session, DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, true, -1019818302);
|
||||
testHash(session, DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, false, 828198337);
|
||||
|
||||
testHash(session, DataComponentTypes.FOOD, new FoodProperties(5, 1.4F, false), 445786378);
|
||||
testHash(session, DataComponentTypes.FOOD, new FoodProperties(3, 5.7F, true), 1917653498);
|
||||
testHash(session, DataComponentTypes.FOOD, new FoodProperties(7, 0.15F, false), -184166204);
|
||||
|
||||
testHash(session, DataComponentTypes.CONSUMABLE, new Consumable(2.0F, Consumable.ItemUseAnimation.EAT,
|
||||
BuiltinSound.ITEM_OMINOUS_BOTTLE_DISPOSE, true,
|
||||
List.of(new ConsumeEffect.RemoveEffects(new HolderSet(new int[]{Effect.BAD_OMEN.ordinal(), Effect.REGENERATION.ordinal()})),
|
||||
new ConsumeEffect.TeleportRandomly(3.0F))), 1742669333);
|
||||
|
||||
testHash(session, DataComponentTypes.USE_REMAINDER, new ItemStack(Items.MELON.javaId(), 52), -1279684916);
|
||||
|
||||
DataComponents specialComponents = new DataComponents(new HashMap<>());
|
||||
specialComponents.put(DataComponentTypes.ITEM_MODEL, MinecraftKey.key("testing"));
|
||||
specialComponents.put(DataComponentTypes.MAX_STACK_SIZE, 44);
|
||||
testHash(session, DataComponentTypes.USE_REMAINDER, new ItemStack(Items.PUMPKIN.javaId(), 32, specialComponents), 1991032843);
|
||||
|
||||
testHash(session, DataComponentTypes.DAMAGE_RESISTANT, MinecraftKey.key("testing"), -1230493835);
|
||||
|
||||
testHash(session, DataComponentTypes.TOOL, new ToolData(List.of(), 5.0F, 3, false), -1789071928);
|
||||
testHash(session, DataComponentTypes.TOOL, new ToolData(List.of(), 3.0F, 1, true), -7422944);
|
||||
testHash(session, DataComponentTypes.TOOL, new ToolData(List.of(
|
||||
new ToolData.Rule(new HolderSet(MinecraftKey.key("acacia_logs")), null, null),
|
||||
new ToolData.Rule(new HolderSet(new int[]{Blocks.JACK_O_LANTERN.javaId(), Blocks.WALL_TORCH.javaId()}), 4.2F, true),
|
||||
new ToolData.Rule(new HolderSet(new int[]{Blocks.PUMPKIN.javaId()}), 7.0F, false)),
|
||||
1.0F, 1, true), 2103678261);
|
||||
|
||||
testHash(session, DataComponentTypes.WEAPON, new Weapon(5, 2.0F), -154556976);
|
||||
testHash(session, DataComponentTypes.WEAPON, new Weapon(1, 7.3F), 885347995);
|
||||
|
||||
testHash(session, DataComponentTypes.ENCHANTABLE, 3, -1834983819);
|
||||
|
||||
testHash(session, DataComponentTypes.EQUIPPABLE, new Equippable(EquipmentSlot.BODY, BuiltinSound.ITEM_ARMOR_EQUIP_GENERIC, null, null, null,
|
||||
true, true, true, false), 1294431019);
|
||||
testHash(session, DataComponentTypes.EQUIPPABLE, new Equippable(EquipmentSlot.BODY, BuiltinSound.ITEM_ARMOR_EQUIP_CHAIN, MinecraftKey.key("testing"), null, null,
|
||||
true, true, true, false), 1226203061);
|
||||
testHash(session, DataComponentTypes.EQUIPPABLE, new Equippable(EquipmentSlot.BODY, BuiltinSound.AMBIENT_CAVE, null, null, null,
|
||||
false, true, false, false), 1416408052);
|
||||
testHash(session, DataComponentTypes.EQUIPPABLE, new Equippable(EquipmentSlot.BODY, BuiltinSound.ENTITY_BREEZE_WIND_BURST, null, MinecraftKey.key("testing"),
|
||||
new HolderSet(new int[]{EntityType.ACACIA_BOAT.ordinal()}), false, true, false, false), 1711275245);
|
||||
|
||||
testHash(session, DataComponentTypes.EQUIPPABLE, new Equippable(EquipmentSlot.HELMET, BuiltinSound.ITEM_ARMOR_EQUIP_GENERIC, null, null, null,
|
||||
true, true, true, false), 497790992); // TODO broken because equipment slot names don't match
|
||||
|
||||
testHash(session, DataComponentTypes.REPAIRABLE, new HolderSet(new int[]{Items.AMETHYST_BLOCK.javaId(), Items.PUMPKIN.javaId()}), -36715567);
|
||||
|
||||
NbtMap mapDecorations = NbtMap.builder()
|
||||
.putCompound("test_decoration", NbtMap.builder()
|
||||
.putString("type", "minecraft:player")
|
||||
.putDouble("x", 45.0)
|
||||
.putDouble("z", 67.4)
|
||||
.putFloat("rotation", 39.5F)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
testHash(session, DataComponentTypes.MAP_DECORATIONS, mapDecorations, -625782954);
|
||||
|
||||
ItemStack bundleStack1 = new ItemStack(Items.PUMPKIN.javaId());
|
||||
ItemStack bundleStack2 = new ItemStack(Items.MELON.javaId(), 24);
|
||||
|
||||
DataComponents bundleStackComponents = new DataComponents(new HashMap<>());
|
||||
bundleStackComponents.put(DataComponentTypes.CUSTOM_NAME, Component.text("magic potato!"));
|
||||
|
||||
ItemStack bundleStack3 = new ItemStack(Items.POTATO.javaId(), 30, bundleStackComponents);
|
||||
testHash(session, DataComponentTypes.BUNDLE_CONTENTS, List.of(bundleStack1, bundleStack2, bundleStack3), 1817891504);
|
||||
|
||||
testHash(session, DataComponentTypes.POTION_CONTENTS, new PotionContents(Potion.FIRE_RESISTANCE.ordinal(), -1, List.of(), null), -772576502);
|
||||
testHash(session, DataComponentTypes.POTION_CONTENTS, new PotionContents(-1, 20,
|
||||
List.of(new MobEffectInstance(Effect.CONDUIT_POWER, new MobEffectDetails(0, 0, false, true, true, null))),
|
||||
null), -902075187);
|
||||
testHash(session, DataComponentTypes.POTION_CONTENTS, new PotionContents(-1, 96,
|
||||
List.of(new MobEffectInstance(Effect.JUMP_BOOST, new MobEffectDetails(57, 17, true, false, false, null))),
|
||||
null), -17231244);
|
||||
testHash(session, DataComponentTypes.POTION_CONTENTS, new PotionContents(-1, 87,
|
||||
List.of(new MobEffectInstance(Effect.SPEED, new MobEffectDetails(29, 1004, false, true, true, null))),
|
||||
"testing"), 2007296036);
|
||||
|
||||
// TODO testing trim, instrument, trim material, jukebox playable requires registries
|
||||
|
||||
testHash(session, DataComponentTypes.LODESTONE_TRACKER,
|
||||
new LodestoneTracker(new GlobalPos(MinecraftKey.key("overworld"), Vector3i.from(5, 6, 7)), true), 63561894);
|
||||
testHash(session, DataComponentTypes.LODESTONE_TRACKER,
|
||||
new LodestoneTracker(null, false), 1595667667);
|
||||
}
|
||||
|
||||
private static <T> void testHash(GeyserSession session, DataComponentType<T> component, T value, int expected) {
|
||||
int got = hash(session, component, value).asInt();
|
||||
System.out.println("Testing hashing component " + component.getKey() + ", expected " + expected + ", got " + got + " " + (got == expected ? "PASS" : "ERROR"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.hashing;
|
||||
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
/**
|
||||
* {@link MapBuilder}s can be used to define map-like structures to encode a {@link Type} using a {@link MapHasher}.
|
||||
*
|
||||
* @param <Type> the type to encode.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface MapBuilder<Type> extends UnaryOperator<MapHasher<Type>> {
|
||||
|
||||
/**
|
||||
* Casts this map builder to a {@link Casted}. This cast is done unsafely, only use this if you are sure the object being encoded is of the type being cast to!
|
||||
*
|
||||
* @param <Casted> the type to cast to.
|
||||
*/
|
||||
default <Casted> MapBuilder<Casted> cast() {
|
||||
return builder -> builder.accept(this, casted -> (Type) casted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map builder that doesn't contain anything.
|
||||
*
|
||||
* @param <Type> the type to encode.
|
||||
*/
|
||||
static <Type> MapBuilder<Type> empty() {
|
||||
return builder -> builder;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.hashing;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* {@link MapHasher}s are used to encode a {@link Type} to a map-like structure, which is then hashed using a {@link MinecraftHashEncoder}.
|
||||
*
|
||||
* <p>{@link MapHasher}s store the {@link Type} they are encoding, but it isn't directly accessible. Instead, extractor functions are used to extract specific properties of the {@link Type}.</p>
|
||||
*
|
||||
* @param <Type> the type this {@link MapHasher} encodes.
|
||||
*/
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public class MapHasher<Type> {
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private final MinecraftHashEncoder encoder;
|
||||
private final Type object;
|
||||
private final Map<HashCode, HashCode> map;
|
||||
private final Map<String, Object> unhashed;
|
||||
|
||||
MapHasher(Type object, MinecraftHashEncoder encoder) {
|
||||
this(object, encoder, new HashMap<>(), DEBUG ? new HashMap<>() : null);
|
||||
}
|
||||
|
||||
private MapHasher(Type object, MinecraftHashEncoder encoder, Map<HashCode, HashCode> map, Map<String, Object> unhashed) {
|
||||
this.encoder = encoder;
|
||||
this.object = object;
|
||||
this.map = map;
|
||||
this.unhashed = unhashed;
|
||||
}
|
||||
|
||||
private MapHasher<Type> accept(String key, HashCode hash) {
|
||||
map.put(encoder.string(key), hash);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a constant {@link Value} to the map.
|
||||
*
|
||||
* @param key the key to put the constant in.
|
||||
* @param hasher the hasher used to hash a {@link Value}.
|
||||
* @param value the {@link Value}.
|
||||
* @param <Value> the type of the value.
|
||||
*/
|
||||
public <Value> MapHasher<Type> acceptConstant(String key, MinecraftHasher<Value> hasher, Value value) {
|
||||
if (unhashed != null) {
|
||||
unhashed.put(key, value);
|
||||
}
|
||||
return accept(key, hasher.hash(value, encoder));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a {@link Value} from a {@link Type} using the {@code extractor}, and adds it to the map.
|
||||
*
|
||||
* @param key the key to put the {@link Value} in.
|
||||
* @param hasher the hasher used to hash a {@link Value}.
|
||||
* @param extractor the function that extracts a {@link Value} from a {@link Type}.
|
||||
* @param <Value> the type of the value.
|
||||
*/
|
||||
public <Value> MapHasher<Type> accept(String key, MinecraftHasher<Value> hasher, Function<Type, Value> extractor) {
|
||||
return acceptConstant(key, hasher, extractor.apply(object));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the {@link MapBuilder} to this {@link MapHasher}, essentially adding all the keys it defines here.
|
||||
*/
|
||||
public MapHasher<Type> accept(MapBuilder<Type> builder) {
|
||||
builder.apply(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a {@link Value} from a {@link Type} using the {@code extractor}, and applies the given {@link MapBuilder} for it to this {@link MapHasher},
|
||||
* essentially adding the keys it defines here.
|
||||
*
|
||||
* @param builder the {@link MapBuilder} that encodes a {@link Value}.
|
||||
* @param extractor the function that extracts a {@link Value} from a {@link Type}.
|
||||
* @param <Value> the type of the value.
|
||||
*/
|
||||
public <Value> MapHasher<Type> accept(MapBuilder<Value> builder, Function<Type, Value> extractor) {
|
||||
builder.apply(new MapHasher<>(extractor.apply(object), encoder, map, unhashed));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a {@link Value} from a {@link Type} using the {@code extractor}, then dispatches a {@link MapBuilder} from the {@link Value} using the {@code builderDispatcher},
|
||||
* and applies it to this {@link MapHasher}, essentially adding the keys it defines here.
|
||||
*
|
||||
* @param extractor the function that extracts a {@link Value} from a {@link Type}.
|
||||
* @param builderDispatcher the function that dispatches a {@link MapBuilder} from a {@link Value}.
|
||||
* @param <Value> the type of the value.
|
||||
*/
|
||||
public <Value> MapHasher<Type> accept(Function<Type, Value> extractor, Function<Value, MapBuilder<Type>> builderDispatcher) {
|
||||
builderDispatcher.apply(extractor.apply(object)).apply(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a {@link Value} from a {@link Type} using the {@code extractor}, and adds it to the map if it is not null.
|
||||
*
|
||||
* @param key the key to put the {@link Value} in.
|
||||
* @param hasher the hasher used to hash a {@link Value}.
|
||||
* @param extractor the function that extracts a {@link Value} from a {@link Type}.
|
||||
* @param <Value> the type of the value.
|
||||
*/
|
||||
public <Value> MapHasher<Type> optionalNullable(String key, MinecraftHasher<Value> hasher, Function<Type, Value> extractor) {
|
||||
Value value = extractor.apply(object);
|
||||
if (value != null) {
|
||||
acceptConstant(key, hasher, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts an {@link Optional} of a {@link Value} from a {@link Type} using the {@code extractor}, and adds it to the map if it is present.
|
||||
*
|
||||
* @param key the key to put the {@link Value} in.
|
||||
* @param hasher the hasher used to hash a {@link Value}.
|
||||
* @param extractor the function that extracts a {@link Value} from a {@link Type}.
|
||||
* @param <Value> the type of the value.
|
||||
*/
|
||||
public <Value> MapHasher<Type> optional(String key, MinecraftHasher<Value> hasher, Function<Type, Optional<Value>> extractor) {
|
||||
Optional<Value> value = extractor.apply(object);
|
||||
value.ifPresent(v -> acceptConstant(key, hasher, v));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a {@link Value} from a {@link Type} using the {@code extractor}, and adds it to the map if it's not equal to {@code defaultValue}.
|
||||
*
|
||||
* @param key the key to put the {@link Value} in.
|
||||
* @param hasher the hasher used to hash a {@link Value}.
|
||||
* @param extractor the function that extracts a {@link Value} from a {@link Type}.
|
||||
* @param defaultValue the default {@link Value}. The {@link Value} won't be added to the map if it equals the default.
|
||||
* @param <Value> the type of the value.
|
||||
*/
|
||||
public <Value> MapHasher<Type> optional(String key, MinecraftHasher<Value> hasher, Function<Type, Value> extractor, Value defaultValue) {
|
||||
Value value = extractor.apply(object);
|
||||
if (!value.equals(defaultValue)) {
|
||||
acceptConstant(key, hasher, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a list of {@link Value}s from a {@link Type}, and adds it to the map.
|
||||
*
|
||||
* @param key the key to put the list of {@link Value}s in.
|
||||
* @param valueHasher the hasher used to hash a single {@link Value}.
|
||||
* @param extractor the function that extracts a list of {@link Value}s from a {@link Type}.
|
||||
* @param <Value> the type of the value.
|
||||
*/
|
||||
public <Value> MapHasher<Type> acceptList(String key, MinecraftHasher<Value> valueHasher, Function<Type, List<Value>> extractor) {
|
||||
return acceptConstant(key, valueHasher.list(), extractor.apply(object));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a list of {@link Value}s from a {@link Type}, and adds it to the map if it is not empty.
|
||||
*
|
||||
* @param key the key to put the list of {@link Value}s in.
|
||||
* @param valueHasher the hasher used to hash a single {@link Value}.
|
||||
* @param extractor the function that extracts a list of {@link Value}s from a {@link Type}.
|
||||
* @param <Value> the type of the value.
|
||||
*/
|
||||
public <Value> MapHasher<Type> optionalList(String key, MinecraftHasher<Value> valueHasher, Function<Type, List<Value>> extractor) {
|
||||
List<Value> list = extractor.apply(object);
|
||||
if (!list.isEmpty()) {
|
||||
acceptConstant(key, valueHasher.list(), list);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public HashCode build() {
|
||||
if (unhashed != null) {
|
||||
System.out.println(unhashed);
|
||||
}
|
||||
return encoder.map(map);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.hashing;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
import com.google.common.hash.HashFunction;
|
||||
import com.google.common.hash.Hasher;
|
||||
import com.google.common.hash.Hashing;
|
||||
import org.cloudburstmc.nbt.NbtList;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtType;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Encodes primitive Java objects, lists, and maps into a {@link HashCode}, using {@link Hashing#crc32c()} as hash function.
|
||||
*
|
||||
* <p>Based off the {@code HashOps} class in vanilla Java 1.21.5, and is used by {@link MinecraftHasher}.</p>
|
||||
*/
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public class MinecraftHashEncoder {
|
||||
private static final byte TAG_EMPTY = 1;
|
||||
private static final byte TAG_MAP_START = 2;
|
||||
private static final byte TAG_MAP_END = 3;
|
||||
private static final byte TAG_LIST_START = 4;
|
||||
private static final byte TAG_LIST_END = 5;
|
||||
private static final byte TAG_BYTE = 6;
|
||||
private static final byte TAG_SHORT = 7;
|
||||
private static final byte TAG_INT = 8;
|
||||
private static final byte TAG_LONG = 9;
|
||||
private static final byte TAG_FLOAT = 10;
|
||||
private static final byte TAG_DOUBLE = 11;
|
||||
private static final byte TAG_STRING = 12;
|
||||
private static final byte TAG_BOOLEAN = 13;
|
||||
private static final byte TAG_BYTE_ARRAY_START = 14;
|
||||
private static final byte TAG_BYTE_ARRAY_END = 15;
|
||||
private static final byte TAG_INT_ARRAY_START = 16;
|
||||
private static final byte TAG_INT_ARRAY_END = 17;
|
||||
private static final byte TAG_LONG_ARRAY_START = 18;
|
||||
private static final byte TAG_LONG_ARRAY_END = 19;
|
||||
|
||||
private static final Comparator<HashCode> HASH_COMPARATOR = Comparator.comparingLong(HashCode::padToLong);
|
||||
private static final Comparator<Map.Entry<HashCode, HashCode>> MAP_ENTRY_ORDER = Map.Entry.<HashCode, HashCode>comparingByKey(HASH_COMPARATOR)
|
||||
.thenComparing(Map.Entry.comparingByValue(HASH_COMPARATOR));
|
||||
|
||||
private static final byte[] EMPTY = new byte[]{TAG_EMPTY};
|
||||
public static final byte[] EMPTY_MAP = new byte[]{TAG_MAP_START, TAG_MAP_END};
|
||||
private static final byte[] FALSE = new byte[]{TAG_BOOLEAN, 0};
|
||||
private static final byte[] TRUE = new byte[]{TAG_BOOLEAN, 1};
|
||||
|
||||
private final HashFunction hasher;
|
||||
private final GeyserSession session;
|
||||
|
||||
private final HashCode empty;
|
||||
private final HashCode emptyMap;
|
||||
private final HashCode falseHash;
|
||||
private final HashCode trueHash;
|
||||
|
||||
public MinecraftHashEncoder(GeyserSession session) {
|
||||
hasher = Hashing.crc32c();
|
||||
this.session = session;
|
||||
|
||||
empty = hasher.hashBytes(EMPTY);
|
||||
emptyMap = hasher.hashBytes(EMPTY_MAP);
|
||||
falseHash = hasher.hashBytes(FALSE);
|
||||
trueHash = hasher.hashBytes(TRUE);
|
||||
}
|
||||
|
||||
public GeyserSession session() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public HashCode empty() {
|
||||
return empty;
|
||||
}
|
||||
|
||||
public HashCode emptyMap() {
|
||||
return emptyMap;
|
||||
}
|
||||
|
||||
public HashCode number(Number number) {
|
||||
if (number instanceof Byte b) {
|
||||
return hasher.newHasher(2).putByte(TAG_BYTE).putByte(b).hash();
|
||||
} else if (number instanceof Short s) {
|
||||
return hasher.newHasher(3).putByte(TAG_SHORT).putShort(s).hash();
|
||||
} else if (number instanceof Integer i) {
|
||||
return hasher.newHasher(5).putByte(TAG_INT).putInt(i).hash();
|
||||
} else if (number instanceof Long l) {
|
||||
return hasher.newHasher(9).putByte(TAG_LONG).putLong(l).hash();
|
||||
} else if (number instanceof Float f) {
|
||||
return hasher.newHasher(5).putByte(TAG_FLOAT).putFloat(f).hash();
|
||||
}
|
||||
|
||||
return hasher.newHasher(9).putByte(TAG_DOUBLE).putDouble(number.doubleValue()).hash();
|
||||
}
|
||||
|
||||
public HashCode string(String string) {
|
||||
return hasher.newHasher().putByte(TAG_STRING).putInt(string.length()).putUnencodedChars(string).hash();
|
||||
}
|
||||
|
||||
public HashCode bool(boolean b) {
|
||||
return b ? trueHash : falseHash;
|
||||
}
|
||||
|
||||
public HashCode map(Map<HashCode, HashCode> map) {
|
||||
Hasher mapHasher = hasher.newHasher();
|
||||
mapHasher.putByte(TAG_MAP_START);
|
||||
map.entrySet().stream()
|
||||
.sorted(MAP_ENTRY_ORDER)
|
||||
.forEach(entry -> mapHasher.putBytes(entry.getKey().asBytes()).putBytes(entry.getValue().asBytes()));
|
||||
mapHasher.putByte(TAG_MAP_END);
|
||||
return mapHasher.hash();
|
||||
}
|
||||
|
||||
public HashCode nbtMap(NbtMap map) {
|
||||
Map<HashCode, HashCode> hashed = new HashMap<>();
|
||||
for (String key : map.keySet()) {
|
||||
HashCode hashedKey = string(key);
|
||||
Object value = map.get(key);
|
||||
if (value instanceof NbtList<?> list) {
|
||||
hashed.put(hashedKey, nbtList(list));
|
||||
} else {
|
||||
map.listenForNumber(key, n -> hashed.put(hashedKey, number(n)));
|
||||
map.listenForString(key, s -> hashed.put(hashedKey, string(s)));
|
||||
map.listenForCompound(key, compound -> hashed.put(hashedKey, nbtMap(compound)));
|
||||
|
||||
map.listenForByteArray(key, bytes -> hashed.put(hashedKey, byteArray(bytes)));
|
||||
map.listenForIntArray(key, ints -> hashed.put(hashedKey, intArray(ints)));
|
||||
map.listenForLongArray(key, longs -> hashed.put(hashedKey, longArray(longs)));
|
||||
}
|
||||
}
|
||||
return map(hashed);
|
||||
}
|
||||
|
||||
public HashCode list(List<HashCode> list) {
|
||||
Hasher listHasher = hasher.newHasher();
|
||||
listHasher.putByte(TAG_LIST_START);
|
||||
list.forEach(hash -> listHasher.putBytes(hash.asBytes()));
|
||||
listHasher.putByte(TAG_LIST_END);
|
||||
return listHasher.hash();
|
||||
}
|
||||
|
||||
// TODO can this be written better?
|
||||
@SuppressWarnings("unchecked")
|
||||
public HashCode nbtList(NbtList<?> nbtList) {
|
||||
NbtType<?> type = nbtList.getType();
|
||||
List<HashCode> hashed = new ArrayList<>();
|
||||
|
||||
if (type == NbtType.BYTE) {
|
||||
hashed.addAll(((List<Byte>) nbtList).stream().map(this::number).toList());
|
||||
} else if (type == NbtType.SHORT) {
|
||||
hashed.addAll(((List<Short>) nbtList).stream().map(this::number).toList());
|
||||
} else if (type == NbtType.INT) {
|
||||
hashed.addAll(((List<Integer>) nbtList).stream().map(this::number).toList());
|
||||
} else if (type == NbtType.LONG) {
|
||||
hashed.addAll(((List<Long>) nbtList).stream().map(this::number).toList());
|
||||
} else if (type == NbtType.FLOAT) {
|
||||
hashed.addAll(((List<Float>) nbtList).stream().map(this::number).toList());
|
||||
} else if (type == NbtType.DOUBLE) {
|
||||
hashed.addAll(((List<Double>) nbtList).stream().map(this::number).toList());
|
||||
} else if (type == NbtType.STRING) {
|
||||
hashed.addAll(((List<String>) nbtList).stream().map(this::string).toList());
|
||||
} else if (type == NbtType.LIST) {
|
||||
for (NbtList<?> list : (List<NbtList<?>>) nbtList) {
|
||||
hashed.add(nbtList(list));
|
||||
}
|
||||
} else if (type == NbtType.COMPOUND) {
|
||||
for (NbtMap compound : (List<NbtMap>) nbtList) {
|
||||
hashed.add(nbtMap(compound));
|
||||
}
|
||||
}
|
||||
|
||||
return list(hashed);
|
||||
}
|
||||
|
||||
public HashCode byteArray(byte[] bytes) {
|
||||
Hasher arrayHasher = hasher.newHasher();
|
||||
arrayHasher.putByte(TAG_BYTE_ARRAY_START);
|
||||
arrayHasher.putBytes(bytes);
|
||||
arrayHasher.putByte(TAG_BYTE_ARRAY_END);
|
||||
return arrayHasher.hash();
|
||||
}
|
||||
|
||||
public HashCode intArray(int[] ints) {
|
||||
Hasher arrayHasher = hasher.newHasher();
|
||||
arrayHasher.putByte(TAG_INT_ARRAY_START);
|
||||
for (int i : ints) {
|
||||
arrayHasher.putInt(i);
|
||||
}
|
||||
arrayHasher.putByte(TAG_INT_ARRAY_END);
|
||||
return arrayHasher.hash();
|
||||
}
|
||||
|
||||
public HashCode longArray(long[] longs) {
|
||||
Hasher arrayHasher = hasher.newHasher();
|
||||
arrayHasher.putByte(TAG_LONG_ARRAY_START);
|
||||
for (long l : longs) {
|
||||
arrayHasher.putLong(l);
|
||||
}
|
||||
arrayHasher.putByte(TAG_LONG_ARRAY_END);
|
||||
return arrayHasher.hash();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,376 @@
|
|||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.hashing;
|
||||
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.google.common.hash.HashCode;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.nbt.NbtList;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.geysermc.geyser.inventory.item.DyeColor;
|
||||
import org.geysermc.geyser.item.components.Rarity;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.mcprotocollib.auth.GameProfile;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.EquipmentSlot;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.GlobalPos;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.Filterable;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ItemAttributeModifiers;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.Unit;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* Encodes an object into a {@link HashCode} using a {@link MinecraftHashEncoder}.
|
||||
*
|
||||
* <p>Hashers have been implemented for common types, such as all Java primitives, units (encodes an empty map), NBT maps and lists,
|
||||
* {@link Vector3i} positions, {@link Key} resource locations and {@code #}-prefixed tags, UUIDs, and more types. Furthermore, in {@link RegistryHasher}
|
||||
* more hashers can be found for more specific for Minecraft types, and {@link ComponentHasher#COMPONENT} hashes {@link net.kyori.adventure.text.Component}s.</p>
|
||||
*
|
||||
* <p>Most hashers are not created by implementing them directly, rather, they are built on top of other hashers, using various methods to manipulate and map them.
|
||||
* Here are some commonly used ones:</p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link MinecraftHasher#list()} creates a hasher that hashes a list of the objects.</li>
|
||||
* <li>{@link MinecraftHasher#cast(Function)} and {@link MinecraftHasher#sessionCast(BiFunction)} create a new hasher that delegates to this hasher with a converter function.</li>
|
||||
* <li>{@link MinecraftHasher#filterable()} creates a hasher that hashes a {@link Filterable} instance of the object.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>On top of these there are more methods, as well as static helper methods in this class, to create hashers.
|
||||
* One worth pointing out is {@link MinecraftHasher#mapBuilder(MapBuilder)}, which hashes an object into a map-like structure. Read the documentation there, on {@link MapBuilder}, and on {@link MapHasher} on how to use it.</p>
|
||||
*
|
||||
* <p>As of right now, hashers are used to create hash codes for data components, which Minecraft requires to be sent in inventory transactions. You'll find all the hashers for data components in
|
||||
* {@link DataComponentHashers}. In the future, hashers may be used elsewhere as well. If necessary, this system can even be refactored to write to different data structures, such as NBT or JSON files, as well.</p>
|
||||
*
|
||||
* <p>When creating new hashers, please be sure to put them in the proper place:</p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>Hashers that hash very generic types, or types that are used broadly across Minecraft (like key, UUID, game profile, etc.) belong here in {@link MinecraftHasher}.</li>
|
||||
* <li>Hashers that hash more specific types, are more complicated, or depend on a hasher in {@link RegistryHasher}, belong in {@link RegistryHasher}.</li>
|
||||
* <li>Hashers that hash components, and are used nowhere else, belong in {@link DataComponentHashers}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param <Type> the type this hasher hashes.
|
||||
*/
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
@FunctionalInterface
|
||||
public interface MinecraftHasher<Type> {
|
||||
|
||||
MinecraftHasher<Unit> UNIT = (unit, encoder) -> encoder.emptyMap();
|
||||
|
||||
MinecraftHasher<Byte> BYTE = (b, encoder) -> encoder.number(b);
|
||||
|
||||
MinecraftHasher<Short> SHORT = (s, encoder) -> encoder.number(s);
|
||||
|
||||
MinecraftHasher<Integer> INT = (i, encoder) -> encoder.number(i);
|
||||
|
||||
MinecraftHasher<Long> LONG = (l, encoder) -> encoder.number(l);
|
||||
|
||||
MinecraftHasher<Float> FLOAT = (f, encoder) -> encoder.number(f);
|
||||
|
||||
MinecraftHasher<Double> DOUBLE = (d, encoder) -> encoder.number(d);
|
||||
|
||||
MinecraftHasher<String> STRING = (s, encoder) -> encoder.string(s);
|
||||
|
||||
MinecraftHasher<Boolean> BOOL = (b, encoder) -> encoder.bool(b);
|
||||
|
||||
MinecraftHasher<IntStream> INT_ARRAY = (ints, encoder) -> encoder.intArray(ints.toArray());
|
||||
|
||||
MinecraftHasher<NbtMap> NBT_MAP = (map, encoder) -> encoder.nbtMap(map);
|
||||
|
||||
MinecraftHasher<NbtList<?>> NBT_LIST = (list, encoder) -> encoder.nbtList(list);
|
||||
|
||||
MinecraftHasher<Vector3i> POS = INT_ARRAY.cast(pos -> IntStream.of(pos.getX(), pos.getY(), pos.getZ()));
|
||||
|
||||
MinecraftHasher<Key> KEY = STRING.cast(Key::asString);
|
||||
|
||||
MinecraftHasher<Key> TAG = STRING.cast(key -> '#' + key.asString());
|
||||
|
||||
MinecraftHasher<Key> KEY_REMOVAL = STRING.cast(key -> '!' + key.asString());
|
||||
|
||||
MinecraftHasher<UUID> UUID = INT_ARRAY.cast(uuid -> {
|
||||
long mostSignificant = uuid.getMostSignificantBits();
|
||||
long leastSignificant = uuid.getLeastSignificantBits();
|
||||
return IntStream.of((int) (mostSignificant >> 32), (int) mostSignificant, (int) (leastSignificant >> 32), (int) leastSignificant);
|
||||
}); // TODO test
|
||||
|
||||
MinecraftHasher<GameProfile.Property> GAME_PROFILE_PROPERTY = mapBuilder(builder -> builder
|
||||
.accept("name", STRING, GameProfile.Property::getName)
|
||||
.accept("value", STRING, GameProfile.Property::getValue)
|
||||
.optionalNullable("signature", STRING, GameProfile.Property::getSignature));
|
||||
|
||||
MinecraftHasher<GameProfile> GAME_PROFILE = mapBuilder(builder -> builder
|
||||
.optionalNullable("name", STRING, GameProfile::getName)
|
||||
.optionalNullable("id", UUID, GameProfile::getId)
|
||||
.optionalList("properties", GAME_PROFILE_PROPERTY, GameProfile::getProperties));
|
||||
|
||||
MinecraftHasher<Integer> RARITY = fromIdEnum(Rarity.values(), Rarity::getName);
|
||||
|
||||
MinecraftHasher<Integer> DYE_COLOR = fromIdEnum(DyeColor.values(), DyeColor::getJavaIdentifier);
|
||||
|
||||
MinecraftHasher<EquipmentSlot> EQUIPMENT_SLOT = fromEnum(slot -> switch (slot) {
|
||||
case MAIN_HAND -> "mainhand";
|
||||
case OFF_HAND -> "offhand";
|
||||
case BOOTS -> "feet";
|
||||
case LEGGINGS -> "legs";
|
||||
case CHESTPLATE -> "chest";
|
||||
case HELMET -> "head";
|
||||
case BODY -> "body";
|
||||
case SADDLE -> "saddle";
|
||||
});
|
||||
|
||||
MinecraftHasher<ItemAttributeModifiers.EquipmentSlotGroup> EQUIPMENT_SLOT_GROUP = fromEnum(group -> switch (group) {
|
||||
case ANY -> "any";
|
||||
case MAIN_HAND -> "mainhand";
|
||||
case OFF_HAND -> "offhand";
|
||||
case HAND -> "hand";
|
||||
case FEET -> "feet";
|
||||
case LEGS -> "legs";
|
||||
case CHEST -> "chest";
|
||||
case HEAD -> "head";
|
||||
case ARMOR -> "armor";
|
||||
case BODY -> "body";
|
||||
case SADDLE -> "saddle";
|
||||
});
|
||||
|
||||
MinecraftHasher<GlobalPos> GLOBAL_POS = mapBuilder(builder -> builder
|
||||
.accept("dimension", KEY, GlobalPos::getDimension)
|
||||
.accept("pos", POS, GlobalPos::getPosition));
|
||||
|
||||
HashCode hash(Type value, MinecraftHashEncoder encoder);
|
||||
|
||||
/**
|
||||
* Creates a hasher that hashes a list of objects this hasher hashes.
|
||||
*/
|
||||
default MinecraftHasher<List<Type>> list() {
|
||||
return (list, encoder) -> encoder.list(list.stream().map(element -> hash(element, encoder)).toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* "Casts" this hasher to another hash a different object, with a converter method. The returned hasher takes a {@link Casted}, converts it to a {@link Type} using the {@code converter}, and then hashes it.
|
||||
*
|
||||
* <p>If a {@link GeyserSession} object is needed for conversion, use {@link MinecraftHasher#sessionCast(BiFunction)}.</p>
|
||||
*
|
||||
* @param converter the converter function that converts a {@link Casted} into a {@link Type}.
|
||||
* @param <Casted> the type of the new hasher.
|
||||
* @see MinecraftHasher#sessionCast(BiFunction)
|
||||
*/
|
||||
default <Casted> MinecraftHasher<Casted> cast(Function<Casted, Type> converter) {
|
||||
return (value, encoder) -> hash(converter.apply(value), encoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link MinecraftHasher#cast(Function)}, but has access to {@link GeyserSession}.
|
||||
*
|
||||
* @param converter the converter function.
|
||||
* @param <Casted> the type of the new hasher.
|
||||
* @see MinecraftHasher#cast(Function)
|
||||
*/
|
||||
default <Casted> MinecraftHasher<Casted> sessionCast(BiFunction<GeyserSession, Casted, Type> converter) {
|
||||
return (value, encoder) -> hash(converter.apply(encoder.session(), value), encoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to {@link MinecraftHasher#dispatch(String, Function, Function)}, uses {@code "type"} as the {@code typeKey}.
|
||||
*
|
||||
* @see MinecraftHasher#dispatch(String, Function, Function)
|
||||
*/
|
||||
default <Dispatched> MinecraftHasher<Dispatched> dispatch(Function<Dispatched, Type> typeExtractor, Function<Type, MapBuilder<Dispatched>> hashDispatch) {
|
||||
return dispatch("type", typeExtractor, hashDispatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a hasher that dispatches a {@link Type} from a {@link Dispatched} using {@code typeExtractor}, puts this in the {@code typeKey} key in a map, and uses a
|
||||
* {@link MapBuilder} provided by {@code mapDispatch} to build the rest of the map.
|
||||
*
|
||||
* <p>This can be used to create hashers that hash an abstract type or interface into a map with different keys depending on the type.</p>
|
||||
*
|
||||
* @param typeKey the key to store the {@link Type} in.
|
||||
* @param typeExtractor the function that extracts a {@link Type} from a {@link Dispatched}.
|
||||
* @param mapDispatch the function that provides a {@link MapBuilder} based on a {@link Type}.
|
||||
* @param <Dispatched> the type of the new hasher.
|
||||
*/
|
||||
default <Dispatched> MinecraftHasher<Dispatched> dispatch(String typeKey, Function<Dispatched, Type> typeExtractor, Function<Type, MapBuilder<Dispatched>> mapDispatch) {
|
||||
return mapBuilder(builder -> builder
|
||||
.accept(typeKey, this, typeExtractor)
|
||||
.accept(typeExtractor, mapDispatch));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a hasher that wraps the {@link Type} in a {@link Filterable}.
|
||||
*/
|
||||
default MinecraftHasher<Filterable<Type>> filterable() {
|
||||
return mapBuilder(builder -> builder
|
||||
.accept("raw", this, Filterable::getRaw)
|
||||
.optionalNullable("filtered", this, Filterable::getOptional));
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily-initialises the given hasher using {@link Suppliers#memoize(com.google.common.base.Supplier)}.
|
||||
*/
|
||||
static <Type> MinecraftHasher<Type> lazyInitialize(Supplier<MinecraftHasher<Type>> hasher) {
|
||||
Supplier<MinecraftHasher<Type>> memoized = Suppliers.memoize(hasher::get);
|
||||
return (value, encoder) -> memoized.get().hash(value, encoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses {@link Enum#name()} (lowercased) as {@code toName} function in {@link MinecraftHasher#fromIdEnum(Enum[], Function)}.
|
||||
*
|
||||
* <p>Please be aware that you are using literal enum constants as string values here, meaning that if there is a typo in a constant, or a constant changes name, things
|
||||
* may break. Use cautiously.</p>
|
||||
*
|
||||
* @param values the array of {@link EnumConstant}s.
|
||||
* @param <EnumConstant> the enum.
|
||||
* @see MinecraftHasher#fromIdEnum(Enum[], Function)
|
||||
*/
|
||||
static <EnumConstant extends Enum<EnumConstant>> MinecraftHasher<Integer> fromIdEnum(EnumConstant[] values) {
|
||||
return fromIdEnum(values, constant -> constant.name().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a hasher that looks up an int in the array of {@link EnumConstant}s, and uses {@code toName} to turn the constant into a string, which it then hashes.
|
||||
*
|
||||
* @param values the array of {@link EnumConstant}s.
|
||||
* @param toName the function that turns a {@link EnumConstant} into a string.
|
||||
* @param <EnumConstant> the enum.
|
||||
* @see MinecraftHasher#fromIdEnum(Enum[])
|
||||
*/
|
||||
static <EnumConstant extends Enum<EnumConstant>> MinecraftHasher<Integer> fromIdEnum(EnumConstant[] values, Function<EnumConstant, String> toName) {
|
||||
return STRING.cast(id -> toName.apply(values[id]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses {@link Enum#name()} (lowercased) as {@code toName} function in {@link MinecraftHasher#fromEnum(Function)}.
|
||||
*
|
||||
* <p>Please be aware that you are using literal enum constants as string values here, meaning that if there is a typo in a constant, or a constant changes name, things
|
||||
* may break. Use cautiously.</p>
|
||||
*
|
||||
* @param <EnumConstant> the enum.
|
||||
* @see MinecraftHasher#fromEnum(Function)
|
||||
*/
|
||||
static <EnumConstant extends Enum<EnumConstant>> MinecraftHasher<EnumConstant> fromEnum() {
|
||||
return fromEnum(constant -> constant.name().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a hasher for {@link EnumConstant}s that uses {@code toName} to turn a {@link EnumConstant} into a string, which it then hashes.
|
||||
*
|
||||
* @param toName the function that turns a {@link EnumConstant} into a string.
|
||||
* @param <EnumConstant> the enum.
|
||||
* @see MinecraftHasher#fromEnum()
|
||||
*/
|
||||
static <EnumConstant extends Enum<EnumConstant>> MinecraftHasher<EnumConstant> fromEnum(Function<EnumConstant, String> toName) {
|
||||
return STRING.cast(toName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a hasher that uses a {@link MapBuilder}, which uses a {@link MapHasher} to hash a {@link Type} into a map.
|
||||
*
|
||||
* @param builder the builder that creates a map from a {@link Type}.
|
||||
* @param <Type> the type to hash.
|
||||
*/
|
||||
static <Type> MinecraftHasher<Type> mapBuilder(MapBuilder<Type> builder) {
|
||||
return (value, encoder) -> builder.apply(new MapHasher<>(value, encoder)).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a hasher that uses {@link MinecraftHasher#mapSet(MinecraftHasher, MinecraftHasher)} to hash {@link K} keys and {@link V} values into a map,
|
||||
* using the {@code keyHasher} and {@code valueHasher} respectively.
|
||||
*
|
||||
* @param keyHasher the hasher that hashes objects of type {@link K}.
|
||||
* @param valueHasher the hasher that hashes objects of type {@link V}.
|
||||
* @param <K> the key type.
|
||||
* @param <V> the value type.
|
||||
* @see MinecraftHasher#mapSet(MinecraftHasher, MinecraftHasher)
|
||||
*/
|
||||
static <K, V> MinecraftHasher<Map<K, V>> map(MinecraftHasher<K> keyHasher, MinecraftHasher<V> valueHasher) {
|
||||
return MinecraftHasher.<Map.Entry<K, V>>mapSet(keyHasher.cast(Map.Entry::getKey), valueHasher.cast(Map.Entry::getValue)).cast(Map::entrySet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a hasher that hashes a set of {@link Type} entries into a map of keys and values, using {@code keyHasher} and {@code valueHasher} respectively.
|
||||
*
|
||||
* <p>Note that the key hasher is usually expected to hash into a string, but this is not necessarily a requirement. It may be once different encoders are used to encode to NBT or JSON,
|
||||
* but this is not the case yet.</p>
|
||||
*
|
||||
* @param keyHasher the hasher that hashes a {@link Type} into a key to be used in the map.
|
||||
* @param valueHasher the hasher that hashes a {@link Type} into a value to be used in the map.
|
||||
* @param <Type> the entry type.
|
||||
* @see MinecraftHasher#map(MinecraftHasher, MinecraftHasher)
|
||||
*/
|
||||
static <Type> MinecraftHasher<Collection<Type>> mapSet(MinecraftHasher<Type> keyHasher, MinecraftHasher<Type> valueHasher) {
|
||||
return (set, encoder) -> encoder.map(set.stream()
|
||||
.collect(Collectors.toMap(value -> keyHasher.hash(value, encoder), value -> valueHasher.hash(value, encoder))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a hasher that hashes {@link Type} using either {@code firstHasher} to hash {@link First} (obtained using {@code firstExtractor}), or uses {@code secondHasher}
|
||||
* to hash {@link Second} (obtained via {@code secondExtractor}).
|
||||
*
|
||||
* <p>Specifically, the hasher first tries to use {@code firstExtractor} to obtain a {@link First} from {@link Type}. If this returns a not-null value, {@code firstHasher} is used to hash the value.</p>
|
||||
*
|
||||
* <p>If the returned value is null, then {@code secondExtractor} is used to obtain a {@link Second} from {@link Type}. This must never be null. {@code secondHasher} is then used to hash the value.</p>
|
||||
*
|
||||
* <p>Note: {@code secondExtractor} must never return null if {@code firstExtractor} does!</p>
|
||||
*
|
||||
* @param firstHasher the hasher used to hash {@link First}.
|
||||
* @param firstExtractor the function used to obtain a {@link First} from a {@link Type}.
|
||||
* @param secondHasher the hasher used to hash {@link Second}.
|
||||
* @param secondExtractor the function used to obtain a {@link Second} from a {@link Type}.
|
||||
* @param <Type> the type to hash.
|
||||
* @param <First> the first either type to hash.
|
||||
* @param <Second> the second either type to hash.
|
||||
*/
|
||||
static <Type, First, Second> MinecraftHasher<Type> either(MinecraftHasher<First> firstHasher, Function<Type, First> firstExtractor,
|
||||
MinecraftHasher<Second> secondHasher, Function<Type, Second> secondExtractor) {
|
||||
return (value, encoder) -> {
|
||||
First first = firstExtractor.apply(value);
|
||||
if (first != null) {
|
||||
return firstHasher.hash(first, encoder);
|
||||
}
|
||||
return secondHasher.hash(secondExtractor.apply(value), encoder);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a hasher that dispatches a {@link MinecraftHasher} from a {@link Type}, and then uses that hasher to encode {@link Type}.
|
||||
*
|
||||
* @param hashDispatch the function that returns a {@link MinecraftHasher} from a {@link Type}.
|
||||
* @param <Type> the type to hash.
|
||||
*/
|
||||
static <Type> MinecraftHasher<Type> dispatch(Function<Type, MinecraftHasher<Type>> hashDispatch) {
|
||||
return (value, encoder) -> hashDispatch.apply(value).hash(value, encoder);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,470 @@
|
|||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.hashing;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.geysermc.geyser.inventory.item.Potion;
|
||||
import org.geysermc.geyser.item.hashing.data.ConsumeEffectType;
|
||||
import org.geysermc.geyser.item.hashing.data.FireworkExplosionShape;
|
||||
import org.geysermc.geyser.item.hashing.data.ItemContainerSlot;
|
||||
import org.geysermc.geyser.item.hashing.data.entity.AxolotlVariant;
|
||||
import org.geysermc.geyser.item.hashing.data.entity.FoxVariant;
|
||||
import org.geysermc.geyser.item.hashing.data.entity.HorseVariant;
|
||||
import org.geysermc.geyser.item.hashing.data.entity.LlamaVariant;
|
||||
import org.geysermc.geyser.item.hashing.data.entity.MooshroomVariant;
|
||||
import org.geysermc.geyser.item.hashing.data.entity.ParrotVariant;
|
||||
import org.geysermc.geyser.item.hashing.data.entity.RabbitVariant;
|
||||
import org.geysermc.geyser.item.hashing.data.entity.SalmonVariant;
|
||||
import org.geysermc.geyser.item.hashing.data.entity.TropicalFishPattern;
|
||||
import org.geysermc.geyser.item.hashing.data.entity.VillagerVariant;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistryKey;
|
||||
import org.geysermc.geyser.util.MinecraftKey;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.Holder;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.Effect;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.AttributeType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.ModifierOperation;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.PaintingVariant;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.AdventureModePredicate;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ArmorTrim;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.BannerPatternLayer;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.BeehiveOccupant;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.BlocksAttacks;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ConsumeEffect;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponent;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.Fireworks;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.HolderSet;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.InstrumentComponent;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ItemAttributeModifiers;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ItemEnchantments;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.JukeboxPlayable;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.MobEffectDetails;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.MobEffectInstance;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ProvidesTrimMaterial;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.SuspiciousStewEffect;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ToolData;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.Unit;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.sound.BuiltinSound;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.sound.CustomSound;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.sound.Sound;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* {@link RegistryHasher}s are hashers that hash a network integer ID to a namespaced identifier. {@link RegistryHasher}s can be created using static utility methods in this class, and all registry hashers should be kept in here.
|
||||
*
|
||||
* <p>The {@link DirectType} parameter is only used for registry hashers that are able to encode {@link Holder}s, and must be left as a {@code ?} if this functionality is not in use. This makes it clear the hasher is not
|
||||
* supposed to be able to encode holders.</p>
|
||||
*
|
||||
* <p>To create a hasher that can encode a {@link Holder}, a direct hasher should be created that hashes a {@link DirectType} (in case of a custom holder), and {@link RegistryHasher#registry(JavaRegistryKey, MinecraftHasher)}
|
||||
* should be used to create the registry hasher. {@link RegistryHasher#holder()} can then be used to obtain a hasher that encodes a holder of {@link DirectType}.</p>
|
||||
*
|
||||
* <p>Along with {@link RegistryHasher}s, this class also contains a bunch of hashers for various Minecraft objects. For organisational purposes, these are grouped in various sections with comments.</p>
|
||||
*
|
||||
* @param <DirectType> the type this hasher hashes. Only used for registry hashers that can hash holders.
|
||||
*/
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public interface RegistryHasher<DirectType> extends MinecraftHasher<Integer> {
|
||||
|
||||
// Java registries
|
||||
|
||||
RegistryHasher<?> BLOCK = registry(JavaRegistries.BLOCK);
|
||||
|
||||
RegistryHasher<?> ITEM = registry(JavaRegistries.ITEM);
|
||||
|
||||
RegistryHasher<?> ENTITY_TYPE = enumIdRegistry(EntityType.values());
|
||||
|
||||
RegistryHasher<?> ENCHANTMENT = registry(JavaRegistries.ENCHANTMENT);
|
||||
|
||||
RegistryHasher<?> ATTRIBUTE = enumIdRegistry(AttributeType.Builtin.values(), AttributeType::getIdentifier);
|
||||
|
||||
MinecraftHasher<DataComponentType<?>> DATA_COMPONENT_TYPE = KEY.cast(DataComponentType::getKey);
|
||||
|
||||
// Mob effects can both be an enum constant or ID in MCPL.
|
||||
MinecraftHasher<Effect> EFFECT = enumRegistry();
|
||||
|
||||
RegistryHasher<?> EFFECT_ID = enumIdRegistry(Effect.values());
|
||||
|
||||
RegistryHasher<?> POTION = enumIdRegistry(Potion.values());
|
||||
|
||||
RegistryHasher<?> VILLAGER_TYPE = enumIdRegistry(VillagerVariant.values());
|
||||
|
||||
// Java data-driven registries
|
||||
|
||||
MinecraftHasher<BuiltinSound> BUILTIN_SOUND = KEY.cast(sound -> MinecraftKey.key(sound.getName()));
|
||||
|
||||
MinecraftHasher<CustomSound> CUSTOM_SOUND = MinecraftHasher.mapBuilder(builder -> builder
|
||||
.accept("sound_id", KEY, sound -> MinecraftKey.key(sound.getName()))
|
||||
.optional("range", FLOAT, CustomSound::getRange, 16.0F));
|
||||
|
||||
MinecraftHasher<Sound> SOUND_EVENT = (sound, encoder) -> {
|
||||
if (sound instanceof BuiltinSound builtin) {
|
||||
return BUILTIN_SOUND.hash(builtin, encoder);
|
||||
}
|
||||
return CUSTOM_SOUND.hash((CustomSound) sound, encoder);
|
||||
};
|
||||
|
||||
RegistryHasher<?> DAMAGE_TYPE = registry(JavaRegistries.DAMAGE_TYPE);
|
||||
|
||||
MinecraftHasher<InstrumentComponent.Instrument> DIRECT_INSTRUMENT = MinecraftHasher.mapBuilder(builder -> builder
|
||||
.accept("sound_event", SOUND_EVENT, InstrumentComponent.Instrument::soundEvent)
|
||||
.accept("use_duration", FLOAT, InstrumentComponent.Instrument::useDuration)
|
||||
.accept("range", FLOAT, InstrumentComponent.Instrument::range)
|
||||
.accept("description", ComponentHasher.COMPONENT, InstrumentComponent.Instrument::description));
|
||||
|
||||
RegistryHasher<InstrumentComponent.Instrument> INSTRUMENT = registry(JavaRegistries.INSTRUMENT, DIRECT_INSTRUMENT);
|
||||
|
||||
MinecraftHasher<ArmorTrim.TrimMaterial> DIRECT_TRIM_MATERIAL = MinecraftHasher.mapBuilder(builder -> builder
|
||||
.accept("asset_name", MinecraftHasher.STRING, ArmorTrim.TrimMaterial::assetBase)
|
||||
.optional("override_armor_assets", MinecraftHasher.map(KEY, STRING), ArmorTrim.TrimMaterial::assetOverrides, Map.of())
|
||||
.accept("description", ComponentHasher.COMPONENT, ArmorTrim.TrimMaterial::description));
|
||||
|
||||
RegistryHasher<ArmorTrim.TrimMaterial> TRIM_MATERIAL = registry(JavaRegistries.TRIM_MATERIAL, DIRECT_TRIM_MATERIAL);
|
||||
|
||||
MinecraftHasher<ArmorTrim.TrimPattern> DIRECT_TRIM_PATTERN = MinecraftHasher.mapBuilder(builder -> builder
|
||||
.accept("asset_id", KEY, ArmorTrim.TrimPattern::assetId)
|
||||
.accept("description", ComponentHasher.COMPONENT, ArmorTrim.TrimPattern::description)
|
||||
.accept("decal", BOOL, ArmorTrim.TrimPattern::decal));
|
||||
|
||||
RegistryHasher<ArmorTrim.TrimPattern> TRIM_PATTERN = registry(JavaRegistries.TRIM_PATTERN, DIRECT_TRIM_PATTERN);
|
||||
|
||||
MinecraftHasher<JukeboxPlayable.JukeboxSong> DIRECT_JUKEBOX_SONG = MinecraftHasher.mapBuilder(builder -> builder
|
||||
.accept("sound_event", SOUND_EVENT, JukeboxPlayable.JukeboxSong::soundEvent)
|
||||
.accept("description", ComponentHasher.COMPONENT, JukeboxPlayable.JukeboxSong::description)
|
||||
.accept("length_in_seconds", FLOAT, JukeboxPlayable.JukeboxSong::lengthInSeconds)
|
||||
.accept("comparator_output", INT, JukeboxPlayable.JukeboxSong::comparatorOutput));
|
||||
|
||||
RegistryHasher<JukeboxPlayable.JukeboxSong> JUKEBOX_SONG = registry(JavaRegistries.JUKEBOX_SONG, DIRECT_JUKEBOX_SONG);
|
||||
|
||||
MinecraftHasher<BannerPatternLayer.BannerPattern> DIRECT_BANNER_PATTERN = MinecraftHasher.mapBuilder(builder -> builder
|
||||
.accept("asset_id", KEY, BannerPatternLayer.BannerPattern::getAssetId)
|
||||
.accept("translation_key", STRING, BannerPatternLayer.BannerPattern::getTranslationKey));
|
||||
|
||||
RegistryHasher<BannerPatternLayer.BannerPattern> BANNER_PATTERN = registry(JavaRegistries.BANNER_PATTERN, DIRECT_BANNER_PATTERN);
|
||||
|
||||
RegistryHasher<?> WOLF_VARIANT = registry(JavaRegistries.WOLF_VARIANT);
|
||||
|
||||
RegistryHasher<?> WOLF_SOUND_VARIANT = registry(JavaRegistries.WOLF_SOUND_VARIANT);
|
||||
|
||||
RegistryHasher<?> PIG_VARIANT = registry(JavaRegistries.PIG_VARIANT);
|
||||
|
||||
RegistryHasher<?> COW_VARIANT = registry(JavaRegistries.COW_VARIANT);
|
||||
|
||||
RegistryHasher<?> FROG_VARIANT = registry(JavaRegistries.FROG_VARIANT);
|
||||
|
||||
MinecraftHasher<PaintingVariant> DIRECT_PAINTING_VARIANT = MinecraftHasher.mapBuilder(builder -> builder
|
||||
.accept("width", INT, PaintingVariant::width)
|
||||
.accept("height", INT, PaintingVariant::height)
|
||||
.accept("asset_id", KEY, PaintingVariant::assetId)
|
||||
.optionalNullable("title", ComponentHasher.COMPONENT, PaintingVariant::title)
|
||||
.optionalNullable("author", ComponentHasher.COMPONENT, PaintingVariant::author));
|
||||
|
||||
RegistryHasher<PaintingVariant> PAINTING_VARIANT = registry(JavaRegistries.PAINTING_VARIANT, DIRECT_PAINTING_VARIANT);
|
||||
|
||||
RegistryHasher<?> CAT_VARIANT = registry(JavaRegistries.CAT_VARIANT);
|
||||
|
||||
// Entity variants
|
||||
// These are all not registries on Java, meaning they serialise as just literal strings, not namespaced IDs
|
||||
|
||||
MinecraftHasher<Integer> FOX_VARIANT = MinecraftHasher.fromIdEnum(FoxVariant.values());
|
||||
|
||||
MinecraftHasher<Integer> SALMON_VARIANT = MinecraftHasher.fromIdEnum(SalmonVariant.values());
|
||||
|
||||
MinecraftHasher<Integer> PARROT_VARIANT = MinecraftHasher.fromIdEnum(ParrotVariant.values());
|
||||
|
||||
MinecraftHasher<Integer> TROPICAL_FISH_PATTERN = MinecraftHasher.<TropicalFishPattern>fromEnum().cast(TropicalFishPattern::fromPackedId);
|
||||
|
||||
MinecraftHasher<Integer> MOOSHROOM_VARIANT = MinecraftHasher.fromIdEnum(MooshroomVariant.values());
|
||||
|
||||
MinecraftHasher<Integer> RABBIT_VARIANT = MinecraftHasher.<RabbitVariant>fromEnum().cast(RabbitVariant::fromId);
|
||||
|
||||
MinecraftHasher<Integer> HORSE_VARIANT = MinecraftHasher.fromIdEnum(HorseVariant.values());
|
||||
|
||||
MinecraftHasher<Integer> LLAMA_VARIANT = MinecraftHasher.fromIdEnum(LlamaVariant.values());
|
||||
|
||||
MinecraftHasher<Integer> AXOLOTL_VARIANT = MinecraftHasher.fromIdEnum(AxolotlVariant.values());
|
||||
|
||||
// Widely used Minecraft types
|
||||
|
||||
MinecraftHasher<DataComponent<?, ?>> DATA_COMPONENT_KEY = MinecraftHasher.either(KEY,
|
||||
component -> component.getValue() == null ? null : component.getType().getKey(), KEY_REMOVAL, component -> component.getType().getKey());
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"}) // Java generics :(
|
||||
MinecraftHasher<DataComponent<?, ?>> DATA_COMPONENT_VALUE = (component, encoder) -> {
|
||||
if (component.getValue() == null) {
|
||||
return UNIT.hash(Unit.INSTANCE, encoder);
|
||||
}
|
||||
MinecraftHasher hasher = DataComponentHashers.hasher(component.getType());
|
||||
return hasher.hash(component.getValue(), encoder);
|
||||
};
|
||||
|
||||
MinecraftHasher<DataComponents> DATA_COMPONENTS = MinecraftHasher.mapSet(DATA_COMPONENT_KEY, DATA_COMPONENT_VALUE).cast(components -> components.getDataComponents().values());
|
||||
|
||||
MinecraftHasher<ItemStack> ITEM_STACK = MinecraftHasher.mapBuilder(builder -> builder
|
||||
.accept("id", ITEM, ItemStack::getId)
|
||||
.accept("count", INT, ItemStack::getAmount)
|
||||
.optionalNullable("components", DATA_COMPONENTS, ItemStack::getDataComponentsPatch));
|
||||
|
||||
// Encoding of hidden effects is unfortunately not possible
|
||||
MapBuilder<MobEffectDetails> MOB_EFFECT_DETAILS = builder -> builder
|
||||
.optional("amplifier", BYTE, instance -> (byte) instance.getAmplifier(), (byte) 0)
|
||||
.optional("duration", INT, MobEffectDetails::getDuration, 0)
|
||||
.optional("ambient", BOOL, MobEffectDetails::isAmbient, false)
|
||||
.optional("show_particles", BOOL, MobEffectDetails::isShowParticles, true)
|
||||
.accept("show_icon", BOOL, MobEffectDetails::isShowIcon); // Yes, this is not an optional. I checked. Maybe it will be in the future and break everything!
|
||||
|
||||
MinecraftHasher<MobEffectInstance> MOB_EFFECT_INSTANCE = MinecraftHasher.mapBuilder(builder -> builder
|
||||
.accept("id", RegistryHasher.EFFECT, MobEffectInstance::getEffect)
|
||||
.accept(MOB_EFFECT_DETAILS, MobEffectInstance::getDetails));
|
||||
|
||||
MinecraftHasher<ModifierOperation> ATTRIBUTE_MODIFIER_OPERATION = MinecraftHasher.fromEnum(operation -> switch (operation) {
|
||||
case ADD -> "add_value";
|
||||
case ADD_MULTIPLIED_BASE -> "add_multiplied_base";
|
||||
case ADD_MULTIPLIED_TOTAL -> "add_multiplied_total";
|
||||
});
|
||||
|
||||
// Component-specific types
|
||||
|
||||
MinecraftHasher<ItemEnchantments> ITEM_ENCHANTMENTS = MinecraftHasher.map(RegistryHasher.ENCHANTMENT, MinecraftHasher.INT).cast(ItemEnchantments::getEnchantments);
|
||||
|
||||
MinecraftHasher<ItemContainerSlot> CONTAINER_SLOT = MinecraftHasher.mapBuilder(builder -> builder
|
||||
.accept("slot", INT, ItemContainerSlot::index)
|
||||
.accept("item", ITEM_STACK, ItemContainerSlot::item));
|
||||
|
||||
MinecraftHasher<List<ItemStack>> ITEM_CONTAINER_CONTENTS = CONTAINER_SLOT.list().cast(stacks -> {
|
||||
List<ItemContainerSlot> slots = new ArrayList<>();
|
||||
for (int i = 0; i < stacks.size(); i++) {
|
||||
ItemStack stack = stacks.get(i);
|
||||
if (stack != null) {
|
||||
slots.add(new ItemContainerSlot(i, stacks.get(i)));
|
||||
}
|
||||
}
|
||||
return slots;
|
||||
});
|
||||
|
||||
MinecraftHasher<AdventureModePredicate.BlockPredicate> BLOCK_PREDICATE = MinecraftHasher.mapBuilder(builder -> builder
|
||||
.optionalNullable("blocks", BLOCK.holderSet(), AdventureModePredicate.BlockPredicate::getBlocks)
|
||||
.optionalNullable("nbt", NBT_MAP, AdventureModePredicate.BlockPredicate::getNbt)); // Property and data component matchers are, unfortunately, too complicated to include here
|
||||
|
||||
// Encode as a single element if the list only has one element
|
||||
MinecraftHasher<AdventureModePredicate> ADVENTURE_MODE_PREDICATE = MinecraftHasher.either(BLOCK_PREDICATE,
|
||||
predicate -> predicate.getPredicates().size() == 1 ? predicate.getPredicates().get(0) : null, BLOCK_PREDICATE.list(), AdventureModePredicate::getPredicates);
|
||||
|
||||
MinecraftHasher<ItemAttributeModifiers.Entry> ATTRIBUTE_MODIFIER_ENTRY = MinecraftHasher.mapBuilder(builder -> builder
|
||||
.accept("type", RegistryHasher.ATTRIBUTE, ItemAttributeModifiers.Entry::getAttribute)
|
||||
.accept("id", KEY, entry -> entry.getModifier().getId())
|
||||
.accept("amount", DOUBLE, entry -> entry.getModifier().getAmount())
|
||||
.accept("operation", ATTRIBUTE_MODIFIER_OPERATION, entry -> entry.getModifier().getOperation())
|
||||
.optional("slot", EQUIPMENT_SLOT_GROUP, ItemAttributeModifiers.Entry::getSlot, ItemAttributeModifiers.EquipmentSlotGroup.ANY));
|
||||
|
||||
MinecraftHasher<Consumable.ItemUseAnimation> ITEM_USE_ANIMATION = MinecraftHasher.fromEnum();
|
||||
|
||||
MinecraftHasher<ConsumeEffectType> CONSUME_EFFECT_TYPE = enumRegistry();
|
||||
|
||||
MinecraftHasher<ConsumeEffect> CONSUME_EFFECT = CONSUME_EFFECT_TYPE.dispatch(ConsumeEffectType::fromEffect, type -> type.getBuilder().cast());
|
||||
|
||||
MinecraftHasher<SuspiciousStewEffect> SUSPICIOUS_STEW_EFFECT = MinecraftHasher.mapBuilder(builder -> builder
|
||||
.accept("id", EFFECT_ID, SuspiciousStewEffect::getMobEffectId)
|
||||
.optional("duration", INT, SuspiciousStewEffect::getDuration, 160));
|
||||
|
||||
MinecraftHasher<InstrumentComponent> INSTRUMENT_COMPONENT = MinecraftHasher.either(INSTRUMENT.holder(), InstrumentComponent::instrumentHolder, KEY, InstrumentComponent::instrumentLocation);
|
||||
|
||||
MinecraftHasher<ToolData.Rule> TOOL_RULE = MinecraftHasher.mapBuilder(builder -> builder
|
||||
.accept("blocks", RegistryHasher.BLOCK.holderSet(), ToolData.Rule::getBlocks)
|
||||
.optionalNullable("speed", MinecraftHasher.FLOAT, ToolData.Rule::getSpeed)
|
||||
.optionalNullable("correct_for_drops", MinecraftHasher.BOOL, ToolData.Rule::getCorrectForDrops));
|
||||
|
||||
MinecraftHasher<BlocksAttacks.DamageReduction> BLOCKS_ATTACKS_DAMAGE_REDUCTION = MinecraftHasher.mapBuilder(builder -> builder
|
||||
.optional("horizontal_blocking_angle", FLOAT, BlocksAttacks.DamageReduction::horizontalBlockingAngle, 90.0F)
|
||||
.optionalNullable("type", DAMAGE_TYPE.holderSet(), BlocksAttacks.DamageReduction::type)
|
||||
.accept("base", FLOAT, BlocksAttacks.DamageReduction::base)
|
||||
.accept("factor", FLOAT, BlocksAttacks.DamageReduction::factor));
|
||||
|
||||
MinecraftHasher<BlocksAttacks.ItemDamageFunction> BLOCKS_ATTACKS_ITEM_DAMAGE_FUNCTION = MinecraftHasher.mapBuilder(builder -> builder
|
||||
.accept("threshold", FLOAT, BlocksAttacks.ItemDamageFunction::threshold)
|
||||
.accept("base", FLOAT, BlocksAttacks.ItemDamageFunction::base)
|
||||
.accept("factor", FLOAT, BlocksAttacks.ItemDamageFunction::factor));
|
||||
|
||||
MinecraftHasher<ProvidesTrimMaterial> PROVIDES_TRIM_MATERIAL = MinecraftHasher.either(TRIM_MATERIAL.holder(), ProvidesTrimMaterial::materialHolder, KEY, ProvidesTrimMaterial::materialLocation);
|
||||
|
||||
MinecraftHasher<ArmorTrim> ARMOR_TRIM = MinecraftHasher.mapBuilder(builder -> builder
|
||||
.accept("material", TRIM_MATERIAL.holder(), ArmorTrim::material)
|
||||
.accept("pattern", TRIM_PATTERN.holder(), ArmorTrim::pattern));
|
||||
|
||||
MinecraftHasher<JukeboxPlayable> JUKEBOX_PLAYABLE = MinecraftHasher.either(JUKEBOX_SONG.holder(), JukeboxPlayable::songHolder, KEY, JukeboxPlayable::songLocation);
|
||||
|
||||
MinecraftHasher<BannerPatternLayer> BANNER_PATTERN_LAYER = MinecraftHasher.mapBuilder(builder -> builder
|
||||
.accept("pattern", BANNER_PATTERN.holder(), BannerPatternLayer::getPattern)
|
||||
.accept("color", DYE_COLOR, BannerPatternLayer::getColorId));
|
||||
|
||||
MinecraftHasher<Integer> FIREWORK_EXPLOSION_SHAPE = MinecraftHasher.fromIdEnum(FireworkExplosionShape.values());
|
||||
|
||||
MinecraftHasher<Fireworks.FireworkExplosion> FIREWORK_EXPLOSION = MinecraftHasher.mapBuilder(builder -> builder
|
||||
.accept("shape", FIREWORK_EXPLOSION_SHAPE, Fireworks.FireworkExplosion::getShapeId)
|
||||
.optionalList("colors", INT, explosion -> IntStream.of(explosion.getColors()).boxed().toList())
|
||||
.optionalList("fade_colors", INT, explosion -> IntStream.of(explosion.getFadeColors()).boxed().toList())
|
||||
.optional("has_trail", BOOL, Fireworks.FireworkExplosion::isHasTrail, false)
|
||||
.optional("has_twinkle", BOOL, Fireworks.FireworkExplosion::isHasTwinkle, false));
|
||||
|
||||
MinecraftHasher<BeehiveOccupant> BEEHIVE_OCCUPANT = MinecraftHasher.mapBuilder(builder -> builder
|
||||
.optional("entity_data", NBT_MAP, BeehiveOccupant::getEntityData, NbtMap.EMPTY)
|
||||
.accept("ticks_in_hive", INT, BeehiveOccupant::getTicksInHive)
|
||||
.accept("min_ticks_in_hive", INT, BeehiveOccupant::getMinTicksInHive));
|
||||
|
||||
/**
|
||||
* Creates a hasher that uses the {@link JavaRegistryKey#keyFromNetworkId(GeyserSession, int)} method to turn a network ID into a {@link Key}, and then encodes this key.
|
||||
*
|
||||
* @param registry the registry to create a hasher for.
|
||||
*/
|
||||
static RegistryHasher<?> registry(JavaRegistryKey<?> registry) {
|
||||
MinecraftHasher<Integer> hasher = KEY.sessionCast(registry::keyFromNetworkId);
|
||||
return hasher::hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a hasher that encodes network IDs using {@link RegistryHasher#registry(JavaRegistryKey)}, and is also able to encode {@link Holder}s by using the {@code directHasher}.
|
||||
*
|
||||
* <p>A hasher that encodes {@link Holder}s can be obtained by using {@link RegistryHasher#holder()}</p>
|
||||
*
|
||||
* @param registry the registry to create a hasher for.
|
||||
* @param directHasher the hasher that encodes a custom object.
|
||||
* @param <DirectType> the type of custom objects.
|
||||
* @see RegistryHasher#holder()
|
||||
*/
|
||||
// We don't use the registry generic type, because various registries don't use the MCPL type as their type
|
||||
static <DirectType> RegistryHasher<DirectType> registry(JavaRegistryKey<?> registry, MinecraftHasher<DirectType> directHasher) {
|
||||
return new RegistryHasherWithDirectHasher<>(registry(registry), directHasher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a hasher that encodes a {@link Holder} of {@link DirectType}. If the holder has an ID, the {@link RegistryHasher} is used to encode it. If the holder is custom,
|
||||
* a direct hasher specified in {@link RegistryHasher#registry(JavaRegistryKey, MinecraftHasher)} is used to encode it.
|
||||
*
|
||||
* <p>This method can only be used if this hasher has a direct hasher attached to it. That is only the case if {@link DirectType} is not {@code ?}. If this hasher doesn't have
|
||||
* a direct hasher, a {@link IllegalStateException} will be thrown upon use.</p>
|
||||
*
|
||||
* @throws IllegalStateException when this hasher does not have a direct hasher attached to it.
|
||||
*/
|
||||
default MinecraftHasher<Holder<DirectType>> holder() {
|
||||
if (this instanceof RegistryHasher.RegistryHasherWithDirectHasher<DirectType> withDirect) {
|
||||
return withDirect.holderHasher;
|
||||
}
|
||||
throw new IllegalStateException("Tried to create a holder hasher on a registry hasher that does not have a direct hasher specified");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a hasher that hashes a {@link HolderSet} of the registry. {@link HolderSet}s can encode as a hash-prefixed tag, a single namespaced ID, or a list of namespaced IDs.
|
||||
*
|
||||
* <p>The hasher throws a {@link IllegalStateException} if the holder set does not have a tag nor a list of IDs. This should never happen.</p>
|
||||
*/
|
||||
default MinecraftHasher<HolderSet> holderSet() {
|
||||
return (holder, encoder) -> {
|
||||
if (holder.getLocation() != null) {
|
||||
return TAG.hash(holder.getLocation(), encoder);
|
||||
} else if (holder.getHolders() != null) {
|
||||
if (holder.getHolders().length == 1) {
|
||||
return hash(holder.getHolders()[0], encoder);
|
||||
}
|
||||
return list().hash(Arrays.stream(holder.getHolders()).boxed().toList(), encoder);
|
||||
}
|
||||
throw new IllegalStateException("HolderSet must have either tag location or holders");
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a hasher that uses {@link Enum#name()} (lowercased) to create a key in the {@code minecraft} namespace, and then hashes it.
|
||||
*
|
||||
* <p>Please be aware that you are using literal enum constants as key paths here, meaning that if there is a typo in a constant, or a constant changes name, things
|
||||
* may break. Use cautiously.</p>
|
||||
*
|
||||
* @param <EnumConstant> the enum.
|
||||
*/
|
||||
static <EnumConstant extends Enum<EnumConstant>> MinecraftHasher<EnumConstant> enumRegistry() {
|
||||
return KEY.cast(constant -> MinecraftKey.key(constant.name().toLowerCase(Locale.ROOT)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses {@link Enum#name()} (lowercased) to create a function that creates a {@link Key} from a {@link EnumConstant}, and uses this as {@code toKey}
|
||||
* function in {@link RegistryHasher#enumIdRegistry(Enum[], Function)}.
|
||||
*
|
||||
* <p>Please be aware that you are using literal enum constants as key paths here, meaning that if there is a typo in a constant, or a constant changes name, things
|
||||
* may break. Use cautiously.</p>
|
||||
*
|
||||
* @param values the array of {@link EnumConstant}s.
|
||||
* @param <EnumConstant> the enum.
|
||||
* @see RegistryHasher#enumIdRegistry(Enum[], Function)
|
||||
*/
|
||||
static <EnumConstant extends Enum<EnumConstant>> RegistryHasher<?> enumIdRegistry(EnumConstant[] values) {
|
||||
return enumIdRegistry(values, constant -> MinecraftKey.key(constant.name().toLowerCase(Locale.ROOT)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a hasher that looks up a network ID in the array of {@link EnumConstant}s, and then uses {@code toKey} to turn the constant into a key, which it then hashes.
|
||||
*
|
||||
* @param values the array of {@link EnumConstant}s.
|
||||
* @param toKey the function that turns a {@link EnumConstant} into a {@link Key}.
|
||||
* @param <EnumConstant> the enum.
|
||||
* @see MinecraftHasher#fromIdEnum(Enum[])
|
||||
*/
|
||||
static <EnumConstant extends Enum<EnumConstant>> RegistryHasher<?> enumIdRegistry(EnumConstant[] values, Function<EnumConstant, Key> toKey) {
|
||||
MinecraftHasher<Integer> hasher = KEY.cast(i -> toKey.apply(values[i]));
|
||||
return hasher::hash;
|
||||
}
|
||||
|
||||
class RegistryHasherWithDirectHasher<DirectType> implements RegistryHasher<DirectType> {
|
||||
private final MinecraftHasher<Integer> id;
|
||||
private final MinecraftHasher<Holder<DirectType>> holderHasher;
|
||||
|
||||
public RegistryHasherWithDirectHasher(MinecraftHasher<Integer> id, MinecraftHasher<DirectType> direct) {
|
||||
this.id = id;
|
||||
this.holderHasher = (value, encoder) -> {
|
||||
if (value.isId()) {
|
||||
return hash(value.id(), encoder);
|
||||
}
|
||||
return direct.hash(value.custom(), encoder);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashCode hash(Integer value, MinecraftHashEncoder encoder) {
|
||||
return id.hash(value, encoder);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.hashing.data;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.geysermc.geyser.item.hashing.MapBuilder;
|
||||
import org.geysermc.geyser.item.hashing.MinecraftHasher;
|
||||
import org.geysermc.geyser.item.hashing.RegistryHasher;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ConsumeEffect;
|
||||
|
||||
public enum ConsumeEffectType {
|
||||
APPLY_EFFECTS(ConsumeEffect.ApplyEffects.class, builder -> builder
|
||||
.acceptList("effects", RegistryHasher.MOB_EFFECT_INSTANCE, ConsumeEffect.ApplyEffects::effects)
|
||||
.optional("probability", MinecraftHasher.FLOAT, ConsumeEffect.ApplyEffects::probability, 1.0F)),
|
||||
REMOVE_EFFECTS(ConsumeEffect.RemoveEffects.class, builder -> builder
|
||||
.accept("effects", RegistryHasher.EFFECT_ID.holderSet(), ConsumeEffect.RemoveEffects::effects)),
|
||||
CLEAR_ALL_EFFECTS(ConsumeEffect.ClearAllEffects.class),
|
||||
TELEPORT_RANDOMLY(ConsumeEffect.TeleportRandomly.class, builder -> builder
|
||||
.optional("diameter", MinecraftHasher.FLOAT, ConsumeEffect.TeleportRandomly::diameter, 16.0F)),
|
||||
PLAY_SOUND(ConsumeEffect.PlaySound.class, builder -> builder
|
||||
.accept("sound", RegistryHasher.SOUND_EVENT, ConsumeEffect.PlaySound::sound));
|
||||
|
||||
private final Class<? extends ConsumeEffect> clazz;
|
||||
@Getter
|
||||
private final MapBuilder<? extends ConsumeEffect> builder;
|
||||
|
||||
<T extends ConsumeEffect> ConsumeEffectType(Class<T> clazz) {
|
||||
this.clazz = clazz;
|
||||
this.builder = MapBuilder.empty();
|
||||
}
|
||||
|
||||
<T extends ConsumeEffect> ConsumeEffectType(Class<T> clazz, MapBuilder<T> builder) {
|
||||
this.clazz = clazz;
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
public static ConsumeEffectType fromEffect(ConsumeEffect effect) {
|
||||
Class<? extends ConsumeEffect> clazz = effect.getClass();
|
||||
for (ConsumeEffectType type : values()) {
|
||||
if (clazz == type.clazz) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Unimplemented consume effect type for hashing");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.hashing.data;
|
||||
|
||||
// Ordered and named by Java ID
|
||||
public enum FireworkExplosionShape {
|
||||
SMALL_BALL,
|
||||
LARGE_BALL,
|
||||
STAR,
|
||||
CREEPER,
|
||||
BURST
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.hashing.data;
|
||||
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
|
||||
|
||||
public record ItemContainerSlot(int index, ItemStack item) {
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.hashing.data.entity;
|
||||
|
||||
// Ordered and named by Java ID
|
||||
public enum AxolotlVariant {
|
||||
LUCY,
|
||||
WILD,
|
||||
GOLD,
|
||||
CYAN,
|
||||
BLUE
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.hashing.data.entity;
|
||||
|
||||
// Ordered and named by Java ID
|
||||
public enum FoxVariant {
|
||||
RED,
|
||||
SNOW
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.hashing.data.entity;
|
||||
|
||||
// Ordered and named by Java ID
|
||||
public enum HorseVariant {
|
||||
WHITE,
|
||||
CREAMY,
|
||||
CHESTNUT,
|
||||
BROWN,
|
||||
BLACK,
|
||||
GRAY,
|
||||
DARK_BROWN
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.hashing.data.entity;
|
||||
|
||||
// Ordered and named by Java ID
|
||||
public enum LlamaVariant {
|
||||
CREAMY,
|
||||
WHITE,
|
||||
BROWN,
|
||||
GRAY
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.hashing.data.entity;
|
||||
|
||||
// Ordered and named by Java ID
|
||||
public enum MooshroomVariant {
|
||||
RED,
|
||||
BROWN
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.hashing.data.entity;
|
||||
|
||||
// Ordered and named by Java ID
|
||||
public enum ParrotVariant {
|
||||
RED_BLUE,
|
||||
BLUE,
|
||||
GREEN,
|
||||
YELLOW_BLUE,
|
||||
GRAY
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.hashing.data.entity;
|
||||
|
||||
// Named by Java ID
|
||||
public enum RabbitVariant {
|
||||
BROWN(0),
|
||||
WHITE(1),
|
||||
BLACK(2),
|
||||
WHITE_SPLOTCHED(3),
|
||||
GOLD(4),
|
||||
SALT(5),
|
||||
EVIL(99);
|
||||
|
||||
private final int id;
|
||||
|
||||
RabbitVariant(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public static RabbitVariant fromId(int id) {
|
||||
for (RabbitVariant variant : values()) {
|
||||
if (variant.id == id) {
|
||||
return variant;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown rabbit variant ID");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.hashing.data.entity;
|
||||
|
||||
// Ordered and named by Java ID
|
||||
public enum SalmonVariant {
|
||||
SMALL,
|
||||
MEDIUM,
|
||||
LARGE
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.hashing.data.entity;
|
||||
|
||||
// Named by Java ID
|
||||
public enum TropicalFishPattern {
|
||||
KOB(Base.SMALL, 0),
|
||||
SUNSTREAK(Base.SMALL, 1),
|
||||
SNOOPER(Base.SMALL, 2),
|
||||
DASHER(Base.SMALL, 3),
|
||||
BRINELY(Base.SMALL, 4),
|
||||
SPOTTY(Base.SMALL, 5),
|
||||
FLOPPER(Base.LARGE, 0),
|
||||
STRIPEY(Base.LARGE, 1),
|
||||
GLITTER(Base.LARGE, 2),
|
||||
BLOCKFISH(Base.LARGE, 3),
|
||||
BETTY(Base.LARGE, 4),
|
||||
CLAYFISH(Base.LARGE, 5);
|
||||
|
||||
private final int packedId;
|
||||
|
||||
TropicalFishPattern(Base base, int id) {
|
||||
this.packedId = base.ordinal() | id << 8;
|
||||
}
|
||||
|
||||
// Ordered by Java ID
|
||||
enum Base {
|
||||
SMALL,
|
||||
LARGE
|
||||
}
|
||||
|
||||
public static TropicalFishPattern fromPackedId(int packedId) {
|
||||
for (TropicalFishPattern pattern : values()) {
|
||||
if (pattern.packedId == packedId) {
|
||||
return pattern;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Illegal packed tropical fish pattern ID");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.hashing.data.entity;
|
||||
|
||||
// Ordered and named by Java ID
|
||||
public enum VillagerVariant {
|
||||
DESERT,
|
||||
JUNGLE,
|
||||
PLAINS,
|
||||
SAVANNA,
|
||||
SNOW,
|
||||
SWAMP,
|
||||
TAIGA
|
||||
}
|
|
@ -30,7 +30,9 @@ import org.cloudburstmc.nbt.NbtMap;
|
|||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
import org.cloudburstmc.protocol.bedrock.data.TrimMaterial;
|
||||
import org.cloudburstmc.protocol.bedrock.data.TrimPattern;
|
||||
import org.geysermc.geyser.item.TooltipOptions;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
|
||||
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ArmorTrim;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
|
||||
|
@ -43,17 +45,18 @@ public class ArmorItem extends Item {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, builder);
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull TooltipOptions tooltip, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, tooltip, builder);
|
||||
|
||||
ArmorTrim trim = components.get(DataComponentTypes.TRIM);
|
||||
if (trim != null) {
|
||||
TrimMaterial material = session.getRegistryCache().trimMaterials().byId(trim.material().id());
|
||||
TrimPattern pattern = session.getRegistryCache().trimPatterns().byId(trim.pattern().id());
|
||||
TrimMaterial material = session.getRegistryCache().registry(JavaRegistries.TRIM_MATERIAL).byId(trim.material().id());
|
||||
TrimPattern pattern = session.getRegistryCache().registry(JavaRegistries.TRIM_PATTERN).byId(trim.pattern().id());
|
||||
|
||||
// discard custom trim patterns/materials to prevent visual glitches on bedrock
|
||||
if (!getNamespace(material.getMaterialId()).equals("minecraft")
|
||||
|| !getNamespace(pattern.getPatternId()).equals("minecraft")) {
|
||||
// TODO - how is this shown in tooltip? should we add a custom trim tooltip to the lore here
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
package org.geysermc.geyser.item.type;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.item.TooltipOptions;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.MinecraftLocale;
|
||||
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
|
||||
|
@ -37,8 +38,8 @@ public class AxolotlBucketItem extends Item {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, builder);
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull TooltipOptions tooltip, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, tooltip, builder);
|
||||
|
||||
// Bedrock Edition displays the properties of the axolotl. Java does not.
|
||||
// To work around this, set the custom name to the Axolotl translation and it's displayed correctly
|
||||
|
|
|
@ -36,9 +36,11 @@ import org.cloudburstmc.nbt.NbtMap;
|
|||
import org.cloudburstmc.nbt.NbtType;
|
||||
import org.geysermc.geyser.inventory.item.BannerPattern;
|
||||
import org.geysermc.geyser.inventory.item.DyeColor;
|
||||
import org.geysermc.geyser.item.TooltipOptions;
|
||||
import org.geysermc.geyser.level.block.type.Block;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistry;
|
||||
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
|
||||
import org.geysermc.geyser.util.MinecraftKey;
|
||||
|
@ -46,7 +48,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.Holder;
|
|||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.BannerPatternLayer;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.Unit;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.TooltipDisplay;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -86,7 +88,7 @@ public class BannerItem extends BlockItem {
|
|||
!patternLayer.getPattern().isId()) {
|
||||
return false;
|
||||
}
|
||||
BannerPattern bannerPattern = session.getRegistryCache().bannerPatterns().byId(patternLayer.getPattern().id());
|
||||
BannerPattern bannerPattern = session.getRegistryCache().registry(JavaRegistries.BANNER_PATTERN).byId(patternLayer.getPattern().id());
|
||||
if (bannerPattern != pair.left()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -145,14 +147,12 @@ public class BannerItem extends BlockItem {
|
|||
List<NbtMap> patternList = new ArrayList<>(patterns.size());
|
||||
for (BannerPatternLayer patternLayer : patterns) {
|
||||
patternLayer.getPattern().ifId(id -> {
|
||||
BannerPattern bannerPattern = session.getRegistryCache().bannerPatterns().byId(id);
|
||||
if (bannerPattern != null) {
|
||||
NbtMap tag = NbtMap.builder()
|
||||
.putString("Pattern", bannerPattern.getBedrockIdentifier())
|
||||
.putInt("Color", 15 - patternLayer.getColorId())
|
||||
.build();
|
||||
patternList.add(tag);
|
||||
}
|
||||
BannerPattern bannerPattern = session.getRegistryCache().registry(JavaRegistries.BANNER_PATTERN).byId(id);
|
||||
NbtMap tag = NbtMap.builder()
|
||||
.putString("Pattern", bannerPattern.getBedrockIdentifier())
|
||||
.putInt("Color", 15 - patternLayer.getColorId())
|
||||
.build();
|
||||
patternList.add(tag);
|
||||
});
|
||||
}
|
||||
builder.putList("Patterns", NbtType.COMPOUND, patternList);
|
||||
|
@ -186,10 +186,10 @@ public class BannerItem extends BlockItem {
|
|||
* @return The Java edition format pattern layer
|
||||
*/
|
||||
public static BannerPatternLayer getJavaBannerPattern(GeyserSession session, NbtMap pattern) {
|
||||
JavaRegistry<BannerPattern> registry = session.getRegistryCache().bannerPatterns();
|
||||
JavaRegistry<BannerPattern> registry = session.getRegistryCache().registry(JavaRegistries.BANNER_PATTERN);
|
||||
BannerPattern bannerPattern = BannerPattern.getByBedrockIdentifier(pattern.getString("Pattern"));
|
||||
DyeColor dyeColor = DyeColor.getById(15 - pattern.getInt("Color"));
|
||||
if (bannerPattern != null && dyeColor != null) {
|
||||
if (dyeColor != null) {
|
||||
int id = registry.byValue(bannerPattern);
|
||||
if (id != -1) {
|
||||
return new BannerPatternLayer(Holder.ofId(id), dyeColor.ordinal());
|
||||
|
@ -203,8 +203,8 @@ public class BannerItem extends BlockItem {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, builder);
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull TooltipOptions tooltip, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, tooltip, builder);
|
||||
|
||||
List<BannerPatternLayer> patterns = components.get(DataComponentTypes.BANNER_PATTERNS);
|
||||
if (patterns != null) {
|
||||
|
@ -221,12 +221,13 @@ public class BannerItem extends BlockItem {
|
|||
List<BannerPatternLayer> patternLayers = new ArrayList<>();
|
||||
for (int i = 0; i < OMINOUS_BANNER_PATTERN.size(); i++) {
|
||||
var pair = OMINOUS_BANNER_PATTERN.get(i);
|
||||
patternLayers.add(new BannerPatternLayer(Holder.ofId(session.getRegistryCache().bannerPatterns().byValue(pair.left())),
|
||||
patternLayers.add(new BannerPatternLayer(Holder.ofId(session.getRegistryCache().registry(JavaRegistries.BANNER_PATTERN).byValue(pair.left())),
|
||||
pair.right().ordinal()));
|
||||
}
|
||||
|
||||
components.put(DataComponentTypes.BANNER_PATTERNS, patternLayers);
|
||||
components.put(DataComponentTypes.HIDE_ADDITIONAL_TOOLTIP, Unit.INSTANCE);
|
||||
// The ominous banner item in the Java creative menu just has banner patterns hidden as of 1.21.5
|
||||
components.put(DataComponentTypes.TOOLTIP_DISPLAY, new TooltipDisplay(false, List.of(DataComponentTypes.BANNER_PATTERNS)));
|
||||
components.put(DataComponentTypes.ITEM_NAME, Component
|
||||
.translatable("block.minecraft.ominous_banner")
|
||||
.style(Style.style(TextColor.color(16755200)))
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
|||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.item.TooltipOptions;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.registry.type.ItemMappings;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
@ -59,8 +60,8 @@ public class CompassItem extends Item {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, builder);
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull TooltipOptions tooltip, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, tooltip, builder);
|
||||
|
||||
LodestoneTracker tracker = components.get(DataComponentTypes.LODESTONE_TRACKER);
|
||||
if (tracker != null) {
|
||||
|
|
|
@ -28,6 +28,7 @@ package org.geysermc.geyser.item.type;
|
|||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
||||
import org.geysermc.geyser.item.TooltipOptions;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
|
||||
|
@ -44,8 +45,8 @@ public class CrossbowItem extends Item {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, builder);
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull TooltipOptions tooltip, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, tooltip, builder);
|
||||
|
||||
List<ItemStack> chargedProjectiles = components.get(DataComponentTypes.CHARGED_PROJECTILES);
|
||||
if (chargedProjectiles != null && !chargedProjectiles.isEmpty()) {
|
||||
|
|
|
@ -27,6 +27,7 @@ package org.geysermc.geyser.item.type;
|
|||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.cloudburstmc.nbt.NbtType;
|
||||
import org.geysermc.geyser.item.TooltipOptions;
|
||||
import org.geysermc.geyser.level.block.type.Block;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
@ -44,8 +45,8 @@ public class DecoratedPotItem extends BlockItem {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, builder);
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull TooltipOptions tooltip, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, tooltip, builder);
|
||||
|
||||
List<Integer> decorations = components.get(DataComponentTypes.POT_DECORATIONS); // TODO maybe unbox in MCProtocolLib
|
||||
if (decorations != null) {
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
package org.geysermc.geyser.item.type;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.item.TooltipOptions;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
|
||||
|
@ -36,8 +37,8 @@ public class DyeableArmorItem extends ArmorItem {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, builder);
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull TooltipOptions tooltip, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, tooltip, builder);
|
||||
|
||||
// Note that this is handled as of 1.20.5 in the ItemColors class.
|
||||
// But horse leather armor and body leather armor are now both armor items. So it works!
|
||||
|
|
|
@ -32,9 +32,11 @@ import org.cloudburstmc.nbt.NbtMap;
|
|||
import org.cloudburstmc.nbt.NbtType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.inventory.item.BedrockEnchantment;
|
||||
import org.geysermc.geyser.item.TooltipOptions;
|
||||
import org.geysermc.geyser.item.enchantment.Enchantment;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
|
||||
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
|
||||
|
@ -50,8 +52,8 @@ public class EnchantedBookItem extends Item {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, builder);
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull TooltipOptions tooltip, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, tooltip, builder);
|
||||
|
||||
List<NbtMap> bedrockEnchants = new ArrayList<>();
|
||||
ItemEnchantments enchantments = components.get(DataComponentTypes.STORED_ENCHANTMENTS);
|
||||
|
@ -81,7 +83,7 @@ public class EnchantedBookItem extends Item {
|
|||
|
||||
BedrockEnchantment enchantment = BedrockEnchantment.getByBedrockId(bedrockId);
|
||||
if (enchantment != null) {
|
||||
List<Enchantment> enchantments = session.getRegistryCache().enchantments().values();
|
||||
List<Enchantment> enchantments = session.getRegistryCache().registry(JavaRegistries.ENCHANTMENT).values();
|
||||
for (int i = 0; i < enchantments.size(); i++) {
|
||||
if (enchantments.get(i).bedrockEnchantment() == enchantment) {
|
||||
int level = bedrockEnchantment.getShort("lvl", (short) 1);
|
||||
|
@ -94,7 +96,7 @@ public class EnchantedBookItem extends Item {
|
|||
}
|
||||
}
|
||||
if (!javaEnchantments.isEmpty()) {
|
||||
components.put(DataComponentTypes.STORED_ENCHANTMENTS, new ItemEnchantments(javaEnchantments, true));
|
||||
components.put(DataComponentTypes.STORED_ENCHANTMENTS, new ItemEnchantments(javaEnchantments));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.cloudburstmc.nbt.NbtList;
|
|||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
import org.cloudburstmc.nbt.NbtType;
|
||||
import org.geysermc.geyser.item.TooltipOptions;
|
||||
import org.geysermc.geyser.level.FireworkColor;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
@ -48,8 +49,8 @@ public class FireworkRocketItem extends Item implements BedrockRequiresTagItem {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, builder);
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull TooltipOptions tooltip, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, tooltip, builder);
|
||||
|
||||
Fireworks fireworks = components.get(DataComponentTypes.FIREWORKS);
|
||||
if (fireworks == null) {
|
||||
|
|
|
@ -27,6 +27,7 @@ package org.geysermc.geyser.item.type;
|
|||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.geysermc.geyser.item.TooltipOptions;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
|
||||
|
@ -40,8 +41,8 @@ public class FireworkStarItem extends Item {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, builder);
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull TooltipOptions tooltip, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, tooltip, builder);
|
||||
|
||||
Fireworks.FireworkExplosion explosion = components.get(DataComponentTypes.FIREWORK_EXPLOSION);
|
||||
if (explosion != null) {
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
package org.geysermc.geyser.item.type;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.item.TooltipOptions;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
|
||||
|
@ -36,8 +37,8 @@ public class FishingRodItem extends Item {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, builder);
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull TooltipOptions tooltip, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, tooltip, builder);
|
||||
|
||||
// Fix damage inconsistency
|
||||
builder.getDamage().ifPresent(damage -> builder.setDamage(getBedrockDamage(damage)));
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
|||
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.inventory.item.GeyserInstrument;
|
||||
import org.geysermc.geyser.item.TooltipOptions;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.registry.type.ItemMappings;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
@ -36,7 +37,7 @@ import org.geysermc.geyser.translator.item.BedrockItemBuilder;
|
|||
import org.geysermc.mcprotocollib.protocol.data.game.Holder;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.Instrument;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.InstrumentComponent;
|
||||
|
||||
public class GoatHornItem extends Item {
|
||||
public GoatHornItem(String javaIdentifier, Builder builder) {
|
||||
|
@ -50,9 +51,9 @@ public class GoatHornItem extends Item {
|
|||
return builder;
|
||||
}
|
||||
|
||||
Holder<Instrument> holder = components.get(DataComponentTypes.INSTRUMENT);
|
||||
if (holder != null) {
|
||||
GeyserInstrument instrument = GeyserInstrument.fromHolder(session, holder);
|
||||
InstrumentComponent instrumentComponent = components.get(DataComponentTypes.INSTRUMENT);
|
||||
if (instrumentComponent != null) {
|
||||
GeyserInstrument instrument = GeyserInstrument.fromComponent(session, instrumentComponent);
|
||||
int bedrockId = instrument.bedrockId();
|
||||
if (bedrockId >= 0) {
|
||||
builder.damage(bedrockId);
|
||||
|
@ -63,13 +64,12 @@ public class GoatHornItem extends Item {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, builder);
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull TooltipOptions tooltip, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, tooltip, builder);
|
||||
|
||||
Holder<Instrument> holder = components.get(DataComponentTypes.INSTRUMENT);
|
||||
if (holder != null && components.get(DataComponentTypes.HIDE_TOOLTIP) == null
|
||||
&& components.get(DataComponentTypes.HIDE_ADDITIONAL_TOOLTIP) == null) {
|
||||
GeyserInstrument instrument = GeyserInstrument.fromHolder(session, holder);
|
||||
InstrumentComponent component = components.get(DataComponentTypes.INSTRUMENT);
|
||||
if (component != null && tooltip.showInTooltip(DataComponentTypes.INSTRUMENT)) {
|
||||
GeyserInstrument instrument = GeyserInstrument.fromComponent(session, component);
|
||||
if (instrument.bedrockInstrument() == null) {
|
||||
builder.getOrCreateLore().add(instrument.description());
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ public class GoatHornItem extends Item {
|
|||
|
||||
int damage = itemData.getDamage();
|
||||
// This could cause an issue since -1 is returned for non-vanilla goat horns
|
||||
itemStack.getOrCreateComponents().put(DataComponentTypes.INSTRUMENT, Holder.ofId(GeyserInstrument.bedrockIdToJava(session, damage)));
|
||||
itemStack.getOrCreateComponents().put(DataComponentTypes.INSTRUMENT, new InstrumentComponent(Holder.ofId(GeyserInstrument.bedrockIdToJava(session, damage)), null));
|
||||
|
||||
return itemStack;
|
||||
}
|
||||
|
|
|
@ -37,12 +37,14 @@ import org.geysermc.geyser.GeyserImpl;
|
|||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.inventory.item.BedrockEnchantment;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.item.TooltipOptions;
|
||||
import org.geysermc.geyser.item.enchantment.Enchantment;
|
||||
import org.geysermc.geyser.level.block.type.Block;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.registry.type.ItemMappings;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
|
||||
import org.geysermc.geyser.text.ChatColor;
|
||||
import org.geysermc.geyser.text.MinecraftLocale;
|
||||
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
|
||||
|
@ -51,7 +53,6 @@ import org.geysermc.geyser.util.MinecraftKey;
|
|||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DyedItemColor;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ItemEnchantments;
|
||||
import org.jetbrains.annotations.UnmodifiableView;
|
||||
|
||||
|
@ -75,10 +76,15 @@ public class Item {
|
|||
this.attackDamage = builder.attackDamage;
|
||||
}
|
||||
|
||||
// TODO maybe deprecate?
|
||||
public String javaIdentifier() {
|
||||
return javaIdentifier.asString();
|
||||
}
|
||||
|
||||
public Key javaKey() {
|
||||
return javaIdentifier;
|
||||
}
|
||||
|
||||
public int javaId() {
|
||||
return javaId;
|
||||
}
|
||||
|
@ -157,9 +163,9 @@ public class Item {
|
|||
/**
|
||||
* Takes components from Java Edition and map them into Bedrock.
|
||||
*/
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull BedrockItemBuilder builder) {
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull TooltipOptions tooltip, @NonNull BedrockItemBuilder builder) {
|
||||
List<Component> loreComponents = components.get(DataComponentTypes.LORE);
|
||||
if (loreComponents != null && components.get(DataComponentTypes.HIDE_TOOLTIP) == null) {
|
||||
if (loreComponents != null && tooltip.showInTooltip(DataComponentTypes.LORE)) {
|
||||
List<String> lore = builder.getOrCreateLore();
|
||||
for (Component loreComponent : loreComponents) {
|
||||
lore.add(MessageTranslator.convertMessage(loreComponent, session.locale()));
|
||||
|
@ -240,7 +246,7 @@ public class Item {
|
|||
}
|
||||
|
||||
protected final @Nullable NbtMap remapEnchantment(GeyserSession session, int enchantId, int level, BedrockItemBuilder builder) {
|
||||
Enchantment enchantment = session.getRegistryCache().enchantments().byId(enchantId);
|
||||
Enchantment enchantment = session.getRegistryCache().registry(JavaRegistries.ENCHANTMENT).byId(enchantId);
|
||||
if (enchantment == null) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Unknown Java enchantment while NBT item translating: " + enchantId);
|
||||
return null;
|
||||
|
@ -266,9 +272,9 @@ public class Item {
|
|||
}
|
||||
|
||||
protected final void translateDyedColor(DataComponents components, BedrockItemBuilder builder) {
|
||||
DyedItemColor dyedItemColor = components.get(DataComponentTypes.DYED_COLOR);
|
||||
Integer dyedItemColor = components.get(DataComponentTypes.DYED_COLOR);
|
||||
if (dyedItemColor != null) {
|
||||
builder.putInt("customColor", dyedItemColor.getRgb());
|
||||
builder.putInt("customColor", dyedItemColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
package org.geysermc.geyser.item.type;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.item.TooltipOptions;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
|
||||
|
@ -37,8 +38,8 @@ public class MapItem extends Item {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, builder);
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull TooltipOptions tooltip, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, tooltip, builder);
|
||||
|
||||
Integer mapValue = components.get(DataComponentTypes.MAP_ID);
|
||||
if (mapValue == null) {
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
package org.geysermc.geyser.item.type;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.item.TooltipOptions;
|
||||
import org.geysermc.geyser.item.components.Rarity;
|
||||
import org.geysermc.geyser.level.block.type.Block;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
@ -42,8 +43,8 @@ public class PlayerHeadItem extends BlockItem {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, builder);
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull TooltipOptions tooltip, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, tooltip, builder);
|
||||
|
||||
// Use the correct color, determined by the rarity of the item
|
||||
char rarity = Rarity.fromId(components.get(DataComponentTypes.RARITY)).getColor();
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
package org.geysermc.geyser.item.type;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.item.TooltipOptions;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.BannerPatternLayer;
|
||||
|
@ -40,8 +41,8 @@ public class ShieldItem extends Item {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, builder);
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull TooltipOptions tooltip, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, tooltip, builder);
|
||||
|
||||
List<BannerPatternLayer> patterns = components.get(DataComponentTypes.BANNER_PATTERNS);
|
||||
if (patterns != null) {
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.cloudburstmc.nbt.NbtType;
|
|||
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
||||
import org.geysermc.geyser.inventory.item.Potion;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.item.TooltipOptions;
|
||||
import org.geysermc.geyser.level.block.type.Block;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
@ -53,8 +54,8 @@ public class ShulkerBoxItem extends BlockItem {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, builder);
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull TooltipOptions tooltip, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, tooltip, builder);
|
||||
|
||||
List<ItemStack> contents = components.get(DataComponentTypes.CONTAINER);
|
||||
if (contents == null || contents.isEmpty()) {
|
||||
|
|
|
@ -30,8 +30,8 @@ import net.kyori.adventure.text.format.NamedTextColor;
|
|||
import net.kyori.adventure.text.format.Style;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.geysermc.geyser.entity.type.living.animal.TropicalFishEntity;
|
||||
import org.geysermc.geyser.item.TooltipOptions;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.MinecraftLocale;
|
||||
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
|
||||
|
@ -49,37 +49,54 @@ public class TropicalFishBucketItem extends Item {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, builder);
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull TooltipOptions tooltip, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, tooltip, builder);
|
||||
|
||||
// Prevent name from appearing as "Bucket of"
|
||||
builder.putByte("AppendCustomName", (byte) 1);
|
||||
builder.putString("CustomName", MinecraftLocale.getLocaleString("entity.minecraft.tropical_fish", session.locale()));
|
||||
|
||||
// Add Java's client side lore tag
|
||||
// Do you know how frequently Java NBT used to be before 1.20.5? It was a lot. And now it's just this lowly check.
|
||||
NbtMap entityTag = components.get(DataComponentTypes.BUCKET_ENTITY_DATA);
|
||||
if (entityTag != null && !entityTag.isEmpty()) {
|
||||
//TODO test
|
||||
int bucketVariant = entityTag.getInt("BucketVariantTag");
|
||||
Integer pattern = components.get(DataComponentTypes.TROPICAL_FISH_PATTERN);
|
||||
Integer baseColor = components.get(DataComponentTypes.TROPICAL_FISH_BASE_COLOR);
|
||||
Integer patternColor = components.get(DataComponentTypes.TROPICAL_FISH_PATTERN_COLOR);
|
||||
|
||||
// The pattern component decides whether to show the tooltip of all 3 components, as of Java 1.21.5
|
||||
if ((pattern != null || (baseColor != null && patternColor != null)) && tooltip.showInTooltip(DataComponentTypes.TROPICAL_FISH_PATTERN)) {
|
||||
//TODO test this for 1.21.5
|
||||
int packedVariant = getPackedVariant(pattern, baseColor, patternColor);
|
||||
List<String> lore = builder.getOrCreateLore();
|
||||
|
||||
int predefinedVariantId = TropicalFishEntity.getPredefinedId(bucketVariant);
|
||||
int predefinedVariantId = TropicalFishEntity.getPredefinedId(packedVariant);
|
||||
if (predefinedVariantId != -1) {
|
||||
Component tooltip = Component.translatable("entity.minecraft.tropical_fish.predefined." + predefinedVariantId, LORE_STYLE);
|
||||
lore.add(0, MessageTranslator.convertMessage(tooltip, session.locale()));
|
||||
Component line = Component.translatable("entity.minecraft.tropical_fish.predefined." + predefinedVariantId, LORE_STYLE);
|
||||
lore.add(0, MessageTranslator.convertMessage(line, session.locale()));
|
||||
} else {
|
||||
Component typeTooltip = Component.translatable("entity.minecraft.tropical_fish.type." + TropicalFishEntity.getVariantName(bucketVariant), LORE_STYLE);
|
||||
Component typeTooltip = Component.translatable("entity.minecraft.tropical_fish.type." + TropicalFishEntity.getVariantName(packedVariant), LORE_STYLE);
|
||||
lore.add(0, MessageTranslator.convertMessage(typeTooltip, session.locale()));
|
||||
|
||||
byte baseColor = TropicalFishEntity.getBaseColor(bucketVariant);
|
||||
byte patternColor = TropicalFishEntity.getPatternColor(bucketVariant);
|
||||
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));
|
||||
if (baseColor != null && patternColor != null) {
|
||||
Component colorTooltip = Component.translatable("color.minecraft." + TropicalFishEntity.getColorName(baseColor.byteValue()), LORE_STYLE);
|
||||
if (!baseColor.equals(patternColor)) {
|
||||
colorTooltip = colorTooltip.append(Component.text(", ", LORE_STYLE))
|
||||
.append(Component.translatable("color.minecraft." + TropicalFishEntity.getColorName(patternColor.byteValue()), LORE_STYLE));
|
||||
}
|
||||
lore.add(1, MessageTranslator.convertMessage(colorTooltip, session.locale()));
|
||||
}
|
||||
lore.add(1, MessageTranslator.convertMessage(colorTooltip, session.locale()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int getPackedVariant(Integer pattern, Integer baseColor, Integer patternColor) {
|
||||
if (pattern == null) {
|
||||
pattern = 0;
|
||||
}
|
||||
if (baseColor == null) {
|
||||
baseColor = 0;
|
||||
}
|
||||
if (patternColor == null) {
|
||||
patternColor = 0;
|
||||
}
|
||||
return TropicalFishEntity.getPackedVariant(pattern, baseColor, patternColor);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
package org.geysermc.geyser.item.type;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.item.TooltipOptions;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
|
||||
|
@ -36,8 +37,8 @@ public class WolfArmorItem extends Item {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, builder);
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull TooltipOptions tooltip, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, tooltip, builder);
|
||||
|
||||
// Note that this is handled as of 1.21 in the ItemColors class.
|
||||
translateDyedColor(components, builder);
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
|||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
import org.cloudburstmc.nbt.NbtType;
|
||||
import org.geysermc.geyser.item.TooltipOptions;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
|
@ -46,8 +47,8 @@ public class WritableBookItem extends Item {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, builder);
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull TooltipOptions tooltip, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, tooltip, builder);
|
||||
|
||||
WritableBookContent bookContent = components.get(DataComponentTypes.WRITABLE_BOOK_CONTENT);
|
||||
if (bookContent == null) {
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
|||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
import org.cloudburstmc.nbt.NbtType;
|
||||
import org.geysermc.geyser.item.TooltipOptions;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
|
@ -51,8 +52,8 @@ public class WrittenBookItem extends Item {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, builder);
|
||||
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull TooltipOptions tooltip, @NonNull BedrockItemBuilder builder) {
|
||||
super.translateComponentsToBedrock(session, components, tooltip, builder);
|
||||
|
||||
WrittenBookContent bookContent = components.get(DataComponentTypes.WRITTEN_BOOK_CONTENT);
|
||||
if (bookContent == null) {
|
||||
|
|
|
@ -93,11 +93,11 @@ public enum PaintingType {
|
|||
|
||||
public static PaintingType getByName(Key key) {
|
||||
if (!key.namespace().equals("minecraft")) {
|
||||
return null;
|
||||
return KEBAB;
|
||||
}
|
||||
for (PaintingType paintingName : VALUES) {
|
||||
if (paintingName.name().toLowerCase(Locale.ROOT).equals(key.value())) return paintingName;
|
||||
}
|
||||
return null;
|
||||
return KEBAB; // We use kebab as default. Yummy!
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue