mirror of
				https://github.com/GeyserMC/Geyser.git
				synced 2024-08-14 23:57:35 +00:00 
			
		
		
		
	Merge branch 'master' of https://github.com/GeyserMC/Geyser into fix/4837
# Conflicts: # core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java
This commit is contained in:
		
						commit
						6299903ac3
					
				
					 54 changed files with 1056 additions and 502 deletions
				
			
		| 
						 | 
				
			
			@ -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!
 | 
			
		||||
 | 
			
		||||
### 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
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
## 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.
 | 
			
		||||
 | 
			
		||||
## Libraries Used:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.protocol.ProtocolConstants;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.Nullable;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
import org.geysermc.geyser.ping.GeyserPingInfo;
 | 
			
		||||
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -43,6 +44,7 @@ import java.net.InetSocketAddress;
 | 
			
		|||
import java.net.SocketAddress;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, Listener {
 | 
			
		||||
| 
						 | 
				
			
			@ -59,7 +61,17 @@ public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, List
 | 
			
		|||
                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();
 | 
			
		||||
        return new GeyserPingInfo(
 | 
			
		||||
                response.getDescriptionComponent().toLegacyText(),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,7 +25,7 @@ dependencies {
 | 
			
		|||
    shadow(libs.protocol.connection) { isTransitive = false }
 | 
			
		||||
    shadow(libs.protocol.common) { isTransitive = false }
 | 
			
		||||
    shadow(libs.protocol.codec) { isTransitive = false }
 | 
			
		||||
    shadow(libs.mcauthlib) { isTransitive = false }
 | 
			
		||||
    shadow(libs.minecraftauth) { isTransitive = false }
 | 
			
		||||
    shadow(libs.raknet) { isTransitive = false }
 | 
			
		||||
 | 
			
		||||
    // Consequences of shading + relocating mcauthlib: shadow/relocate mcpl!
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -4,6 +4,7 @@
 | 
			
		|||
  "package": "org.geysermc.geyser.platform.mod.mixin",
 | 
			
		||||
  "compatibilityLevel": "JAVA_17",
 | 
			
		||||
  "mixins": [
 | 
			
		||||
    "server.BlockPlaceMixin",
 | 
			
		||||
    "server.ServerConnectionListenerMixin"
 | 
			
		||||
  ],
 | 
			
		||||
  "server": [
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,11 +25,10 @@ dependencies {
 | 
			
		|||
 | 
			
		||||
    api(libs.bundles.protocol)
 | 
			
		||||
 | 
			
		||||
    api(libs.mcauthlib)
 | 
			
		||||
    api(libs.minecraftauth)
 | 
			
		||||
    api(libs.mcprotocollib) {
 | 
			
		||||
        exclude("io.netty", "netty-all")
 | 
			
		||||
        exclude("com.github.GeyserMC", "packetlib")
 | 
			
		||||
        exclude("com.github.GeyserMC", "mcauthlib")
 | 
			
		||||
        exclude("net.raphimc", "MinecraftAuth")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    implementation(libs.raknet) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,7 +39,9 @@ public final class Constants {
 | 
			
		|||
    public static final String GEYSER_DOWNLOAD_LOCATION = "https://geysermc.org/download";
 | 
			
		||||
    public static final String UPDATE_PERMISSION = "geyser.update";
 | 
			
		||||
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    static final String SAVED_REFRESH_TOKEN_FILE = "saved-refresh-tokens.json";
 | 
			
		||||
    static final String SAVED_AUTH_CHAINS_FILE = "saved-auth-chains.json";
 | 
			
		||||
 | 
			
		||||
    public static final String GEYSER_CUSTOM_NAMESPACE = "geyser_custom";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -54,4 +56,4 @@ public final class Constants {
 | 
			
		|||
        }
 | 
			
		||||
        GLOBAL_API_WS_URI = wsUri;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,6 +29,7 @@ import com.fasterxml.jackson.core.JsonParser;
 | 
			
		|||
import com.fasterxml.jackson.core.type.TypeReference;
 | 
			
		||||
import com.fasterxml.jackson.databind.DeserializationFeature;
 | 
			
		||||
import com.fasterxml.jackson.databind.ObjectMapper;
 | 
			
		||||
import com.google.gson.Gson;
 | 
			
		||||
import io.netty.channel.epoll.Epoll;
 | 
			
		||||
import io.netty.util.NettyRuntime;
 | 
			
		||||
import io.netty.util.concurrent.DefaultThreadFactory;
 | 
			
		||||
| 
						 | 
				
			
			@ -38,6 +39,8 @@ import lombok.Getter;
 | 
			
		|||
import lombok.Setter;
 | 
			
		||||
import net.kyori.adventure.text.Component;
 | 
			
		||||
import net.kyori.adventure.text.format.NamedTextColor;
 | 
			
		||||
import net.raphimc.minecraftauth.step.java.session.StepFullJavaSession;
 | 
			
		||||
import net.raphimc.minecraftauth.step.msa.StepMsaToken;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.Nullable;
 | 
			
		||||
| 
						 | 
				
			
			@ -55,7 +58,11 @@ import org.geysermc.geyser.api.GeyserApi;
 | 
			
		|||
import org.geysermc.geyser.api.command.CommandSource;
 | 
			
		||||
import org.geysermc.geyser.api.event.EventBus;
 | 
			
		||||
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.BedrockListener;
 | 
			
		||||
import org.geysermc.geyser.api.network.RemoteServer;
 | 
			
		||||
| 
						 | 
				
			
			@ -85,7 +92,14 @@ import org.geysermc.geyser.skin.SkinProvider;
 | 
			
		|||
import org.geysermc.geyser.text.GeyserLocale;
 | 
			
		||||
import org.geysermc.geyser.text.MinecraftLocale;
 | 
			
		||||
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.MinecraftAuthLogger;
 | 
			
		||||
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 java.io.File;
 | 
			
		||||
| 
						 | 
				
			
			@ -97,11 +111,19 @@ import java.net.UnknownHostException;
 | 
			
		|||
import java.nio.file.Path;
 | 
			
		||||
import java.security.Key;
 | 
			
		||||
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.ConcurrentHashMap;
 | 
			
		||||
import java.util.concurrent.Executors;
 | 
			
		||||
import java.util.concurrent.ScheduledExecutorService;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
import java.util.regex.Matcher;
 | 
			
		||||
import java.util.regex.Pattern;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -161,7 +183,7 @@ public class GeyserImpl implements GeyserApi {
 | 
			
		|||
 | 
			
		||||
    private PendingMicrosoftAuthentication pendingMicrosoftAuthentication;
 | 
			
		||||
    @Getter(AccessLevel.NONE)
 | 
			
		||||
    private Map<String, String> savedRefreshTokens;
 | 
			
		||||
    private Map<String, String> savedAuthChains;
 | 
			
		||||
 | 
			
		||||
    @Getter
 | 
			
		||||
    private static GeyserImpl instance;
 | 
			
		||||
| 
						 | 
				
			
			@ -534,37 +556,84 @@ public class GeyserImpl implements GeyserApi {
 | 
			
		|||
 | 
			
		||||
        if (config.getRemote().authType() == AuthType.ONLINE) {
 | 
			
		||||
            // May be written/read to on multiple threads from each GeyserSession as well as writing the config
 | 
			
		||||
            savedRefreshTokens = new ConcurrentHashMap<>();
 | 
			
		||||
            savedAuthChains = new ConcurrentHashMap<>();
 | 
			
		||||
 | 
			
		||||
            File tokensFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile();
 | 
			
		||||
            if (tokensFile.exists()) {
 | 
			
		||||
            // TODO Remove after a while - just a migration help
 | 
			
		||||
            //noinspection deprecation
 | 
			
		||||
            File refreshTokensFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile();
 | 
			
		||||
            if (refreshTokensFile.exists()) {
 | 
			
		||||
                logger.info("Migrating refresh tokens to auth chains...");
 | 
			
		||||
                TypeReference<Map<String, String>> type = new TypeReference<>() { };
 | 
			
		||||
                Map<String, String> refreshTokens = null;
 | 
			
		||||
                try {
 | 
			
		||||
                    refreshTokens = JSON_MAPPER.readValue(refreshTokensFile, type);
 | 
			
		||||
                } catch (IOException e) {
 | 
			
		||||
                    // ignored - we'll just delete this file :))
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (refreshTokens != null) {
 | 
			
		||||
                    List<String> validUsers = config.getSavedUserLogins();
 | 
			
		||||
                    final Gson gson = new Gson();
 | 
			
		||||
                    for (Map.Entry<String, String> entry : refreshTokens.entrySet()) {
 | 
			
		||||
                        String user = entry.getKey();
 | 
			
		||||
                        if (!validUsers.contains(user)) {
 | 
			
		||||
                            continue;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        // Migrate refresh tokens to auth chains
 | 
			
		||||
                        try {
 | 
			
		||||
                            StepFullJavaSession javaSession = PendingMicrosoftAuthentication.AUTH_FLOW.apply(false, 10);
 | 
			
		||||
                            StepFullJavaSession.FullJavaSession fullJavaSession = javaSession.getFromInput(
 | 
			
		||||
                                MinecraftAuthLogger.INSTANCE,
 | 
			
		||||
                                PendingMicrosoftAuthentication.AUTH_CLIENT,
 | 
			
		||||
                                new StepMsaToken.RefreshToken(entry.getValue())
 | 
			
		||||
                            );
 | 
			
		||||
 | 
			
		||||
                            String authChain = gson.toJson(javaSession.toJson(fullJavaSession));
 | 
			
		||||
                            savedAuthChains.put(user, authChain);
 | 
			
		||||
                        } catch (Exception e) {
 | 
			
		||||
                            GeyserImpl.getInstance().getLogger().warning("Could not migrate " + entry.getKey() + " to an auth chain! " +
 | 
			
		||||
                                "They will need to sign in the next time they join Geyser.");
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        // Ensure the new additions are written to the file
 | 
			
		||||
                        scheduleAuthChainsWrite();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Finally: Delete it. Goodbye!
 | 
			
		||||
                refreshTokensFile.delete();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            File authChainsFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_AUTH_CHAINS_FILE).toFile();
 | 
			
		||||
            if (authChainsFile.exists()) {
 | 
			
		||||
                TypeReference<Map<String, String>> type = new TypeReference<>() { };
 | 
			
		||||
 | 
			
		||||
                Map<String, String> refreshTokenFile = null;
 | 
			
		||||
                Map<String, String> authChainFile = null;
 | 
			
		||||
                try {
 | 
			
		||||
                    refreshTokenFile = JSON_MAPPER.readValue(tokensFile, type);
 | 
			
		||||
                    authChainFile = JSON_MAPPER.readValue(authChainsFile, type);
 | 
			
		||||
                } catch (IOException e) {
 | 
			
		||||
                    logger.error("Cannot load saved user tokens!", e);
 | 
			
		||||
                }
 | 
			
		||||
                if (refreshTokenFile != null) {
 | 
			
		||||
                if (authChainFile != null) {
 | 
			
		||||
                    List<String> validUsers = config.getSavedUserLogins();
 | 
			
		||||
                    boolean doWrite = false;
 | 
			
		||||
                    for (Map.Entry<String, String> entry : refreshTokenFile.entrySet()) {
 | 
			
		||||
                    for (Map.Entry<String, String> entry : authChainFile.entrySet()) {
 | 
			
		||||
                        String user = entry.getKey();
 | 
			
		||||
                        if (!validUsers.contains(user)) {
 | 
			
		||||
                            // Perform a write to this file to purge the now-unused name
 | 
			
		||||
                            doWrite = true;
 | 
			
		||||
                            continue;
 | 
			
		||||
                        }
 | 
			
		||||
                        savedRefreshTokens.put(user, entry.getValue());
 | 
			
		||||
                        savedAuthChains.put(user, entry.getValue());
 | 
			
		||||
                    }
 | 
			
		||||
                    if (doWrite) {
 | 
			
		||||
                        scheduleRefreshTokensWrite();
 | 
			
		||||
                        scheduleAuthChainsWrite();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            savedRefreshTokens = null;
 | 
			
		||||
            savedAuthChains = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED);
 | 
			
		||||
| 
						 | 
				
			
			@ -645,16 +714,11 @@ public class GeyserImpl implements GeyserApi {
 | 
			
		|||
            bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.kick.done"));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        scheduledThread.shutdown();
 | 
			
		||||
        geyserServer.shutdown();
 | 
			
		||||
        if (skinUploader != null) {
 | 
			
		||||
            skinUploader.close();
 | 
			
		||||
        }
 | 
			
		||||
        newsHandler.shutdown();
 | 
			
		||||
 | 
			
		||||
        if (this.erosionUnixListener != null) {
 | 
			
		||||
            this.erosionUnixListener.close();
 | 
			
		||||
        }
 | 
			
		||||
        runIfNonNull(scheduledThread, ScheduledExecutorService::shutdown);
 | 
			
		||||
        runIfNonNull(geyserServer, GeyserServer::shutdown);
 | 
			
		||||
        runIfNonNull(skinUploader, FloodgateSkinUploader::close);
 | 
			
		||||
        runIfNonNull(newsHandler, NewsHandler::shutdown);
 | 
			
		||||
        runIfNonNull(erosionUnixListener, UnixSocketClientListener::close);
 | 
			
		||||
 | 
			
		||||
        Registries.RESOURCE_PACKS.get().clear();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -816,11 +880,11 @@ public class GeyserImpl implements GeyserApi {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public String refreshTokenFor(@NonNull String bedrockName) {
 | 
			
		||||
        return savedRefreshTokens.get(bedrockName);
 | 
			
		||||
    public String authChainFor(@NonNull String bedrockName) {
 | 
			
		||||
        return savedAuthChains.get(bedrockName);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void saveRefreshToken(@NonNull String bedrockName, @NonNull String refreshToken) {
 | 
			
		||||
    public void saveAuthChain(@NonNull String bedrockName, @NonNull String authChain) {
 | 
			
		||||
        if (!getConfig().getSavedUserLogins().contains(bedrockName)) {
 | 
			
		||||
            // Do not save this login
 | 
			
		||||
            return;
 | 
			
		||||
| 
						 | 
				
			
			@ -828,20 +892,26 @@ public class GeyserImpl implements GeyserApi {
 | 
			
		|||
 | 
			
		||||
        // We can safely overwrite old instances because MsaAuthenticationService#getLoginResponseFromRefreshToken
 | 
			
		||||
        // refreshes the token for us
 | 
			
		||||
        if (!Objects.equals(refreshToken, savedRefreshTokens.put(bedrockName, refreshToken))) {
 | 
			
		||||
            scheduleRefreshTokensWrite();
 | 
			
		||||
        if (!Objects.equals(authChain, savedAuthChains.put(bedrockName, authChain))) {
 | 
			
		||||
            scheduleAuthChainsWrite();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void scheduleRefreshTokensWrite() {
 | 
			
		||||
    private <T> void runIfNonNull(T nullable, Consumer<T> consumer) {
 | 
			
		||||
        if (nullable != null) {
 | 
			
		||||
            consumer.accept(nullable);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void scheduleAuthChainsWrite() {
 | 
			
		||||
        scheduledThread.execute(() -> {
 | 
			
		||||
            // Ensure all writes are handled on the same thread
 | 
			
		||||
            File savedTokens = getBootstrap().getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile();
 | 
			
		||||
            File savedAuthChains = getBootstrap().getSavedUserLoginsFolder().resolve(Constants.SAVED_AUTH_CHAINS_FILE).toFile();
 | 
			
		||||
            TypeReference<Map<String, String>> type = new TypeReference<>() { };
 | 
			
		||||
            try (FileWriter writer = new FileWriter(savedTokens)) {
 | 
			
		||||
            try (FileWriter writer = new FileWriter(savedAuthChains)) {
 | 
			
		||||
                JSON_MAPPER.writerFor(type)
 | 
			
		||||
                        .withDefaultPrettyPrinter()
 | 
			
		||||
                        .writeValue(writer, savedRefreshTokens);
 | 
			
		||||
                        .writeValue(writer, this.savedAuthChains);
 | 
			
		||||
            } catch (IOException e) {
 | 
			
		||||
                getLogger().error("Unable to write saved refresh tokens!", e);
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -327,6 +327,7 @@ public final class EntityDefinitions {
 | 
			
		|||
            TEXT_DISPLAY = EntityDefinition.inherited(TextDisplayEntity::new, displayBase)
 | 
			
		||||
                    .type(EntityType.TEXT_DISPLAY)
 | 
			
		||||
                    .identifier("minecraft:armor_stand")
 | 
			
		||||
                    .offset(-0.5f)
 | 
			
		||||
                    .addTranslator(MetadataType.CHAT, TextDisplayEntity::setText)
 | 
			
		||||
                    .addTranslator(null) // Line width
 | 
			
		||||
                    .addTranslator(null) // Background color
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -49,7 +49,8 @@ public enum GeyserAttributeType {
 | 
			
		|||
    ATTACK_KNOCKBACK("minecraft:generic.attack_knockback", null, 1.5f, Float.MAX_VALUE, 0f),
 | 
			
		||||
    ATTACK_SPEED("minecraft:generic.attack_speed", null, 0f, 1024f, 4f),
 | 
			
		||||
    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
 | 
			
		||||
    ABSORPTION(null, "minecraft:absorption", 0f, 1024f, 0f),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -48,6 +48,7 @@ import org.geysermc.geyser.session.GeyserSession;
 | 
			
		|||
import org.geysermc.geyser.translator.item.ItemTranslator;
 | 
			
		||||
import org.geysermc.geyser.util.AttributeUtils;
 | 
			
		||||
import org.geysermc.geyser.util.InteractionResult;
 | 
			
		||||
import org.geysermc.geyser.util.MathUtils;
 | 
			
		||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.Attribute;
 | 
			
		||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.AttributeType;
 | 
			
		||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
 | 
			
		||||
| 
						 | 
				
			
			@ -252,7 +253,7 @@ public class LivingEntity extends Entity {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    private void setAttributeScale(float scale) {
 | 
			
		||||
        this.attributeScale = scale;
 | 
			
		||||
        this.attributeScale = MathUtils.clamp(scale, GeyserAttributeType.SCALE.getMinimum(), GeyserAttributeType.SCALE.getMaximum());
 | 
			
		||||
        applyScale();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
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) {
 | 
			
		||||
        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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,7 +31,6 @@ import org.cloudburstmc.math.vector.Vector3f;
 | 
			
		|||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
 | 
			
		||||
import org.geysermc.geyser.entity.EntityDefinition;
 | 
			
		||||
import org.geysermc.geyser.inventory.GeyserItemStack;
 | 
			
		||||
import org.geysermc.geyser.item.Items;
 | 
			
		||||
import org.geysermc.geyser.item.type.Item;
 | 
			
		||||
import org.geysermc.geyser.session.GeyserSession;
 | 
			
		||||
import org.geysermc.geyser.session.cache.tags.ItemTag;
 | 
			
		||||
| 
						 | 
				
			
			@ -39,13 +38,9 @@ import org.geysermc.geyser.util.InteractionResult;
 | 
			
		|||
import org.geysermc.geyser.util.InteractiveTag;
 | 
			
		||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
 | 
			
		||||
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
        super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -64,6 +64,11 @@ public class SessionPlayerEntity extends PlayerEntity {
 | 
			
		|||
     */
 | 
			
		||||
    @Getter
 | 
			
		||||
    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.
 | 
			
		||||
     */
 | 
			
		||||
| 
						 | 
				
			
			@ -232,6 +237,8 @@ public class SessionPlayerEntity extends PlayerEntity {
 | 
			
		|||
    protected void updateAttribute(Attribute javaAttribute, List<AttributeData> newAttributes) {
 | 
			
		||||
        if (javaAttribute.getType() == AttributeType.Builtin.GENERIC_ATTACK_SPEED) {
 | 
			
		||||
            session.setAttackSpeed(AttributeUtils.calculateValue(javaAttribute));
 | 
			
		||||
        } else if (javaAttribute.getType() == AttributeType.Builtin.PLAYER_BLOCK_INTERACTION_RANGE) {
 | 
			
		||||
            this.blockInteractionRange = AttributeUtils.calculateValue(javaAttribute);
 | 
			
		||||
        } else {
 | 
			
		||||
            super.updateAttribute(javaAttribute, newAttributes);
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -295,6 +302,7 @@ public class SessionPlayerEntity extends PlayerEntity {
 | 
			
		|||
    public void resetAttributes() {
 | 
			
		||||
        attributes.clear();
 | 
			
		||||
        maxHealth = GeyserAttributeType.MAX_HEALTH.getDefaultValue();
 | 
			
		||||
        blockInteractionRange = GeyserAttributeType.BLOCK_INTERACTION_RANGE.getDefaultValue();
 | 
			
		||||
 | 
			
		||||
        UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
 | 
			
		||||
        attributesPacket.setRuntimeEntityId(geyserId);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,6 +29,7 @@ import lombok.Getter;
 | 
			
		|||
import lombok.Setter;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
import org.geysermc.geyser.item.type.Item;
 | 
			
		||||
import org.geysermc.geyser.session.GeyserSession;
 | 
			
		||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
 | 
			
		||||
import org.jetbrains.annotations.Range;
 | 
			
		||||
| 
						 | 
				
			
			@ -73,6 +74,10 @@ public class PlayerInventory extends Inventory {
 | 
			
		|||
        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) {
 | 
			
		||||
        if (36 + heldItemSlot > this.size) {
 | 
			
		||||
            GeyserImpl.getInstance().getLogger().debug("Held item slot was larger than expected!");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -50,6 +50,7 @@ public class StoredItemMappings {
 | 
			
		|||
    private final ItemMapping milkBucket;
 | 
			
		||||
    private final ItemMapping powderSnowBucket;
 | 
			
		||||
    private final ItemMapping shield;
 | 
			
		||||
    private final ItemMapping totem;
 | 
			
		||||
    private final ItemMapping upgradeTemplate;
 | 
			
		||||
    private final ItemMapping wheat;
 | 
			
		||||
    private final ItemMapping writableBook;
 | 
			
		||||
| 
						 | 
				
			
			@ -66,6 +67,7 @@ public class StoredItemMappings {
 | 
			
		|||
        this.milkBucket = load(itemMappings, Items.MILK_BUCKET);
 | 
			
		||||
        this.powderSnowBucket = load(itemMappings, Items.POWDER_SNOW_BUCKET);
 | 
			
		||||
        this.shield = load(itemMappings, Items.SHIELD);
 | 
			
		||||
        this.totem = load(itemMappings, Items.TOTEM_OF_UNDYING);
 | 
			
		||||
        this.upgradeTemplate = load(itemMappings, Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE);
 | 
			
		||||
        this.wheat = load(itemMappings, Items.WHEAT);
 | 
			
		||||
        this.writableBook = load(itemMappings, Items.WRITABLE_BOOK);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,6 +25,7 @@
 | 
			
		|||
 | 
			
		||||
package org.geysermc.geyser.item;
 | 
			
		||||
 | 
			
		||||
import org.geysermc.geyser.item.components.Rarity;
 | 
			
		||||
import org.geysermc.geyser.item.components.ToolTier;
 | 
			
		||||
import org.geysermc.geyser.item.type.*;
 | 
			
		||||
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_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 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 BUDDING_AMETHYST = register(new BlockItem(builder(), Blocks.BUDDING_AMETHYST));
 | 
			
		||||
    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_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 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 ENDER_CHEST = register(new BlockItem(builder(), Blocks.ENDER_CHEST));
 | 
			
		||||
    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 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 COMMAND_BLOCK = register(new BlockItem(builder(), Blocks.COMMAND_BLOCK));
 | 
			
		||||
    public static final Item BEACON = register(new BlockItem(builder(), Blocks.BEACON));
 | 
			
		||||
    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().rarity(Rarity.RARE), Blocks.BEACON));
 | 
			
		||||
    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 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 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 BARRIER = register(new BlockItem(builder(), Blocks.BARRIER));
 | 
			
		||||
    public static final Item LIGHT = register(new BlockItem(builder(), Blocks.LIGHT));
 | 
			
		||||
    public static final Item BARRIER = register(new BlockItem(builder().rarity(Rarity.EPIC), Blocks.BARRIER));
 | 
			
		||||
    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 WHITE_CARPET = register(new BlockItem(builder(), Blocks.WHITE_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 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 REPEATING_COMMAND_BLOCK = register(new BlockItem(builder(), Blocks.REPEATING_COMMAND_BLOCK));
 | 
			
		||||
    public static final Item CHAIN_COMMAND_BLOCK = register(new BlockItem(builder(), Blocks.CHAIN_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().rarity(Rarity.EPIC), Blocks.CHAIN_COMMAND_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 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 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 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));
 | 
			
		||||
| 
						 | 
				
			
			@ -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_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 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 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));
 | 
			
		||||
| 
						 | 
				
			
			@ -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 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 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_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)));
 | 
			
		||||
| 
						 | 
				
			
			@ -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 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 STRUCTURE_BLOCK = register(new BlockItem(builder(), Blocks.STRUCTURE_BLOCK));
 | 
			
		||||
    public static final Item JIGSAW = register(new BlockItem(builder(), Blocks.JIGSAW));
 | 
			
		||||
    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().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_SCUTE = register(new Item("turtle_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 COOKED_PORKCHOP = register(new Item("cooked_porkchop", 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 ENCHANTED_GOLDEN_APPLE = register(new Item("enchanted_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().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 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));
 | 
			
		||||
| 
						 | 
				
			
			@ -1042,7 +1043,7 @@ public final class Items {
 | 
			
		|||
    public static final Item BLAZE_POWDER = register(new Item("blaze_powder", builder()));
 | 
			
		||||
    public static final Item MAGMA_CREAM = register(new Item("magma_cream", builder()));
 | 
			
		||||
    public static final Item BREWING_STAND = register(new BlockItem(builder(), Blocks.BREWING_STAND));
 | 
			
		||||
    public static final Item CAULDRON = register(new BlockItem(builder(), Blocks.CAULDRON, Blocks.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 GLISTERING_MELON_SLICE = register(new Item("glistering_melon_slice", 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_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 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 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 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 GLOW_ITEM_FRAME = register(new Item("glow_item_frame", builder()));
 | 
			
		||||
    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 MAP = register(new MapItem("map", 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 WITHER_SKELETON_SKULL = register(new BlockItem(builder(), 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 ZOMBIE_HEAD = register(new BlockItem(builder(), 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 DRAGON_HEAD = register(new BlockItem(builder(), 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 NETHER_STAR = register(new Item("nether_star", builder()));
 | 
			
		||||
    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().rarity(Rarity.UNCOMMON), Blocks.WITHER_SKELETON_SKULL, Blocks.WITHER_SKELETON_WALL_SKULL));
 | 
			
		||||
    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().rarity(Rarity.UNCOMMON), Blocks.ZOMBIE_HEAD, Blocks.ZOMBIE_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().rarity(Rarity.UNCOMMON), Blocks.DRAGON_HEAD, Blocks.DRAGON_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().rarity(Rarity.UNCOMMON)));
 | 
			
		||||
    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_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 PRISMARINE_SHARD = register(new Item("prismarine_shard", 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 LEAD = register(new Item("lead", 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 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));
 | 
			
		||||
| 
						 | 
				
			
			@ -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 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 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 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));
 | 
			
		||||
| 
						 | 
				
			
			@ -1194,52 +1195,52 @@ public final class Items {
 | 
			
		|||
    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_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 SPECTRAL_ARROW = register(new Item("spectral_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 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 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 DEBUG_STICK = register(new Item("debug_stick", builder().stackSize(1)));
 | 
			
		||||
    public static final Item MUSIC_DISC_13 = register(new Item("music_disc_13", builder().stackSize(1)));
 | 
			
		||||
    public static final Item MUSIC_DISC_CAT = register(new Item("music_disc_cat", builder().stackSize(1)));
 | 
			
		||||
    public static final Item MUSIC_DISC_BLOCKS = register(new Item("music_disc_blocks", builder().stackSize(1)));
 | 
			
		||||
    public static final Item MUSIC_DISC_CHIRP = register(new Item("music_disc_chirp", builder().stackSize(1)));
 | 
			
		||||
    public static final Item MUSIC_DISC_CREATOR = register(new Item("music_disc_creator", builder().stackSize(1)));
 | 
			
		||||
    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_FAR = register(new Item("music_disc_far", builder().stackSize(1)));
 | 
			
		||||
    public static final Item MUSIC_DISC_MALL = register(new Item("music_disc_mall", builder().stackSize(1)));
 | 
			
		||||
    public static final Item MUSIC_DISC_MELLOHI = register(new Item("music_disc_mellohi", builder().stackSize(1)));
 | 
			
		||||
    public static final Item MUSIC_DISC_STAL = register(new Item("music_disc_stal", builder().stackSize(1)));
 | 
			
		||||
    public static final Item MUSIC_DISC_STRAD = register(new Item("music_disc_strad", builder().stackSize(1)));
 | 
			
		||||
    public static final Item MUSIC_DISC_WARD = register(new Item("music_disc_ward", builder().stackSize(1)));
 | 
			
		||||
    public static final Item MUSIC_DISC_11 = register(new Item("music_disc_11", builder().stackSize(1)));
 | 
			
		||||
    public static final Item MUSIC_DISC_WAIT = register(new Item("music_disc_wait", builder().stackSize(1)));
 | 
			
		||||
    public static final Item MUSIC_DISC_OTHERSIDE = register(new Item("music_disc_otherside", builder().stackSize(1)));
 | 
			
		||||
    public static final Item MUSIC_DISC_RELIC = register(new Item("music_disc_relic", builder().stackSize(1)));
 | 
			
		||||
    public static final Item MUSIC_DISC_5 = register(new Item("music_disc_5", builder().stackSize(1)));
 | 
			
		||||
    public static final Item MUSIC_DISC_PIGSTEP = register(new Item("music_disc_pigstep", builder().stackSize(1)));
 | 
			
		||||
    public static final Item MUSIC_DISC_PRECIPICE = register(new Item("music_disc_precipice", 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).rarity(Rarity.EPIC).glint(true)));
 | 
			
		||||
    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).rarity(Rarity.RARE)));
 | 
			
		||||
    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).rarity(Rarity.RARE)));
 | 
			
		||||
    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).rarity(Rarity.RARE)));
 | 
			
		||||
    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).rarity(Rarity.RARE)));
 | 
			
		||||
    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).rarity(Rarity.RARE)));
 | 
			
		||||
    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).rarity(Rarity.RARE)));
 | 
			
		||||
    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).rarity(Rarity.RARE)));
 | 
			
		||||
    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).rarity(Rarity.RARE)));
 | 
			
		||||
    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).rarity(Rarity.RARE)));
 | 
			
		||||
    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 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 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 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 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 SKULL_BANNER_PATTERN = register(new Item("skull_banner_pattern", builder().stackSize(1)));
 | 
			
		||||
    public static final Item MOJANG_BANNER_PATTERN = register(new Item("mojang_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).rarity(Rarity.UNCOMMON)));
 | 
			
		||||
    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 PIGLIN_BANNER_PATTERN = register(new Item("piglin_banner_pattern", builder().stackSize(1)));
 | 
			
		||||
    public static final Item FLOW_BANNER_PATTERN = register(new Item("flow_banner_pattern", builder().stackSize(1)));
 | 
			
		||||
    public static final Item GUSTER_BANNER_PATTERN = register(new Item("guster_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).rarity(Rarity.RARE)));
 | 
			
		||||
    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 COMPOSTER = register(new BlockItem(builder(), Blocks.COMPOSTER));
 | 
			
		||||
    public static final Item BARREL = register(new BlockItem(builder(), Blocks.BARREL));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -27,6 +27,8 @@ package org.geysermc.geyser.item.type;
 | 
			
		|||
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.IntArrays;
 | 
			
		||||
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.NbtMapBuilder;
 | 
			
		||||
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.List;
 | 
			
		||||
 | 
			
		||||
public class FireworkRocketItem extends Item {
 | 
			
		||||
public class FireworkRocketItem extends Item implements BedrockRequiresTagItem {
 | 
			
		||||
    public FireworkRocketItem(String javaIdentifier, Builder builder) {
 | 
			
		||||
        super(javaIdentifier, builder);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -58,14 +60,16 @@ public class FireworkRocketItem extends Item {
 | 
			
		|||
        fireworksNbt.putByte("Flight", (byte) fireworks.getFlightDuration());
 | 
			
		||||
 | 
			
		||||
        List<Fireworks.FireworkExplosion> explosions = fireworks.getExplosions();
 | 
			
		||||
        if (explosions.isEmpty()) {
 | 
			
		||||
            return;
 | 
			
		||||
        if (!explosions.isEmpty()) {
 | 
			
		||||
            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());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -138,4 +142,20 @@ public class FireworkRocketItem extends Item {
 | 
			
		|||
            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());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,6 +35,7 @@ import org.geysermc.geyser.GeyserImpl;
 | 
			
		|||
import org.geysermc.geyser.inventory.GeyserItemStack;
 | 
			
		||||
import org.geysermc.geyser.inventory.item.BedrockEnchantment;
 | 
			
		||||
import org.geysermc.geyser.item.Items;
 | 
			
		||||
import org.geysermc.geyser.item.components.Rarity;
 | 
			
		||||
import org.geysermc.geyser.item.enchantment.Enchantment;
 | 
			
		||||
import org.geysermc.geyser.level.block.type.Block;
 | 
			
		||||
import org.geysermc.geyser.registry.type.ItemMapping;
 | 
			
		||||
| 
						 | 
				
			
			@ -63,12 +64,16 @@ public class Item {
 | 
			
		|||
    private final int stackSize;
 | 
			
		||||
    private final int attackDamage;
 | 
			
		||||
    private final int maxDamage;
 | 
			
		||||
    private final Rarity rarity;
 | 
			
		||||
    private final boolean glint;
 | 
			
		||||
 | 
			
		||||
    public Item(String javaIdentifier, Builder builder) {
 | 
			
		||||
        this.javaIdentifier = MinecraftKey.key(javaIdentifier).asString().intern();
 | 
			
		||||
        this.stackSize = builder.stackSize;
 | 
			
		||||
        this.maxDamage = builder.maxDamage;
 | 
			
		||||
        this.attackDamage = builder.attackDamage;
 | 
			
		||||
        this.rarity = builder.rarity;
 | 
			
		||||
        this.glint = builder.glint;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String javaIdentifier() {
 | 
			
		||||
| 
						 | 
				
			
			@ -91,6 +96,14 @@ public class Item {
 | 
			
		|||
        return stackSize;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Rarity rarity() {
 | 
			
		||||
        return rarity;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean glint() {
 | 
			
		||||
        return glint;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isValidRepairItem(Item other) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -157,6 +170,11 @@ public class Item {
 | 
			
		|||
            builder.putInt("RepairCost", repairCost);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // If the tag exists, it's unbreakable; the value is just weather to show the tooltip. As of Java 1.21
 | 
			
		||||
        if (components.getDataComponents().containsKey(DataComponentType.UNBREAKABLE)) {
 | 
			
		||||
            builder.putByte("Unbreakable", (byte) 1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Prevents the client from trying to stack items with untranslated components
 | 
			
		||||
        // Relies on correct hash code implementation, and some luck
 | 
			
		||||
        builder.putInt("GeyserHash", components.hashCode()); // TODO: don't rely on this
 | 
			
		||||
| 
						 | 
				
			
			@ -275,6 +293,8 @@ public class Item {
 | 
			
		|||
        private int stackSize = 64;
 | 
			
		||||
        private int maxDamage;
 | 
			
		||||
        private int attackDamage;
 | 
			
		||||
        private Rarity rarity = Rarity.COMMON;
 | 
			
		||||
        private boolean glint = false;
 | 
			
		||||
 | 
			
		||||
        public Builder stackSize(int stackSize) {
 | 
			
		||||
            this.stackSize = stackSize;
 | 
			
		||||
| 
						 | 
				
			
			@ -292,6 +312,16 @@ public class Item {
 | 
			
		|||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Builder rarity(Rarity rarity) {
 | 
			
		||||
            this.rarity = rarity;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Builder glint(boolean glintOverride) {
 | 
			
		||||
            this.glint = glintOverride;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Builder() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,7 +25,7 @@
 | 
			
		|||
 | 
			
		||||
package org.geysermc.geyser.item.type;
 | 
			
		||||
 | 
			
		||||
import com.github.steveice10.mc.auth.data.GameProfile;
 | 
			
		||||
import org.geysermc.mcprotocollib.auth.GameProfile;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.geysermc.geyser.level.block.type.Block;
 | 
			
		||||
import org.geysermc.geyser.session.GeyserSession;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,7 +25,7 @@
 | 
			
		|||
 | 
			
		||||
package org.geysermc.geyser.level.block.type;
 | 
			
		||||
 | 
			
		||||
import com.github.steveice10.mc.auth.data.GameProfile;
 | 
			
		||||
import org.geysermc.mcprotocollib.auth.GameProfile;
 | 
			
		||||
import org.cloudburstmc.math.vector.Vector3i;
 | 
			
		||||
import org.cloudburstmc.nbt.NbtMap;
 | 
			
		||||
import org.cloudburstmc.nbt.NbtMapBuilder;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -73,7 +73,9 @@ public final class GameProtocol {
 | 
			
		|||
        SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(Bedrock_v685.CODEC.toBuilder()
 | 
			
		||||
            .minecraftVersion("1.21.0/1.21.1")
 | 
			
		||||
            .build()));
 | 
			
		||||
        SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC);
 | 
			
		||||
        SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder()
 | 
			
		||||
            .minecraftVersion("1.21.2/1.21.3")
 | 
			
		||||
            .build());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -274,10 +274,10 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
 | 
			
		|||
 | 
			
		||||
    private boolean couldLoginUserByName(String bedrockUsername) {
 | 
			
		||||
        if (geyser.getConfig().getSavedUserLogins().contains(bedrockUsername)) {
 | 
			
		||||
            String refreshToken = geyser.refreshTokenFor(bedrockUsername);
 | 
			
		||||
            if (refreshToken != null) {
 | 
			
		||||
            String authChain = geyser.authChainFor(bedrockUsername);
 | 
			
		||||
            if (authChain != null) {
 | 
			
		||||
                geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().name()));
 | 
			
		||||
                session.authenticateWithRefreshToken(refreshToken);
 | 
			
		||||
                session.authenticateWithAuthChain(authChain);
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,10 +35,10 @@ import java.net.InetSocketAddress;
 | 
			
		|||
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
 | 
			
		||||
     * @return string of the MOTD
 | 
			
		||||
     * @return the ping information
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -80,7 +80,6 @@ public class CreativeItemRegistryPopulator {
 | 
			
		|||
    private static ItemData.@Nullable Builder createItemData(JsonNode itemNode, BlockMappings blockMappings, Map<String, ItemDefinition> definitions) {
 | 
			
		||||
        int count = 1;
 | 
			
		||||
        int damage = 0;
 | 
			
		||||
        int bedrockBlockRuntimeId;
 | 
			
		||||
        NbtMap tag = null;
 | 
			
		||||
 | 
			
		||||
        String identifier = itemNode.get("id").textValue();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -199,7 +199,13 @@ public class CustomItemRegistryPopulator {
 | 
			
		|||
                    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());
 | 
			
		||||
        builder.putCompound("components", componentBuilder.build());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -58,6 +58,8 @@ import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData;
 | 
			
		|||
import org.geysermc.geyser.inventory.item.StoredItemMappings;
 | 
			
		||||
import org.geysermc.geyser.item.GeyserCustomMappingData;
 | 
			
		||||
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.registry.BlockRegistries;
 | 
			
		||||
import org.geysermc.geyser.registry.Registries;
 | 
			
		||||
| 
						 | 
				
			
			@ -165,6 +167,7 @@ public class ItemRegistryPopulator {
 | 
			
		|||
            Map<Item, ItemMapping> javaItemToMapping = new Object2ObjectOpenHashMap<>();
 | 
			
		||||
 | 
			
		||||
            List<ItemData> creativeItems = new ArrayList<>();
 | 
			
		||||
            Set<String> noBlockDefinitions = new ObjectOpenHashSet<>();
 | 
			
		||||
 | 
			
		||||
            AtomicInteger creativeNetId = new AtomicInteger();
 | 
			
		||||
            CreativeItemRegistryPopulator.populate(palette, definitions, itemBuilder -> {
 | 
			
		||||
| 
						 | 
				
			
			@ -185,6 +188,9 @@ public class ItemRegistryPopulator {
 | 
			
		|||
                            bedrockBlockIdOverrides.put(identifier, item.getBlockDefinition());
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Item mappings should also NOT have a block definition for these.
 | 
			
		||||
                    noBlockDefinitions.add(item.getDefinition().getIdentifier());
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -252,7 +258,12 @@ public class ItemRegistryPopulator {
 | 
			
		|||
                    } else {
 | 
			
		||||
                        // Try to get an example block runtime ID from the creative contents packet, for Bedrock identifier obtaining
 | 
			
		||||
                        int aValidBedrockBlockId = blacklistedIdentifiers.getOrDefault(bedrockIdentifier, customBlockItemOverride != null ? customBlockItemOverride.getRuntimeId() : -1);
 | 
			
		||||
                        if (aValidBedrockBlockId != -1 || customBlockItemOverride != null) {
 | 
			
		||||
                        if (aValidBedrockBlockId == -1 && customBlockItemOverride == null) {
 | 
			
		||||
                            // Fallback
 | 
			
		||||
                            if (!noBlockDefinitions.contains(entry.getValue().getBedrockIdentifier())) {
 | 
			
		||||
                                bedrockBlock = blockMappings.getBedrockBlock(firstBlockRuntimeId);
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            // 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.
 | 
			
		||||
                            // However, in order for some visuals and crafting to work, we need to send the first matching block state
 | 
			
		||||
| 
						 | 
				
			
			@ -399,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
 | 
			
		||||
                    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.");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,6 +28,7 @@ package org.geysermc.geyser.registry.type;
 | 
			
		|||
import it.unimi.dsi.fastutil.Pair;
 | 
			
		||||
import lombok.Builder;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
import lombok.ToString;
 | 
			
		||||
import lombok.Value;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
 | 
			
		||||
| 
						 | 
				
			
			@ -42,6 +43,7 @@ import java.util.List;
 | 
			
		|||
@Value
 | 
			
		||||
@Builder
 | 
			
		||||
@EqualsAndHashCode
 | 
			
		||||
@ToString
 | 
			
		||||
public class ItemMapping {
 | 
			
		||||
    public static final ItemMapping AIR = new ItemMapping(
 | 
			
		||||
            "minecraft:air",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,9 +25,8 @@
 | 
			
		|||
 | 
			
		||||
package org.geysermc.geyser.session;
 | 
			
		||||
 | 
			
		||||
import com.github.steveice10.mc.auth.data.GameProfile;
 | 
			
		||||
import com.github.steveice10.mc.auth.exception.request.RequestException;
 | 
			
		||||
import com.github.steveice10.mc.auth.service.MsaAuthenticationService;
 | 
			
		||||
import com.google.gson.Gson;
 | 
			
		||||
import com.google.gson.JsonObject;
 | 
			
		||||
import io.netty.channel.Channel;
 | 
			
		||||
import io.netty.channel.EventLoop;
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 | 
			
		||||
| 
						 | 
				
			
			@ -41,22 +40,60 @@ import lombok.Getter;
 | 
			
		|||
import lombok.Setter;
 | 
			
		||||
import lombok.experimental.Accessors;
 | 
			
		||||
import net.kyori.adventure.key.Key;
 | 
			
		||||
import net.raphimc.minecraftauth.step.java.StepMCProfile;
 | 
			
		||||
import net.raphimc.minecraftauth.step.java.StepMCToken;
 | 
			
		||||
import net.raphimc.minecraftauth.step.java.session.StepFullJavaSession;
 | 
			
		||||
import org.checkerframework.checker.index.qual.NonNegative;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.Nullable;
 | 
			
		||||
import org.checkerframework.common.value.qual.IntRange;
 | 
			
		||||
import org.cloudburstmc.math.vector.*;
 | 
			
		||||
import org.cloudburstmc.math.vector.Vector2f;
 | 
			
		||||
import org.cloudburstmc.math.vector.Vector2i;
 | 
			
		||||
import org.cloudburstmc.math.vector.Vector3d;
 | 
			
		||||
import org.cloudburstmc.math.vector.Vector3f;
 | 
			
		||||
import org.cloudburstmc.math.vector.Vector3i;
 | 
			
		||||
import org.cloudburstmc.nbt.NbtMap;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.BedrockDisconnectReasons;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.BedrockServerSession;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.data.*;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.data.Ability;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.data.AbilityLayer;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.data.AuthoritativeMovementMode;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.data.ChatRestrictionLevel;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.data.ExperimentData;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.data.GamePublishSetting;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.data.GameRuleData;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.data.GameType;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.data.PlayerPermission;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.data.SpawnBiomeType;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.data.command.CommandEnumData;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.data.command.SoftEnumUpdateType;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.*;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.AvailableEntityIdentifiersPacket;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.BiomeDefinitionListPacket;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.CameraPresetsPacket;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.ChunkRadiusUpdatedPacket;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.CreativeContentPacket;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.EmoteListPacket;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.GameRulesChangedPacket;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.ItemComponentPacket;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEvent2Packet;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.PlayStatusPacket;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.SetTimePacket;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.StartGamePacket;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.SyncEntityPropertyPacket;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.TextPacket;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.TransferPacket;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateAbilitiesPacket;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateAdventureSettingsPacket;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateClientInputLocksPacket;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateSoftEnumPacket;
 | 
			
		||||
import org.cloudburstmc.protocol.common.util.OptionalBoolean;
 | 
			
		||||
import org.geysermc.api.util.BedrockPlatform;
 | 
			
		||||
import org.geysermc.api.util.InputMode;
 | 
			
		||||
| 
						 | 
				
			
			@ -106,7 +143,22 @@ import org.geysermc.geyser.registry.type.BlockMappings;
 | 
			
		|||
import org.geysermc.geyser.registry.type.ItemMappings;
 | 
			
		||||
import org.geysermc.geyser.session.auth.AuthData;
 | 
			
		||||
import org.geysermc.geyser.session.auth.BedrockClientData;
 | 
			
		||||
import org.geysermc.geyser.session.cache.*;
 | 
			
		||||
import org.geysermc.geyser.session.cache.AdvancementsCache;
 | 
			
		||||
import org.geysermc.geyser.session.cache.BookEditCache;
 | 
			
		||||
import org.geysermc.geyser.session.cache.ChunkCache;
 | 
			
		||||
import org.geysermc.geyser.session.cache.EntityCache;
 | 
			
		||||
import org.geysermc.geyser.session.cache.EntityEffectCache;
 | 
			
		||||
import org.geysermc.geyser.session.cache.FormCache;
 | 
			
		||||
import org.geysermc.geyser.session.cache.LodestoneCache;
 | 
			
		||||
import org.geysermc.geyser.session.cache.PistonCache;
 | 
			
		||||
import org.geysermc.geyser.session.cache.PreferencesCache;
 | 
			
		||||
import org.geysermc.geyser.session.cache.RegistryCache;
 | 
			
		||||
import org.geysermc.geyser.session.cache.SkullCache;
 | 
			
		||||
import org.geysermc.geyser.session.cache.StructureBlockCache;
 | 
			
		||||
import org.geysermc.geyser.session.cache.TagCache;
 | 
			
		||||
import org.geysermc.geyser.session.cache.TeleportCache;
 | 
			
		||||
import org.geysermc.geyser.session.cache.WorldBorder;
 | 
			
		||||
import org.geysermc.geyser.session.cache.WorldCache;
 | 
			
		||||
import org.geysermc.geyser.skin.FloodgateSkinUploader;
 | 
			
		||||
import org.geysermc.geyser.text.GeyserLocale;
 | 
			
		||||
import org.geysermc.geyser.text.MinecraftLocale;
 | 
			
		||||
| 
						 | 
				
			
			@ -116,9 +168,15 @@ import org.geysermc.geyser.util.ChunkUtils;
 | 
			
		|||
import org.geysermc.geyser.util.DimensionUtils;
 | 
			
		||||
import org.geysermc.geyser.util.EntityUtils;
 | 
			
		||||
import org.geysermc.geyser.util.LoginEncryptionUtils;
 | 
			
		||||
import org.geysermc.geyser.util.MinecraftAuthLogger;
 | 
			
		||||
import org.geysermc.mcprotocollib.auth.GameProfile;
 | 
			
		||||
import org.geysermc.mcprotocollib.network.BuiltinFlags;
 | 
			
		||||
import org.geysermc.mcprotocollib.network.Session;
 | 
			
		||||
import org.geysermc.mcprotocollib.network.event.session.*;
 | 
			
		||||
import org.geysermc.mcprotocollib.network.event.session.ConnectedEvent;
 | 
			
		||||
import org.geysermc.mcprotocollib.network.event.session.DisconnectedEvent;
 | 
			
		||||
import org.geysermc.mcprotocollib.network.event.session.PacketErrorEvent;
 | 
			
		||||
import org.geysermc.mcprotocollib.network.event.session.PacketSendingEvent;
 | 
			
		||||
import org.geysermc.mcprotocollib.network.event.session.SessionAdapter;
 | 
			
		||||
import org.geysermc.mcprotocollib.network.packet.Packet;
 | 
			
		||||
import org.geysermc.mcprotocollib.network.tcp.TcpClientSession;
 | 
			
		||||
import org.geysermc.mcprotocollib.network.tcp.TcpSession;
 | 
			
		||||
| 
						 | 
				
			
			@ -153,7 +211,16 @@ import java.net.ConnectException;
 | 
			
		|||
import java.net.InetSocketAddress;
 | 
			
		||||
import java.nio.charset.StandardCharsets;
 | 
			
		||||
import java.time.Instant;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.BitSet;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Queue;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
import java.util.concurrent.ConcurrentLinkedQueue;
 | 
			
		||||
import java.util.concurrent.ScheduledFuture;
 | 
			
		||||
| 
						 | 
				
			
			@ -163,6 +230,8 @@ import java.util.concurrent.atomic.AtomicInteger;
 | 
			
		|||
@Getter
 | 
			
		||||
public class GeyserSession implements GeyserConnection, GeyserCommandSource {
 | 
			
		||||
 | 
			
		||||
    private static final Gson GSON = new Gson();
 | 
			
		||||
 | 
			
		||||
    private final GeyserImpl geyser;
 | 
			
		||||
    private final UpstreamSession upstream;
 | 
			
		||||
    private DownstreamSession downstream;
 | 
			
		||||
| 
						 | 
				
			
			@ -222,7 +291,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
 | 
			
		|||
    private boolean closingInventory;
 | 
			
		||||
 | 
			
		||||
    @Setter
 | 
			
		||||
    private InventoryTranslator inventoryTranslator = InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR;
 | 
			
		||||
    private @NonNull InventoryTranslator inventoryTranslator = InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Use {@link #getNextItemNetId()} instead for consistency
 | 
			
		||||
| 
						 | 
				
			
			@ -688,7 +757,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void authenticateWithRefreshToken(String refreshToken) {
 | 
			
		||||
    public void authenticateWithAuthChain(String authChain) {
 | 
			
		||||
        if (loggedIn) {
 | 
			
		||||
            geyser.getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.auth.already_loggedin", getAuthData().name()));
 | 
			
		||||
            return;
 | 
			
		||||
| 
						 | 
				
			
			@ -697,24 +766,23 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
 | 
			
		|||
        loggingIn = true;
 | 
			
		||||
 | 
			
		||||
        CompletableFuture.supplyAsync(() -> {
 | 
			
		||||
            MsaAuthenticationService service = new MsaAuthenticationService(GeyserImpl.OAUTH_CLIENT_ID);
 | 
			
		||||
            service.setRefreshToken(refreshToken);
 | 
			
		||||
            StepFullJavaSession step = PendingMicrosoftAuthentication.AUTH_FLOW.apply(true, 30);
 | 
			
		||||
            StepFullJavaSession.FullJavaSession response;
 | 
			
		||||
            try {
 | 
			
		||||
                service.login();
 | 
			
		||||
            } catch (RequestException e) {
 | 
			
		||||
                geyser.getLogger().error("Error while attempting to use refresh token for " + bedrockUsername() + "!", e);
 | 
			
		||||
                response = step.refresh(MinecraftAuthLogger.INSTANCE, PendingMicrosoftAuthentication.AUTH_CLIENT, step.fromJson(GSON.fromJson(authChain, JsonObject.class)));
 | 
			
		||||
            } catch (Exception e) {
 | 
			
		||||
                geyser.getLogger().error("Error while attempting to use auth chain for " + bedrockUsername() + "!", e);
 | 
			
		||||
                return Boolean.FALSE;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            GameProfile profile = service.getSelectedProfile();
 | 
			
		||||
            if (profile == null) {
 | 
			
		||||
                // Java account is offline
 | 
			
		||||
                disconnect(GeyserLocale.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode()));
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            StepMCProfile.MCProfile mcProfile = response.getMcProfile();
 | 
			
		||||
            StepMCToken.MCToken mcToken = mcProfile.getMcToken();
 | 
			
		||||
 | 
			
		||||
            protocol = new MinecraftProtocol(profile, service.getAccessToken());
 | 
			
		||||
            geyser.saveRefreshToken(bedrockUsername(), service.getRefreshToken());
 | 
			
		||||
            protocol = new MinecraftProtocol(
 | 
			
		||||
                    new GameProfile(mcProfile.getId(), mcProfile.getName()),
 | 
			
		||||
                    mcToken.getAccessToken()
 | 
			
		||||
            );
 | 
			
		||||
            geyser.saveAuthChain(bedrockUsername(), GSON.toJson(step.toJson(response)));
 | 
			
		||||
            return Boolean.TRUE;
 | 
			
		||||
        }).whenComplete((successful, ex) -> {
 | 
			
		||||
            if (this.closed) {
 | 
			
		||||
| 
						 | 
				
			
			@ -759,25 +827,15 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
 | 
			
		|||
        final PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getOrCreateTask(
 | 
			
		||||
                getAuthData().xuid()
 | 
			
		||||
        );
 | 
			
		||||
        task.setOnline(true);
 | 
			
		||||
        task.resetTimer();
 | 
			
		||||
 | 
			
		||||
        if (task.getAuthentication().isDone()) {
 | 
			
		||||
        if (task.getAuthentication() != null && task.getAuthentication().isDone()) {
 | 
			
		||||
            onMicrosoftLoginComplete(task);
 | 
			
		||||
        } else {
 | 
			
		||||
            task.getCode(offlineAccess).whenComplete((response, ex) -> {
 | 
			
		||||
                boolean connected = !closed;
 | 
			
		||||
                if (ex != null) {
 | 
			
		||||
                    if (connected) {
 | 
			
		||||
                        geyser.getLogger().error("Failed to get Microsoft auth code", ex);
 | 
			
		||||
                        disconnect(ex.toString());
 | 
			
		||||
                    }
 | 
			
		||||
                    task.cleanup(); // error getting auth code -> clean up immediately
 | 
			
		||||
                } else if (connected) {
 | 
			
		||||
                    LoginEncryptionUtils.buildAndShowMicrosoftCodeWindow(this, response);
 | 
			
		||||
                    task.getAuthentication().whenComplete((r, $) -> onMicrosoftLoginComplete(task));
 | 
			
		||||
            task.resetRunningFlow();
 | 
			
		||||
            task.performLoginAttempt(offlineAccess, code -> {
 | 
			
		||||
                if (!closed) {
 | 
			
		||||
                    LoginEncryptionUtils.buildAndShowMicrosoftCodeWindow(this, code);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            }).handle((r, e) -> onMicrosoftLoginComplete(task));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -789,36 +847,32 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
 | 
			
		|||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        task.cleanup(); // player is online -> remove pending authentication immediately
 | 
			
		||||
        Throwable ex = task.getLoginException();
 | 
			
		||||
        if (ex != null) {
 | 
			
		||||
            geyser.getLogger().error("Failed to log in with Microsoft code!", ex);
 | 
			
		||||
            disconnect(ex.toString());
 | 
			
		||||
        } else {
 | 
			
		||||
            MsaAuthenticationService service = task.getMsaAuthenticationService();
 | 
			
		||||
            GameProfile selectedProfile = service.getSelectedProfile();
 | 
			
		||||
            if (selectedProfile == null) {
 | 
			
		||||
                disconnect(GeyserLocale.getPlayerLocaleString(
 | 
			
		||||
                        "geyser.network.remote.invalid_account",
 | 
			
		||||
                        clientData.getLanguageCode()
 | 
			
		||||
                ));
 | 
			
		||||
            } else {
 | 
			
		||||
                this.protocol = new MinecraftProtocol(
 | 
			
		||||
                        selectedProfile,
 | 
			
		||||
                        service.getAccessToken()
 | 
			
		||||
                );
 | 
			
		||||
                try {
 | 
			
		||||
                    connectDownstream();
 | 
			
		||||
                } catch (Throwable t) {
 | 
			
		||||
                    t.printStackTrace();
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
        return task.getAuthentication().handle((result, ex) -> {
 | 
			
		||||
             if (ex != null) {
 | 
			
		||||
                 geyser.getLogger().error("Failed to log in with Microsoft code!", ex);
 | 
			
		||||
                 disconnect(ex.toString());
 | 
			
		||||
                 return false;
 | 
			
		||||
             }
 | 
			
		||||
 | 
			
		||||
                // Save our refresh token for later use
 | 
			
		||||
                geyser.saveRefreshToken(bedrockUsername(), service.getRefreshToken());
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
             StepMCProfile.MCProfile mcProfile = result.session().getMcProfile();
 | 
			
		||||
             StepMCToken.MCToken mcToken = mcProfile.getMcToken();
 | 
			
		||||
 | 
			
		||||
             this.protocol = new MinecraftProtocol(
 | 
			
		||||
                     new GameProfile(mcProfile.getId(), mcProfile.getName()),
 | 
			
		||||
                     mcToken.getAccessToken()
 | 
			
		||||
             );
 | 
			
		||||
 | 
			
		||||
             try {
 | 
			
		||||
                 connectDownstream();
 | 
			
		||||
             } catch (Throwable t) {
 | 
			
		||||
                 t.printStackTrace();
 | 
			
		||||
                 return false;
 | 
			
		||||
             }
 | 
			
		||||
 | 
			
		||||
             // Save our auth chain for later use
 | 
			
		||||
             geyser.saveAuthChain(bedrockUsername(), GSON.toJson(result.step().toJson(result.session())));
 | 
			
		||||
             return true;
 | 
			
		||||
         }).getNow(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
| 
						 | 
				
			
			@ -1101,7 +1155,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
 | 
			
		|||
            if (authData != null) {
 | 
			
		||||
                PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getTask(authData.xuid());
 | 
			
		||||
                if (task != null) {
 | 
			
		||||
                    task.setOnline(false);
 | 
			
		||||
                    task.resetRunningFlow();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,27 +25,44 @@
 | 
			
		|||
 | 
			
		||||
package org.geysermc.geyser.session;
 | 
			
		||||
 | 
			
		||||
import com.github.steveice10.mc.auth.exception.request.AuthPendingException;
 | 
			
		||||
import com.github.steveice10.mc.auth.exception.request.RequestException;
 | 
			
		||||
import com.github.steveice10.mc.auth.service.MsaAuthenticationService;
 | 
			
		||||
import com.google.common.cache.CacheBuilder;
 | 
			
		||||
import com.google.common.cache.CacheLoader;
 | 
			
		||||
import com.google.common.cache.LoadingCache;
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import lombok.Setter;
 | 
			
		||||
import lombok.SneakyThrows;
 | 
			
		||||
import net.lenni0451.commons.httpclient.HttpClient;
 | 
			
		||||
import net.raphimc.minecraftauth.MinecraftAuth;
 | 
			
		||||
import net.raphimc.minecraftauth.step.java.session.StepFullJavaSession;
 | 
			
		||||
import net.raphimc.minecraftauth.step.msa.StepMsaDeviceCode;
 | 
			
		||||
import net.raphimc.minecraftauth.util.MicrosoftConstants;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
import org.geysermc.geyser.GeyserLogger;
 | 
			
		||||
import org.geysermc.geyser.util.MinecraftAuthLogger;
 | 
			
		||||
 | 
			
		||||
import java.io.Serial;
 | 
			
		||||
import java.util.concurrent.*;
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
import java.util.concurrent.CompletionException;
 | 
			
		||||
import java.util.concurrent.ExecutionException;
 | 
			
		||||
import java.util.concurrent.Executor;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
import java.util.function.BiFunction;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Pending Microsoft authentication task cache.
 | 
			
		||||
 * It permits user to exit the server while they authorize Geyser to access their Microsoft account.
 | 
			
		||||
 */
 | 
			
		||||
public class PendingMicrosoftAuthentication {
 | 
			
		||||
    public static final HttpClient AUTH_CLIENT = MinecraftAuth.createHttpClient();
 | 
			
		||||
    public static final BiFunction<Boolean, Integer, StepFullJavaSession> AUTH_FLOW = (offlineAccess, timeoutSec) -> MinecraftAuth.builder()
 | 
			
		||||
            .withClientId(GeyserImpl.OAUTH_CLIENT_ID)
 | 
			
		||||
            .withScope(offlineAccess ? "XboxLive.signin XboxLive.offline_access" : "XboxLive.signin")
 | 
			
		||||
            .withTimeout(timeoutSec)
 | 
			
		||||
            .deviceCode()
 | 
			
		||||
            .withoutDeviceToken()
 | 
			
		||||
            .regularAuthentication(MicrosoftConstants.JAVA_XSTS_RELYING_PARTY)
 | 
			
		||||
            .buildMinecraftJavaProfileStep(false);
 | 
			
		||||
    /**
 | 
			
		||||
     * For GeyserConnect usage.
 | 
			
		||||
     */
 | 
			
		||||
| 
						 | 
				
			
			@ -57,8 +74,8 @@ public class PendingMicrosoftAuthentication {
 | 
			
		|||
                .build(new CacheLoader<>() {
 | 
			
		||||
                    @Override
 | 
			
		||||
                    public AuthenticationTask load(@NonNull String userKey) {
 | 
			
		||||
                        return storeServerInformation ? new ProxyAuthenticationTask(userKey, timeoutSeconds * 1000L)
 | 
			
		||||
                                : new AuthenticationTask(userKey, timeoutSeconds * 1000L);
 | 
			
		||||
                        return storeServerInformation ? new ProxyAuthenticationTask(userKey, timeoutSeconds)
 | 
			
		||||
                                : new AuthenticationTask(userKey, timeoutSeconds);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -80,37 +97,23 @@ public class PendingMicrosoftAuthentication {
 | 
			
		|||
    public class AuthenticationTask {
 | 
			
		||||
        private static final Executor DELAYED_BY_ONE_SECOND = CompletableFuture.delayedExecutor(1, TimeUnit.SECONDS);
 | 
			
		||||
 | 
			
		||||
        @Getter
 | 
			
		||||
        private final MsaAuthenticationService msaAuthenticationService = new MsaAuthenticationService(GeyserImpl.OAUTH_CLIENT_ID);
 | 
			
		||||
        private final String userKey;
 | 
			
		||||
        private final long timeoutMs;
 | 
			
		||||
 | 
			
		||||
        private long remainingTimeMs;
 | 
			
		||||
 | 
			
		||||
        @Setter
 | 
			
		||||
        private boolean online = true;
 | 
			
		||||
 | 
			
		||||
        private final int timeoutSec;
 | 
			
		||||
        @Getter
 | 
			
		||||
        private final CompletableFuture<MsaAuthenticationService> authentication;
 | 
			
		||||
        private CompletableFuture<StepChainResult> authentication;
 | 
			
		||||
 | 
			
		||||
        @Getter
 | 
			
		||||
        private volatile Throwable loginException;
 | 
			
		||||
 | 
			
		||||
        private AuthenticationTask(String userKey, long timeoutMs) {
 | 
			
		||||
        private AuthenticationTask(String userKey, int timeoutSec) {
 | 
			
		||||
            this.userKey = userKey;
 | 
			
		||||
            this.timeoutMs = timeoutMs;
 | 
			
		||||
            this.remainingTimeMs = timeoutMs;
 | 
			
		||||
 | 
			
		||||
            this.authentication = new CompletableFuture<>();
 | 
			
		||||
            this.authentication.whenComplete((r, ex) -> {
 | 
			
		||||
                this.loginException = ex;
 | 
			
		||||
                // avoid memory leak, in case player doesn't connect again
 | 
			
		||||
                CompletableFuture.delayedExecutor(timeoutMs, TimeUnit.MILLISECONDS).execute(this::cleanup);
 | 
			
		||||
            });
 | 
			
		||||
            this.timeoutSec = timeoutSec;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void resetTimer() {
 | 
			
		||||
            this.remainingTimeMs = this.timeoutMs;
 | 
			
		||||
        public void resetRunningFlow() {
 | 
			
		||||
            if (authentication == null) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Interrupt the current flow
 | 
			
		||||
            this.authentication.cancel(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void cleanup() {
 | 
			
		||||
| 
						 | 
				
			
			@ -121,52 +124,18 @@ public class PendingMicrosoftAuthentication {
 | 
			
		|||
            authentications.invalidate(userKey);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public CompletableFuture<MsaAuthenticationService.MsCodeResponse> getCode(boolean offlineAccess) {
 | 
			
		||||
            // Request the code
 | 
			
		||||
            CompletableFuture<MsaAuthenticationService.MsCodeResponse> code = CompletableFuture.supplyAsync(
 | 
			
		||||
                    () -> tryGetCode(offlineAccess));
 | 
			
		||||
            // Once the code is received, continuously try to request the access token, profile, etc
 | 
			
		||||
            code.thenRun(() -> performLoginAttempt(System.currentTimeMillis()));
 | 
			
		||||
            return code;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * @param offlineAccess whether we want a refresh token for later use.
 | 
			
		||||
         */
 | 
			
		||||
        private MsaAuthenticationService.MsCodeResponse tryGetCode(boolean offlineAccess) throws CompletionException {
 | 
			
		||||
            try {
 | 
			
		||||
                return msaAuthenticationService.getAuthCode(offlineAccess);
 | 
			
		||||
            } catch (RequestException e) {
 | 
			
		||||
                throw new CompletionException(e);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void performLoginAttempt(long lastAttempt) {
 | 
			
		||||
            CompletableFuture.runAsync(() -> {
 | 
			
		||||
        public CompletableFuture<StepChainResult> performLoginAttempt(boolean offlineAccess, Consumer<StepMsaDeviceCode.MsaDeviceCode> deviceCodeConsumer) {
 | 
			
		||||
            return authentication = CompletableFuture.supplyAsync(() -> {
 | 
			
		||||
                try {
 | 
			
		||||
                    msaAuthenticationService.login();
 | 
			
		||||
                } catch (AuthPendingException e) {
 | 
			
		||||
                    long currentAttempt = System.currentTimeMillis();
 | 
			
		||||
                    if (!online) {
 | 
			
		||||
                        // decrement timer only when player's offline
 | 
			
		||||
                        remainingTimeMs -= currentAttempt - lastAttempt;
 | 
			
		||||
                        if (remainingTimeMs <= 0L) {
 | 
			
		||||
                            // time's up
 | 
			
		||||
                            authentication.completeExceptionally(new TaskTimeoutException());
 | 
			
		||||
                            cleanup();
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    // try again in 1 second
 | 
			
		||||
                    performLoginAttempt(currentAttempt);
 | 
			
		||||
                    return;
 | 
			
		||||
                    StepFullJavaSession step = AUTH_FLOW.apply(offlineAccess, timeoutSec);
 | 
			
		||||
                    return new StepChainResult(step, step.getFromInput(MinecraftAuthLogger.INSTANCE, AUTH_CLIENT, new StepMsaDeviceCode.MsaDeviceCodeCallback(deviceCodeConsumer)));
 | 
			
		||||
                } catch (Exception e) {
 | 
			
		||||
                    authentication.completeExceptionally(e);
 | 
			
		||||
                    return;
 | 
			
		||||
                    throw new CompletionException(e);
 | 
			
		||||
                }
 | 
			
		||||
                // login successful
 | 
			
		||||
                authentication.complete(msaAuthenticationService);
 | 
			
		||||
            }, DELAYED_BY_ONE_SECOND);
 | 
			
		||||
            }, DELAYED_BY_ONE_SECOND).whenComplete((r, ex) -> {
 | 
			
		||||
                // avoid memory leak, in case player doesn't connect again
 | 
			
		||||
                CompletableFuture.delayedExecutor(timeoutSec, TimeUnit.SECONDS).execute(this::cleanup);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
| 
						 | 
				
			
			@ -181,22 +150,11 @@ public class PendingMicrosoftAuthentication {
 | 
			
		|||
        private String server;
 | 
			
		||||
        private int port;
 | 
			
		||||
 | 
			
		||||
        private ProxyAuthenticationTask(String userKey, long timeoutMs) {
 | 
			
		||||
            super(userKey, timeoutMs);
 | 
			
		||||
        private ProxyAuthenticationTask(String userKey, int timeoutSec) {
 | 
			
		||||
            super(userKey, timeoutSec);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see PendingMicrosoftAuthentication
 | 
			
		||||
     */
 | 
			
		||||
    public static class TaskTimeoutException extends Exception {
 | 
			
		||||
 | 
			
		||||
        @Serial
 | 
			
		||||
        private static final long serialVersionUID = 1L;
 | 
			
		||||
 | 
			
		||||
        TaskTimeoutException() {
 | 
			
		||||
            super("It took too long to authorize Geyser to access your Microsoft account. " +
 | 
			
		||||
                    "Please request new code and try again.");
 | 
			
		||||
        }
 | 
			
		||||
    public record StepChainResult(StepFullJavaSession step, StepFullJavaSession.FullJavaSession session) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -141,6 +141,10 @@ public class EntityCache {
 | 
			
		|||
        return playerEntities.values();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void removeAllPlayerEntities() {
 | 
			
		||||
        playerEntities.clear();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void addBossBar(UUID uuid, BossBar bossBar) {
 | 
			
		||||
        bossBars.put(uuid, bossBar);
 | 
			
		||||
        bossBar.addBossBar();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -243,8 +243,16 @@ public class SkullCache {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    public void clear() {
 | 
			
		||||
        for (Skull skull : skulls.values()) {
 | 
			
		||||
            if (skull.entity != null) {
 | 
			
		||||
                skull.entity.despawnEntity();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        skulls.clear();
 | 
			
		||||
        inRangeSkulls.clear();
 | 
			
		||||
        for (SkullPlayerEntity skull : unusedSkullEntities) {
 | 
			
		||||
            skull.despawnEntity();
 | 
			
		||||
        }
 | 
			
		||||
        unusedSkullEntities.clear();
 | 
			
		||||
        totalSkullEntities = 0;
 | 
			
		||||
        lastPlayerPosition = null;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -201,4 +201,4 @@ public final class WorldCache {
 | 
			
		|||
    public String removeActiveRecord(Vector3i pos) {
 | 
			
		||||
        return this.activeRecords.remove(pos);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,11 +25,10 @@
 | 
			
		|||
 | 
			
		||||
package org.geysermc.geyser.skin;
 | 
			
		||||
 | 
			
		||||
import com.github.steveice10.mc.auth.data.GameProfile;
 | 
			
		||||
import com.github.steveice10.mc.auth.data.GameProfile.Texture;
 | 
			
		||||
import com.github.steveice10.mc.auth.data.GameProfile.TextureModel;
 | 
			
		||||
import com.github.steveice10.mc.auth.data.GameProfile.TextureType;
 | 
			
		||||
import com.github.steveice10.mc.auth.exception.property.PropertyException;
 | 
			
		||||
import org.geysermc.mcprotocollib.auth.GameProfile;
 | 
			
		||||
import org.geysermc.mcprotocollib.auth.GameProfile.Texture;
 | 
			
		||||
import org.geysermc.mcprotocollib.auth.GameProfile.TextureModel;
 | 
			
		||||
import org.geysermc.mcprotocollib.auth.GameProfile.TextureType;
 | 
			
		||||
import com.google.common.cache.CacheBuilder;
 | 
			
		||||
import com.google.common.cache.CacheLoader;
 | 
			
		||||
import com.google.common.cache.LoadingCache;
 | 
			
		||||
| 
						 | 
				
			
			@ -113,12 +112,7 @@ public class FakeHeadProvider {
 | 
			
		|||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Map<TextureType, Texture> textures = null;
 | 
			
		||||
        try {
 | 
			
		||||
            textures = profile.getTextures(false);
 | 
			
		||||
        } catch (PropertyException e) {
 | 
			
		||||
            session.getGeyser().getLogger().debug("Failed to get textures from GameProfile: " + e);
 | 
			
		||||
        }
 | 
			
		||||
        Map<TextureType, Texture> textures = profile.getTextures(false);
 | 
			
		||||
 | 
			
		||||
        if (textures == null || textures.isEmpty()) {
 | 
			
		||||
            loadHead(session, entity, profile.getName());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,15 +25,16 @@
 | 
			
		|||
 | 
			
		||||
package org.geysermc.geyser.translator.item;
 | 
			
		||||
 | 
			
		||||
import com.github.steveice10.mc.auth.data.GameProfile;
 | 
			
		||||
import com.github.steveice10.mc.auth.data.GameProfile.Texture;
 | 
			
		||||
import com.github.steveice10.mc.auth.data.GameProfile.TextureType;
 | 
			
		||||
import com.github.steveice10.mc.auth.exception.property.PropertyException;
 | 
			
		||||
import org.geysermc.mcprotocollib.auth.GameProfile;
 | 
			
		||||
import org.geysermc.mcprotocollib.auth.GameProfile.Texture;
 | 
			
		||||
import org.geysermc.mcprotocollib.auth.GameProfile.TextureType;
 | 
			
		||||
import net.kyori.adventure.text.Component;
 | 
			
		||||
import net.kyori.adventure.text.format.NamedTextColor;
 | 
			
		||||
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.NbtMapBuilder;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
 | 
			
		||||
| 
						 | 
				
			
			@ -41,7 +42,9 @@ import org.geysermc.geyser.GeyserImpl;
 | 
			
		|||
import org.geysermc.geyser.api.block.custom.CustomBlockData;
 | 
			
		||||
import org.geysermc.geyser.inventory.GeyserItemStack;
 | 
			
		||||
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.BedrockRequiresTagItem;
 | 
			
		||||
import org.geysermc.geyser.level.block.type.Block;
 | 
			
		||||
import org.geysermc.geyser.registry.BlockRegistries;
 | 
			
		||||
import org.geysermc.geyser.registry.Registries;
 | 
			
		||||
| 
						 | 
				
			
			@ -145,7 +148,26 @@ public final class ItemTranslator {
 | 
			
		|||
            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) {
 | 
			
		||||
            nbtBuilder.setCustomName(customName);
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -162,6 +184,12 @@ public final class ItemTranslator {
 | 
			
		|||
            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());
 | 
			
		||||
        // Finalize the Bedrock NBT
 | 
			
		||||
        builder.tag(nbtBuilder.build());
 | 
			
		||||
| 
						 | 
				
			
			@ -401,16 +429,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.
 | 
			
		||||
     *                         Normally, this should just be white, but for shulker boxes this should be gray.
 | 
			
		||||
| 
						 | 
				
			
			@ -468,12 +486,7 @@ public final class ItemTranslator {
 | 
			
		|||
        
 | 
			
		||||
        GameProfile profile = components.get(DataComponentType.PROFILE);
 | 
			
		||||
        if (profile != null) {
 | 
			
		||||
            Map<TextureType, Texture> textures = null;
 | 
			
		||||
            try {
 | 
			
		||||
                textures = profile.getTextures(false);
 | 
			
		||||
            } catch (PropertyException e) {
 | 
			
		||||
                GeyserImpl.getInstance().getLogger().debug("Failed to get textures from GameProfile: " + e);
 | 
			
		||||
            }
 | 
			
		||||
            Map<TextureType, Texture> textures = profile.getTextures(false);
 | 
			
		||||
 | 
			
		||||
            if (textures == null || textures.isEmpty()) {
 | 
			
		||||
                return null;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,10 +42,9 @@ public class CampfireBlockEntityTranslator extends BlockEntityTranslator {
 | 
			
		|||
    public void translateTag(GeyserSession session, NbtMapBuilder bedrockNbt, NbtMap javaNbt, BlockState blockState) {
 | 
			
		||||
        List<NbtMap> items = javaNbt.getList("Items", NbtType.COMPOUND);
 | 
			
		||||
        if (items != null) {
 | 
			
		||||
            int i = 1;
 | 
			
		||||
            for (NbtMap itemTag : items) {
 | 
			
		||||
                bedrockNbt.put("Item" + i, getItem(session, itemTag));
 | 
			
		||||
                i++;
 | 
			
		||||
                int slot = itemTag.getByte("Slot") + 1;
 | 
			
		||||
                bedrockNbt.put("Item" + slot, getItem(session, itemTag));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -55,8 +54,7 @@ public class CampfireBlockEntityTranslator extends BlockEntityTranslator {
 | 
			
		|||
        if (mapping == null) {
 | 
			
		||||
            mapping = ItemMapping.AIR;
 | 
			
		||||
        }
 | 
			
		||||
        NbtMapBuilder tagBuilder = BedrockItemBuilder.createItemNbt(mapping, tag.getByte("Count"), mapping.getBedrockData());
 | 
			
		||||
        tagBuilder.put("tag", NbtMap.builder().build()); // I don't think this is necessary... - Camo, 1.20.5/1.20.80
 | 
			
		||||
        NbtMapBuilder tagBuilder = BedrockItemBuilder.createItemNbt(mapping, tag.getInt("count"), mapping.getBedrockData());
 | 
			
		||||
        return tagBuilder.build();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -70,7 +70,11 @@ import org.geysermc.geyser.translator.inventory.InventoryTranslator;
 | 
			
		|||
import org.geysermc.geyser.translator.item.ItemTranslator;
 | 
			
		||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
 | 
			
		||||
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.player.GameMode;
 | 
			
		||||
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.item.ItemStack;
 | 
			
		||||
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.concurrent.TimeUnit;
 | 
			
		||||
| 
						 | 
				
			
			@ -90,11 +98,6 @@ import java.util.concurrent.TimeUnit;
 | 
			
		|||
@Translator(packet = InventoryTransactionPacket.class)
 | 
			
		||||
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
 | 
			
		||||
    public void translate(GeyserSession session, InventoryTransactionPacket packet) {
 | 
			
		||||
        if (packet.getTransactionType() == InventoryTransactionType.NORMAL && packet.getActions().size() == 3) {
 | 
			
		||||
| 
						 | 
				
			
			@ -243,17 +246,11 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
 | 
			
		|||
                            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.
 | 
			
		||||
                        Vector3f playerPosition = session.getPlayerEntity().getPosition();
 | 
			
		||||
                        playerPosition = playerPosition.down(EntityDefinitions.PLAYER.offset() - session.getEyeHeight());
 | 
			
		||||
 | 
			
		||||
                        boolean creative = session.getGameMode() == GameMode.CREATIVE;
 | 
			
		||||
 | 
			
		||||
                        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)) {
 | 
			
		||||
                        if (!canInteractWithBlock(session, playerPosition, packetBlockPosition)) {
 | 
			
		||||
                            restoreCorrectBlock(session, blockPos, packet);
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
| 
						 | 
				
			
			@ -262,26 +259,8 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
 | 
			
		|||
                        double clickPositionFullY = (double) packetBlockPosition.getY() + (double) packet.getClickPosition().getY();
 | 
			
		||||
                        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);
 | 
			
		||||
                        // 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 clickDistanceY = clickPositionFullY - blockCenter.getY();
 | 
			
		||||
                        double clickDistanceZ = clickPositionFullZ - blockCenter.getZ();
 | 
			
		||||
| 
						 | 
				
			
			@ -303,13 +282,16 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
 | 
			
		|||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        // Storing the block position allows inconsistencies in block place checking from post-1.19 - pre-1.20.5 to be resolved.
 | 
			
		||||
                        int sequence = session.getWorldCache().nextPredictionSequence();
 | 
			
		||||
                        session.getWorldCache().markPositionInSequence(blockPos);
 | 
			
		||||
                        ServerboundUseItemOnPacket blockPacket = new ServerboundUseItemOnPacket(
 | 
			
		||||
                                packet.getBlockPosition(),
 | 
			
		||||
                                Direction.VALUES[packet.getBlockFace()],
 | 
			
		||||
                                Hand.MAIN_HAND,
 | 
			
		||||
                                packet.getClickPosition().getX(), packet.getClickPosition().getY(), packet.getClickPosition().getZ(),
 | 
			
		||||
                                false,
 | 
			
		||||
                                session.getWorldCache().nextPredictionSequence());
 | 
			
		||||
                                sequence);
 | 
			
		||||
                        session.sendDownstreamGamePacket(blockPacket);
 | 
			
		||||
 | 
			
		||||
                        Item item = session.getPlayerInventory().getItemInHand().asItem();
 | 
			
		||||
| 
						 | 
				
			
			@ -433,14 +415,10 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
 | 
			
		|||
                            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 floatBlockPosition = packet.getBlockPosition().toFloat();
 | 
			
		||||
                        float diffX = playerPosition.getX() - (floatBlockPosition.getX() + 0.5f);
 | 
			
		||||
                        float diffY = (playerPosition.getY() - EntityDefinitions.PLAYER.offset()) - (floatBlockPosition.getY() + 0.5f) + 1.5f;
 | 
			
		||||
                        float diffZ = playerPosition.getZ() - (floatBlockPosition.getZ() + 0.5f);
 | 
			
		||||
                        float distanceSquared = diffX * diffX + diffY * diffY + diffZ * diffZ;
 | 
			
		||||
                        if (distanceSquared > MAXIMUM_BLOCK_DESTROYING_DISTANCE) {
 | 
			
		||||
                        playerPosition = playerPosition.down(EntityDefinitions.PLAYER.offset() - session.getEyeHeight());
 | 
			
		||||
 | 
			
		||||
                        if (!canInteractWithBlock(session, playerPosition, packet.getBlockPosition())) {
 | 
			
		||||
                            restoreCorrectBlock(session, packet.getBlockPosition(), packet);
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
| 
						 | 
				
			
			@ -550,6 +528,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.
 | 
			
		||||
     *
 | 
			
		||||
| 
						 | 
				
			
			@ -696,4 +696,4 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
 | 
			
		|||
            }, 150, TimeUnit.MILLISECONDS));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -46,10 +46,10 @@ public class BedrockSetLocalPlayerAsInitializedTranslator extends PacketTranslat
 | 
			
		|||
                if (session.remoteServer().authType() == AuthType.ONLINE) {
 | 
			
		||||
                    if (!session.isLoggedIn()) {
 | 
			
		||||
                        if (session.getGeyser().getConfig().getSavedUserLogins().contains(session.bedrockUsername())) {
 | 
			
		||||
                            if (session.getGeyser().refreshTokenFor(session.bedrockUsername()) == null) {
 | 
			
		||||
                            if (session.getGeyser().authChainFor(session.bedrockUsername()) == null) {
 | 
			
		||||
                                LoginEncryptionUtils.buildAndShowConsentWindow(session);
 | 
			
		||||
                            } else {
 | 
			
		||||
                                // If the refresh token is not null and we're here, then the refresh token expired
 | 
			
		||||
                                // If the auth chain is not null and we're here, then it expired
 | 
			
		||||
                                // and the expiration form has been cached
 | 
			
		||||
                                session.getFormCache().resendAllForms();
 | 
			
		||||
                            }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.entity.EntityEventType;
 | 
			
		||||
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.entity.type.Entity;
 | 
			
		||||
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.CooldownUtils;
 | 
			
		||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction;
 | 
			
		||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.*;
 | 
			
		||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.*;
 | 
			
		||||
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.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)
 | 
			
		||||
public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket> {
 | 
			
		||||
| 
						 | 
				
			
			@ -70,7 +84,7 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
 | 
			
		|||
        Vector3i vector = packet.getBlockPosition();
 | 
			
		||||
 | 
			
		||||
        switch (packet.getAction()) {
 | 
			
		||||
            case RESPAWN:
 | 
			
		||||
            case RESPAWN -> {
 | 
			
		||||
                // Respawn process is finished and the server and client are both OK with respawning.
 | 
			
		||||
                EntityEventPacket eventPacket = new EntityEventPacket();
 | 
			
		||||
                eventPacket.setRuntimeEntityId(entity.getGeyserId());
 | 
			
		||||
| 
						 | 
				
			
			@ -88,16 +102,16 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
 | 
			
		|||
 | 
			
		||||
                // Needed here since 1.19.81 for dimension switching
 | 
			
		||||
                session.getEntityCache().updateBossBars();
 | 
			
		||||
                break;
 | 
			
		||||
            case START_SWIMMING:
 | 
			
		||||
            }
 | 
			
		||||
            case START_SWIMMING -> {
 | 
			
		||||
                if (!entity.getFlag(EntityFlag.SWIMMING)) {
 | 
			
		||||
                    ServerboundPlayerCommandPacket startSwimPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SPRINTING);
 | 
			
		||||
                    session.sendDownstreamGamePacket(startSwimPacket);
 | 
			
		||||
 | 
			
		||||
                    session.setSwimming(true);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case STOP_SWIMMING:
 | 
			
		||||
            }
 | 
			
		||||
            case STOP_SWIMMING -> {
 | 
			
		||||
                // Prevent packet spam when Bedrock players are crawling near the edge of a block
 | 
			
		||||
                if (!session.getCollisionManager().mustPlayerCrawlHere()) {
 | 
			
		||||
                    ServerboundPlayerCommandPacket stopSwimPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SPRINTING);
 | 
			
		||||
| 
						 | 
				
			
			@ -105,56 +119,55 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
 | 
			
		|||
 | 
			
		||||
                    session.setSwimming(false);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case START_GLIDE:
 | 
			
		||||
            }
 | 
			
		||||
            case START_GLIDE -> {
 | 
			
		||||
                // Otherwise gliding will not work in creative
 | 
			
		||||
                ServerboundPlayerAbilitiesPacket playerAbilitiesPacket = new ServerboundPlayerAbilitiesPacket(false);
 | 
			
		||||
                session.sendDownstreamGamePacket(playerAbilitiesPacket);
 | 
			
		||||
            case STOP_GLIDE:
 | 
			
		||||
                ServerboundPlayerCommandPacket glidePacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_ELYTRA_FLYING);
 | 
			
		||||
                session.sendDownstreamGamePacket(glidePacket);
 | 
			
		||||
                break;
 | 
			
		||||
            case START_SNEAK:
 | 
			
		||||
                sendPlayerGlideToggle(session, entity);
 | 
			
		||||
            }
 | 
			
		||||
            case STOP_GLIDE -> sendPlayerGlideToggle(session, entity);
 | 
			
		||||
            case START_SNEAK -> {
 | 
			
		||||
                ServerboundPlayerCommandPacket startSneakPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SNEAKING);
 | 
			
		||||
                session.sendDownstreamGamePacket(startSneakPacket);
 | 
			
		||||
 | 
			
		||||
                session.startSneaking();
 | 
			
		||||
                break;
 | 
			
		||||
            case STOP_SNEAK:
 | 
			
		||||
            }
 | 
			
		||||
            case STOP_SNEAK -> {
 | 
			
		||||
                ServerboundPlayerCommandPacket stopSneakPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SNEAKING);
 | 
			
		||||
                session.sendDownstreamGamePacket(stopSneakPacket);
 | 
			
		||||
 | 
			
		||||
                session.stopSneaking();
 | 
			
		||||
                break;
 | 
			
		||||
            case START_SPRINT:
 | 
			
		||||
            }
 | 
			
		||||
            case START_SPRINT -> {
 | 
			
		||||
                if (!entity.getFlag(EntityFlag.SWIMMING)) {
 | 
			
		||||
                    ServerboundPlayerCommandPacket startSprintPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SPRINTING);
 | 
			
		||||
                    session.sendDownstreamGamePacket(startSprintPacket);
 | 
			
		||||
                    session.setSprinting(true);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case STOP_SPRINT:
 | 
			
		||||
            }
 | 
			
		||||
            case STOP_SPRINT -> {
 | 
			
		||||
                if (!entity.getFlag(EntityFlag.SWIMMING)) {
 | 
			
		||||
                    ServerboundPlayerCommandPacket stopSprintPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SPRINTING);
 | 
			
		||||
                    session.sendDownstreamGamePacket(stopSprintPacket);
 | 
			
		||||
                }
 | 
			
		||||
                session.setSprinting(false);
 | 
			
		||||
                break;
 | 
			
		||||
            case DROP_ITEM:
 | 
			
		||||
            }
 | 
			
		||||
            case DROP_ITEM -> {
 | 
			
		||||
                ServerboundPlayerActionPacket dropItemPacket = new ServerboundPlayerActionPacket(PlayerAction.DROP_ITEM,
 | 
			
		||||
                        vector, Direction.VALUES[packet.getFace()], 0);
 | 
			
		||||
                    vector, Direction.VALUES[packet.getFace()], 0);
 | 
			
		||||
                session.sendDownstreamGamePacket(dropItemPacket);
 | 
			
		||||
                break;
 | 
			
		||||
            case STOP_SLEEP:
 | 
			
		||||
            }
 | 
			
		||||
            case STOP_SLEEP -> {
 | 
			
		||||
                ServerboundPlayerCommandPacket stopSleepingPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.LEAVE_BED);
 | 
			
		||||
                session.sendDownstreamGamePacket(stopSleepingPacket);
 | 
			
		||||
                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 
 | 
			
		||||
                if (session.getGameMode() == GameMode.CREATIVE) { 
 | 
			
		||||
            }
 | 
			
		||||
            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
 | 
			
		||||
                if (session.getGameMode() == GameMode.CREATIVE) {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                // Start the block breaking animation
 | 
			
		||||
                int blockState = session.getGeyser().getWorldManager().getBlockAt(session, vector);
 | 
			
		||||
                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.
 | 
			
		||||
                Vector3i fireBlockPos = BlockUtils.getBlockPosition(vector, packet.getFace());
 | 
			
		||||
                Block block = session.getGeyser().getWorldManager().blockAt(session, fireBlockPos).block();
 | 
			
		||||
                Direction direction = Direction.VALUES[packet.getFace()];
 | 
			
		||||
                if (block == Blocks.FIRE || block == Blocks.SOUL_FIRE) {
 | 
			
		||||
                    ServerboundPlayerActionPacket startBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.START_DIGGING, fireBlockPos,
 | 
			
		||||
                            Direction.VALUES[packet.getFace()], session.getWorldCache().nextPredictionSequence());
 | 
			
		||||
                        direction, session.getWorldCache().nextPredictionSequence());
 | 
			
		||||
                    session.sendDownstreamGamePacket(startBreakingPacket);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                ServerboundPlayerActionPacket startBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.START_DIGGING,
 | 
			
		||||
                        vector, Direction.VALUES[packet.getFace()], session.getWorldCache().nextPredictionSequence());
 | 
			
		||||
                    vector, direction, session.getWorldCache().nextPredictionSequence());
 | 
			
		||||
                session.sendDownstreamGamePacket(startBreakingPacket);
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
                spawnBlockBreakParticles(session, direction, vector, BlockState.of(blockState));
 | 
			
		||||
            }
 | 
			
		||||
            case CONTINUE_BREAK:
 | 
			
		||||
            case CONTINUE_BREAK -> {
 | 
			
		||||
                if (session.getGameMode() == GameMode.CREATIVE) {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
| 
						 | 
				
			
			@ -201,52 +216,48 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
 | 
			
		|||
                }
 | 
			
		||||
 | 
			
		||||
                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
 | 
			
		||||
                long blockBreakStartTime = session.getBlockBreakStartTime();
 | 
			
		||||
                if (blockBreakStartTime != 0) {
 | 
			
		||||
                    long timeSinceStart = System.currentTimeMillis() - blockBreakStartTime;
 | 
			
		||||
                    // 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
 | 
			
		||||
                        LevelEventPacket effectPacket = new LevelEventPacket();
 | 
			
		||||
                        effectPacket.setPosition(vectorFloat);
 | 
			
		||||
                        effectPacket.setType(LevelEvent.PARTICLE_DESTROY_BLOCK);
 | 
			
		||||
                        effectPacket.setData(session.getBlockMappings().getBedrockBlockId(breakingBlock));
 | 
			
		||||
                        session.sendUpstreamPacket(effectPacket);
 | 
			
		||||
                        
 | 
			
		||||
 | 
			
		||||
                        // Break the block
 | 
			
		||||
                        ServerboundPlayerActionPacket finishBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.FINISH_DIGGING,
 | 
			
		||||
                                vector, Direction.VALUES[packet.getFace()], session.getWorldCache().nextPredictionSequence());
 | 
			
		||||
                            vector, direction, session.getWorldCache().nextPredictionSequence());
 | 
			
		||||
                        session.sendDownstreamGamePacket(finishBreakingPacket);
 | 
			
		||||
                        session.setBlockBreakStartTime(0);
 | 
			
		||||
                        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));
 | 
			
		||||
                session.sendUpstreamPacket(updateBreak);
 | 
			
		||||
                break;
 | 
			
		||||
            case ABORT_BREAK:
 | 
			
		||||
            }
 | 
			
		||||
            case ABORT_BREAK -> {
 | 
			
		||||
                if (session.getGameMode() != GameMode.CREATIVE) {
 | 
			
		||||
                    // 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
 | 
			
		||||
                    Entity itemFrameEntity = ItemFrameEntity.getItemFrameEntity(session, vector);
 | 
			
		||||
                    if (itemFrameEntity != null) {
 | 
			
		||||
                        ServerboundInteractPacket interactPacket = new ServerboundInteractPacket(itemFrameEntity.getEntityId(),
 | 
			
		||||
                                InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking());
 | 
			
		||||
                            InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking());
 | 
			
		||||
                        session.sendDownstreamGamePacket(interactPacket);
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
| 
						 | 
				
			
			@ -260,25 +271,23 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
 | 
			
		|||
                stopBreak.setData(0);
 | 
			
		||||
                session.setBreakingBlock(-1);
 | 
			
		||||
                session.sendUpstreamPacket(stopBreak);
 | 
			
		||||
                break;
 | 
			
		||||
            case STOP_BREAK:
 | 
			
		||||
                // Handled in BedrockInventoryTransactionTranslator
 | 
			
		||||
                break;
 | 
			
		||||
            case DIMENSION_CHANGE_SUCCESS:
 | 
			
		||||
            }
 | 
			
		||||
            // Handled in BedrockInventoryTransactionTranslator
 | 
			
		||||
            case STOP_BREAK -> {
 | 
			
		||||
            }
 | 
			
		||||
            case DIMENSION_CHANGE_SUCCESS -> {
 | 
			
		||||
                //sometimes the client doesn't feel like loading
 | 
			
		||||
                PlayStatusPacket spawnPacket = new PlayStatusPacket();
 | 
			
		||||
                spawnPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN);
 | 
			
		||||
                session.sendUpstreamPacket(spawnPacket);
 | 
			
		||||
 | 
			
		||||
                attributesPacket = new UpdateAttributesPacket();
 | 
			
		||||
                UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
 | 
			
		||||
                attributesPacket.setRuntimeEntityId(entity.getGeyserId());
 | 
			
		||||
                attributesPacket.getAttributes().addAll(entity.getAttributes().values());
 | 
			
		||||
                session.sendUpstreamPacket(attributesPacket);
 | 
			
		||||
                break;
 | 
			
		||||
            case JUMP:
 | 
			
		||||
                entity.setOnGround(false); // Increase block break time while jumping
 | 
			
		||||
                break;
 | 
			
		||||
            case MISSED_SWING:
 | 
			
		||||
            }
 | 
			
		||||
            case JUMP -> entity.setOnGround(false); // Increase block break time while jumping
 | 
			
		||||
            case MISSED_SWING -> {
 | 
			
		||||
                // Java edition sends a cooldown when hitting air.
 | 
			
		||||
                // Normally handled by BedrockLevelSoundEventTranslator, but there is no sound on Java for this.
 | 
			
		||||
                CooldownUtils.sendCooldown(session);
 | 
			
		||||
| 
						 | 
				
			
			@ -294,18 +303,18 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
 | 
			
		|||
                    animatePacket.setAction(AnimatePacket.Action.SWING_ARM);
 | 
			
		||||
                    session.sendUpstreamPacket(animatePacket);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case START_FLYING: // Since 1.20.30
 | 
			
		||||
            }
 | 
			
		||||
            case START_FLYING -> { // Since 1.20.30
 | 
			
		||||
                if (session.isCanFly()) {
 | 
			
		||||
                    if (session.getGameMode() == GameMode.SPECTATOR) {
 | 
			
		||||
                         // should already be flying
 | 
			
		||||
                        // should already be flying
 | 
			
		||||
                        session.sendAdventureSettings();
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    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
 | 
			
		||||
                         // 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
 | 
			
		||||
                        // 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
 | 
			
		||||
                        session.sendAdventureSettings();
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
| 
						 | 
				
			
			@ -313,9 +322,9 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
 | 
			
		|||
                    session.setFlying(true);
 | 
			
		||||
                    session.sendDownstreamGamePacket(new ServerboundPlayerAbilitiesPacket(true));
 | 
			
		||||
                } else {
 | 
			
		||||
                     // update whether we can fly
 | 
			
		||||
                    // update whether we can fly
 | 
			
		||||
                    session.sendAdventureSettings();
 | 
			
		||||
                     // stop flying
 | 
			
		||||
                    // stop flying
 | 
			
		||||
                    PlayerActionPacket stopFlyingPacket = new PlayerActionPacket();
 | 
			
		||||
                    stopFlyingPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId());
 | 
			
		||||
                    stopFlyingPacket.setAction(PlayerActionType.STOP_FLYING);
 | 
			
		||||
| 
						 | 
				
			
			@ -324,24 +333,24 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
 | 
			
		|||
                    stopFlyingPacket.setFace(0);
 | 
			
		||||
                    session.sendUpstreamPacket(stopFlyingPacket);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case STOP_FLYING:
 | 
			
		||||
            }
 | 
			
		||||
            case STOP_FLYING -> {
 | 
			
		||||
                session.setFlying(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);
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                if (state.getValue(Properties.HAS_BOOK, false)) {
 | 
			
		||||
                    session.setDroppingLecternBook(true);
 | 
			
		||||
 | 
			
		||||
                    ServerboundUseItemOnPacket blockPacket = new ServerboundUseItemOnPacket(
 | 
			
		||||
                            vector,
 | 
			
		||||
                            Direction.DOWN,
 | 
			
		||||
                            Hand.MAIN_HAND,
 | 
			
		||||
                            0, 0, 0,
 | 
			
		||||
                            false,
 | 
			
		||||
                            session.getWorldCache().nextPredictionSequence());
 | 
			
		||||
                        vector,
 | 
			
		||||
                        Direction.DOWN,
 | 
			
		||||
                        Hand.MAIN_HAND,
 | 
			
		||||
                        0, 0, 0,
 | 
			
		||||
                        false,
 | 
			
		||||
                        session.getWorldCache().nextPredictionSequence());
 | 
			
		||||
                    session.sendDownstreamGamePacket(blockPacket);
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
| 
						 | 
				
			
			@ -349,10 +358,30 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
 | 
			
		|||
                Entity itemFrame = ItemFrameEntity.getItemFrameEntity(session, packet.getBlockPosition());
 | 
			
		||||
                if (itemFrame != null) {
 | 
			
		||||
                    ServerboundInteractPacket interactPacket = new ServerboundInteractPacket(itemFrame.getEntityId(),
 | 
			
		||||
                            InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking());
 | 
			
		||||
                        InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking());
 | 
			
		||||
                    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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -25,7 +25,7 @@
 | 
			
		|||
 | 
			
		||||
package org.geysermc.geyser.translator.protocol.java;
 | 
			
		||||
 | 
			
		||||
import com.github.steveice10.mc.auth.data.GameProfile;
 | 
			
		||||
import org.geysermc.mcprotocollib.auth.GameProfile;
 | 
			
		||||
import net.kyori.adventure.key.Key;
 | 
			
		||||
import org.geysermc.geyser.api.network.AuthType;
 | 
			
		||||
import org.geysermc.geyser.entity.type.player.PlayerEntity;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -83,25 +83,6 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
 | 
			
		|||
            // Remove extra hearts, hunger, etc.
 | 
			
		||||
            entity.resetAttributes();
 | 
			
		||||
            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());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,7 +25,11 @@
 | 
			
		|||
 | 
			
		||||
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.EqualsAndHashCode;
 | 
			
		||||
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.TrimDataPacket;
 | 
			
		||||
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.type.ItemMapping;
 | 
			
		||||
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.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 static org.geysermc.geyser.util.InventoryUtils.LAST_RECIPE_NET_ID;
 | 
			
		||||
| 
						 | 
				
			
			@ -191,6 +209,9 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
 | 
			
		|||
                case CRAFTING_SPECIAL_MAPCLONING -> {
 | 
			
		||||
                    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 -> {
 | 
			
		||||
                    List<GeyserRecipe> recipes = Registries.RECIPES.get(recipe.getType());
 | 
			
		||||
                    if (recipes != null) {
 | 
			
		||||
| 
						 | 
				
			
			@ -427,7 +448,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
 | 
			
		|||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            // 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());
 | 
			
		||||
            if (inputCombinations == null) {
 | 
			
		||||
                return null;
 | 
			
		||||
| 
						 | 
				
			
			@ -451,7 +472,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
 | 
			
		|||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            // 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());
 | 
			
		||||
            if (inputCombinations == null) {
 | 
			
		||||
                return null;
 | 
			
		||||
| 
						 | 
				
			
			@ -475,7 +496,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
 | 
			
		|||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            // See above
 | 
			
		||||
            output = output.toBuilder().tag(null).build();
 | 
			
		||||
            //output = output.toBuilder().tag(null).build();
 | 
			
		||||
            ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients());
 | 
			
		||||
            if (inputCombinations == null) {
 | 
			
		||||
                return null;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,7 +29,9 @@ import org.cloudburstmc.protocol.bedrock.data.ParticleType;
 | 
			
		|||
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
 | 
			
		||||
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.InventoryContentPacket;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEvent2Packet;
 | 
			
		||||
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.living.animal.ArmadilloEntity;
 | 
			
		||||
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.translator.inventory.InventoryTranslator;
 | 
			
		||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
 | 
			
		||||
import org.geysermc.geyser.translator.protocol.Translator;
 | 
			
		||||
import org.geysermc.geyser.util.InventoryUtils;
 | 
			
		||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundEntityEventPacket;
 | 
			
		||||
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.concurrent.ThreadLocalRandom;
 | 
			
		||||
 | 
			
		||||
@Translator(packet = ClientboundEntityEventPacket.class)
 | 
			
		||||
| 
						 | 
				
			
			@ -154,6 +160,16 @@ public class JavaEntityEventTranslator extends PacketTranslator<ClientboundEntit
 | 
			
		|||
                entityEventPacket.setType(EntityEventType.WITCH_HAT_MAGIC); //TODO: CHECK
 | 
			
		||||
                break;
 | 
			
		||||
            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);
 | 
			
		||||
 | 
			
		||||
                PlaySoundPacket playSoundPacket = new PlaySoundPacket();
 | 
			
		||||
| 
						 | 
				
			
			@ -162,7 +178,16 @@ public class JavaEntityEventTranslator extends PacketTranslator<ClientboundEntit
 | 
			
		|||
                playSoundPacket.setVolume(1.0F);
 | 
			
		||||
                playSoundPacket.setPitch(1.0F + (ThreadLocalRandom.current().nextFloat() * 0.1F) - 0.05F);
 | 
			
		||||
                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:
 | 
			
		||||
                if (entity.getDefinition() == EntityDefinitions.SHEEP) {
 | 
			
		||||
                    entityEventPacket.setType(EntityEventType.EAT_GRASS);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,7 +25,7 @@
 | 
			
		|||
 | 
			
		||||
package org.geysermc.geyser.translator.protocol.java.entity.player;
 | 
			
		||||
 | 
			
		||||
import com.github.steveice10.mc.auth.data.GameProfile;
 | 
			
		||||
import org.geysermc.mcprotocollib.auth.GameProfile;
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.Nullable;
 | 
			
		||||
import org.cloudburstmc.math.vector.Vector3f;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.PlayerListPacket;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -70,6 +70,7 @@ public class JavaOpenBookTranslator extends PacketTranslator<ClientboundOpenBook
 | 
			
		|||
            }
 | 
			
		||||
 | 
			
		||||
            InventoryTranslator translator = InventoryTranslator.inventoryTranslator(ContainerType.LECTERN);
 | 
			
		||||
            Objects.requireNonNull(translator, "could not find lectern inventory translator!");
 | 
			
		||||
            session.setInventoryTranslator(translator);
 | 
			
		||||
 | 
			
		||||
            // Should never be null
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,7 +43,15 @@ import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator;
 | 
			
		|||
import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity;
 | 
			
		||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
 | 
			
		||||
import org.geysermc.geyser.translator.protocol.Translator;
 | 
			
		||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.*;
 | 
			
		||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.BellValue;
 | 
			
		||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.BlockValue;
 | 
			
		||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.ChestValue;
 | 
			
		||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.DecoratedPotValue;
 | 
			
		||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.EndGatewayValue;
 | 
			
		||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.MobSpawnerValue;
 | 
			
		||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.NoteBlockValue;
 | 
			
		||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.PistonValue;
 | 
			
		||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.PistonValueType;
 | 
			
		||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.level.ClientboundBlockEventPacket;
 | 
			
		||||
 | 
			
		||||
@Translator(packet = ClientboundBlockEventPacket.class)
 | 
			
		||||
| 
						 | 
				
			
			@ -80,7 +88,7 @@ public class JavaBlockEventTranslator extends PacketTranslator<ClientboundBlockE
 | 
			
		|||
                // See https://github.com/PaperMC/Paper/blob/6fa1983e9ce177a4a412d5b950fd978620174777/patches/server/0304-Fire-BlockPistonRetractEvent-for-all-empty-pistons.patch
 | 
			
		||||
                if (action == PistonValueType.PULLING || action == PistonValueType.CANCELLED_MID_PUSH) {
 | 
			
		||||
                    BlockState pistonBlock = session.getGeyser().getWorldManager().blockAt(session, position);
 | 
			
		||||
                    if (!pistonBlock.is(Blocks.STICKY_PISTON)) {
 | 
			
		||||
                    if (!isSticky(pistonBlock)) {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (action != PistonValueType.CANCELLED_MID_PUSH) {
 | 
			
		||||
| 
						 | 
				
			
			@ -99,7 +107,7 @@ public class JavaBlockEventTranslator extends PacketTranslator<ClientboundBlockE
 | 
			
		|||
            } else {
 | 
			
		||||
                PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(position, pos -> {
 | 
			
		||||
                    BlockState state = session.getGeyser().getWorldManager().blockAt(session, position);
 | 
			
		||||
                    boolean sticky = state.is(Blocks.STICKY_PISTON);
 | 
			
		||||
                    boolean sticky = isSticky(state);
 | 
			
		||||
                    boolean extended = action != PistonValueType.PUSHING;
 | 
			
		||||
                    return new PistonBlockEntity(session, pos, direction, sticky, extended);
 | 
			
		||||
                });
 | 
			
		||||
| 
						 | 
				
			
			@ -149,4 +157,8 @@ public class JavaBlockEventTranslator extends PacketTranslator<ClientboundBlockE
 | 
			
		|||
            session.getGeyser().getLogger().debug("Unhandled block event packet: " + packet);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static boolean isSticky(BlockState state) {
 | 
			
		||||
        return state.is(Blocks.STICKY_PISTON) || (state.is(Blocks.MOVING_PISTON) && "sticky".equals(state.getValue(Properties.PISTON_TYPE)));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,8 +28,8 @@ package org.geysermc.geyser.translator.protocol.java.level;
 | 
			
		|||
import org.cloudburstmc.math.vector.Vector3i;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
 | 
			
		||||
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.level.WorldManager;
 | 
			
		||||
import org.geysermc.geyser.level.block.type.BlockState;
 | 
			
		||||
import org.geysermc.geyser.session.GeyserSession;
 | 
			
		||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
 | 
			
		||||
| 
						 | 
				
			
			@ -43,24 +43,27 @@ public class JavaBlockUpdateTranslator extends PacketTranslator<ClientboundBlock
 | 
			
		|||
    @Override
 | 
			
		||||
    public void translate(GeyserSession session, ClientboundBlockUpdatePacket packet) {
 | 
			
		||||
        Vector3i pos = packet.getEntry().getPosition();
 | 
			
		||||
        boolean updatePlacement = session.getGeyser().getPlatformType() != PlatformType.SPIGOT && // Spigot simply listens for the block place event
 | 
			
		||||
                !session.getErosionHandler().isActive() && session.getGeyser().getWorldManager().getBlockAt(session, pos) != packet.getEntry().getBlock();
 | 
			
		||||
        WorldManager worldManager = session.getGeyser().getWorldManager();
 | 
			
		||||
        // 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());
 | 
			
		||||
        if (updatePlacement) {
 | 
			
		||||
            this.checkPlace(session, packet);
 | 
			
		||||
            this.checkPlaceSound(session, packet);
 | 
			
		||||
        }
 | 
			
		||||
        this.checkInteract(session, packet);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean checkPlace(GeyserSession session, ClientboundBlockUpdatePacket packet) {
 | 
			
		||||
    private void checkPlaceSound(GeyserSession session, ClientboundBlockUpdatePacket packet) {
 | 
			
		||||
        Vector3i lastPlacePos = session.getLastBlockPlacePosition();
 | 
			
		||||
        if (lastPlacePos == null) {
 | 
			
		||||
            return false;
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if ((lastPlacePos.getX() != packet.getEntry().getPosition().getX()
 | 
			
		||||
                || lastPlacePos.getY() != packet.getEntry().getPosition().getY()
 | 
			
		||||
                || 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
 | 
			
		||||
| 
						 | 
				
			
			@ -74,7 +77,7 @@ public class JavaBlockUpdateTranslator extends PacketTranslator<ClientboundBlock
 | 
			
		|||
        if (!contains) {
 | 
			
		||||
            session.setLastBlockPlacePosition(null);
 | 
			
		||||
            session.setLastBlockPlaced(null);
 | 
			
		||||
            return false;
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 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.setLastBlockPlacePosition(null);
 | 
			
		||||
        session.setLastBlockPlaced(null);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void checkInteract(GeyserSession session, ClientboundBlockUpdatePacket packet) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.component.DataComponents;
 | 
			
		||||
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.ServerboundSetCreativeModeSlotPacket;
 | 
			
		||||
import org.jetbrains.annotations.Contract;
 | 
			
		||||
| 
						 | 
				
			
			@ -91,7 +92,7 @@ public class InventoryUtils {
 | 
			
		|||
 | 
			
		||||
    public static void displayInventory(GeyserSession session, Inventory inventory) {
 | 
			
		||||
        InventoryTranslator translator = session.getInventoryTranslator();
 | 
			
		||||
        if (translator != null && translator.prepareInventory(session, inventory)) {
 | 
			
		||||
        if (translator.prepareInventory(session, inventory)) {
 | 
			
		||||
            if (translator instanceof DoubleChestInventoryTranslator && !((Container) inventory).isUsingRealBlock()) {
 | 
			
		||||
                session.scheduleInEventLoop(() -> {
 | 
			
		||||
                    Inventory openInv = session.getOpenInventory();
 | 
			
		||||
| 
						 | 
				
			
			@ -110,7 +111,11 @@ public class InventoryUtils {
 | 
			
		|||
                inventory.setDisplayed(true);
 | 
			
		||||
            }
 | 
			
		||||
        } 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.setInventoryTranslator(InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -248,6 +253,12 @@ public class InventoryUtils {
 | 
			
		|||
                .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}.
 | 
			
		||||
     *
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,7 +28,7 @@ package org.geysermc.geyser.util;
 | 
			
		|||
import com.fasterxml.jackson.databind.DeserializationFeature;
 | 
			
		||||
import com.fasterxml.jackson.databind.JsonNode;
 | 
			
		||||
import com.fasterxml.jackson.databind.ObjectMapper;
 | 
			
		||||
import com.github.steveice10.mc.auth.service.MsaAuthenticationService;
 | 
			
		||||
import net.raphimc.minecraftauth.step.msa.StepMsaDeviceCode;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.LoginPacket;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.packet.ServerToClientHandshakePacket;
 | 
			
		||||
import org.cloudburstmc.protocol.bedrock.util.ChainValidationResult;
 | 
			
		||||
| 
						 | 
				
			
			@ -203,7 +203,7 @@ public class LoginEncryptionUtils {
 | 
			
		|||
    /**
 | 
			
		||||
     * Shows the code that a user must input into their browser
 | 
			
		||||
     */
 | 
			
		||||
    public static void buildAndShowMicrosoftCodeWindow(GeyserSession session, MsaAuthenticationService.MsCodeResponse msCode) {
 | 
			
		||||
    public static void buildAndShowMicrosoftCodeWindow(GeyserSession session, StepMsaDeviceCode.MsaDeviceCode msCode) {
 | 
			
		||||
        String locale = session.locale();
 | 
			
		||||
 | 
			
		||||
        StringBuilder message = new StringBuilder("%xbox.signin.website\n")
 | 
			
		||||
| 
						 | 
				
			
			@ -212,7 +212,7 @@ public class LoginEncryptionUtils {
 | 
			
		|||
                .append(ChatColor.RESET)
 | 
			
		||||
                .append("\n%xbox.signin.enterCode\n")
 | 
			
		||||
                .append(ChatColor.GREEN)
 | 
			
		||||
                .append(msCode.user_code);
 | 
			
		||||
                .append(msCode.getUserCode());
 | 
			
		||||
        int timeout = session.getGeyser().getConfig().getPendingAuthenticationTimeout();
 | 
			
		||||
        if (timeout != 0) {
 | 
			
		||||
            message.append("\n\n")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.util;
 | 
			
		||||
 | 
			
		||||
import net.raphimc.minecraftauth.util.logging.ILogger;
 | 
			
		||||
import org.geysermc.geyser.GeyserImpl;
 | 
			
		||||
 | 
			
		||||
public class MinecraftAuthLogger implements ILogger {
 | 
			
		||||
 | 
			
		||||
    public static final MinecraftAuthLogger INSTANCE = new MinecraftAuthLogger();
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void info(String message) {
 | 
			
		||||
        GeyserImpl.getInstance().getLogger().debug(message);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void warn(String message) {
 | 
			
		||||
        GeyserImpl.getInstance().getLogger().warning(message);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void error(String message) {
 | 
			
		||||
        GeyserImpl.getInstance().getLogger().error(message);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -12,8 +12,8 @@ gson = "2.3.1" # Provided by Spigot 1.8.8
 | 
			
		|||
websocket = "1.5.1"
 | 
			
		||||
protocol = "3.0.0.Beta2-20240704.153116-14"
 | 
			
		||||
raknet = "1.0.0.CR3-20240416.144209-1"
 | 
			
		||||
mcauthlib = "e5b0bcc"
 | 
			
		||||
mcprotocollib = "1.21-20240616.154144-5"
 | 
			
		||||
minecraftauth = "4.1.0"
 | 
			
		||||
mcprotocollib = "1.21-20240725.013034-16"
 | 
			
		||||
adventure = "4.14.0"
 | 
			
		||||
adventure-platform = "4.3.0"
 | 
			
		||||
junit = "5.9.2"
 | 
			
		||||
| 
						 | 
				
			
			@ -107,7 +107,7 @@ commodore = { group = "me.lucko", name = "commodore", version.ref = "commodore"
 | 
			
		|||
guava = { group = "com.google.guava", name = "guava", version.ref = "guava" }
 | 
			
		||||
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
 | 
			
		||||
junit = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit" }
 | 
			
		||||
mcauthlib = { group = "com.github.GeyserMC", name = "MCAuthLib", version.ref = "mcauthlib" }
 | 
			
		||||
minecraftauth = { group = "net.raphimc", name = "MinecraftAuth", version.ref = "minecraftauth" }
 | 
			
		||||
mcprotocollib = { group = "org.geysermc.mcprotocollib", name = "protocol", version.ref = "mcprotocollib" }
 | 
			
		||||
raknet = { group = "org.cloudburstmc.netty", name = "netty-transport-raknet", version.ref = "raknet" }
 | 
			
		||||
terminalconsoleappender = { group = "net.minecrell", name = "terminalconsoleappender", version.ref = "terminalconsoleappender" }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue