Merge branch 'master' into api/2.4.1

This commit is contained in:
chris 2024-07-23 14:29:42 +02:00 committed by GitHub
commit e82dda9678
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 727 additions and 290 deletions

View file

@ -14,7 +14,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! Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
### Currently supporting Minecraft Bedrock 1.20.80 - 1.21.2 and Minecraft Java 1.21 ### Currently supporting Minecraft Bedrock 1.20.80 - 1.21.3 and Minecraft Java 1.21
## Setting Up ## Setting Up
Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser.
@ -42,7 +42,7 @@ There are a few things Geyser is unable to support due to various differences be
3. Run `gradlew build` and locate to `bootstrap/build` folder. 3. Run `gradlew build` and locate to `bootstrap/build` folder.
## Contributing ## Contributing
Any contributions are appreciated. Please feel free to reach out to us on [Discord](http://discord.geysermc.org/) if Any contributions are appreciated. Please feel free to reach out to us on [Discord](https://discord.gg/geysermc) if
you're interested in helping out with Geyser. you're interested in helping out with Geyser.
## Libraries Used: ## Libraries Used:

View file

@ -36,6 +36,7 @@ import net.md_5.bungee.api.event.ProxyPingEvent;
import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.protocol.ProtocolConstants; import net.md_5.bungee.protocol.ProtocolConstants;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.ping.GeyserPingInfo; import org.geysermc.geyser.ping.GeyserPingInfo;
import org.geysermc.geyser.ping.IGeyserPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough;
@ -43,6 +44,7 @@ import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@AllArgsConstructor @AllArgsConstructor
public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, Listener { public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, Listener {
@ -59,7 +61,17 @@ public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, List
future.complete(event); future.complete(event);
} }
})); }));
ProxyPingEvent event = future.join();
ProxyPingEvent event;
try {
event = future.get(100, TimeUnit.MILLISECONDS);
} catch (Throwable cause) {
String address = GeyserImpl.getInstance().getConfig().isLogPlayerIpAddresses() ? inetSocketAddress.toString() : "<IP address withheld>";
GeyserImpl.getInstance().getLogger().error("Failed to get ping information for " + address, cause);
return null;
}
ServerPing response = event.getResponse(); ServerPing response = event.getResponse();
return new GeyserPingInfo( return new GeyserPingInfo(
response.getDescriptionComponent().toLegacyText(), response.getDescriptionComponent().toLegacyText(),

View file

@ -0,0 +1,79 @@
/*
* Copyright (c) 2024 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.platform.mod.mixin.server;
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;
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.session.GeyserSession;
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;
}
GeyserSession session = GeyserImpl.getInstance().connectionByUuid(player.getUUID());
if (session == null) {
return;
}
Vector3f position = Vector3f.from(
blockPos.getX(),
blockPos.getY(),
blockPos.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.setIdentifier(":");
session.sendUpstreamPacket(placeBlockSoundPacket);
session.setLastBlockPlacePosition(null);
session.setLastBlockPlaced(null);
}
}

View file

@ -4,6 +4,7 @@
"package": "org.geysermc.geyser.platform.mod.mixin", "package": "org.geysermc.geyser.platform.mod.mixin",
"compatibilityLevel": "JAVA_17", "compatibilityLevel": "JAVA_17",
"mixins": [ "mixins": [
"server.BlockPlaceMixin",
"server.ServerConnectionListenerMixin" "server.ServerConnectionListenerMixin"
], ],
"server": [ "server": [

View file

@ -55,7 +55,11 @@ import org.geysermc.geyser.api.GeyserApi;
import org.geysermc.geyser.api.command.CommandSource; import org.geysermc.geyser.api.command.CommandSource;
import org.geysermc.geyser.api.event.EventBus; import org.geysermc.geyser.api.event.EventBus;
import org.geysermc.geyser.api.event.EventRegistrar; import org.geysermc.geyser.api.event.EventRegistrar;
import org.geysermc.geyser.api.event.lifecycle.*; import org.geysermc.geyser.api.event.lifecycle.GeyserPostInitializeEvent;
import org.geysermc.geyser.api.event.lifecycle.GeyserPostReloadEvent;
import org.geysermc.geyser.api.event.lifecycle.GeyserPreInitializeEvent;
import org.geysermc.geyser.api.event.lifecycle.GeyserPreReloadEvent;
import org.geysermc.geyser.api.event.lifecycle.GeyserShutdownEvent;
import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.api.network.BedrockListener; import org.geysermc.geyser.api.network.BedrockListener;
import org.geysermc.geyser.api.network.RemoteServer; import org.geysermc.geyser.api.network.RemoteServer;
@ -85,7 +89,13 @@ import org.geysermc.geyser.skin.SkinProvider;
import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.text.MinecraftLocale;
import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.*; import org.geysermc.geyser.util.AssetUtils;
import org.geysermc.geyser.util.CooldownUtils;
import org.geysermc.geyser.util.DimensionUtils;
import org.geysermc.geyser.util.Metrics;
import org.geysermc.geyser.util.NewsHandler;
import org.geysermc.geyser.util.VersionCheckUtils;
import org.geysermc.geyser.util.WebUtils;
import org.geysermc.mcprotocollib.network.tcp.TcpSession; import org.geysermc.mcprotocollib.network.tcp.TcpSession;
import java.io.File; import java.io.File;
@ -97,11 +107,19 @@ import java.net.UnknownHostException;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.Key; import java.security.Key;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.*; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Consumer;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -646,16 +664,11 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.kick.done")); bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.kick.done"));
} }
scheduledThread.shutdown(); runIfNonNull(scheduledThread, ScheduledExecutorService::shutdown);
geyserServer.shutdown(); runIfNonNull(geyserServer, GeyserServer::shutdown);
if (skinUploader != null) { runIfNonNull(skinUploader, FloodgateSkinUploader::close);
skinUploader.close(); runIfNonNull(newsHandler, NewsHandler::shutdown);
} runIfNonNull(erosionUnixListener, UnixSocketClientListener::close);
newsHandler.shutdown();
if (this.erosionUnixListener != null) {
this.erosionUnixListener.close();
}
Registries.RESOURCE_PACKS.get().clear(); Registries.RESOURCE_PACKS.get().clear();
@ -836,6 +849,12 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
} }
} }
private <T> void runIfNonNull(T nullable, Consumer<T> consumer) {
if (nullable != null) {
consumer.accept(nullable);
}
}
private void scheduleRefreshTokensWrite() { private void scheduleRefreshTokensWrite() {
scheduledThread.execute(() -> { scheduledThread.execute(() -> {
// Ensure all writes are handled on the same thread // Ensure all writes are handled on the same thread

View file

@ -327,6 +327,7 @@ public final class EntityDefinitions {
TEXT_DISPLAY = EntityDefinition.inherited(TextDisplayEntity::new, displayBase) TEXT_DISPLAY = EntityDefinition.inherited(TextDisplayEntity::new, displayBase)
.type(EntityType.TEXT_DISPLAY) .type(EntityType.TEXT_DISPLAY)
.identifier("minecraft:armor_stand") .identifier("minecraft:armor_stand")
.offset(-0.5f)
.addTranslator(MetadataType.CHAT, TextDisplayEntity::setText) .addTranslator(MetadataType.CHAT, TextDisplayEntity::setText)
.addTranslator(null) // Line width .addTranslator(null) // Line width
.addTranslator(null) // Background color .addTranslator(null) // Background color

View file

@ -49,7 +49,8 @@ public enum GeyserAttributeType {
ATTACK_KNOCKBACK("minecraft:generic.attack_knockback", null, 1.5f, Float.MAX_VALUE, 0f), ATTACK_KNOCKBACK("minecraft:generic.attack_knockback", null, 1.5f, Float.MAX_VALUE, 0f),
ATTACK_SPEED("minecraft:generic.attack_speed", null, 0f, 1024f, 4f), ATTACK_SPEED("minecraft:generic.attack_speed", null, 0f, 1024f, 4f),
MAX_HEALTH("minecraft:generic.max_health", null, 0f, 1024f, 20f), MAX_HEALTH("minecraft:generic.max_health", null, 0f, 1024f, 20f),
SCALE("minecraft:generic.scale", null, 0.0625f, 16f, 1f), // Unused. Do we need this? SCALE("minecraft:generic.scale", null, 0.0625f, 16f, 1f),
BLOCK_INTERACTION_RANGE("minecraft:player.block_interaction_range", null, 0.0f, 64f, 4.5f),
// Bedrock Attributes // Bedrock Attributes
ABSORPTION(null, "minecraft:absorption", 0f, 1024f, 0f), ABSORPTION(null, "minecraft:absorption", 0f, 1024f, 0f),

View file

@ -48,6 +48,7 @@ import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.item.ItemTranslator; import org.geysermc.geyser.translator.item.ItemTranslator;
import org.geysermc.geyser.util.AttributeUtils; import org.geysermc.geyser.util.AttributeUtils;
import org.geysermc.geyser.util.InteractionResult; import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.MathUtils;
import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.Attribute; 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.attribute.AttributeType;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
@ -252,7 +253,7 @@ public class LivingEntity extends Entity {
} }
private void setAttributeScale(float scale) { private void setAttributeScale(float scale) {
this.attributeScale = scale; this.attributeScale = MathUtils.clamp(scale, GeyserAttributeType.SCALE.getMinimum(), GeyserAttributeType.SCALE.getMaximum());
applyScale(); applyScale();
} }

View file

@ -38,7 +38,17 @@ import java.util.UUID;
// Note: 1.19.4 requires that the billboard is set to something in order to show, on Java Edition // Note: 1.19.4 requires that the billboard is set to something in order to show, on Java Edition
public class TextDisplayEntity extends DisplayBaseEntity { public class TextDisplayEntity extends DisplayBaseEntity {
public TextDisplayEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { public TextDisplayEntity(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); super(session, entityId, geyserId, uuid, definition, position.add(0, definition.offset(), 0), motion, yaw, pitch, headYaw);
}
@Override
public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, boolean isOnGround) {
super.moveRelative(relX, relY + definition.offset(), relZ, yaw, pitch, isOnGround);
}
@Override
public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) {
super.moveAbsolute(position.add(Vector3f.from(0, definition.offset(), 0)), yaw, pitch, headYaw, isOnGround, teleported);
} }
@Override @Override

View file

@ -31,7 +31,6 @@ import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag; import org.geysermc.geyser.session.cache.tags.ItemTag;
@ -39,13 +38,9 @@ import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag; import org.geysermc.geyser.util.InteractiveTag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
public class ParrotEntity extends TameableEntity { public class ParrotEntity extends TameableEntity {
// Note: is the same as chicken. Reuse?
private static final Set<Item> TAMING_FOOD = Set.of(Items.WHEAT_SEEDS, Items.MELON_SEEDS, Items.PUMPKIN_SEEDS, Items.BEETROOT_SEEDS);
public ParrotEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { public ParrotEntity(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); super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
} }

View file

@ -64,6 +64,11 @@ public class SessionPlayerEntity extends PlayerEntity {
*/ */
@Getter @Getter
protected final Map<GeyserAttributeType, AttributeData> attributes = new Object2ObjectOpenHashMap<>(); protected final Map<GeyserAttributeType, AttributeData> attributes = new Object2ObjectOpenHashMap<>();
/**
* Java-only attribute
*/
@Getter
private double blockInteractionRange = GeyserAttributeType.BLOCK_INTERACTION_RANGE.getDefaultValue();
/** /**
* Used in PlayerInputTranslator for movement checks. * Used in PlayerInputTranslator for movement checks.
*/ */
@ -232,6 +237,8 @@ public class SessionPlayerEntity extends PlayerEntity {
protected void updateAttribute(Attribute javaAttribute, List<AttributeData> newAttributes) { protected void updateAttribute(Attribute javaAttribute, List<AttributeData> newAttributes) {
if (javaAttribute.getType() == AttributeType.Builtin.GENERIC_ATTACK_SPEED) { if (javaAttribute.getType() == AttributeType.Builtin.GENERIC_ATTACK_SPEED) {
session.setAttackSpeed(AttributeUtils.calculateValue(javaAttribute)); session.setAttackSpeed(AttributeUtils.calculateValue(javaAttribute));
} else if (javaAttribute.getType() == AttributeType.Builtin.PLAYER_BLOCK_INTERACTION_RANGE) {
this.blockInteractionRange = AttributeUtils.calculateValue(javaAttribute);
} else { } else {
super.updateAttribute(javaAttribute, newAttributes); super.updateAttribute(javaAttribute, newAttributes);
} }
@ -295,6 +302,7 @@ public class SessionPlayerEntity extends PlayerEntity {
public void resetAttributes() { public void resetAttributes() {
attributes.clear(); attributes.clear();
maxHealth = GeyserAttributeType.MAX_HEALTH.getDefaultValue(); maxHealth = GeyserAttributeType.MAX_HEALTH.getDefaultValue();
blockInteractionRange = GeyserAttributeType.BLOCK_INTERACTION_RANGE.getDefaultValue();
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
attributesPacket.setRuntimeEntityId(geyserId); attributesPacket.setRuntimeEntityId(geyserId);

View file

@ -29,6 +29,7 @@ import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import org.jetbrains.annotations.Range; import org.jetbrains.annotations.Range;
@ -73,6 +74,10 @@ public class PlayerInventory extends Inventory {
return items[36 + heldItemSlot]; return items[36 + heldItemSlot];
} }
public boolean eitherHandMatchesItem(@NonNull Item item) {
return getItemInHand().asItem() == item || getItemInHand(Hand.OFF_HAND).asItem() == item;
}
public void setItemInHand(@NonNull GeyserItemStack item) { public void setItemInHand(@NonNull GeyserItemStack item) {
if (36 + heldItemSlot > this.size) { if (36 + heldItemSlot > this.size) {
GeyserImpl.getInstance().getLogger().debug("Held item slot was larger than expected!"); GeyserImpl.getInstance().getLogger().debug("Held item slot was larger than expected!");

View file

@ -50,6 +50,7 @@ public class StoredItemMappings {
private final ItemMapping milkBucket; private final ItemMapping milkBucket;
private final ItemMapping powderSnowBucket; private final ItemMapping powderSnowBucket;
private final ItemMapping shield; private final ItemMapping shield;
private final ItemMapping totem;
private final ItemMapping upgradeTemplate; private final ItemMapping upgradeTemplate;
private final ItemMapping wheat; private final ItemMapping wheat;
private final ItemMapping writableBook; private final ItemMapping writableBook;
@ -66,6 +67,7 @@ public class StoredItemMappings {
this.milkBucket = load(itemMappings, Items.MILK_BUCKET); this.milkBucket = load(itemMappings, Items.MILK_BUCKET);
this.powderSnowBucket = load(itemMappings, Items.POWDER_SNOW_BUCKET); this.powderSnowBucket = load(itemMappings, Items.POWDER_SNOW_BUCKET);
this.shield = load(itemMappings, Items.SHIELD); this.shield = load(itemMappings, Items.SHIELD);
this.totem = load(itemMappings, Items.TOTEM_OF_UNDYING);
this.upgradeTemplate = load(itemMappings, Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE); this.upgradeTemplate = load(itemMappings, Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE);
this.wheat = load(itemMappings, Items.WHEAT); this.wheat = load(itemMappings, Items.WHEAT);
this.writableBook = load(itemMappings, Items.WRITABLE_BOOK); this.writableBook = load(itemMappings, Items.WRITABLE_BOOK);

View file

@ -25,6 +25,7 @@
package org.geysermc.geyser.item; package org.geysermc.geyser.item;
import org.geysermc.geyser.item.components.Rarity;
import org.geysermc.geyser.item.components.ToolTier; import org.geysermc.geyser.item.components.ToolTier;
import org.geysermc.geyser.item.type.*; import org.geysermc.geyser.item.type.*;
import org.geysermc.geyser.level.block.Blocks; import org.geysermc.geyser.level.block.Blocks;
@ -122,7 +123,7 @@ public final class Items {
public static final Item RAW_IRON_BLOCK = register(new BlockItem(builder(), Blocks.RAW_IRON_BLOCK)); public static final Item RAW_IRON_BLOCK = register(new BlockItem(builder(), Blocks.RAW_IRON_BLOCK));
public static final Item RAW_COPPER_BLOCK = register(new BlockItem(builder(), Blocks.RAW_COPPER_BLOCK)); public static final Item RAW_COPPER_BLOCK = register(new BlockItem(builder(), Blocks.RAW_COPPER_BLOCK));
public static final Item RAW_GOLD_BLOCK = register(new BlockItem(builder(), Blocks.RAW_GOLD_BLOCK)); public static final Item RAW_GOLD_BLOCK = register(new BlockItem(builder(), Blocks.RAW_GOLD_BLOCK));
public static final Item HEAVY_CORE = register(new BlockItem(builder(), Blocks.HEAVY_CORE)); public static final Item HEAVY_CORE = register(new BlockItem(builder().rarity(Rarity.EPIC), Blocks.HEAVY_CORE));
public static final Item AMETHYST_BLOCK = register(new BlockItem(builder(), Blocks.AMETHYST_BLOCK)); public static final Item AMETHYST_BLOCK = register(new BlockItem(builder(), Blocks.AMETHYST_BLOCK));
public static final Item BUDDING_AMETHYST = register(new BlockItem(builder(), Blocks.BUDDING_AMETHYST)); public static final Item BUDDING_AMETHYST = register(new BlockItem(builder(), Blocks.BUDDING_AMETHYST));
public static final Item IRON_BLOCK = register(new BlockItem(builder(), Blocks.IRON_BLOCK)); public static final Item IRON_BLOCK = register(new BlockItem(builder(), Blocks.IRON_BLOCK));
@ -416,7 +417,7 @@ public final class Items {
public static final Item END_PORTAL_FRAME = register(new BlockItem(builder(), Blocks.END_PORTAL_FRAME)); public static final Item END_PORTAL_FRAME = register(new BlockItem(builder(), Blocks.END_PORTAL_FRAME));
public static final Item END_STONE = register(new BlockItem(builder(), Blocks.END_STONE)); public static final Item END_STONE = register(new BlockItem(builder(), Blocks.END_STONE));
public static final Item END_STONE_BRICKS = register(new BlockItem(builder(), Blocks.END_STONE_BRICKS)); public static final Item END_STONE_BRICKS = register(new BlockItem(builder(), Blocks.END_STONE_BRICKS));
public static final Item DRAGON_EGG = register(new BlockItem(builder(), Blocks.DRAGON_EGG)); public static final Item DRAGON_EGG = register(new BlockItem(builder().rarity(Rarity.EPIC), Blocks.DRAGON_EGG));
public static final Item SANDSTONE_STAIRS = register(new BlockItem(builder(), Blocks.SANDSTONE_STAIRS)); public static final Item SANDSTONE_STAIRS = register(new BlockItem(builder(), Blocks.SANDSTONE_STAIRS));
public static final Item ENDER_CHEST = register(new BlockItem(builder(), Blocks.ENDER_CHEST)); public static final Item ENDER_CHEST = register(new BlockItem(builder(), Blocks.ENDER_CHEST));
public static final Item EMERALD_BLOCK = register(new BlockItem(builder(), Blocks.EMERALD_BLOCK)); public static final Item EMERALD_BLOCK = register(new BlockItem(builder(), Blocks.EMERALD_BLOCK));
@ -432,8 +433,8 @@ public final class Items {
public static final Item BAMBOO_MOSAIC_STAIRS = register(new BlockItem(builder(), Blocks.BAMBOO_MOSAIC_STAIRS)); public static final Item BAMBOO_MOSAIC_STAIRS = register(new BlockItem(builder(), Blocks.BAMBOO_MOSAIC_STAIRS));
public static final Item CRIMSON_STAIRS = register(new BlockItem(builder(), Blocks.CRIMSON_STAIRS)); public static final Item CRIMSON_STAIRS = register(new BlockItem(builder(), Blocks.CRIMSON_STAIRS));
public static final Item WARPED_STAIRS = register(new BlockItem(builder(), Blocks.WARPED_STAIRS)); public static final Item WARPED_STAIRS = register(new BlockItem(builder(), Blocks.WARPED_STAIRS));
public static final Item COMMAND_BLOCK = register(new BlockItem(builder(), Blocks.COMMAND_BLOCK)); public static final Item COMMAND_BLOCK = register(new BlockItem(builder().rarity(Rarity.EPIC), Blocks.COMMAND_BLOCK));
public static final Item BEACON = register(new BlockItem(builder(), Blocks.BEACON)); public static final Item BEACON = register(new BlockItem(builder().rarity(Rarity.RARE), Blocks.BEACON));
public static final Item COBBLESTONE_WALL = register(new BlockItem(builder(), Blocks.COBBLESTONE_WALL)); public static final Item COBBLESTONE_WALL = register(new BlockItem(builder(), Blocks.COBBLESTONE_WALL));
public static final Item MOSSY_COBBLESTONE_WALL = register(new BlockItem(builder(), Blocks.MOSSY_COBBLESTONE_WALL)); public static final Item MOSSY_COBBLESTONE_WALL = register(new BlockItem(builder(), Blocks.MOSSY_COBBLESTONE_WALL));
public static final Item BRICK_WALL = register(new BlockItem(builder(), Blocks.BRICK_WALL)); public static final Item BRICK_WALL = register(new BlockItem(builder(), Blocks.BRICK_WALL));
@ -480,8 +481,8 @@ public final class Items {
public static final Item GREEN_TERRACOTTA = register(new BlockItem(builder(), Blocks.GREEN_TERRACOTTA)); public static final Item GREEN_TERRACOTTA = register(new BlockItem(builder(), Blocks.GREEN_TERRACOTTA));
public static final Item RED_TERRACOTTA = register(new BlockItem(builder(), Blocks.RED_TERRACOTTA)); public static final Item RED_TERRACOTTA = register(new BlockItem(builder(), Blocks.RED_TERRACOTTA));
public static final Item BLACK_TERRACOTTA = register(new BlockItem(builder(), Blocks.BLACK_TERRACOTTA)); public static final Item BLACK_TERRACOTTA = register(new BlockItem(builder(), Blocks.BLACK_TERRACOTTA));
public static final Item BARRIER = register(new BlockItem(builder(), Blocks.BARRIER)); public static final Item BARRIER = register(new BlockItem(builder().rarity(Rarity.EPIC), Blocks.BARRIER));
public static final Item LIGHT = register(new BlockItem(builder(), Blocks.LIGHT)); public static final Item LIGHT = register(new BlockItem(builder().rarity(Rarity.EPIC), Blocks.LIGHT));
public static final Item HAY_BLOCK = register(new BlockItem(builder(), Blocks.HAY_BLOCK)); public static final Item HAY_BLOCK = register(new BlockItem(builder(), Blocks.HAY_BLOCK));
public static final Item WHITE_CARPET = register(new BlockItem(builder(), Blocks.WHITE_CARPET)); public static final Item WHITE_CARPET = register(new BlockItem(builder(), Blocks.WHITE_CARPET));
public static final Item ORANGE_CARPET = register(new BlockItem(builder(), Blocks.ORANGE_CARPET)); public static final Item ORANGE_CARPET = register(new BlockItem(builder(), Blocks.ORANGE_CARPET));
@ -551,14 +552,14 @@ public final class Items {
public static final Item CHISELED_RED_SANDSTONE = register(new BlockItem(builder(), Blocks.CHISELED_RED_SANDSTONE)); public static final Item CHISELED_RED_SANDSTONE = register(new BlockItem(builder(), Blocks.CHISELED_RED_SANDSTONE));
public static final Item CUT_RED_SANDSTONE = register(new BlockItem(builder(), Blocks.CUT_RED_SANDSTONE)); public static final Item CUT_RED_SANDSTONE = register(new BlockItem(builder(), Blocks.CUT_RED_SANDSTONE));
public static final Item RED_SANDSTONE_STAIRS = register(new BlockItem(builder(), Blocks.RED_SANDSTONE_STAIRS)); public static final Item RED_SANDSTONE_STAIRS = register(new BlockItem(builder(), Blocks.RED_SANDSTONE_STAIRS));
public static final Item REPEATING_COMMAND_BLOCK = register(new BlockItem(builder(), Blocks.REPEATING_COMMAND_BLOCK)); public static final Item REPEATING_COMMAND_BLOCK = register(new BlockItem(builder().rarity(Rarity.EPIC), Blocks.REPEATING_COMMAND_BLOCK));
public static final Item CHAIN_COMMAND_BLOCK = register(new BlockItem(builder(), Blocks.CHAIN_COMMAND_BLOCK)); public static final Item CHAIN_COMMAND_BLOCK = register(new BlockItem(builder().rarity(Rarity.EPIC), Blocks.CHAIN_COMMAND_BLOCK));
public static final Item MAGMA_BLOCK = register(new BlockItem(builder(), Blocks.MAGMA_BLOCK)); public static final Item MAGMA_BLOCK = register(new BlockItem(builder(), Blocks.MAGMA_BLOCK));
public static final Item NETHER_WART_BLOCK = register(new BlockItem(builder(), Blocks.NETHER_WART_BLOCK)); public static final Item NETHER_WART_BLOCK = register(new BlockItem(builder(), Blocks.NETHER_WART_BLOCK));
public static final Item WARPED_WART_BLOCK = register(new BlockItem(builder(), Blocks.WARPED_WART_BLOCK)); public static final Item WARPED_WART_BLOCK = register(new BlockItem(builder(), Blocks.WARPED_WART_BLOCK));
public static final Item RED_NETHER_BRICKS = register(new BlockItem(builder(), Blocks.RED_NETHER_BRICKS)); public static final Item RED_NETHER_BRICKS = register(new BlockItem(builder(), Blocks.RED_NETHER_BRICKS));
public static final Item BONE_BLOCK = register(new BlockItem(builder(), Blocks.BONE_BLOCK)); public static final Item BONE_BLOCK = register(new BlockItem(builder(), Blocks.BONE_BLOCK));
public static final Item STRUCTURE_VOID = register(new BlockItem(builder(), Blocks.STRUCTURE_VOID)); public static final Item STRUCTURE_VOID = register(new BlockItem(builder().rarity(Rarity.EPIC), Blocks.STRUCTURE_VOID));
public static final Item SHULKER_BOX = register(new ShulkerBoxItem(builder().stackSize(1), Blocks.SHULKER_BOX)); public static final Item SHULKER_BOX = register(new ShulkerBoxItem(builder().stackSize(1), Blocks.SHULKER_BOX));
public static final Item WHITE_SHULKER_BOX = register(new ShulkerBoxItem(builder().stackSize(1), Blocks.WHITE_SHULKER_BOX)); public static final Item WHITE_SHULKER_BOX = register(new ShulkerBoxItem(builder().stackSize(1), Blocks.WHITE_SHULKER_BOX));
public static final Item ORANGE_SHULKER_BOX = register(new ShulkerBoxItem(builder().stackSize(1), Blocks.ORANGE_SHULKER_BOX)); public static final Item ORANGE_SHULKER_BOX = register(new ShulkerBoxItem(builder().stackSize(1), Blocks.ORANGE_SHULKER_BOX));
@ -657,7 +658,7 @@ public final class Items {
public static final Item DEAD_FIRE_CORAL_FAN = register(new BlockItem(builder(), Blocks.DEAD_FIRE_CORAL_FAN, Blocks.DEAD_FIRE_CORAL_WALL_FAN)); public static final Item DEAD_FIRE_CORAL_FAN = register(new BlockItem(builder(), Blocks.DEAD_FIRE_CORAL_FAN, Blocks.DEAD_FIRE_CORAL_WALL_FAN));
public static final Item DEAD_HORN_CORAL_FAN = register(new BlockItem(builder(), Blocks.DEAD_HORN_CORAL_FAN, Blocks.DEAD_HORN_CORAL_WALL_FAN)); public static final Item DEAD_HORN_CORAL_FAN = register(new BlockItem(builder(), Blocks.DEAD_HORN_CORAL_FAN, Blocks.DEAD_HORN_CORAL_WALL_FAN));
public static final Item BLUE_ICE = register(new BlockItem(builder(), Blocks.BLUE_ICE)); public static final Item BLUE_ICE = register(new BlockItem(builder(), Blocks.BLUE_ICE));
public static final Item CONDUIT = register(new BlockItem(builder(), Blocks.CONDUIT)); public static final Item CONDUIT = register(new BlockItem(builder().rarity(Rarity.RARE), Blocks.CONDUIT));
public static final Item POLISHED_GRANITE_STAIRS = register(new BlockItem(builder(), Blocks.POLISHED_GRANITE_STAIRS)); public static final Item POLISHED_GRANITE_STAIRS = register(new BlockItem(builder(), Blocks.POLISHED_GRANITE_STAIRS));
public static final Item SMOOTH_RED_SANDSTONE_STAIRS = register(new BlockItem(builder(), Blocks.SMOOTH_RED_SANDSTONE_STAIRS)); public static final Item SMOOTH_RED_SANDSTONE_STAIRS = register(new BlockItem(builder(), Blocks.SMOOTH_RED_SANDSTONE_STAIRS));
public static final Item MOSSY_STONE_BRICK_STAIRS = register(new BlockItem(builder(), Blocks.MOSSY_STONE_BRICK_STAIRS)); public static final Item MOSSY_STONE_BRICK_STAIRS = register(new BlockItem(builder(), Blocks.MOSSY_STONE_BRICK_STAIRS));
@ -810,7 +811,7 @@ public final class Items {
public static final Item HOPPER_MINECART = register(new Item("hopper_minecart", builder().stackSize(1))); public static final Item HOPPER_MINECART = register(new Item("hopper_minecart", builder().stackSize(1)));
public static final Item CARROT_ON_A_STICK = register(new Item("carrot_on_a_stick", builder().stackSize(1).maxDamage(25))); public static final Item CARROT_ON_A_STICK = register(new Item("carrot_on_a_stick", builder().stackSize(1).maxDamage(25)));
public static final Item WARPED_FUNGUS_ON_A_STICK = register(new Item("warped_fungus_on_a_stick", builder().stackSize(1).maxDamage(100))); public static final Item WARPED_FUNGUS_ON_A_STICK = register(new Item("warped_fungus_on_a_stick", builder().stackSize(1).maxDamage(100)));
public static final Item ELYTRA = register(new ElytraItem("elytra", builder().stackSize(1).maxDamage(432))); public static final Item ELYTRA = register(new ElytraItem("elytra", builder().stackSize(1).maxDamage(432).rarity(Rarity.UNCOMMON)));
public static final Item OAK_BOAT = register(new BoatItem("oak_boat", builder().stackSize(1))); public static final Item OAK_BOAT = register(new BoatItem("oak_boat", builder().stackSize(1)));
public static final Item OAK_CHEST_BOAT = register(new BoatItem("oak_chest_boat", builder().stackSize(1))); public static final Item OAK_CHEST_BOAT = register(new BoatItem("oak_chest_boat", builder().stackSize(1)));
public static final Item SPRUCE_BOAT = register(new BoatItem("spruce_boat", builder().stackSize(1))); public static final Item SPRUCE_BOAT = register(new BoatItem("spruce_boat", builder().stackSize(1)));
@ -829,8 +830,8 @@ public final class Items {
public static final Item MANGROVE_CHEST_BOAT = register(new BoatItem("mangrove_chest_boat", builder().stackSize(1))); public static final Item MANGROVE_CHEST_BOAT = register(new BoatItem("mangrove_chest_boat", builder().stackSize(1)));
public static final Item BAMBOO_RAFT = register(new BoatItem("bamboo_raft", builder().stackSize(1))); public static final Item BAMBOO_RAFT = register(new BoatItem("bamboo_raft", builder().stackSize(1)));
public static final Item BAMBOO_CHEST_RAFT = register(new BoatItem("bamboo_chest_raft", builder().stackSize(1))); public static final Item BAMBOO_CHEST_RAFT = register(new BoatItem("bamboo_chest_raft", builder().stackSize(1)));
public static final Item STRUCTURE_BLOCK = register(new BlockItem(builder(), Blocks.STRUCTURE_BLOCK)); public static final Item STRUCTURE_BLOCK = register(new BlockItem(builder().rarity(Rarity.EPIC), Blocks.STRUCTURE_BLOCK));
public static final Item JIGSAW = register(new BlockItem(builder(), Blocks.JIGSAW)); public static final Item JIGSAW = register(new BlockItem(builder().rarity(Rarity.EPIC), Blocks.JIGSAW));
public static final Item TURTLE_HELMET = register(new ArmorItem("turtle_helmet", ArmorMaterial.TURTLE, builder().stackSize(1).maxDamage(275))); public static final Item TURTLE_HELMET = register(new ArmorItem("turtle_helmet", ArmorMaterial.TURTLE, builder().stackSize(1).maxDamage(275)));
public static final Item TURTLE_SCUTE = register(new Item("turtle_scute", 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())); public static final Item ARMADILLO_SCUTE = register(new Item("armadillo_scute", builder()));
@ -921,8 +922,8 @@ public final class Items {
public static final Item PORKCHOP = register(new Item("porkchop", builder())); public static final Item PORKCHOP = register(new Item("porkchop", builder()));
public static final Item COOKED_PORKCHOP = register(new Item("cooked_porkchop", builder())); public static final Item COOKED_PORKCHOP = register(new Item("cooked_porkchop", builder()));
public static final Item PAINTING = register(new Item("painting", builder())); public static final Item PAINTING = register(new Item("painting", builder()));
public static final Item GOLDEN_APPLE = register(new Item("golden_apple", builder())); public static final Item GOLDEN_APPLE = register(new Item("golden_apple", builder().rarity(Rarity.RARE)));
public static final Item ENCHANTED_GOLDEN_APPLE = register(new Item("enchanted_golden_apple", builder())); public static final Item ENCHANTED_GOLDEN_APPLE = register(new Item("enchanted_golden_apple", builder().rarity(Rarity.EPIC)));
public static final Item OAK_SIGN = register(new BlockItem(builder().stackSize(16), Blocks.OAK_SIGN, Blocks.OAK_WALL_SIGN)); public static final Item OAK_SIGN = register(new BlockItem(builder().stackSize(16), Blocks.OAK_SIGN, Blocks.OAK_WALL_SIGN));
public static final Item SPRUCE_SIGN = register(new BlockItem(builder().stackSize(16), Blocks.SPRUCE_SIGN, Blocks.SPRUCE_WALL_SIGN)); public static final Item SPRUCE_SIGN = register(new BlockItem(builder().stackSize(16), Blocks.SPRUCE_SIGN, Blocks.SPRUCE_WALL_SIGN));
public static final Item BIRCH_SIGN = register(new BlockItem(builder().stackSize(16), Blocks.BIRCH_SIGN, Blocks.BIRCH_WALL_SIGN)); public static final Item BIRCH_SIGN = register(new BlockItem(builder().stackSize(16), Blocks.BIRCH_SIGN, Blocks.BIRCH_WALL_SIGN));
@ -1042,7 +1043,7 @@ public final class Items {
public static final Item BLAZE_POWDER = register(new Item("blaze_powder", builder())); 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 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 BREWING_STAND = register(new BlockItem(builder(), Blocks.BREWING_STAND));
public static final Item CAULDRON = register(new BlockItem(builder(), Blocks.CAULDRON, Blocks.WATER_CAULDRON, Blocks.LAVA_CAULDRON, Blocks.POWDER_SNOW_CAULDRON)); public static final Item CAULDRON = register(new BlockItem(builder(), Blocks.CAULDRON, Blocks.WATER_CAULDRON, Blocks.POWDER_SNOW_CAULDRON, Blocks.LAVA_CAULDRON));
public static final Item ENDER_EYE = register(new Item("ender_eye", builder())); 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 GLISTERING_MELON_SLICE = register(new Item("glistering_melon_slice", builder()));
public static final Item ARMADILLO_SPAWN_EGG = register(new SpawnEggItem("armadillo_spawn_egg", builder())); public static final Item ARMADILLO_SPAWN_EGG = register(new SpawnEggItem("armadillo_spawn_egg", builder()));
@ -1125,12 +1126,12 @@ public final class Items {
public static final Item ZOMBIE_HORSE_SPAWN_EGG = register(new SpawnEggItem("zombie_horse_spawn_egg", builder())); public static final Item ZOMBIE_HORSE_SPAWN_EGG = register(new SpawnEggItem("zombie_horse_spawn_egg", builder()));
public static final Item ZOMBIE_VILLAGER_SPAWN_EGG = register(new SpawnEggItem("zombie_villager_spawn_egg", builder())); public static final Item ZOMBIE_VILLAGER_SPAWN_EGG = register(new SpawnEggItem("zombie_villager_spawn_egg", builder()));
public static final Item ZOMBIFIED_PIGLIN_SPAWN_EGG = register(new SpawnEggItem("zombified_piglin_spawn_egg", builder())); public static final Item ZOMBIFIED_PIGLIN_SPAWN_EGG = register(new SpawnEggItem("zombified_piglin_spawn_egg", builder()));
public static final Item EXPERIENCE_BOTTLE = register(new Item("experience_bottle", builder())); public static final Item EXPERIENCE_BOTTLE = register(new Item("experience_bottle", builder().rarity(Rarity.UNCOMMON)));
public static final Item FIRE_CHARGE = register(new Item("fire_charge", builder())); public static final Item FIRE_CHARGE = register(new Item("fire_charge", builder()));
public static final Item WIND_CHARGE = register(new Item("wind_charge", builder())); public static final Item WIND_CHARGE = register(new Item("wind_charge", builder()));
public static final Item WRITABLE_BOOK = register(new WritableBookItem("writable_book", builder().stackSize(1))); public static final Item WRITABLE_BOOK = register(new WritableBookItem("writable_book", builder().stackSize(1)));
public static final Item WRITTEN_BOOK = register(new WrittenBookItem("written_book", builder().stackSize(16))); public static final Item WRITTEN_BOOK = register(new WrittenBookItem("written_book", builder().stackSize(16)));
public static final Item MACE = register(new MaceItem("mace", builder().stackSize(1).maxDamage(500))); public static final Item MACE = register(new MaceItem("mace", builder().stackSize(1).maxDamage(500).rarity(Rarity.EPIC)));
public static final Item ITEM_FRAME = register(new Item("item_frame", builder())); 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 GLOW_ITEM_FRAME = register(new Item("glow_item_frame", builder()));
public static final Item FLOWER_POT = register(new BlockItem(builder(), Blocks.FLOWER_POT)); public static final Item FLOWER_POT = register(new BlockItem(builder(), Blocks.FLOWER_POT));
@ -1140,18 +1141,18 @@ public final class Items {
public static final Item POISONOUS_POTATO = register(new Item("poisonous_potato", builder())); public static final Item POISONOUS_POTATO = register(new Item("poisonous_potato", builder()));
public static final Item MAP = register(new MapItem("map", builder())); public static final Item MAP = register(new MapItem("map", builder()));
public static final Item GOLDEN_CARROT = register(new Item("golden_carrot", builder())); public static final Item GOLDEN_CARROT = register(new Item("golden_carrot", builder()));
public static final Item SKELETON_SKULL = register(new BlockItem(builder(), Blocks.SKELETON_SKULL, Blocks.SKELETON_WALL_SKULL)); public static final Item SKELETON_SKULL = register(new BlockItem(builder().rarity(Rarity.UNCOMMON), Blocks.SKELETON_SKULL, Blocks.SKELETON_WALL_SKULL));
public static final Item WITHER_SKELETON_SKULL = register(new BlockItem(builder(), Blocks.WITHER_SKELETON_SKULL, Blocks.WITHER_SKELETON_WALL_SKULL)); public static final Item WITHER_SKELETON_SKULL = register(new BlockItem(builder().rarity(Rarity.UNCOMMON), Blocks.WITHER_SKELETON_SKULL, Blocks.WITHER_SKELETON_WALL_SKULL));
public static final Item PLAYER_HEAD = register(new PlayerHeadItem(builder(), Blocks.PLAYER_HEAD, Blocks.PLAYER_WALL_HEAD)); public static final Item PLAYER_HEAD = register(new PlayerHeadItem(builder().rarity(Rarity.UNCOMMON), Blocks.PLAYER_HEAD, Blocks.PLAYER_WALL_HEAD));
public static final Item ZOMBIE_HEAD = register(new BlockItem(builder(), Blocks.ZOMBIE_HEAD, Blocks.ZOMBIE_WALL_HEAD)); public static final Item ZOMBIE_HEAD = register(new BlockItem(builder().rarity(Rarity.UNCOMMON), Blocks.ZOMBIE_HEAD, Blocks.ZOMBIE_WALL_HEAD));
public static final Item CREEPER_HEAD = register(new BlockItem(builder(), Blocks.CREEPER_HEAD, Blocks.CREEPER_WALL_HEAD)); public static final Item CREEPER_HEAD = register(new BlockItem(builder().rarity(Rarity.UNCOMMON), Blocks.CREEPER_HEAD, Blocks.CREEPER_WALL_HEAD));
public static final Item DRAGON_HEAD = register(new BlockItem(builder(), Blocks.DRAGON_HEAD, Blocks.DRAGON_WALL_HEAD)); public static final Item DRAGON_HEAD = register(new BlockItem(builder().rarity(Rarity.UNCOMMON), Blocks.DRAGON_HEAD, Blocks.DRAGON_WALL_HEAD));
public static final Item PIGLIN_HEAD = register(new BlockItem(builder(), Blocks.PIGLIN_HEAD, Blocks.PIGLIN_WALL_HEAD)); public static final Item PIGLIN_HEAD = register(new BlockItem(builder().rarity(Rarity.UNCOMMON), Blocks.PIGLIN_HEAD, Blocks.PIGLIN_WALL_HEAD));
public static final Item NETHER_STAR = register(new Item("nether_star", builder())); public static final Item NETHER_STAR = register(new Item("nether_star", builder().rarity(Rarity.UNCOMMON)));
public static final Item PUMPKIN_PIE = register(new Item("pumpkin_pie", builder())); public static final Item PUMPKIN_PIE = register(new Item("pumpkin_pie", builder()));
public static final Item FIREWORK_ROCKET = register(new FireworkRocketItem("firework_rocket", builder())); public static final Item FIREWORK_ROCKET = register(new FireworkRocketItem("firework_rocket", builder()));
public static final Item FIREWORK_STAR = register(new FireworkStarItem("firework_star", builder())); public static final Item FIREWORK_STAR = register(new FireworkStarItem("firework_star", builder()));
public static final Item ENCHANTED_BOOK = register(new EnchantedBookItem("enchanted_book", builder().stackSize(1))); public static final Item ENCHANTED_BOOK = register(new EnchantedBookItem("enchanted_book", builder().stackSize(1).rarity(Rarity.UNCOMMON)));
public static final Item NETHER_BRICK = register(new Item("nether_brick", builder())); public static final Item NETHER_BRICK = register(new Item("nether_brick", builder()));
public static final Item PRISMARINE_SHARD = register(new Item("prismarine_shard", builder())); public static final Item PRISMARINE_SHARD = register(new Item("prismarine_shard", builder()));
public static final Item PRISMARINE_CRYSTALS = register(new Item("prismarine_crystals", builder())); public static final Item PRISMARINE_CRYSTALS = register(new Item("prismarine_crystals", builder()));
@ -1167,7 +1168,7 @@ public final class Items {
public static final Item LEATHER_HORSE_ARMOR = register(new DyeableArmorItem("leather_horse_armor", ArmorMaterial.LEATHER, builder().stackSize(1))); public static final Item LEATHER_HORSE_ARMOR = register(new DyeableArmorItem("leather_horse_armor", ArmorMaterial.LEATHER, builder().stackSize(1)));
public static final Item LEAD = register(new Item("lead", builder())); public static final Item LEAD = register(new Item("lead", builder()));
public static final Item NAME_TAG = register(new Item("name_tag", builder())); public static final Item NAME_TAG = register(new Item("name_tag", builder()));
public static final Item COMMAND_BLOCK_MINECART = register(new Item("command_block_minecart", builder().stackSize(1))); public static final Item COMMAND_BLOCK_MINECART = register(new Item("command_block_minecart", builder().stackSize(1).rarity(Rarity.EPIC)));
public static final Item MUTTON = register(new Item("mutton", builder())); public static final Item MUTTON = register(new Item("mutton", builder()));
public static final Item COOKED_MUTTON = register(new Item("cooked_mutton", builder())); public static final Item COOKED_MUTTON = register(new Item("cooked_mutton", builder()));
public static final Item WHITE_BANNER = register(new BannerItem(builder().stackSize(16), Blocks.WHITE_BANNER, Blocks.WHITE_WALL_BANNER)); public static final Item WHITE_BANNER = register(new BannerItem(builder().stackSize(16), Blocks.WHITE_BANNER, Blocks.WHITE_WALL_BANNER));
@ -1186,7 +1187,7 @@ public final class Items {
public static final Item GREEN_BANNER = register(new BannerItem(builder().stackSize(16), Blocks.GREEN_BANNER, Blocks.GREEN_WALL_BANNER)); public static final Item GREEN_BANNER = register(new BannerItem(builder().stackSize(16), Blocks.GREEN_BANNER, Blocks.GREEN_WALL_BANNER));
public static final Item RED_BANNER = register(new BannerItem(builder().stackSize(16), Blocks.RED_BANNER, Blocks.RED_WALL_BANNER)); public static final Item RED_BANNER = register(new BannerItem(builder().stackSize(16), Blocks.RED_BANNER, Blocks.RED_WALL_BANNER));
public static final Item BLACK_BANNER = register(new BannerItem(builder().stackSize(16), Blocks.BLACK_BANNER, Blocks.BLACK_WALL_BANNER)); public static final Item BLACK_BANNER = register(new BannerItem(builder().stackSize(16), Blocks.BLACK_BANNER, Blocks.BLACK_WALL_BANNER));
public static final Item END_CRYSTAL = register(new Item("end_crystal", builder())); public static final Item END_CRYSTAL = register(new Item("end_crystal", builder().rarity(Rarity.RARE)));
public static final Item CHORUS_FRUIT = register(new Item("chorus_fruit", builder())); public static final Item CHORUS_FRUIT = register(new Item("chorus_fruit", builder()));
public static final Item POPPED_CHORUS_FRUIT = register(new Item("popped_chorus_fruit", builder())); public static final Item POPPED_CHORUS_FRUIT = register(new Item("popped_chorus_fruit", builder()));
public static final Item TORCHFLOWER_SEEDS = register(new BlockItem("torchflower_seeds", builder(), Blocks.TORCHFLOWER_CROP)); public static final Item TORCHFLOWER_SEEDS = register(new BlockItem("torchflower_seeds", builder(), Blocks.TORCHFLOWER_CROP));
@ -1194,52 +1195,52 @@ public final class Items {
public static final Item BEETROOT = register(new Item("beetroot", builder())); public static final Item BEETROOT = register(new Item("beetroot", builder()));
public static final Item BEETROOT_SEEDS = register(new BlockItem("beetroot_seeds", builder(), Blocks.BEETROOTS)); public static final Item BEETROOT_SEEDS = register(new BlockItem("beetroot_seeds", builder(), Blocks.BEETROOTS));
public static final Item BEETROOT_SOUP = register(new Item("beetroot_soup", builder().stackSize(1))); public static final Item BEETROOT_SOUP = register(new Item("beetroot_soup", builder().stackSize(1)));
public static final Item DRAGON_BREATH = register(new Item("dragon_breath", builder())); public static final Item DRAGON_BREATH = register(new Item("dragon_breath", builder().rarity(Rarity.UNCOMMON)));
public static final Item SPLASH_POTION = register(new PotionItem("splash_potion", builder().stackSize(1))); public static final Item SPLASH_POTION = register(new PotionItem("splash_potion", builder().stackSize(1)));
public static final Item SPECTRAL_ARROW = register(new Item("spectral_arrow", builder())); public static final Item SPECTRAL_ARROW = register(new Item("spectral_arrow", builder()));
public static final Item TIPPED_ARROW = register(new TippedArrowItem("tipped_arrow", builder())); public static final Item TIPPED_ARROW = register(new TippedArrowItem("tipped_arrow", builder()));
public static final Item LINGERING_POTION = register(new PotionItem("lingering_potion", builder().stackSize(1))); public static final Item LINGERING_POTION = register(new PotionItem("lingering_potion", builder().stackSize(1)));
public static final Item SHIELD = register(new ShieldItem("shield", builder().stackSize(1).maxDamage(336))); public static final Item SHIELD = register(new ShieldItem("shield", builder().stackSize(1).maxDamage(336)));
public static final Item TOTEM_OF_UNDYING = register(new Item("totem_of_undying", builder().stackSize(1))); public static final Item TOTEM_OF_UNDYING = register(new Item("totem_of_undying", builder().stackSize(1).rarity(Rarity.UNCOMMON)));
public static final Item SHULKER_SHELL = register(new Item("shulker_shell", builder())); public static final Item SHULKER_SHELL = register(new Item("shulker_shell", builder()));
public static final Item IRON_NUGGET = register(new Item("iron_nugget", builder())); public static final Item IRON_NUGGET = register(new Item("iron_nugget", builder()));
public static final Item KNOWLEDGE_BOOK = register(new Item("knowledge_book", builder().stackSize(1))); public static final Item KNOWLEDGE_BOOK = register(new Item("knowledge_book", builder().stackSize(1).rarity(Rarity.EPIC)));
public static final Item DEBUG_STICK = register(new Item("debug_stick", builder().stackSize(1))); public static final Item DEBUG_STICK = register(new Item("debug_stick", builder().stackSize(1).rarity(Rarity.EPIC).glint(true)));
public static final Item MUSIC_DISC_13 = register(new Item("music_disc_13", builder().stackSize(1))); public static final Item MUSIC_DISC_13 = register(new Item("music_disc_13", builder().stackSize(1).rarity(Rarity.RARE)));
public static final Item MUSIC_DISC_CAT = register(new Item("music_disc_cat", builder().stackSize(1))); public static final Item MUSIC_DISC_CAT = register(new Item("music_disc_cat", builder().stackSize(1).rarity(Rarity.RARE)));
public static final Item MUSIC_DISC_BLOCKS = register(new Item("music_disc_blocks", builder().stackSize(1))); public static final Item MUSIC_DISC_BLOCKS = register(new Item("music_disc_blocks", builder().stackSize(1).rarity(Rarity.RARE)));
public static final Item MUSIC_DISC_CHIRP = register(new Item("music_disc_chirp", builder().stackSize(1))); public static final Item MUSIC_DISC_CHIRP = register(new Item("music_disc_chirp", builder().stackSize(1).rarity(Rarity.RARE)));
public static final Item MUSIC_DISC_CREATOR = register(new Item("music_disc_creator", builder().stackSize(1))); public static final Item MUSIC_DISC_CREATOR = register(new Item("music_disc_creator", builder().stackSize(1).rarity(Rarity.RARE)));
public static final Item MUSIC_DISC_CREATOR_MUSIC_BOX = register(new Item("music_disc_creator_music_box", builder().stackSize(1))); public static final Item MUSIC_DISC_CREATOR_MUSIC_BOX = register(new Item("music_disc_creator_music_box", builder().stackSize(1).rarity(Rarity.RARE)));
public static final Item MUSIC_DISC_FAR = register(new Item("music_disc_far", builder().stackSize(1))); public static final Item MUSIC_DISC_FAR = register(new Item("music_disc_far", builder().stackSize(1).rarity(Rarity.RARE)));
public static final Item MUSIC_DISC_MALL = register(new Item("music_disc_mall", builder().stackSize(1))); public static final Item MUSIC_DISC_MALL = register(new Item("music_disc_mall", builder().stackSize(1).rarity(Rarity.RARE)));
public static final Item MUSIC_DISC_MELLOHI = register(new Item("music_disc_mellohi", builder().stackSize(1))); public static final Item MUSIC_DISC_MELLOHI = register(new Item("music_disc_mellohi", builder().stackSize(1).rarity(Rarity.RARE)));
public static final Item MUSIC_DISC_STAL = register(new Item("music_disc_stal", builder().stackSize(1))); public static final Item MUSIC_DISC_STAL = register(new Item("music_disc_stal", builder().stackSize(1).rarity(Rarity.RARE)));
public static final Item MUSIC_DISC_STRAD = register(new Item("music_disc_strad", builder().stackSize(1))); public static final Item MUSIC_DISC_STRAD = register(new Item("music_disc_strad", builder().stackSize(1).rarity(Rarity.RARE)));
public static final Item MUSIC_DISC_WARD = register(new Item("music_disc_ward", builder().stackSize(1))); public static final Item MUSIC_DISC_WARD = register(new Item("music_disc_ward", builder().stackSize(1).rarity(Rarity.RARE)));
public static final Item MUSIC_DISC_11 = register(new Item("music_disc_11", builder().stackSize(1))); public static final Item MUSIC_DISC_11 = register(new Item("music_disc_11", builder().stackSize(1).rarity(Rarity.RARE)));
public static final Item MUSIC_DISC_WAIT = register(new Item("music_disc_wait", builder().stackSize(1))); public static final Item MUSIC_DISC_WAIT = register(new Item("music_disc_wait", builder().stackSize(1).rarity(Rarity.RARE)));
public static final Item MUSIC_DISC_OTHERSIDE = register(new Item("music_disc_otherside", builder().stackSize(1))); public static final Item MUSIC_DISC_OTHERSIDE = register(new Item("music_disc_otherside", builder().stackSize(1).rarity(Rarity.RARE)));
public static final Item MUSIC_DISC_RELIC = register(new Item("music_disc_relic", builder().stackSize(1))); public static final Item MUSIC_DISC_RELIC = register(new Item("music_disc_relic", builder().stackSize(1).rarity(Rarity.RARE)));
public static final Item MUSIC_DISC_5 = register(new Item("music_disc_5", builder().stackSize(1))); public static final Item MUSIC_DISC_5 = register(new Item("music_disc_5", builder().stackSize(1).rarity(Rarity.RARE)));
public static final Item MUSIC_DISC_PIGSTEP = register(new Item("music_disc_pigstep", builder().stackSize(1))); public static final Item MUSIC_DISC_PIGSTEP = register(new Item("music_disc_pigstep", builder().stackSize(1).rarity(Rarity.RARE)));
public static final Item MUSIC_DISC_PRECIPICE = register(new Item("music_disc_precipice", builder().stackSize(1))); public static final Item MUSIC_DISC_PRECIPICE = register(new Item("music_disc_precipice", builder().stackSize(1).rarity(Rarity.RARE)));
public static final Item DISC_FRAGMENT_5 = register(new Item("disc_fragment_5", builder())); public static final Item DISC_FRAGMENT_5 = register(new Item("disc_fragment_5", builder()));
public static final Item TRIDENT = register(new Item("trident", builder().stackSize(1).maxDamage(250).attackDamage(9.0))); public static final Item TRIDENT = register(new Item("trident", builder().stackSize(1).maxDamage(250).attackDamage(9.0).rarity(Rarity.EPIC)));
public static final Item PHANTOM_MEMBRANE = register(new Item("phantom_membrane", builder())); public static final Item PHANTOM_MEMBRANE = register(new Item("phantom_membrane", builder()));
public static final Item NAUTILUS_SHELL = register(new Item("nautilus_shell", builder())); public static final Item NAUTILUS_SHELL = register(new Item("nautilus_shell", builder()));
public static final Item HEART_OF_THE_SEA = register(new Item("heart_of_the_sea", builder())); public static final Item HEART_OF_THE_SEA = register(new Item("heart_of_the_sea", builder().rarity(Rarity.UNCOMMON)));
public static final Item CROSSBOW = register(new CrossbowItem("crossbow", builder().stackSize(1).maxDamage(465))); public static final Item CROSSBOW = register(new CrossbowItem("crossbow", builder().stackSize(1).maxDamage(465)));
public static final Item SUSPICIOUS_STEW = register(new Item("suspicious_stew", builder().stackSize(1))); public static final Item SUSPICIOUS_STEW = register(new Item("suspicious_stew", builder().stackSize(1)));
public static final Item LOOM = register(new BlockItem(builder(), Blocks.LOOM)); public static final Item LOOM = register(new BlockItem(builder(), Blocks.LOOM));
public static final Item FLOWER_BANNER_PATTERN = register(new Item("flower_banner_pattern", builder().stackSize(1))); public static final Item FLOWER_BANNER_PATTERN = register(new Item("flower_banner_pattern", builder().stackSize(1)));
public static final Item CREEPER_BANNER_PATTERN = register(new Item("creeper_banner_pattern", builder().stackSize(1))); public static final Item CREEPER_BANNER_PATTERN = register(new Item("creeper_banner_pattern", builder().stackSize(1).rarity(Rarity.UNCOMMON)));
public static final Item SKULL_BANNER_PATTERN = register(new Item("skull_banner_pattern", builder().stackSize(1))); public static final Item SKULL_BANNER_PATTERN = register(new Item("skull_banner_pattern", builder().stackSize(1).rarity(Rarity.UNCOMMON)));
public static final Item MOJANG_BANNER_PATTERN = register(new Item("mojang_banner_pattern", builder().stackSize(1))); public static final Item MOJANG_BANNER_PATTERN = register(new Item("mojang_banner_pattern", builder().stackSize(1).rarity(Rarity.EPIC)));
public static final Item GLOBE_BANNER_PATTERN = register(new Item("globe_banner_pattern", builder().stackSize(1))); public static final Item GLOBE_BANNER_PATTERN = register(new Item("globe_banner_pattern", builder().stackSize(1)));
public static final Item PIGLIN_BANNER_PATTERN = register(new Item("piglin_banner_pattern", builder().stackSize(1))); public static final Item PIGLIN_BANNER_PATTERN = register(new Item("piglin_banner_pattern", builder().stackSize(1).rarity(Rarity.UNCOMMON)));
public static final Item FLOW_BANNER_PATTERN = register(new Item("flow_banner_pattern", builder().stackSize(1))); public static final Item FLOW_BANNER_PATTERN = register(new Item("flow_banner_pattern", builder().stackSize(1).rarity(Rarity.RARE)));
public static final Item GUSTER_BANNER_PATTERN = register(new Item("guster_banner_pattern", builder().stackSize(1))); public static final Item GUSTER_BANNER_PATTERN = register(new Item("guster_banner_pattern", builder().stackSize(1).rarity(Rarity.RARE)));
public static final Item GOAT_HORN = register(new GoatHornItem("goat_horn", builder().stackSize(1))); public static final Item GOAT_HORN = register(new GoatHornItem("goat_horn", builder().stackSize(1)));
public static final Item COMPOSTER = register(new BlockItem(builder(), Blocks.COMPOSTER)); public static final Item COMPOSTER = register(new BlockItem(builder(), Blocks.COMPOSTER));
public static final Item BARREL = register(new BlockItem(builder(), Blocks.BARREL)); public static final Item BARREL = register(new BlockItem(builder(), Blocks.BARREL));

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2024 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.components;
import lombok.Getter;
@Getter
public enum Rarity {
COMMON("common", 'f'),
UNCOMMON("uncommon", 'e'),
RARE("rare", 'b'),
EPIC("epic", 'd');
private final String name;
private final char color;
Rarity(final String name, char chatColor) {
this.name = name;
this.color = chatColor;
}
private static final Rarity[] VALUES = values();
public static Rarity fromId(int id) {
return VALUES.length > id ? VALUES[id] : VALUES[0];
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2024 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.type;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
public interface BedrockRequiresTagItem {
void addRequiredNbt(GeyserSession session, @Nullable DataComponents components, BedrockItemBuilder builder);
}

View file

@ -27,6 +27,8 @@ package org.geysermc.geyser.item.type;
import it.unimi.dsi.fastutil.ints.IntArrays; import it.unimi.dsi.fastutil.ints.IntArrays;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.nbt.NbtList;
import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder; import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.nbt.NbtType; import org.cloudburstmc.nbt.NbtType;
@ -41,7 +43,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.item.component.Fireworks;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class FireworkRocketItem extends Item { public class FireworkRocketItem extends Item implements BedrockRequiresTagItem {
public FireworkRocketItem(String javaIdentifier, Builder builder) { public FireworkRocketItem(String javaIdentifier, Builder builder) {
super(javaIdentifier, builder); super(javaIdentifier, builder);
} }
@ -58,14 +60,16 @@ public class FireworkRocketItem extends Item {
fireworksNbt.putByte("Flight", (byte) fireworks.getFlightDuration()); fireworksNbt.putByte("Flight", (byte) fireworks.getFlightDuration());
List<Fireworks.FireworkExplosion> explosions = fireworks.getExplosions(); List<Fireworks.FireworkExplosion> explosions = fireworks.getExplosions();
if (explosions.isEmpty()) { if (!explosions.isEmpty()) {
return; List<NbtMap> explosionNbt = new ArrayList<>();
for (Fireworks.FireworkExplosion explosion : explosions) {
explosionNbt.add(translateExplosionToBedrock(explosion));
}
fireworksNbt.putList("Explosions", NbtType.COMPOUND, explosionNbt);
} else {
// This is the default firework
fireworksNbt.put("Explosions", NbtList.EMPTY);
} }
List<NbtMap> explosionNbt = new ArrayList<>();
for (Fireworks.FireworkExplosion explosion : explosions) {
explosionNbt.add(translateExplosionToBedrock(explosion));
}
fireworksNbt.putList("Explosions", NbtType.COMPOUND, explosionNbt);
builder.putCompound("Fireworks", fireworksNbt.build()); builder.putCompound("Fireworks", fireworksNbt.build());
} }
@ -138,4 +142,20 @@ public class FireworkRocketItem extends Item {
return null; return null;
} }
} }
@Override
public void addRequiredNbt(GeyserSession session, @Nullable DataComponents components, BedrockItemBuilder builder) {
if (components != null) {
Fireworks fireworks = components.get(DataComponentType.FIREWORKS);
if (fireworks != null) {
// Already translated
return;
}
}
NbtMapBuilder fireworksNbt = NbtMap.builder();
fireworksNbt.putByte("Flight", (byte) 1);
fireworksNbt.put("Explosions", NbtList.EMPTY);
builder.putCompound("Fireworks", fireworksNbt.build());
}
} }

View file

@ -35,6 +35,7 @@ import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.inventory.item.BedrockEnchantment; import org.geysermc.geyser.inventory.item.BedrockEnchantment;
import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.components.Rarity;
import org.geysermc.geyser.item.enchantment.Enchantment; import org.geysermc.geyser.item.enchantment.Enchantment;
import org.geysermc.geyser.level.block.type.Block; import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMapping;
@ -63,12 +64,16 @@ public class Item {
private final int stackSize; private final int stackSize;
private final int attackDamage; private final int attackDamage;
private final int maxDamage; private final int maxDamage;
private final Rarity rarity;
private final boolean glint;
public Item(String javaIdentifier, Builder builder) { public Item(String javaIdentifier, Builder builder) {
this.javaIdentifier = MinecraftKey.key(javaIdentifier).asString().intern(); this.javaIdentifier = MinecraftKey.key(javaIdentifier).asString().intern();
this.stackSize = builder.stackSize; this.stackSize = builder.stackSize;
this.maxDamage = builder.maxDamage; this.maxDamage = builder.maxDamage;
this.attackDamage = builder.attackDamage; this.attackDamage = builder.attackDamage;
this.rarity = builder.rarity;
this.glint = builder.glint;
} }
public String javaIdentifier() { public String javaIdentifier() {
@ -91,6 +96,14 @@ public class Item {
return stackSize; return stackSize;
} }
public Rarity rarity() {
return rarity;
}
public boolean glint() {
return glint;
}
public boolean isValidRepairItem(Item other) { public boolean isValidRepairItem(Item other) {
return false; return false;
} }
@ -275,6 +288,8 @@ public class Item {
private int stackSize = 64; private int stackSize = 64;
private int maxDamage; private int maxDamage;
private int attackDamage; private int attackDamage;
private Rarity rarity = Rarity.COMMON;
private boolean glint = false;
public Builder stackSize(int stackSize) { public Builder stackSize(int stackSize) {
this.stackSize = stackSize; this.stackSize = stackSize;
@ -292,6 +307,16 @@ public class Item {
return this; return this;
} }
public Builder rarity(Rarity rarity) {
this.rarity = rarity;
return this;
}
public Builder glint(boolean glintOverride) {
this.glint = glintOverride;
return this;
}
private Builder() { private Builder() {
} }
} }

View file

@ -73,7 +73,9 @@ public final class GameProtocol {
SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(Bedrock_v685.CODEC.toBuilder() SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(Bedrock_v685.CODEC.toBuilder()
.minecraftVersion("1.21.0/1.21.1") .minecraftVersion("1.21.0/1.21.1")
.build())); .build()));
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder()
.minecraftVersion("1.21.2/1.21.3")
.build());
} }
/** /**

View file

@ -35,10 +35,10 @@ import java.net.InetSocketAddress;
public interface IGeyserPingPassthrough { public interface IGeyserPingPassthrough {
/** /**
* Get the MOTD of the server displayed on the multiplayer screen * Gets the ping information, including the MOTD and player count, from the server
* *
* @param inetSocketAddress the ip address of the client pinging the server * @param inetSocketAddress the ip address of the client pinging the server
* @return string of the MOTD * @return the ping information
*/ */
@Nullable @Nullable
GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress); GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress);

View file

@ -49,8 +49,6 @@ import java.util.function.Consumer;
public class CreativeItemRegistryPopulator { public class CreativeItemRegistryPopulator {
private static final List<BiPredicate<String, Integer>> JAVA_ONLY_ITEM_FILTER = List.of( private static final List<BiPredicate<String, Integer>> JAVA_ONLY_ITEM_FILTER = List.of(
// Just shows an empty texture; either way it doesn't exist in the creative menu on Java
(identifier, data) -> identifier.equals("minecraft:debug_stick"),
// Bedrock-only as its own item // Bedrock-only as its own item
(identifier, data) -> identifier.equals("minecraft:empty_map") && data == 2, (identifier, data) -> identifier.equals("minecraft:empty_map") && data == 2,
// Bedrock-only banner patterns // Bedrock-only banner patterns
@ -82,7 +80,6 @@ public class CreativeItemRegistryPopulator {
private static ItemData.@Nullable Builder createItemData(JsonNode itemNode, BlockMappings blockMappings, Map<String, ItemDefinition> definitions) { private static ItemData.@Nullable Builder createItemData(JsonNode itemNode, BlockMappings blockMappings, Map<String, ItemDefinition> definitions) {
int count = 1; int count = 1;
int damage = 0; int damage = 0;
int bedrockBlockRuntimeId;
NbtMap tag = null; NbtMap tag = null;
String identifier = itemNode.get("id").textValue(); String identifier = itemNode.get("id").textValue();
@ -103,16 +100,8 @@ public class CreativeItemRegistryPopulator {
} }
GeyserBedrockBlock blockDefinition = null; GeyserBedrockBlock blockDefinition = null;
JsonNode blockRuntimeIdNode = itemNode.get("blockRuntimeId");
JsonNode blockStateNode; JsonNode blockStateNode;
if (blockRuntimeIdNode != null) { if ((blockStateNode = itemNode.get("block_state_b64")) != null) {
bedrockBlockRuntimeId = blockRuntimeIdNode.asInt();
if (bedrockBlockRuntimeId == 0 && !identifier.equals("minecraft:blue_candle")) { // FIXME
bedrockBlockRuntimeId = -1;
}
blockDefinition = bedrockBlockRuntimeId == -1 ? null : blockMappings.getDefinition(bedrockBlockRuntimeId);
} else if ((blockStateNode = itemNode.get("block_state_b64")) != null) {
byte[] bytes = Base64.getDecoder().decode(blockStateNode.asText()); byte[] bytes = Base64.getDecoder().decode(blockStateNode.asText());
ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
try { try {

View file

@ -199,7 +199,13 @@ public class CustomItemRegistryPopulator {
computeThrowableProperties(componentBuilder); computeThrowableProperties(componentBuilder);
} }
computeRenderOffsets(false, customItemData, componentBuilder); // Hardcoded on Java, and should extend to the custom item
boolean isHat = (javaItem.equals(Items.SKELETON_SKULL) || javaItem.equals(Items.WITHER_SKELETON_SKULL)
|| javaItem.equals(Items.CARVED_PUMPKIN) || javaItem.equals(Items.ZOMBIE_HEAD)
|| javaItem.equals(Items.PIGLIN_HEAD) || javaItem.equals(Items.DRAGON_HEAD)
|| javaItem.equals(Items.CREEPER_HEAD) || javaItem.equals(Items.PLAYER_HEAD)
);
computeRenderOffsets(isHat, customItemData, componentBuilder);
componentBuilder.putCompound("item_properties", itemProperties.build()); componentBuilder.putCompound("item_properties", itemProperties.build());
builder.putCompound("components", componentBuilder.build()); builder.putCompound("components", componentBuilder.build());

View file

@ -58,6 +58,8 @@ import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData;
import org.geysermc.geyser.inventory.item.StoredItemMappings; import org.geysermc.geyser.inventory.item.StoredItemMappings;
import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.GeyserCustomMappingData;
import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.components.Rarity;
import org.geysermc.geyser.item.type.BlockItem;
import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.Registries;
@ -165,6 +167,7 @@ public class ItemRegistryPopulator {
Map<Item, ItemMapping> javaItemToMapping = new Object2ObjectOpenHashMap<>(); Map<Item, ItemMapping> javaItemToMapping = new Object2ObjectOpenHashMap<>();
List<ItemData> creativeItems = new ArrayList<>(); List<ItemData> creativeItems = new ArrayList<>();
Set<String> noBlockDefinitions = new ObjectOpenHashSet<>();
AtomicInteger creativeNetId = new AtomicInteger(); AtomicInteger creativeNetId = new AtomicInteger();
CreativeItemRegistryPopulator.populate(palette, definitions, itemBuilder -> { CreativeItemRegistryPopulator.populate(palette, definitions, itemBuilder -> {
@ -185,6 +188,9 @@ public class ItemRegistryPopulator {
bedrockBlockIdOverrides.put(identifier, item.getBlockDefinition()); bedrockBlockIdOverrides.put(identifier, item.getBlockDefinition());
} }
} }
} else {
// Item mappings should also NOT have a block definition for these.
noBlockDefinitions.add(item.getDefinition().getIdentifier());
} }
}); });
@ -254,7 +260,9 @@ public class ItemRegistryPopulator {
int aValidBedrockBlockId = blacklistedIdentifiers.getOrDefault(bedrockIdentifier, customBlockItemOverride != null ? customBlockItemOverride.getRuntimeId() : -1); int aValidBedrockBlockId = blacklistedIdentifiers.getOrDefault(bedrockIdentifier, customBlockItemOverride != null ? customBlockItemOverride.getRuntimeId() : -1);
if (aValidBedrockBlockId == -1 && customBlockItemOverride == null) { if (aValidBedrockBlockId == -1 && customBlockItemOverride == null) {
// Fallback // Fallback
bedrockBlock = blockMappings.getBedrockBlock(firstBlockRuntimeId); if (!noBlockDefinitions.contains(entry.getValue().getBedrockIdentifier())) {
bedrockBlock = blockMappings.getBedrockBlock(firstBlockRuntimeId);
}
} else { } else {
// As of 1.16.220, every item requires a block runtime ID attached to it. // As of 1.16.220, every item requires a block runtime ID attached to it.
// This is mostly for identifying different blocks with the same item ID - wool, slabs, some walls. // This is mostly for identifying different blocks with the same item ID - wool, slabs, some walls.
@ -266,7 +274,7 @@ public class ItemRegistryPopulator {
boolean firstPass = true; boolean firstPass = true;
// Block states are all grouped together. In the mappings, we store the first block runtime ID in order, // Block states are all grouped together. In the mappings, we store the first block runtime ID in order,
// and the last, if relevant. We then iterate over all those values and get their Bedrock equivalents // and the last, if relevant. We then iterate over all those values and get their Bedrock equivalents
Integer lastBlockRuntimeId = entry.getValue().getLastBlockRuntimeId() == null ? firstBlockRuntimeId : entry.getValue().getLastBlockRuntimeId(); int lastBlockRuntimeId = entry.getValue().getLastBlockRuntimeId() == null ? firstBlockRuntimeId : entry.getValue().getLastBlockRuntimeId();
for (int i = firstBlockRuntimeId; i <= lastBlockRuntimeId; i++) { for (int i = firstBlockRuntimeId; i <= lastBlockRuntimeId; i++) {
GeyserBedrockBlock bedrockBlockRuntimeId = blockMappings.getVanillaBedrockBlock(i); GeyserBedrockBlock bedrockBlockRuntimeId = blockMappings.getVanillaBedrockBlock(i);
NbtMap blockTag = bedrockBlockRuntimeId.getState(); NbtMap blockTag = bedrockBlockRuntimeId.getState();
@ -402,9 +410,10 @@ public class ItemRegistryPopulator {
} }
} }
if (javaOnlyItems.contains(javaItem)) { if (javaOnlyItems.contains(javaItem) || javaItem.rarity() != Rarity.COMMON) {
// These items don't exist on Bedrock, so set up a variable that indicates they should have custom names // These items don't exist on Bedrock, so set up a variable that indicates they should have custom names
mappingBuilder = mappingBuilder.translationString((bedrockBlock != null ? "block." : "item.") + entry.getKey().replace(":", ".")); // Or, ensure that we are translating these at all times to account for rarity colouring
mappingBuilder = mappingBuilder.translationString((javaItem instanceof BlockItem ? "block." : "item.") + entry.getKey().replace(":", "."));
GeyserImpl.getInstance().getLogger().debug("Adding " + entry.getKey() + " as an item that needs to be translated."); GeyserImpl.getInstance().getLogger().debug("Adding " + entry.getKey() + " as an item that needs to be translated.");
} }

View file

@ -28,6 +28,7 @@ package org.geysermc.geyser.registry.type;
import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.Pair;
import lombok.Builder; import lombok.Builder;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.Value; import lombok.Value;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
@ -42,6 +43,7 @@ import java.util.List;
@Value @Value
@Builder @Builder
@EqualsAndHashCode @EqualsAndHashCode
@ToString
public class ItemMapping { public class ItemMapping {
public static final ItemMapping AIR = new ItemMapping( public static final ItemMapping AIR = new ItemMapping(
"minecraft:air", "minecraft:air",

View file

@ -224,7 +224,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
private boolean closingInventory; private boolean closingInventory;
@Setter @Setter
private InventoryTranslator inventoryTranslator = InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR; private @NonNull InventoryTranslator inventoryTranslator = InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR;
/** /**
* Use {@link #getNextItemNetId()} instead for consistency * Use {@link #getNextItemNetId()} instead for consistency

View file

@ -141,6 +141,10 @@ public class EntityCache {
return playerEntities.values(); return playerEntities.values();
} }
public void removeAllPlayerEntities() {
playerEntities.clear();
}
public void addBossBar(UUID uuid, BossBar bossBar) { public void addBossBar(UUID uuid, BossBar bossBar) {
bossBars.put(uuid, bossBar); bossBars.put(uuid, bossBar);
bossBar.addBossBar(); bossBar.addBossBar();

View file

@ -243,8 +243,16 @@ public class SkullCache {
} }
public void clear() { public void clear() {
for (Skull skull : skulls.values()) {
if (skull.entity != null) {
skull.entity.despawnEntity();
}
}
skulls.clear(); skulls.clear();
inRangeSkulls.clear(); inRangeSkulls.clear();
for (SkullPlayerEntity skull : unusedSkullEntities) {
skull.despawnEntity();
}
unusedSkullEntities.clear(); unusedSkullEntities.clear();
totalSkullEntities = 0; totalSkullEntities = 0;
lastPlayerPosition = null; lastPlayerPosition = null;

View file

@ -33,7 +33,9 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.nbt.NbtList;
import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
@ -41,7 +43,9 @@ import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.block.custom.CustomBlockData; import org.geysermc.geyser.api.block.custom.CustomBlockData;
import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.components.Rarity;
import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.item.type.BedrockRequiresTagItem;
import org.geysermc.geyser.level.block.type.Block; import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.Registries;
@ -145,7 +149,26 @@ public final class ItemTranslator {
if (components.get(DataComponentType.HIDE_TOOLTIP) != null) hideTooltips = true; if (components.get(DataComponentType.HIDE_TOOLTIP) != null) hideTooltips = true;
} }
String customName = getCustomName(session, components, bedrockItem); // Fixes fireworks crafting recipe: they always contain a tag
// TODO remove once all items have their default components
if (javaItem instanceof BedrockRequiresTagItem requiresTagItem) {
requiresTagItem.addRequiredNbt(session, components, nbtBuilder);
}
Rarity rarity = javaItem.rarity();
boolean enchantmentGlint = javaItem.glint();
if (components != null) {
Integer rarityIndex = components.get(DataComponentType.RARITY);
if (rarityIndex != null) {
rarity = Rarity.fromId(rarityIndex);
}
Boolean enchantmentGlintOverride = components.get(DataComponentType.ENCHANTMENT_GLINT_OVERRIDE);
if (enchantmentGlintOverride != null) {
enchantmentGlint = enchantmentGlintOverride;
}
}
String customName = getCustomName(session, components, bedrockItem, rarity.getColor());
if (customName != null) { if (customName != null) {
nbtBuilder.setCustomName(customName); nbtBuilder.setCustomName(customName);
} }
@ -162,6 +185,12 @@ public final class ItemTranslator {
addAdvancedTooltips(components, nbtBuilder, javaItem, session.locale()); addAdvancedTooltips(components, nbtBuilder, javaItem, session.locale());
} }
// Add enchantment override. We can't remove it - enchantments would stop showing - but we can add it.
if (enchantmentGlint) {
NbtMapBuilder nbtMapBuilder = nbtBuilder.getOrCreateNbt();
nbtMapBuilder.putIfAbsent("ench", NbtList.EMPTY);
}
ItemData.Builder builder = javaItem.translateToBedrock(count, components, bedrockItem, session.getItemMappings()); ItemData.Builder builder = javaItem.translateToBedrock(count, components, bedrockItem, session.getItemMappings());
// Finalize the Bedrock NBT // Finalize the Bedrock NBT
builder.tag(nbtBuilder.build()); builder.tag(nbtBuilder.build());
@ -401,16 +430,6 @@ public final class ItemTranslator {
} }
} }
/**
* Translates the display name of the item
* @param session the Bedrock client's session
* @param components the components to translate
* @param mapping the item entry, in case it requires translation
*/
public static String getCustomName(GeyserSession session, DataComponents components, ItemMapping mapping) {
return getCustomName(session, components, mapping, 'f');
}
/** /**
* @param translationColor if this item is not available on Java, the color that the new name should be. * @param translationColor if this item is not available on Java, the color that the new name should be.
* Normally, this should just be white, but for shulker boxes this should be gray. * Normally, this should just be white, but for shulker boxes this should be gray.

View file

@ -42,10 +42,9 @@ public class CampfireBlockEntityTranslator extends BlockEntityTranslator {
public void translateTag(GeyserSession session, NbtMapBuilder bedrockNbt, NbtMap javaNbt, BlockState blockState) { public void translateTag(GeyserSession session, NbtMapBuilder bedrockNbt, NbtMap javaNbt, BlockState blockState) {
List<NbtMap> items = javaNbt.getList("Items", NbtType.COMPOUND); List<NbtMap> items = javaNbt.getList("Items", NbtType.COMPOUND);
if (items != null) { if (items != null) {
int i = 1;
for (NbtMap itemTag : items) { for (NbtMap itemTag : items) {
bedrockNbt.put("Item" + i, getItem(session, itemTag)); int slot = itemTag.getByte("Slot") + 1;
i++; bedrockNbt.put("Item" + slot, getItem(session, itemTag));
} }
} }
} }
@ -55,8 +54,7 @@ public class CampfireBlockEntityTranslator extends BlockEntityTranslator {
if (mapping == null) { if (mapping == null) {
mapping = ItemMapping.AIR; mapping = ItemMapping.AIR;
} }
NbtMapBuilder tagBuilder = BedrockItemBuilder.createItemNbt(mapping, tag.getByte("Count"), mapping.getBedrockData()); NbtMapBuilder tagBuilder = BedrockItemBuilder.createItemNbt(mapping, tag.getInt("count"), mapping.getBedrockData());
tagBuilder.put("tag", NbtMap.builder().build()); // I don't think this is necessary... - Camo, 1.20.5/1.20.80
return tagBuilder.build(); return tagBuilder.build();
} }
} }

View file

@ -70,7 +70,11 @@ import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.translator.item.ItemTranslator; import org.geysermc.geyser.translator.item.ItemTranslator;
import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.util.*; import org.geysermc.geyser.util.BlockUtils;
import org.geysermc.geyser.util.CooldownUtils;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InventoryUtils;
import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction; import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode; 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.entity.player.Hand;
@ -78,7 +82,11 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.player.InteractActio
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerAction; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerAction;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack; 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.ServerboundContainerClickPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.*; import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosRotPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundSwingPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundUseItemOnPacket;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -90,11 +98,6 @@ import java.util.concurrent.TimeUnit;
@Translator(packet = InventoryTransactionPacket.class) @Translator(packet = InventoryTransactionPacket.class)
public class BedrockInventoryTransactionTranslator extends PacketTranslator<InventoryTransactionPacket> { public class BedrockInventoryTransactionTranslator extends PacketTranslator<InventoryTransactionPacket> {
private static final float MAXIMUM_BLOCK_PLACING_DISTANCE = 64f;
private static final int CREATIVE_EYE_HEIGHT_PLACE_DISTANCE = 49;
private static final int SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE = 36;
private static final float MAXIMUM_BLOCK_DESTROYING_DISTANCE = 36f;
@Override @Override
public void translate(GeyserSession session, InventoryTransactionPacket packet) { public void translate(GeyserSession session, InventoryTransactionPacket packet) {
if (packet.getTransactionType() == InventoryTransactionType.NORMAL && packet.getActions().size() == 3) { if (packet.getTransactionType() == InventoryTransactionType.NORMAL && packet.getActions().size() == 3) {
@ -243,17 +246,13 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
return; return;
} }
// CraftBukkit+ check - see https://github.com/PaperMC/Paper/blob/458db6206daae76327a64f4e2a17b67a7e38b426/Spigot-Server-Patches/0532-Move-range-check-for-block-placing-up.patch // As of 1.21, Paper does not have any additional range checks that would inconvenience normal players.
// Note that, before these changes, I could replicate on Paper 1.20.4 and iPad 1.21.2 an instance of block ghosting
// that we had not previously implemented. Might be some sort of ray tracing that is currently unimplemented.
Vector3f playerPosition = session.getPlayerEntity().getPosition(); Vector3f playerPosition = session.getPlayerEntity().getPosition();
playerPosition = playerPosition.down(EntityDefinitions.PLAYER.offset() - session.getEyeHeight()); playerPosition = playerPosition.down(EntityDefinitions.PLAYER.offset() - session.getEyeHeight());
boolean creative = session.getGameMode() == GameMode.CREATIVE; if (!canInteractWithBlock(session, playerPosition, packetBlockPosition)) {
float diffX = playerPosition.getX() - packetBlockPosition.getX();
float diffY = playerPosition.getY() - packetBlockPosition.getY();
float diffZ = playerPosition.getZ() - packetBlockPosition.getZ();
if (((diffX * diffX) + (diffY * diffY) + (diffZ * diffZ)) >
(creative ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE)) {
restoreCorrectBlock(session, blockPos, packet); restoreCorrectBlock(session, blockPos, packet);
return; return;
} }
@ -262,26 +261,8 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
double clickPositionFullY = (double) packetBlockPosition.getY() + (double) packet.getClickPosition().getY(); double clickPositionFullY = (double) packetBlockPosition.getY() + (double) packet.getClickPosition().getY();
double clickPositionFullZ = (double) packetBlockPosition.getZ() + (double) packet.getClickPosition().getZ(); double clickPositionFullZ = (double) packetBlockPosition.getZ() + (double) packet.getClickPosition().getZ();
// More recent Paper check - https://github.com/PaperMC/Paper/blob/87e11bf7fdf48ecdf3e1cae383c368b9b61d7df9/patches/server/0470-Move-range-check-for-block-placing-up.patch
double clickDiffX = playerPosition.getX() - clickPositionFullX;
double clickDiffY = playerPosition.getY() - clickPositionFullY;
double clickDiffZ = playerPosition.getZ() - clickPositionFullZ;
if (((clickDiffX * clickDiffX) + (clickDiffY * clickDiffY) + (clickDiffZ * clickDiffZ)) >
(creative ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE)) {
restoreCorrectBlock(session, blockPos, packet);
return;
}
Vector3f blockCenter = Vector3f.from(packetBlockPosition.getX() + 0.5f, packetBlockPosition.getY() + 0.5f, packetBlockPosition.getZ() + 0.5f); Vector3f blockCenter = Vector3f.from(packetBlockPosition.getX() + 0.5f, packetBlockPosition.getY() + 0.5f, packetBlockPosition.getZ() + 0.5f);
// Vanilla check
if (!(session.getPlayerEntity().getPosition().sub(0, EntityDefinitions.PLAYER.offset(), 0)
.distanceSquared(blockCenter) < MAXIMUM_BLOCK_PLACING_DISTANCE)) {
// The client thinks that its blocks have been successfully placed. Restore the server's blocks instead.
restoreCorrectBlock(session, blockPos, packet);
return;
}
// More recent vanilla check (as of 1.18.2)
double clickDistanceX = clickPositionFullX - blockCenter.getX(); double clickDistanceX = clickPositionFullX - blockCenter.getX();
double clickDistanceY = clickPositionFullY - blockCenter.getY(); double clickDistanceY = clickPositionFullY - blockCenter.getY();
double clickDistanceZ = clickPositionFullZ - blockCenter.getZ(); double clickDistanceZ = clickPositionFullZ - blockCenter.getZ();
@ -433,14 +414,10 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
return; return;
} }
// This is working out the distance using 3d Pythagoras and the extra value added to the Y is the sneaking height of a java player.
Vector3f playerPosition = session.getPlayerEntity().getPosition(); Vector3f playerPosition = session.getPlayerEntity().getPosition();
Vector3f floatBlockPosition = packet.getBlockPosition().toFloat(); playerPosition = playerPosition.down(EntityDefinitions.PLAYER.offset() - session.getEyeHeight());
float diffX = playerPosition.getX() - (floatBlockPosition.getX() + 0.5f);
float diffY = (playerPosition.getY() - EntityDefinitions.PLAYER.offset()) - (floatBlockPosition.getY() + 0.5f) + 1.5f; if (!canInteractWithBlock(session, playerPosition, packet.getBlockPosition())) {
float diffZ = playerPosition.getZ() - (floatBlockPosition.getZ() + 0.5f);
float distanceSquared = diffX * diffX + diffY * diffY + diffZ * diffZ;
if (distanceSquared > MAXIMUM_BLOCK_DESTROYING_DISTANCE) {
restoreCorrectBlock(session, packet.getBlockPosition(), packet); restoreCorrectBlock(session, packet.getBlockPosition(), packet);
return; return;
} }
@ -550,6 +527,28 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
} }
} }
private boolean canInteractWithBlock(GeyserSession session, Vector3f playerPosition, Vector3i packetBlockPosition) {
// ViaVersion sends this 1.20.5+ attribute also, so older servers will have correct range checks.
double blockInteractionRange = session.getPlayerEntity().getBlockInteractionRange();
// Mojmap Player#canInteractWithBlock
double additionalRangeCheck = blockInteractionRange + 1.0d;
// AABB.<init>(BlockPos)
float minX = packetBlockPosition.getX();
float minY = packetBlockPosition.getY();
float minZ = packetBlockPosition.getZ();
float maxX = packetBlockPosition.getX() + 1;
float maxY = packetBlockPosition.getY() + 1;
float maxZ = packetBlockPosition.getZ() + 1;
// AABB#distanceToSqr
float diffX = Math.max(Math.max(minX - playerPosition.getX(), playerPosition.getX() - maxX), 0);
float diffY = Math.max(Math.max(minY - playerPosition.getY(), playerPosition.getY() - maxY), 0);
float diffZ = Math.max(Math.max(minZ - playerPosition.getZ(), playerPosition.getZ() - maxZ), 0);
return ((diffX * diffX) + (diffY * diffY) + (diffZ * diffZ)) < (additionalRangeCheck * additionalRangeCheck);
}
/** /**
* Restore the correct block state from the server without updating the chunk cache. * Restore the correct block state from the server without updating the chunk cache.
* *
@ -696,4 +695,4 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
}, 150, TimeUnit.MILLISECONDS)); }, 150, TimeUnit.MILLISECONDS));
} }
} }
} }

View file

@ -32,7 +32,12 @@ import org.cloudburstmc.protocol.bedrock.data.PlayerActionType;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType; import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.packet.*; import org.cloudburstmc.protocol.bedrock.packet.AnimatePacket;
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
import org.cloudburstmc.protocol.bedrock.packet.PlayStatusPacket;
import org.cloudburstmc.protocol.bedrock.packet.PlayerActionPacket;
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
import org.geysermc.geyser.api.block.custom.CustomBlockState; import org.geysermc.geyser.api.block.custom.CustomBlockState;
import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.entity.type.ItemFrameEntity;
@ -52,8 +57,17 @@ import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.util.BlockUtils; import org.geysermc.geyser.util.BlockUtils;
import org.geysermc.geyser.util.CooldownUtils; import org.geysermc.geyser.util.CooldownUtils;
import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction; import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.*; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.*; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.InteractAction;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerAction;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerState;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerAbilitiesPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerCommandPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundSwingPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundUseItemOnPacket;
@Translator(packet = PlayerActionPacket.class) @Translator(packet = PlayerActionPacket.class)
public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket> { public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket> {
@ -70,7 +84,7 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
Vector3i vector = packet.getBlockPosition(); Vector3i vector = packet.getBlockPosition();
switch (packet.getAction()) { switch (packet.getAction()) {
case RESPAWN: case RESPAWN -> {
// Respawn process is finished and the server and client are both OK with respawning. // Respawn process is finished and the server and client are both OK with respawning.
EntityEventPacket eventPacket = new EntityEventPacket(); EntityEventPacket eventPacket = new EntityEventPacket();
eventPacket.setRuntimeEntityId(entity.getGeyserId()); eventPacket.setRuntimeEntityId(entity.getGeyserId());
@ -88,16 +102,16 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
// Needed here since 1.19.81 for dimension switching // Needed here since 1.19.81 for dimension switching
session.getEntityCache().updateBossBars(); session.getEntityCache().updateBossBars();
break; }
case START_SWIMMING: case START_SWIMMING -> {
if (!entity.getFlag(EntityFlag.SWIMMING)) { if (!entity.getFlag(EntityFlag.SWIMMING)) {
ServerboundPlayerCommandPacket startSwimPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SPRINTING); ServerboundPlayerCommandPacket startSwimPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SPRINTING);
session.sendDownstreamGamePacket(startSwimPacket); session.sendDownstreamGamePacket(startSwimPacket);
session.setSwimming(true); session.setSwimming(true);
} }
break; }
case STOP_SWIMMING: case STOP_SWIMMING -> {
// Prevent packet spam when Bedrock players are crawling near the edge of a block // Prevent packet spam when Bedrock players are crawling near the edge of a block
if (!session.getCollisionManager().mustPlayerCrawlHere()) { if (!session.getCollisionManager().mustPlayerCrawlHere()) {
ServerboundPlayerCommandPacket stopSwimPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SPRINTING); ServerboundPlayerCommandPacket stopSwimPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SPRINTING);
@ -105,56 +119,55 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
session.setSwimming(false); session.setSwimming(false);
} }
break; }
case START_GLIDE: case START_GLIDE -> {
// Otherwise gliding will not work in creative // Otherwise gliding will not work in creative
ServerboundPlayerAbilitiesPacket playerAbilitiesPacket = new ServerboundPlayerAbilitiesPacket(false); ServerboundPlayerAbilitiesPacket playerAbilitiesPacket = new ServerboundPlayerAbilitiesPacket(false);
session.sendDownstreamGamePacket(playerAbilitiesPacket); session.sendDownstreamGamePacket(playerAbilitiesPacket);
case STOP_GLIDE: sendPlayerGlideToggle(session, entity);
ServerboundPlayerCommandPacket glidePacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_ELYTRA_FLYING); }
session.sendDownstreamGamePacket(glidePacket); case STOP_GLIDE -> sendPlayerGlideToggle(session, entity);
break; case START_SNEAK -> {
case START_SNEAK:
ServerboundPlayerCommandPacket startSneakPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SNEAKING); ServerboundPlayerCommandPacket startSneakPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SNEAKING);
session.sendDownstreamGamePacket(startSneakPacket); session.sendDownstreamGamePacket(startSneakPacket);
session.startSneaking(); session.startSneaking();
break; }
case STOP_SNEAK: case STOP_SNEAK -> {
ServerboundPlayerCommandPacket stopSneakPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SNEAKING); ServerboundPlayerCommandPacket stopSneakPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SNEAKING);
session.sendDownstreamGamePacket(stopSneakPacket); session.sendDownstreamGamePacket(stopSneakPacket);
session.stopSneaking(); session.stopSneaking();
break; }
case START_SPRINT: case START_SPRINT -> {
if (!entity.getFlag(EntityFlag.SWIMMING)) { if (!entity.getFlag(EntityFlag.SWIMMING)) {
ServerboundPlayerCommandPacket startSprintPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SPRINTING); ServerboundPlayerCommandPacket startSprintPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SPRINTING);
session.sendDownstreamGamePacket(startSprintPacket); session.sendDownstreamGamePacket(startSprintPacket);
session.setSprinting(true); session.setSprinting(true);
} }
break; }
case STOP_SPRINT: case STOP_SPRINT -> {
if (!entity.getFlag(EntityFlag.SWIMMING)) { if (!entity.getFlag(EntityFlag.SWIMMING)) {
ServerboundPlayerCommandPacket stopSprintPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SPRINTING); ServerboundPlayerCommandPacket stopSprintPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SPRINTING);
session.sendDownstreamGamePacket(stopSprintPacket); session.sendDownstreamGamePacket(stopSprintPacket);
} }
session.setSprinting(false); session.setSprinting(false);
break; }
case DROP_ITEM: case DROP_ITEM -> {
ServerboundPlayerActionPacket dropItemPacket = new ServerboundPlayerActionPacket(PlayerAction.DROP_ITEM, ServerboundPlayerActionPacket dropItemPacket = new ServerboundPlayerActionPacket(PlayerAction.DROP_ITEM,
vector, Direction.VALUES[packet.getFace()], 0); vector, Direction.VALUES[packet.getFace()], 0);
session.sendDownstreamGamePacket(dropItemPacket); session.sendDownstreamGamePacket(dropItemPacket);
break; }
case STOP_SLEEP: case STOP_SLEEP -> {
ServerboundPlayerCommandPacket stopSleepingPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.LEAVE_BED); ServerboundPlayerCommandPacket stopSleepingPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.LEAVE_BED);
session.sendDownstreamGamePacket(stopSleepingPacket); session.sendDownstreamGamePacket(stopSleepingPacket);
break; }
case START_BREAK: { case START_BREAK -> {
// Ignore START_BREAK when the player is CREATIVE to avoid Spigot receiving 2 packets it interpets as block breaking. https://github.com/GeyserMC/Geyser/issues/4021 // Ignore START_BREAK when the player is CREATIVE to avoid Spigot receiving 2 packets it interpets as block breaking. https://github.com/GeyserMC/Geyser/issues/4021
if (session.getGameMode() == GameMode.CREATIVE) { if (session.getGameMode() == GameMode.CREATIVE) {
break; break;
} }
// Start the block breaking animation // Start the block breaking animation
int blockState = session.getGeyser().getWorldManager().getBlockAt(session, vector); int blockState = session.getGeyser().getWorldManager().getBlockAt(session, vector);
LevelEventPacket startBreak = new LevelEventPacket(); LevelEventPacket startBreak = new LevelEventPacket();
@ -180,18 +193,20 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
// Account for fire - the client likes to hit the block behind. // Account for fire - the client likes to hit the block behind.
Vector3i fireBlockPos = BlockUtils.getBlockPosition(vector, packet.getFace()); Vector3i fireBlockPos = BlockUtils.getBlockPosition(vector, packet.getFace());
Block block = session.getGeyser().getWorldManager().blockAt(session, fireBlockPos).block(); Block block = session.getGeyser().getWorldManager().blockAt(session, fireBlockPos).block();
Direction direction = Direction.VALUES[packet.getFace()];
if (block == Blocks.FIRE || block == Blocks.SOUL_FIRE) { if (block == Blocks.FIRE || block == Blocks.SOUL_FIRE) {
ServerboundPlayerActionPacket startBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.START_DIGGING, fireBlockPos, ServerboundPlayerActionPacket startBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.START_DIGGING, fireBlockPos,
Direction.VALUES[packet.getFace()], session.getWorldCache().nextPredictionSequence()); direction, session.getWorldCache().nextPredictionSequence());
session.sendDownstreamGamePacket(startBreakingPacket); session.sendDownstreamGamePacket(startBreakingPacket);
} }
ServerboundPlayerActionPacket startBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.START_DIGGING, ServerboundPlayerActionPacket startBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.START_DIGGING,
vector, Direction.VALUES[packet.getFace()], session.getWorldCache().nextPredictionSequence()); vector, direction, session.getWorldCache().nextPredictionSequence());
session.sendDownstreamGamePacket(startBreakingPacket); session.sendDownstreamGamePacket(startBreakingPacket);
break;
spawnBlockBreakParticles(session, direction, vector, BlockState.of(blockState));
} }
case CONTINUE_BREAK: case CONTINUE_BREAK -> {
if (session.getGameMode() == GameMode.CREATIVE) { if (session.getGameMode() == GameMode.CREATIVE) {
break; break;
} }
@ -201,52 +216,48 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
} }
Vector3f vectorFloat = vector.toFloat(); Vector3f vectorFloat = vector.toFloat();
LevelEventPacket continueBreakPacket = new LevelEventPacket();
continueBreakPacket.setType(LevelEvent.PARTICLE_CRACK_BLOCK);
continueBreakPacket.setData((session.getBlockMappings().getBedrockBlockId(breakingBlock)) | (packet.getFace() << 24));
continueBreakPacket.setPosition(vectorFloat);
session.sendUpstreamPacket(continueBreakPacket);
// Update the break time in the event that player conditions changed (jumping, effects applied)
LevelEventPacket updateBreak = new LevelEventPacket();
updateBreak.setType(LevelEvent.BLOCK_UPDATE_BREAK);
updateBreak.setPosition(vectorFloat);
double breakTime = BlockUtils.getSessionBreakTime(session, BlockState.of(breakingBlock).block()) * 20;
BlockState breakingBlockState = BlockState.of(breakingBlock);
Direction direction = Direction.VALUES[packet.getFace()];
spawnBlockBreakParticles(session, direction, vector, breakingBlockState);
double breakTime = BlockUtils.getSessionBreakTime(session, breakingBlockState.block()) * 20;
// If the block is custom, we must keep track of when it should break ourselves // If the block is custom, we must keep track of when it should break ourselves
long blockBreakStartTime = session.getBlockBreakStartTime(); long blockBreakStartTime = session.getBlockBreakStartTime();
if (blockBreakStartTime != 0) { if (blockBreakStartTime != 0) {
long timeSinceStart = System.currentTimeMillis() - blockBreakStartTime; long timeSinceStart = System.currentTimeMillis() - blockBreakStartTime;
// We need to add a slight delay to the break time, otherwise the client breaks blocks too fast // We need to add a slight delay to the break time, otherwise the client breaks blocks too fast
if (timeSinceStart >= (breakTime+=2) * 50) { if (timeSinceStart >= (breakTime += 2) * 50) {
// Play break sound and particle // Play break sound and particle
LevelEventPacket effectPacket = new LevelEventPacket(); LevelEventPacket effectPacket = new LevelEventPacket();
effectPacket.setPosition(vectorFloat); effectPacket.setPosition(vectorFloat);
effectPacket.setType(LevelEvent.PARTICLE_DESTROY_BLOCK); effectPacket.setType(LevelEvent.PARTICLE_DESTROY_BLOCK);
effectPacket.setData(session.getBlockMappings().getBedrockBlockId(breakingBlock)); effectPacket.setData(session.getBlockMappings().getBedrockBlockId(breakingBlock));
session.sendUpstreamPacket(effectPacket); session.sendUpstreamPacket(effectPacket);
// Break the block // Break the block
ServerboundPlayerActionPacket finishBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.FINISH_DIGGING, ServerboundPlayerActionPacket finishBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.FINISH_DIGGING,
vector, Direction.VALUES[packet.getFace()], session.getWorldCache().nextPredictionSequence()); vector, direction, session.getWorldCache().nextPredictionSequence());
session.sendDownstreamGamePacket(finishBreakingPacket); session.sendDownstreamGamePacket(finishBreakingPacket);
session.setBlockBreakStartTime(0); session.setBlockBreakStartTime(0);
break; break;
} }
} }
// Update the break time in the event that player conditions changed (jumping, effects applied)
LevelEventPacket updateBreak = new LevelEventPacket();
updateBreak.setType(LevelEvent.BLOCK_UPDATE_BREAK);
updateBreak.setPosition(vectorFloat);
updateBreak.setData((int) (65535 / breakTime)); updateBreak.setData((int) (65535 / breakTime));
session.sendUpstreamPacket(updateBreak); session.sendUpstreamPacket(updateBreak);
break; }
case ABORT_BREAK: case ABORT_BREAK -> {
if (session.getGameMode() != GameMode.CREATIVE) { if (session.getGameMode() != GameMode.CREATIVE) {
// As of 1.16.210: item frame items are taken out here. // As of 1.16.210: item frame items are taken out here.
// Survival also sends START_BREAK, but by attaching our process here adventure mode also works // Survival also sends START_BREAK, but by attaching our process here adventure mode also works
Entity itemFrameEntity = ItemFrameEntity.getItemFrameEntity(session, vector); Entity itemFrameEntity = ItemFrameEntity.getItemFrameEntity(session, vector);
if (itemFrameEntity != null) { if (itemFrameEntity != null) {
ServerboundInteractPacket interactPacket = new ServerboundInteractPacket(itemFrameEntity.getEntityId(), ServerboundInteractPacket interactPacket = new ServerboundInteractPacket(itemFrameEntity.getEntityId(),
InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking()); InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking());
session.sendDownstreamGamePacket(interactPacket); session.sendDownstreamGamePacket(interactPacket);
break; break;
} }
@ -260,25 +271,23 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
stopBreak.setData(0); stopBreak.setData(0);
session.setBreakingBlock(-1); session.setBreakingBlock(-1);
session.sendUpstreamPacket(stopBreak); session.sendUpstreamPacket(stopBreak);
break; }
case STOP_BREAK: // Handled in BedrockInventoryTransactionTranslator
// Handled in BedrockInventoryTransactionTranslator case STOP_BREAK -> {
break; }
case DIMENSION_CHANGE_SUCCESS: case DIMENSION_CHANGE_SUCCESS -> {
//sometimes the client doesn't feel like loading //sometimes the client doesn't feel like loading
PlayStatusPacket spawnPacket = new PlayStatusPacket(); PlayStatusPacket spawnPacket = new PlayStatusPacket();
spawnPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN); spawnPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN);
session.sendUpstreamPacket(spawnPacket); session.sendUpstreamPacket(spawnPacket);
attributesPacket = new UpdateAttributesPacket(); UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
attributesPacket.setRuntimeEntityId(entity.getGeyserId()); attributesPacket.setRuntimeEntityId(entity.getGeyserId());
attributesPacket.getAttributes().addAll(entity.getAttributes().values()); attributesPacket.getAttributes().addAll(entity.getAttributes().values());
session.sendUpstreamPacket(attributesPacket); session.sendUpstreamPacket(attributesPacket);
break; }
case JUMP: case JUMP -> entity.setOnGround(false); // Increase block break time while jumping
entity.setOnGround(false); // Increase block break time while jumping case MISSED_SWING -> {
break;
case MISSED_SWING:
// Java edition sends a cooldown when hitting air. // Java edition sends a cooldown when hitting air.
// Normally handled by BedrockLevelSoundEventTranslator, but there is no sound on Java for this. // Normally handled by BedrockLevelSoundEventTranslator, but there is no sound on Java for this.
CooldownUtils.sendCooldown(session); CooldownUtils.sendCooldown(session);
@ -294,18 +303,18 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
animatePacket.setAction(AnimatePacket.Action.SWING_ARM); animatePacket.setAction(AnimatePacket.Action.SWING_ARM);
session.sendUpstreamPacket(animatePacket); session.sendUpstreamPacket(animatePacket);
} }
break; }
case START_FLYING: // Since 1.20.30 case START_FLYING -> { // Since 1.20.30
if (session.isCanFly()) { if (session.isCanFly()) {
if (session.getGameMode() == GameMode.SPECTATOR) { if (session.getGameMode() == GameMode.SPECTATOR) {
// should already be flying // should already be flying
session.sendAdventureSettings(); session.sendAdventureSettings();
break; break;
} }
if (session.getPlayerEntity().getFlag(EntityFlag.SWIMMING) && session.getCollisionManager().isPlayerInWater()) { if (session.getPlayerEntity().getFlag(EntityFlag.SWIMMING) && session.getCollisionManager().isPlayerInWater()) {
// As of 1.18.1, Java Edition cannot fly while in water, but it can fly while crawling // As of 1.18.1, Java Edition cannot fly while in water, but it can fly while crawling
// If this isn't present, swimming on a 1.13.2 server and then attempting to fly will put you into a flying/swimming state that is invalid on JE // If this isn't present, swimming on a 1.13.2 server and then attempting to fly will put you into a flying/swimming state that is invalid on JE
session.sendAdventureSettings(); session.sendAdventureSettings();
break; break;
} }
@ -313,9 +322,9 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
session.setFlying(true); session.setFlying(true);
session.sendDownstreamGamePacket(new ServerboundPlayerAbilitiesPacket(true)); session.sendDownstreamGamePacket(new ServerboundPlayerAbilitiesPacket(true));
} else { } else {
// update whether we can fly // update whether we can fly
session.sendAdventureSettings(); session.sendAdventureSettings();
// stop flying // stop flying
PlayerActionPacket stopFlyingPacket = new PlayerActionPacket(); PlayerActionPacket stopFlyingPacket = new PlayerActionPacket();
stopFlyingPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId()); stopFlyingPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId());
stopFlyingPacket.setAction(PlayerActionType.STOP_FLYING); stopFlyingPacket.setAction(PlayerActionType.STOP_FLYING);
@ -324,24 +333,24 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
stopFlyingPacket.setFace(0); stopFlyingPacket.setFace(0);
session.sendUpstreamPacket(stopFlyingPacket); session.sendUpstreamPacket(stopFlyingPacket);
} }
break; }
case STOP_FLYING: case STOP_FLYING -> {
session.setFlying(false); session.setFlying(false);
session.sendDownstreamGamePacket(new ServerboundPlayerAbilitiesPacket(false)); session.sendDownstreamGamePacket(new ServerboundPlayerAbilitiesPacket(false));
break; }
case DIMENSION_CHANGE_REQUEST_OR_CREATIVE_DESTROY_BLOCK: // Used by client to get book from lecterns and items from item frame in creative mode since 1.20.70 case DIMENSION_CHANGE_REQUEST_OR_CREATIVE_DESTROY_BLOCK -> { // Used by client to get book from lecterns and items from item frame in creative mode since 1.20.70
BlockState state = session.getGeyser().getWorldManager().blockAt(session, vector); BlockState state = session.getGeyser().getWorldManager().blockAt(session, vector);
if (state.getValue(Properties.HAS_BOOK, false)) { if (state.getValue(Properties.HAS_BOOK, false)) {
session.setDroppingLecternBook(true); session.setDroppingLecternBook(true);
ServerboundUseItemOnPacket blockPacket = new ServerboundUseItemOnPacket( ServerboundUseItemOnPacket blockPacket = new ServerboundUseItemOnPacket(
vector, vector,
Direction.DOWN, Direction.DOWN,
Hand.MAIN_HAND, Hand.MAIN_HAND,
0, 0, 0, 0, 0, 0,
false, false,
session.getWorldCache().nextPredictionSequence()); session.getWorldCache().nextPredictionSequence());
session.sendDownstreamGamePacket(blockPacket); session.sendDownstreamGamePacket(blockPacket);
break; break;
} }
@ -349,10 +358,30 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
Entity itemFrame = ItemFrameEntity.getItemFrameEntity(session, packet.getBlockPosition()); Entity itemFrame = ItemFrameEntity.getItemFrameEntity(session, packet.getBlockPosition());
if (itemFrame != null) { if (itemFrame != null) {
ServerboundInteractPacket interactPacket = new ServerboundInteractPacket(itemFrame.getEntityId(), ServerboundInteractPacket interactPacket = new ServerboundInteractPacket(itemFrame.getEntityId(),
InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking()); InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking());
session.sendDownstreamGamePacket(interactPacket); session.sendDownstreamGamePacket(interactPacket);
} }
break; }
} }
} }
private void spawnBlockBreakParticles(GeyserSession session, Direction direction, Vector3i position, BlockState blockState) {
LevelEventPacket levelEventPacket = new LevelEventPacket();
switch (direction) {
case UP -> levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_UP);
case DOWN -> levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_DOWN);
case NORTH -> levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_NORTH);
case EAST -> levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_EAST);
case SOUTH -> levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_SOUTH);
case WEST -> levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_WEST);
}
levelEventPacket.setPosition(position.toFloat());
levelEventPacket.setData(session.getBlockMappings().getBedrockBlock(blockState).getRuntimeId());
session.sendUpstreamPacket(levelEventPacket);
}
private void sendPlayerGlideToggle(GeyserSession session, Entity entity) {
ServerboundPlayerCommandPacket glidePacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_ELYTRA_FLYING);
session.sendDownstreamGamePacket(glidePacket);
}
} }

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2024 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.translator.protocol.java;
import org.cloudburstmc.protocol.bedrock.packet.PlayerListPacket;
import org.geysermc.geyser.entity.type.player.PlayerEntity;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.mcprotocollib.protocol.packet.configuration.clientbound.ClientboundFinishConfigurationPacket;
@Translator(packet = ClientboundFinishConfigurationPacket.class)
public class JavaFinishConfigurationPacketTranslator extends PacketTranslator<ClientboundFinishConfigurationPacket> {
@Override
public void translate(GeyserSession session, ClientboundFinishConfigurationPacket packet) {
// Clear the player list, as on Java the player list is cleared after transitioning from config to play phase
PlayerListPacket playerListPacket = new PlayerListPacket();
playerListPacket.setAction(PlayerListPacket.Action.REMOVE);
for (PlayerEntity otherEntity : session.getEntityCache().getAllPlayerEntities()) {
playerListPacket.getEntries().add(new PlayerListPacket.Entry(otherEntity.getTabListUuid()));
}
session.sendUpstreamPacket(playerListPacket);
session.getEntityCache().removeAllPlayerEntities();
}
}

View file

@ -79,25 +79,6 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
// Remove extra hearts, hunger, etc. // Remove extra hearts, hunger, etc.
entity.resetAttributes(); entity.resetAttributes();
entity.resetMetadata(); entity.resetMetadata();
// Reset weather
if (session.isRaining()) {
LevelEventPacket stopRainPacket = new LevelEventPacket();
stopRainPacket.setType(LevelEvent.STOP_RAINING);
stopRainPacket.setData(0);
stopRainPacket.setPosition(Vector3f.ZERO);
session.sendUpstreamPacket(stopRainPacket);
session.setRaining(false);
}
if (session.isThunder()) {
LevelEventPacket stopThunderPacket = new LevelEventPacket();
stopThunderPacket.setType(LevelEvent.STOP_THUNDERSTORM);
stopThunderPacket.setData(0);
stopThunderPacket.setPosition(Vector3f.ZERO);
session.sendUpstreamPacket(stopThunderPacket);
session.setThunder(false);
}
} }
session.setWorldName(spawnInfo.getWorldName()); session.setWorldName(spawnInfo.getWorldName());

View file

@ -25,7 +25,11 @@
package org.geysermc.geyser.translator.protocol.java; package org.geysermc.geyser.translator.protocol.java;
import it.unimi.dsi.fastutil.ints.*; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
@ -40,7 +44,11 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemTagDescri
import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket; import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket;
import org.cloudburstmc.protocol.bedrock.packet.TrimDataPacket; import org.cloudburstmc.protocol.bedrock.packet.TrimDataPacket;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.inventory.recipe.*; import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserStonecutterData;
import org.geysermc.geyser.inventory.recipe.TrimRecipe;
import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
@ -58,7 +66,17 @@ import org.geysermc.mcprotocollib.protocol.data.game.recipe.data.SmithingTransfo
import org.geysermc.mcprotocollib.protocol.data.game.recipe.data.StoneCuttingRecipeData; import org.geysermc.mcprotocollib.protocol.data.game.recipe.data.StoneCuttingRecipeData;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundUpdateRecipesPacket; import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundUpdateRecipesPacket;
import java.util.*; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.geysermc.geyser.util.InventoryUtils.LAST_RECIPE_NET_ID; import static org.geysermc.geyser.util.InventoryUtils.LAST_RECIPE_NET_ID;
@ -191,6 +209,9 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
case CRAFTING_SPECIAL_MAPCLONING -> { case CRAFTING_SPECIAL_MAPCLONING -> {
craftingDataPacket.getCraftingData().add(MultiRecipeData.of(UUID.fromString("85939755-ba10-4d9d-a4cc-efb7a8e943c4"), context.getAndIncrementNetId())); craftingDataPacket.getCraftingData().add(MultiRecipeData.of(UUID.fromString("85939755-ba10-4d9d-a4cc-efb7a8e943c4"), context.getAndIncrementNetId()));
} }
case CRAFTING_SPECIAL_FIREWORK_ROCKET -> {
craftingDataPacket.getCraftingData().add(MultiRecipeData.of(UUID.fromString("00000000-0000-0000-0000-000000000002"), context.getAndIncrementNetId()));
}
default -> { default -> {
List<GeyserRecipe> recipes = Registries.RECIPES.get(recipe.getType()); List<GeyserRecipe> recipes = Registries.RECIPES.get(recipe.getType());
if (recipes != null) { if (recipes != null) {
@ -427,7 +448,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
return null; return null;
} }
// Strip NBT - tools won't appear in the recipe book otherwise // Strip NBT - tools won't appear in the recipe book otherwise
output = output.toBuilder().tag(null).build(); // output = output.toBuilder().tag(null).build(); // TODO confirm???
ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients()); ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients());
if (inputCombinations == null) { if (inputCombinations == null) {
return null; return null;
@ -451,7 +472,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
return null; return null;
} }
// Strip NBT - tools won't appear in the recipe book otherwise // Strip NBT - tools won't appear in the recipe book otherwise
output = output.toBuilder().tag(null).build(); //output = output.toBuilder().tag(null).build(); // TODO confirm this is still true???
ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients()); ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients());
if (inputCombinations == null) { if (inputCombinations == null) {
return null; return null;
@ -475,7 +496,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
return null; return null;
} }
// See above // See above
output = output.toBuilder().tag(null).build(); //output = output.toBuilder().tag(null).build();
ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients()); ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients());
if (inputCombinations == null) { if (inputCombinations == null) {
return null; return null;

View file

@ -29,7 +29,9 @@ import org.cloudburstmc.protocol.bedrock.data.ParticleType;
import org.cloudburstmc.protocol.bedrock.data.SoundEvent; import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType; import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerId;
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket; import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
import org.cloudburstmc.protocol.bedrock.packet.InventoryContentPacket;
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket; import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEvent2Packet; import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEvent2Packet;
import org.cloudburstmc.protocol.bedrock.packet.PlaySoundPacket; import org.cloudburstmc.protocol.bedrock.packet.PlaySoundPacket;
@ -42,11 +44,15 @@ import org.geysermc.geyser.entity.type.FishingHookEntity;
import org.geysermc.geyser.entity.type.LivingEntity; import org.geysermc.geyser.entity.type.LivingEntity;
import org.geysermc.geyser.entity.type.living.animal.ArmadilloEntity; import org.geysermc.geyser.entity.type.living.animal.ArmadilloEntity;
import org.geysermc.geyser.entity.type.living.monster.WardenEntity; import org.geysermc.geyser.entity.type.living.monster.WardenEntity;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.util.InventoryUtils;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundEntityEventPacket; import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundEntityEventPacket;
import java.util.Collections;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
@Translator(packet = ClientboundEntityEventPacket.class) @Translator(packet = ClientboundEntityEventPacket.class)
@ -154,6 +160,16 @@ public class JavaEntityEventTranslator extends PacketTranslator<ClientboundEntit
entityEventPacket.setType(EntityEventType.WITCH_HAT_MAGIC); //TODO: CHECK entityEventPacket.setType(EntityEventType.WITCH_HAT_MAGIC); //TODO: CHECK
break; break;
case TOTEM_OF_UNDYING_MAKE_SOUND: case TOTEM_OF_UNDYING_MAKE_SOUND:
// Bedrock will not play the spinning animation without the item in the hand o.o
// Fixes https://github.com/GeyserMC/Geyser/issues/2446
boolean totemItemWorkaround = !session.getPlayerInventory().eitherHandMatchesItem(Items.TOTEM_OF_UNDYING);
if (totemItemWorkaround) {
InventoryContentPacket offhandPacket = new InventoryContentPacket();
offhandPacket.setContainerId(ContainerId.OFFHAND);
offhandPacket.setContents(Collections.singletonList(InventoryUtils.getTotemOfUndying().apply(session.getUpstream().getProtocolVersion())));
session.sendUpstreamPacket(offhandPacket);
}
entityEventPacket.setType(EntityEventType.CONSUME_TOTEM); entityEventPacket.setType(EntityEventType.CONSUME_TOTEM);
PlaySoundPacket playSoundPacket = new PlaySoundPacket(); PlaySoundPacket playSoundPacket = new PlaySoundPacket();
@ -162,7 +178,16 @@ public class JavaEntityEventTranslator extends PacketTranslator<ClientboundEntit
playSoundPacket.setVolume(1.0F); playSoundPacket.setVolume(1.0F);
playSoundPacket.setPitch(1.0F + (ThreadLocalRandom.current().nextFloat() * 0.1F) - 0.05F); playSoundPacket.setPitch(1.0F + (ThreadLocalRandom.current().nextFloat() * 0.1F) - 0.05F);
session.sendUpstreamPacket(playSoundPacket); session.sendUpstreamPacket(playSoundPacket);
break;
// Sent here early to ensure we have the totem in our hand
session.sendUpstreamPacket(entityEventPacket);
if (totemItemWorkaround) {
// Reset the item again
InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR.updateSlot(session, session.getPlayerInventory(), 45);
}
return;
case SHEEP_GRAZE_OR_TNT_CART_EXPLODE: case SHEEP_GRAZE_OR_TNT_CART_EXPLODE:
if (entity.getDefinition() == EntityDefinitions.SHEEP) { if (entity.getDefinition() == EntityDefinitions.SHEEP) {
entityEventPacket.setType(EntityEventType.EAT_GRASS); entityEventPacket.setType(EntityEventType.EAT_GRASS);

View file

@ -70,6 +70,7 @@ public class JavaOpenBookTranslator extends PacketTranslator<ClientboundOpenBook
} }
InventoryTranslator translator = InventoryTranslator.inventoryTranslator(ContainerType.LECTERN); InventoryTranslator translator = InventoryTranslator.inventoryTranslator(ContainerType.LECTERN);
Objects.requireNonNull(translator, "could not find lectern inventory translator!");
session.setInventoryTranslator(translator); session.setInventoryTranslator(translator);
// Should never be null // Should never be null

View file

@ -28,8 +28,8 @@ package org.geysermc.geyser.translator.protocol.java.level;
import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.SoundEvent; import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket; import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.level.block.type.BlockState; import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator;
@ -43,24 +43,27 @@ public class JavaBlockUpdateTranslator extends PacketTranslator<ClientboundBlock
@Override @Override
public void translate(GeyserSession session, ClientboundBlockUpdatePacket packet) { public void translate(GeyserSession session, ClientboundBlockUpdatePacket packet) {
Vector3i pos = packet.getEntry().getPosition(); Vector3i pos = packet.getEntry().getPosition();
boolean updatePlacement = session.getGeyser().getPlatformType() != PlatformType.SPIGOT && // Spigot simply listens for the block place event WorldManager worldManager = session.getGeyser().getWorldManager();
!session.getErosionHandler().isActive() && session.getGeyser().getWorldManager().getBlockAt(session, pos) != packet.getEntry().getBlock(); // Platforms where Geyser has direct server access don't allow us to detect actual block changes,
// hence why those platforms deal with sounds for block placements differently
boolean updatePlacement = !worldManager.hasOwnChunkCache() &&
!session.getErosionHandler().isActive() && worldManager.getBlockAt(session, pos) != packet.getEntry().getBlock();
session.getWorldCache().updateServerCorrectBlockState(pos, packet.getEntry().getBlock()); session.getWorldCache().updateServerCorrectBlockState(pos, packet.getEntry().getBlock());
if (updatePlacement) { if (updatePlacement) {
this.checkPlace(session, packet); this.checkPlaceSound(session, packet);
} }
this.checkInteract(session, packet); this.checkInteract(session, packet);
} }
private boolean checkPlace(GeyserSession session, ClientboundBlockUpdatePacket packet) { private void checkPlaceSound(GeyserSession session, ClientboundBlockUpdatePacket packet) {
Vector3i lastPlacePos = session.getLastBlockPlacePosition(); Vector3i lastPlacePos = session.getLastBlockPlacePosition();
if (lastPlacePos == null) { if (lastPlacePos == null) {
return false; return;
} }
if ((lastPlacePos.getX() != packet.getEntry().getPosition().getX() if ((lastPlacePos.getX() != packet.getEntry().getPosition().getX()
|| lastPlacePos.getY() != packet.getEntry().getPosition().getY() || lastPlacePos.getY() != packet.getEntry().getPosition().getY()
|| lastPlacePos.getZ() != packet.getEntry().getPosition().getZ())) { || lastPlacePos.getZ() != packet.getEntry().getPosition().getZ())) {
return false; return;
} }
// We need to check if the identifier is the same, else a packet with the sound of what the // We need to check if the identifier is the same, else a packet with the sound of what the
@ -74,7 +77,7 @@ public class JavaBlockUpdateTranslator extends PacketTranslator<ClientboundBlock
if (!contains) { if (!contains) {
session.setLastBlockPlacePosition(null); session.setLastBlockPlacePosition(null);
session.setLastBlockPlaced(null); session.setLastBlockPlaced(null);
return false; return;
} }
// This is not sent from the server, so we need to send it this way // This is not sent from the server, so we need to send it this way
@ -87,7 +90,6 @@ public class JavaBlockUpdateTranslator extends PacketTranslator<ClientboundBlock
session.sendUpstreamPacket(placeBlockSoundPacket); session.sendUpstreamPacket(placeBlockSoundPacket);
session.setLastBlockPlacePosition(null); session.setLastBlockPlacePosition(null);
session.setLastBlockPlaced(null); session.setLastBlockPlaced(null);
return true;
} }
private void checkInteract(GeyserSession session, ClientboundBlockUpdatePacket packet) { private void checkInteract(GeyserSession session, ClientboundBlockUpdatePacket packet) {

View file

@ -27,6 +27,7 @@ package org.geysermc.geyser.util;
import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
import org.cloudburstmc.protocol.bedrock.data.PlayerActionType; import org.cloudburstmc.protocol.bedrock.data.PlayerActionType;
import org.cloudburstmc.protocol.bedrock.packet.*; import org.cloudburstmc.protocol.bedrock.packet.*;
import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.Entity;
@ -110,6 +111,20 @@ public class DimensionUtils {
// Effects are re-sent from server // Effects are re-sent from server
entityEffects.clear(); entityEffects.clear();
// Always reset weather, as it sometimes suddenly starts raining. See https://github.com/GeyserMC/Geyser/issues/3679
LevelEventPacket stopRainPacket = new LevelEventPacket();
stopRainPacket.setType(LevelEvent.STOP_RAINING);
stopRainPacket.setData(0);
stopRainPacket.setPosition(Vector3f.ZERO);
session.sendUpstreamPacket(stopRainPacket);
session.setRaining(false);
LevelEventPacket stopThunderPacket = new LevelEventPacket();
stopThunderPacket.setType(LevelEvent.STOP_THUNDERSTORM);
stopThunderPacket.setData(0);
stopThunderPacket.setPosition(Vector3f.ZERO);
session.sendUpstreamPacket(stopThunderPacket);
session.setThunder(false);
//let java server handle portal travel sound //let java server handle portal travel sound
StopSoundPacket stopSoundPacket = new StopSoundPacket(); StopSoundPacket stopSoundPacket = new StopSoundPacket();
stopSoundPacket.setStoppingAllSound(true); stopSoundPacket.setStoppingAllSound(true);

View file

@ -57,6 +57,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack; import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.Ingredient; import org.geysermc.mcprotocollib.protocol.data.game.recipe.Ingredient;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClosePacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundPickItemPacket; import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundPickItemPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundSetCreativeModeSlotPacket; import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundSetCreativeModeSlotPacket;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
@ -91,7 +92,7 @@ public class InventoryUtils {
public static void displayInventory(GeyserSession session, Inventory inventory) { public static void displayInventory(GeyserSession session, Inventory inventory) {
InventoryTranslator translator = session.getInventoryTranslator(); InventoryTranslator translator = session.getInventoryTranslator();
if (translator != null && translator.prepareInventory(session, inventory)) { if (translator.prepareInventory(session, inventory)) {
if (translator instanceof DoubleChestInventoryTranslator && !((Container) inventory).isUsingRealBlock()) { if (translator instanceof DoubleChestInventoryTranslator && !((Container) inventory).isUsingRealBlock()) {
session.scheduleInEventLoop(() -> { session.scheduleInEventLoop(() -> {
Inventory openInv = session.getOpenInventory(); Inventory openInv = session.getOpenInventory();
@ -110,7 +111,11 @@ public class InventoryUtils {
inventory.setDisplayed(true); inventory.setDisplayed(true);
} }
} else { } else {
// Can occur if we e.g. did not find a spot to put a fake container in
ServerboundContainerClosePacket closePacket = new ServerboundContainerClosePacket(inventory.getJavaId());
session.sendDownstreamGamePacket(closePacket);
session.setOpenInventory(null); session.setOpenInventory(null);
session.setInventoryTranslator(InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR);
} }
} }
@ -248,6 +253,12 @@ public class InventoryUtils {
.count(1).build(); .count(1).build();
} }
public static IntFunction<ItemData> getTotemOfUndying() {
return protocolVersion -> ItemData.builder()
.definition(Registries.ITEMS.forVersion(protocolVersion).getStoredItems().totem().getBedrockDefinition())
.count(1).build();
}
/** /**
* See {@link #findOrCreateItem(GeyserSession, String)}. This is for finding a specified {@link ItemStack}. * See {@link #findOrCreateItem(GeyserSession, String)}. This is for finding a specified {@link ItemStack}.
* *