ProtocolLib

3M Downloads

[1.18.2] PacketPlayOutRespawn changed DimensionManager to holder

games647 opened this issue · 15 comments

commented

Minecraft 1.18.2 changed the dimensionManager field again:

Old:
private final DimensionManager a;

New:
private final Holder<DimensionType> dimensionType;

This results in:
com.comphenix.protocol.reflect.FieldAccessException: No field with type net.minecraft.world.level.dimension.DimensionManager exists

with

respawn.getDimensionTypes().write(0, player.getWorld());

AFAIK there is now way to set the field other than that.

commented

I suppose you want to read the first internal structure of the packet (or any other index, you need to test that) and then read the dimension type from the structure 🤔

commented

The problem is that we want to be able to construct that from a PacketContainer soly, w/o reading the Packet first in a listener...
I've got the same issue, opened in the main repo with additionnal info:
aadnk/ProtocolLib#196

commented

@aroooo Thanks I thought this is now the main one. I managed to use reflection for this, but the misses the point of ProtocolLib.

PacketContainer respawn = new PacketContainer(RESPAWN);

Object dimensionTypeHolder = getDimensionType(world);
respawn.getModifier().write(0, dimensionTypeHolder);
[...]

private Object getDimensionType(World world) {
    try {
        Class<?> holderClass = MinecraftReflection.getMinecraftClass("core.Holder");
        Class<?> nmsWorldClass = MinecraftReflection.getNmsWorldClass();

        // get method by return type, but without any arguments
        // explicitly use new Class[]{} in order to get the correct method without varargs method arguments
        Method dimensionTypeGetter = FuzzyReflection.fromClass(nmsWorldClass)
            .getMethodByParameters("dimensionTypeRegistration", holderClass, new Class[]{});

        Object nmsWorld = BukkitConverters.getWorldConverter().getGeneric(world);
        return dimensionTypeGetter.invoke(nmsWorld);
    } catch (ReflectiveOperationException reflectiveEx) {
        plugin.getLog().error("Failed to get dimension type for skin refresh", reflectiveEx);
    }

    return null;
}
commented

OK nice one, I falled back on using NMS for the time being. Thank you for finding out, I wish that an API method will be provided one day

commented

Many thanks for the solution guys. It keept me busy for many hours to figure out a solution for the dimensionManager problem.

commented

Is there any updates on this packet? The wrappers are way outdated, but setDimensionTypes() throws index out of bounds for length 0. Has this been updated, how can I go about sending this packet? I can't seem to find much information about the new packet structure, and even using NMS directly hasn't yielded much.

The mentioned solution from before also does not seem to work anymore.

commented

@aroooo Do you have a workaround that works in 1.19.3? Any help is much appreciated :)

commented

@aroooo Do you have a workaround that works in 1.19.3? Any help is much appreciated :)

I can provide you with the solution I got using NMS, even though it entirely misses the point of using ProtocolLib, but as it wasn't updated, I did not want to waste any more time fiddling with it:

@Override
public void updateSelf(Player player) {
        final EntityPlayer entityPlayer = ((CraftPlayer) player).getHandle();
        final ResourceKey<World> levelResourceKey = entityPlayer.x().ab();
        final CraftWorld world = entityPlayer.s.getWorld();
        // last boolean is: "has death location" attribute, if true, the optional contains the death dimension and positon.
        // with the boolean being false, we don't need to provide a value and thus we return an empty optional.
        final PacketPlayOutRespawn respawn = new PacketPlayOutRespawn(entityPlayer.x().Z(),
                levelResourceKey, world.getSeed(),
                entityPlayer.d.b(), entityPlayer.d.c(),
                false,
                false,
                false,
                Optional.empty());

        final boolean wasFlying = player.isFlying();
        final ItemStack itemOnCursor = player.getItemOnCursor();
        entityPlayer.b.a(respawn);
        player.setFlying(wasFlying);
        player.setItemOnCursor(itemOnCursor);
        player.teleport(player.getLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN);
        player.updateInventory();
}
commented

By now, proper API methods have been added for Holders:

packetContainer.getHolders(MinecraftReflection.getDimensionManager(), BukkitConverters.getDimensionConverter()).write(0, bukkitWorld);
commented

By now, proper API methods have been added for Holders:

packetContainer.getHolders(MinecraftReflection.getDimensionManager(), BukkitConverters.getDimensionConverter()).write(0, bukkitWorld);

Thank you for keeping us updated!

Edit:
OK, after trying, your code doesn't seem to work unfortunately.
Here's what I wrote:

        if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
            // 1.19 and above
            handle.getHolders(
                    MinecraftReflection.getDimensionManager(),
                    BukkitConverters.getDimensionConverter()
            ).write(0, value);
            return;
        }
        // 1.18 and below
        handle.getDimensionTypes().write(0, value);

That's throwing an FieldAccessException: "Field index 0 is out of bounds for length 0"
Any idea @lukalt ?
Edit 2:
OK I'm just not focused on what I'm doing, the field has been reverted BACK to a ResourceKey in 1.19,
dimensionType/d and dimension/e are indeed both a ResourceKey field in 1.19. The question is now why the packet is being dropped. The thing that bothers me is that I'm only writing 1 World Key field and the packet clearly has 2 fields that are a ResourceKey:

InternalStructure[handle=ResourceKey[minecraft:dimension_type / minecraft:] (ResourceKey)]
InternalStructure[handle=ResourceKey[minecraft:dimension / minecraft:] (ResourceKey)]

(output of some debugging)
Is the dimension type field being written automatically by the World Key index 0?
I'm soo lost and all the research I've made doesn't help me much. Has anyone got an idea on why the packet is being dropped?

commented

To be honest, I'm really starting to get crazy.
Using packet.getWorldKeys().write(0, world);, the packet does NOT throw an error but the player is not "refreshed/re-spawned". @dmulloy2 you're far better knowlegeable than I am on the subject, could you have an idea on why the packet seems to be dropped? Thank you and sorry but this issue has bothered me for over 3 months now. (I'm using ProtocolLib 636, on git-Paper-518, MC 1.19.4)

commented

Anyone? Bump...

commented

Okay, I am a little bit confused. I guess we need to differentiate between different versions.

For 1.19.4, the proper way should be:

PacketContainer container = new PacketContainer(PacketType.Play.Server.RESPAWN);

        World world = Bukkit.getWorlds().get(0);

        // write dimension type
        InternalStructure dimensionType = container.getStructures().read(0);
        dimensionType.getMinecraftKeys().write(0, new MinecraftKey("minecraft", "dimension_type"));
        dimensionType.getMinecraftKeys().write(1, new MinecraftKey("minecraft", "overworld"));
        container.getStructures().write(0, dimensionType);

        // write dimension id / name
        container.getWorldKeys().write(0, world);

        container.getLongs().write(0, 0L);
        container.getGameModes().write(0, EnumWrappers.NativeGameMode.ADVENTURE);
        container.getGameModes().write(1, EnumWrappers.NativeGameMode.ADVENTURE);
        container.getBooleans().write(0, false);
        container.getBooleans().write(0, true);
        container.getBytes().write(0, (byte) 0);
        // we currently do not have a converter for GlobalPos

ProtocolLib currently does not offer a direct wrapper for this. Thus, you need to access the first field through an InternalStructure.

Also please note this hint from wiki.vg:

Avoid changing player's dimension to same dimension they were already in unless they are dead. If you change the dimension to one they are already in, weird bugs can occur, such as the player being unable to attack other players in new world (until they die and respawn).

commented

Okay, I am a little bit confused. I guess we need to differentiate between different versions.

For 1.19.4, the proper way should be:

PacketContainer container = new PacketContainer(PacketType.Play.Server.RESPAWN);

        World world = Bukkit.getWorlds().get(0);

        // write dimension type
        InternalStructure dimensionType = container.getStructures().read(0);
        dimensionType.getMinecraftKeys().write(0, new MinecraftKey("minecraft", "dimension_type"));
        dimensionType.getMinecraftKeys().write(1, new MinecraftKey("minecraft", "overworld"));
        container.getStructures().write(0, dimensionType);

        // write dimension id / name
        container.getWorldKeys().write(0, world);

        container.getLongs().write(0, 0L);
        container.getGameModes().write(0, EnumWrappers.NativeGameMode.ADVENTURE);
        container.getGameModes().write(1, EnumWrappers.NativeGameMode.ADVENTURE);
        container.getBooleans().write(0, false);
        container.getBooleans().write(0, true);
        container.getBytes().write(0, (byte) 0);
        // we currently do not have a converter for GlobalPos

ProtocolLib currently does not offer a direct wrapper for this. Thus, you need to access the first field through an InternalStructure.

Also please note this hint from wiki.vg:

Avoid changing player's dimension to same dimension they were already in unless they are dead. If you change the dimension to one they are already in, weird bugs can occur, such as the player being unable to attack other players in new world (until they die and respawn).

It works. It can't thank you enough (even though being spoonfed isn't my typical approch as to solving problems :'))
I guess I'll have to learn ProtocolLib's inner workings to get around those problems next time.
Thank you very much again! Can we consider this issue resolved?

commented

No problem! I know it can be quite challenging to work with ProtocolLib. It took me a while to understand the concept of ProtocolLib and to efficiently use the library myself.

I've updated the old PacketWrapper to 1.19.4 myself. If you are unsure which structures and converters you need to use in order to access a field of a certain packet, I'd suggest you to have a look into it: https://github.com/lukalt/PacketWrapper/tree/master/src/main/java/com/comphenix/packetwrapper/wrappers

It contains a wrapper class for each current packet with tested getters and setters for almost every field so you do not need to deal with the converters yourself.