395 lines
19 KiB
Java
395 lines
19 KiB
Java
/*
|
|
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
|
|
* Copyright (C) 2016-2023 ViaVersion and contributors
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package com.viaversion.viaversion.protocols.protocol1_19to1_18_2.packets;
|
|
|
|
import com.github.steveice10.opennbt.stringified.SNBT;
|
|
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
|
import com.github.steveice10.opennbt.tag.builtin.IntTag;
|
|
import com.github.steveice10.opennbt.tag.builtin.ListTag;
|
|
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
|
import com.google.common.collect.Maps;
|
|
import com.google.gson.JsonElement;
|
|
import com.viaversion.viaversion.api.Via;
|
|
import com.viaversion.viaversion.api.data.ParticleMappings;
|
|
import com.viaversion.viaversion.api.data.entity.DimensionData;
|
|
import com.viaversion.viaversion.api.minecraft.Particle;
|
|
import com.viaversion.viaversion.api.minecraft.Position;
|
|
import com.viaversion.viaversion.api.minecraft.entities.EntityType;
|
|
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19;
|
|
import com.viaversion.viaversion.api.minecraft.metadata.MetaType;
|
|
import com.viaversion.viaversion.api.minecraft.metadata.Metadata;
|
|
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
|
|
import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers;
|
|
import com.viaversion.viaversion.api.type.Type;
|
|
import com.viaversion.viaversion.api.type.types.version.Types1_18;
|
|
import com.viaversion.viaversion.api.type.types.version.Types1_19;
|
|
import com.viaversion.viaversion.data.entity.DimensionDataImpl;
|
|
import com.viaversion.viaversion.protocols.protocol1_18to1_17_1.ClientboundPackets1_18;
|
|
import com.viaversion.viaversion.protocols.protocol1_19to1_18_2.ClientboundPackets1_19;
|
|
import com.viaversion.viaversion.protocols.protocol1_19to1_18_2.Protocol1_19To1_18_2;
|
|
import com.viaversion.viaversion.protocols.protocol1_19to1_18_2.storage.DimensionRegistryStorage;
|
|
import com.viaversion.viaversion.rewriter.EntityRewriter;
|
|
import com.viaversion.viaversion.util.Key;
|
|
import com.viaversion.viaversion.util.Pair;
|
|
|
|
import java.util.*;
|
|
|
|
public final class EntityPackets extends EntityRewriter<ClientboundPackets1_18, Protocol1_19To1_18_2> {
|
|
|
|
private static final String CHAT_REGISTRY_SNBT = "{\n" +
|
|
" \"minecraft:chat_type\": {\n" +
|
|
" \"type\": \"minecraft:chat_type\",\n" +
|
|
" \"value\": [\n" +
|
|
" {\n" +
|
|
" \"name\": \"minecraft:system\",\n" +
|
|
" \"id\": 1,\n" +
|
|
" \"element\": {\n" +
|
|
" \"chat\": {},\n" +
|
|
" \"narration\": {\n" +
|
|
" \"priority\": \"system\"\n" +
|
|
" }\n" +
|
|
" }\n" +
|
|
" },\n" +
|
|
" {\n" +
|
|
" \"name\": \"minecraft:game_info\",\n" +
|
|
" \"id\": 2,\n" +
|
|
" \"element\": {\n" +
|
|
" \"overlay\": {}\n" +
|
|
" }\n" +
|
|
" }\n" +
|
|
" ]\n" +
|
|
" }\n" +
|
|
"}";
|
|
public static final CompoundTag CHAT_REGISTRY;
|
|
|
|
static {
|
|
CHAT_REGISTRY = SNBT.deserializeCompoundTag(CHAT_REGISTRY_SNBT).get("minecraft:chat_type");
|
|
}
|
|
|
|
public EntityPackets(final Protocol1_19To1_18_2 protocol) {
|
|
super(protocol);
|
|
}
|
|
|
|
@Override
|
|
public void registerPackets() {
|
|
registerTracker(ClientboundPackets1_18.SPAWN_PLAYER, EntityTypes1_19.PLAYER);
|
|
registerMetadataRewriter(ClientboundPackets1_18.ENTITY_METADATA, Types1_18.METADATA_LIST, Types1_19.METADATA_LIST);
|
|
registerRemoveEntities(ClientboundPackets1_18.REMOVE_ENTITIES);
|
|
|
|
protocol.registerClientbound(ClientboundPackets1_18.SPAWN_ENTITY, new PacketHandlers() {
|
|
@Override
|
|
public void register() {
|
|
map(Type.VAR_INT); // Entity id
|
|
map(Type.UUID); // Entity UUID
|
|
map(Type.VAR_INT); // Entity type
|
|
map(Type.DOUBLE); // X
|
|
map(Type.DOUBLE); // Y
|
|
map(Type.DOUBLE); // Z
|
|
map(Type.BYTE); // Pitch
|
|
map(Type.BYTE); // Yaw
|
|
handler(wrapper -> {
|
|
final byte yaw = wrapper.get(Type.BYTE, 1);
|
|
wrapper.write(Type.BYTE, yaw); // Head yaw
|
|
});
|
|
map(Type.INT, Type.VAR_INT); // Data
|
|
handler(trackerHandler());
|
|
handler(wrapper -> {
|
|
final int entityId = wrapper.get(Type.VAR_INT, 0);
|
|
final EntityType entityType = tracker(wrapper.user()).entityType(entityId);
|
|
if (entityType == EntityTypes1_19.FALLING_BLOCK) {
|
|
wrapper.set(Type.VAR_INT, 2, protocol.getMappingData().getNewBlockStateId(wrapper.get(Type.VAR_INT, 2)));
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
protocol.registerClientbound(ClientboundPackets1_18.SPAWN_PAINTING, ClientboundPackets1_19.SPAWN_ENTITY, new PacketHandlers() {
|
|
@Override
|
|
public void register() {
|
|
map(Type.VAR_INT); // Entity id
|
|
map(Type.UUID); // Entity UUID
|
|
handler(wrapper -> {
|
|
wrapper.write(Type.VAR_INT, EntityTypes1_19.PAINTING.getId());
|
|
|
|
final int motive = wrapper.read(Type.VAR_INT);
|
|
final Position blockPosition = wrapper.read(Type.POSITION1_14);
|
|
final byte direction = wrapper.read(Type.BYTE);
|
|
wrapper.write(Type.DOUBLE, blockPosition.x() + 0.5d);
|
|
wrapper.write(Type.DOUBLE, blockPosition.y() + 0.5d);
|
|
wrapper.write(Type.DOUBLE, blockPosition.z() + 0.5d);
|
|
wrapper.write(Type.BYTE, (byte) 0); // Pitch
|
|
wrapper.write(Type.BYTE, (byte) 0); // Yaw
|
|
wrapper.write(Type.BYTE, (byte) 0); // Head yaw
|
|
wrapper.write(Type.VAR_INT, to3dId(direction)); // Data
|
|
wrapper.write(Type.SHORT, (short) 0); // Velocity x
|
|
wrapper.write(Type.SHORT, (short) 0); // Velocity y
|
|
wrapper.write(Type.SHORT, (short) 0); // Velocity z
|
|
|
|
wrapper.send(Protocol1_19To1_18_2.class);
|
|
wrapper.cancel();
|
|
|
|
// Send motive in metadata
|
|
final PacketWrapper metaPacket = wrapper.create(ClientboundPackets1_19.ENTITY_METADATA);
|
|
metaPacket.write(Type.VAR_INT, wrapper.get(Type.VAR_INT, 0)); // Entity id
|
|
final List<Metadata> metadata = new ArrayList<>();
|
|
metadata.add(new Metadata(8, Types1_19.META_TYPES.paintingVariantType, protocol.getMappingData().getPaintingMappings().getNewIdOrDefault(motive, 0)));
|
|
metaPacket.write(Types1_19.METADATA_LIST, metadata);
|
|
metaPacket.send(Protocol1_19To1_18_2.class);
|
|
});
|
|
}
|
|
});
|
|
|
|
protocol.registerClientbound(ClientboundPackets1_18.SPAWN_MOB, ClientboundPackets1_19.SPAWN_ENTITY, new PacketHandlers() {
|
|
@Override
|
|
public void register() {
|
|
map(Type.VAR_INT); // Entity ID
|
|
map(Type.UUID); // Entity UUID
|
|
map(Type.VAR_INT); // Entity Type
|
|
map(Type.DOUBLE); // X
|
|
map(Type.DOUBLE); // Y
|
|
map(Type.DOUBLE); // Z
|
|
handler(wrapper -> {
|
|
// Change order
|
|
final byte yaw = wrapper.read(Type.BYTE);
|
|
final byte pitch = wrapper.read(Type.BYTE);
|
|
wrapper.write(Type.BYTE, pitch);
|
|
wrapper.write(Type.BYTE, yaw);
|
|
});
|
|
map(Type.BYTE); // Head yaw
|
|
create(Type.VAR_INT, 0); // Data
|
|
map(Type.SHORT); // Velocity x
|
|
map(Type.SHORT); // Velocity y
|
|
map(Type.SHORT); // Velocity z
|
|
handler(trackerHandler());
|
|
}
|
|
});
|
|
|
|
protocol.registerClientbound(ClientboundPackets1_18.ENTITY_EFFECT, new PacketHandlers() {
|
|
@Override
|
|
public void register() {
|
|
map(Type.VAR_INT); // Entity id
|
|
map(Type.VAR_INT); // Effect id
|
|
map(Type.BYTE); // Amplifier
|
|
map(Type.VAR_INT); // Duration
|
|
map(Type.BYTE); // Flags
|
|
create(Type.BOOLEAN, false); // No factor data
|
|
}
|
|
});
|
|
|
|
protocol.registerClientbound(ClientboundPackets1_18.JOIN_GAME, new PacketHandlers() {
|
|
@Override
|
|
public void register() {
|
|
map(Type.INT); // Entity ID
|
|
map(Type.BOOLEAN); // Hardcore
|
|
map(Type.BYTE); // Gamemode
|
|
map(Type.BYTE); // Previous Gamemode
|
|
map(Type.STRING_ARRAY); // World List
|
|
map(Type.NAMED_COMPOUND_TAG); // Registry
|
|
handler(wrapper -> {
|
|
final CompoundTag tag = wrapper.get(Type.NAMED_COMPOUND_TAG, 0);
|
|
|
|
// Add necessary chat types
|
|
tag.put("minecraft:chat_type", CHAT_REGISTRY.copy());
|
|
|
|
// Cache a whole lot of data
|
|
final ListTag dimensions = ((CompoundTag) tag.get("minecraft:dimension_type")).get("value");
|
|
final Map<String, DimensionData> dimensionDataMap = new HashMap<>(dimensions.size());
|
|
final Map<CompoundTag, String> dimensionsMap = new HashMap<>(dimensions.size());
|
|
for (final Tag dimension : dimensions) {
|
|
final CompoundTag dimensionCompound = (CompoundTag) dimension;
|
|
final CompoundTag element = dimensionCompound.get("element");
|
|
final String name = (String) dimensionCompound.get("name").getValue();
|
|
addMonsterSpawnData(element);
|
|
dimensionDataMap.put(name, new DimensionDataImpl(element));
|
|
dimensionsMap.put(element.copy(), name);
|
|
}
|
|
tracker(wrapper.user()).setDimensions(dimensionDataMap);
|
|
|
|
final DimensionRegistryStorage registryStorage = wrapper.user().get(DimensionRegistryStorage.class);
|
|
registryStorage.setDimensions(dimensionsMap);
|
|
writeDimensionKey(wrapper, registryStorage);
|
|
});
|
|
map(Type.STRING); // World
|
|
map(Type.LONG); // Seed
|
|
map(Type.VAR_INT); // Max players
|
|
map(Type.VAR_INT); // Chunk radius
|
|
map(Type.VAR_INT); // Simulation distance
|
|
map(Type.BOOLEAN); // Reduced debug info
|
|
map(Type.BOOLEAN); // Show death screen
|
|
map(Type.BOOLEAN); // Debug
|
|
map(Type.BOOLEAN); // Flat
|
|
create(Type.OPTIONAL_GLOBAL_POSITION, null); // Last death location
|
|
handler(playerTrackerHandler());
|
|
handler(worldDataTrackerHandlerByKey());
|
|
handler(biomeSizeTracker());
|
|
handler(wrapper -> {
|
|
// Disable the chat preview
|
|
final PacketWrapper displayPreviewPacket = wrapper.create(ClientboundPackets1_19.SET_DISPLAY_CHAT_PREVIEW);
|
|
displayPreviewPacket.write(Type.BOOLEAN, false);
|
|
displayPreviewPacket.scheduleSend(Protocol1_19To1_18_2.class);
|
|
});
|
|
}
|
|
});
|
|
protocol.registerClientbound(ClientboundPackets1_18.RESPAWN, new PacketHandlers() {
|
|
@Override
|
|
public void register() {
|
|
handler(wrapper -> writeDimensionKey(wrapper, wrapper.user().get(DimensionRegistryStorage.class)));
|
|
map(Type.STRING); // World
|
|
map(Type.LONG); // Seed
|
|
map(Type.UNSIGNED_BYTE); // Gamemode
|
|
map(Type.BYTE); // Previous gamemode
|
|
map(Type.BOOLEAN); // Debug
|
|
map(Type.BOOLEAN); // Flat
|
|
map(Type.BOOLEAN); // Keep player data
|
|
create(Type.OPTIONAL_GLOBAL_POSITION, null); // Last death location
|
|
handler(worldDataTrackerHandlerByKey());
|
|
}
|
|
});
|
|
|
|
protocol.registerClientbound(ClientboundPackets1_18.PLAYER_INFO, wrapper -> {
|
|
final int action = wrapper.passthrough(Type.VAR_INT);
|
|
final int entries = wrapper.passthrough(Type.VAR_INT);
|
|
for (int i = 0; i < entries; i++) {
|
|
wrapper.passthrough(Type.UUID); // UUID
|
|
if (action == 0) { // Add player
|
|
wrapper.passthrough(Type.STRING); // Player Name
|
|
|
|
final int properties = wrapper.passthrough(Type.VAR_INT);
|
|
for (int j = 0; j < properties; j++) {
|
|
wrapper.passthrough(Type.STRING); // Name
|
|
wrapper.passthrough(Type.STRING); // Value
|
|
wrapper.passthrough(Type.OPTIONAL_STRING); // Signature
|
|
}
|
|
|
|
wrapper.passthrough(Type.VAR_INT); // Gamemode
|
|
wrapper.passthrough(Type.VAR_INT); // Ping
|
|
final JsonElement displayName = wrapper.read(Type.OPTIONAL_COMPONENT); // Display name
|
|
if (!Protocol1_19To1_18_2.isTextComponentNull(displayName)) {
|
|
wrapper.write(Type.OPTIONAL_COMPONENT, displayName);
|
|
} else {
|
|
wrapper.write(Type.OPTIONAL_COMPONENT, null);
|
|
}
|
|
|
|
// No public profile signature
|
|
wrapper.write(Type.OPTIONAL_PROFILE_KEY, null);
|
|
} else if (action == 1 || action == 2) { // Update gamemode/update latency
|
|
wrapper.passthrough(Type.VAR_INT);
|
|
} else if (action == 3) { // Update display name
|
|
final JsonElement displayName = wrapper.read(Type.OPTIONAL_COMPONENT); // Display name
|
|
if (!Protocol1_19To1_18_2.isTextComponentNull(displayName)) {
|
|
wrapper.write(Type.OPTIONAL_COMPONENT, displayName);
|
|
} else {
|
|
wrapper.write(Type.OPTIONAL_COMPONENT, null);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
private static void writeDimensionKey(final PacketWrapper wrapper, final DimensionRegistryStorage registryStorage) throws Exception {
|
|
// Find dimension key by data
|
|
final CompoundTag currentDimension = wrapper.read(Type.NAMED_COMPOUND_TAG);
|
|
addMonsterSpawnData(currentDimension);
|
|
String dimensionKey = registryStorage.dimensionKey(currentDimension);
|
|
if (dimensionKey == null) {
|
|
if (!Via.getConfig().isSuppressConversionWarnings()) {
|
|
Via.getPlatform().getLogger().warning("The server tried to send dimension data from a dimension the client wasn't told about on join. " +
|
|
"Plugins and mods have to make sure they are not creating new dimension types while players are online, and proxies need to make sure they don't scramble dimension data." +
|
|
" Received dimension: " + currentDimension + ". Known dimensions: " + registryStorage.dimensions());
|
|
}
|
|
|
|
// Try to find the most similar dimension
|
|
dimensionKey = registryStorage.dimensions().entrySet().stream()
|
|
.map(it -> new Pair<>(it, Maps.difference(currentDimension.getValue(), it.getKey().getValue()).entriesInCommon()))
|
|
.filter(it -> it.value().containsKey("min_y") && it.value().containsKey("height"))
|
|
.max(Comparator.comparingInt(it -> it.value().size()))
|
|
.orElseThrow(() -> new IllegalArgumentException("Dimension not found in registry data from join packet: " + currentDimension))
|
|
.key().getValue();
|
|
}
|
|
|
|
wrapper.write(Type.STRING, dimensionKey);
|
|
}
|
|
|
|
private static int to3dId(final int id) {
|
|
switch (id) {
|
|
case -1: // Both up and down
|
|
return 1; // Up
|
|
case 2: // North
|
|
return 2;
|
|
case 0: // South
|
|
return 3;
|
|
case 1: // West
|
|
return 4;
|
|
case 3: // East
|
|
return 5;
|
|
}
|
|
throw new IllegalArgumentException("Unknown 2d id: " + id);
|
|
}
|
|
|
|
private static void addMonsterSpawnData(final CompoundTag dimension) {
|
|
// The actual values here don't matter
|
|
dimension.put("monster_spawn_block_light_limit", new IntTag(0));
|
|
dimension.put("monster_spawn_light_level", new IntTag(11));
|
|
}
|
|
|
|
@Override
|
|
protected void registerRewrites() {
|
|
filter().handler((event, meta) -> {
|
|
meta.setMetaType(Types1_19.META_TYPES.byId(meta.metaType().typeId()));
|
|
|
|
final MetaType type = meta.metaType();
|
|
if (type == Types1_19.META_TYPES.particleType) {
|
|
final Particle particle = (Particle) meta.getValue();
|
|
final ParticleMappings particleMappings = protocol.getMappingData().getParticleMappings();
|
|
if (particle.getId() == particleMappings.id("vibration")) {
|
|
// Remove the position
|
|
particle.getArguments().remove(0);
|
|
|
|
final String resourceLocation = Key.stripMinecraftNamespace(particle.<String>getArgument(0).getValue());
|
|
if (resourceLocation.equals("entity")) {
|
|
// Add Y offset
|
|
particle.getArguments().add(2, new Particle.ParticleData<>(Type.FLOAT, 0F));
|
|
}
|
|
}
|
|
|
|
rewriteParticle(particle);
|
|
}
|
|
});
|
|
|
|
registerMetaTypeHandler(Types1_19.META_TYPES.itemType, Types1_19.META_TYPES.blockStateType, null, null);
|
|
|
|
filter().filterFamily(EntityTypes1_19.MINECART_ABSTRACT).index(11).handler((event, meta) -> {
|
|
// Convert to new block id
|
|
final int data = (int) meta.getValue();
|
|
meta.setValue(protocol.getMappingData().getNewBlockStateId(data));
|
|
});
|
|
|
|
filter().type(EntityTypes1_19.CAT).index(19).handler((event, meta) -> meta.setMetaType(Types1_19.META_TYPES.catVariantType));
|
|
}
|
|
|
|
@Override
|
|
public void onMappingDataLoaded() {
|
|
mapTypes();
|
|
}
|
|
|
|
@Override
|
|
public EntityType typeFromId(final int type) {
|
|
return EntityTypes1_19.getTypeFromId(type);
|
|
}
|
|
}
|