ViaVersion/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_13to1_12_2/Protocol1_13To1_12_2.java

933 lines
44 KiB
Java

/*
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
* Copyright (C) 2016-2024 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_13to1_12_2;
import com.google.common.collect.Sets;
import com.google.common.primitives.Ints;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.ClientWorld;
import com.viaversion.viaversion.api.minecraft.Position;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_13;
import com.viaversion.viaversion.api.minecraft.item.DataItem;
import com.viaversion.viaversion.api.minecraft.item.Item;
import com.viaversion.viaversion.api.platform.providers.ViaProviders;
import com.viaversion.viaversion.api.protocol.AbstractProtocol;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.protocol.packet.State;
import com.viaversion.viaversion.api.protocol.remapper.PacketHandler;
import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers;
import com.viaversion.viaversion.api.protocol.remapper.ValueTransformer;
import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.api.type.types.misc.ParticleType;
import com.viaversion.viaversion.api.type.types.version.Types1_13;
import com.viaversion.viaversion.data.entity.EntityTrackerBase;
import com.viaversion.viaversion.protocols.protocol1_12_1to1_12.ClientboundPackets1_12_1;
import com.viaversion.viaversion.protocols.protocol1_12_1to1_12.ServerboundPackets1_12_1;
import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.blockconnections.ConnectionData;
import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.blockconnections.providers.BlockConnectionProvider;
import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.blockconnections.providers.PacketBlockConnectionProvider;
import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.data.BlockIdData;
import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.data.ComponentRewriter1_13;
import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.data.MappingData;
import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.data.RecipeData;
import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.data.StatisticData;
import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.data.StatisticMappings;
import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.metadata.MetadataRewriter1_13To1_12_2;
import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.packets.EntityPackets;
import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.packets.InventoryPackets;
import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.packets.WorldPackets;
import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.providers.BlockEntityProvider;
import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.providers.PaintingProvider;
import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.providers.PlayerLookTargetProvider;
import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.storage.BlockConnectionStorage;
import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.storage.BlockStorage;
import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.storage.TabCompleteTracker;
import com.viaversion.viaversion.rewriter.SoundRewriter;
import com.viaversion.viaversion.util.ChatColorUtil;
import com.viaversion.viaversion.util.ComponentUtil;
import com.viaversion.viaversion.util.GsonUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
public class Protocol1_13To1_12_2 extends AbstractProtocol<ClientboundPackets1_12_1, ClientboundPackets1_13, ServerboundPackets1_12_1, ServerboundPackets1_13> {
public static final MappingData MAPPINGS = new MappingData();
// These are arbitrary rewrite values, it just needs an invalid color code character.
private static final Map<Character, Character> SCOREBOARD_TEAM_NAME_REWRITE = new HashMap<>();
private static final Set<Character> FORMATTING_CODES = Sets.newHashSet('k', 'l', 'm', 'n', 'o', 'r');
private final MetadataRewriter1_13To1_12_2 entityRewriter = new MetadataRewriter1_13To1_12_2(this);
private final InventoryPackets itemRewriter = new InventoryPackets(this);
private final ComponentRewriter1_13<ClientboundPackets1_12_1> componentRewriter = new ComponentRewriter1_13<>(this);
static {
SCOREBOARD_TEAM_NAME_REWRITE.put('0', 'g');
SCOREBOARD_TEAM_NAME_REWRITE.put('1', 'h');
SCOREBOARD_TEAM_NAME_REWRITE.put('2', 'i');
SCOREBOARD_TEAM_NAME_REWRITE.put('3', 'j');
SCOREBOARD_TEAM_NAME_REWRITE.put('4', 'p');
SCOREBOARD_TEAM_NAME_REWRITE.put('5', 'q');
SCOREBOARD_TEAM_NAME_REWRITE.put('6', 's');
SCOREBOARD_TEAM_NAME_REWRITE.put('7', 't');
SCOREBOARD_TEAM_NAME_REWRITE.put('8', 'u');
SCOREBOARD_TEAM_NAME_REWRITE.put('9', 'v');
SCOREBOARD_TEAM_NAME_REWRITE.put('a', 'w');
SCOREBOARD_TEAM_NAME_REWRITE.put('b', 'x');
SCOREBOARD_TEAM_NAME_REWRITE.put('c', 'y');
SCOREBOARD_TEAM_NAME_REWRITE.put('d', 'z');
SCOREBOARD_TEAM_NAME_REWRITE.put('e', '!');
SCOREBOARD_TEAM_NAME_REWRITE.put('f', '?');
SCOREBOARD_TEAM_NAME_REWRITE.put('k', '#');
SCOREBOARD_TEAM_NAME_REWRITE.put('l', '(');
SCOREBOARD_TEAM_NAME_REWRITE.put('m', ')');
SCOREBOARD_TEAM_NAME_REWRITE.put('n', ':');
SCOREBOARD_TEAM_NAME_REWRITE.put('o', ';');
SCOREBOARD_TEAM_NAME_REWRITE.put('r', '/');
}
public Protocol1_13To1_12_2() {
super(ClientboundPackets1_12_1.class, ClientboundPackets1_13.class, ServerboundPackets1_12_1.class, ServerboundPackets1_13.class);
}
public static final PacketHandler POS_TO_3_INT = wrapper -> {
Position position = wrapper.read(Type.POSITION1_8);
wrapper.write(Type.INT, position.x());
wrapper.write(Type.INT, position.y());
wrapper.write(Type.INT, position.z());
};
public static final PacketHandler SEND_DECLARE_COMMANDS_AND_TAGS =
w -> {
// Send fake declare commands
w.create(ClientboundPackets1_13.DECLARE_COMMANDS, wrapper -> {
wrapper.write(Type.VAR_INT, 2); // Size
// Write root node
wrapper.write(Type.BYTE, (byte) 0); // Mark as command
wrapper.write(Type.VAR_INT_ARRAY_PRIMITIVE, new int[]{1}); // 1 child at index 1
// Write arg node
wrapper.write(Type.BYTE, (byte) (0x02 | 0x04 | 0x10)); // Mark as command
wrapper.write(Type.VAR_INT_ARRAY_PRIMITIVE, new int[0]); // No children
// Extra data
wrapper.write(Type.STRING, "args"); // Arg name
wrapper.write(Type.STRING, "brigadier:string");
wrapper.write(Type.VAR_INT, 2); // Greedy
wrapper.write(Type.STRING, "minecraft:ask_server"); // Ask server
wrapper.write(Type.VAR_INT, 0); // Root node index
}).scheduleSend(Protocol1_13To1_12_2.class);
// Send tags packet
w.create(ClientboundPackets1_13.TAGS, wrapper -> {
wrapper.write(Type.VAR_INT, MAPPINGS.getBlockTags().size()); // block tags
for (Map.Entry<String, int[]> tag : MAPPINGS.getBlockTags().entrySet()) {
wrapper.write(Type.STRING, tag.getKey());
// Needs copy as other protocols may modify it
wrapper.write(Type.VAR_INT_ARRAY_PRIMITIVE, tag.getValue());
}
wrapper.write(Type.VAR_INT, MAPPINGS.getItemTags().size()); // item tags
for (Map.Entry<String, int[]> tag : MAPPINGS.getItemTags().entrySet()) {
wrapper.write(Type.STRING, tag.getKey());
// Needs copy as other protocols may modify it
wrapper.write(Type.VAR_INT_ARRAY_PRIMITIVE, tag.getValue());
}
wrapper.write(Type.VAR_INT, MAPPINGS.getFluidTags().size()); // fluid tags
for (Map.Entry<String, int[]> tag : MAPPINGS.getFluidTags().entrySet()) {
wrapper.write(Type.STRING, tag.getKey());
// Needs copy as other protocols may modify it
wrapper.write(Type.VAR_INT_ARRAY_PRIMITIVE, tag.getValue());
}
}).scheduleSend(Protocol1_13To1_12_2.class);
};
@Override
protected void registerPackets() {
super.registerPackets();
EntityPackets.register(this);
WorldPackets.register(this);
registerClientbound(State.LOGIN, 0x00, 0x00, wrapper -> componentRewriter.processText(wrapper.passthrough(Type.COMPONENT)));
registerClientbound(State.STATUS, 0x00, 0x00, new PacketHandlers() {
@Override
public void register() {
map(Type.STRING);
handler(wrapper -> {
String response = wrapper.get(Type.STRING, 0);
try {
JsonObject json = GsonUtil.getGson().fromJson(response, JsonObject.class);
if (json.has("favicon")) {
json.addProperty("favicon", json.get("favicon").getAsString().replace("\n", ""));
}
wrapper.set(Type.STRING, 0, GsonUtil.getGson().toJson(json));
} catch (JsonParseException e) {
Via.getPlatform().getLogger().log(Level.SEVERE, "Error transforming status response", e);
}
});
}
});
// New packet 0x04 - Login Plugin Message
// Statistics
registerClientbound(ClientboundPackets1_12_1.STATISTICS, wrapper -> {
int size = wrapper.read(Type.VAR_INT);
List<StatisticData> remappedStats = new ArrayList<>();
for (int i = 0; i < size; i++) {
String name = wrapper.read(Type.STRING);
String[] split = name.split("\\.");
int categoryId = 0;
int newId = -1;
int value = wrapper.read(Type.VAR_INT);
if (split.length == 2) {
// Custom types
categoryId = 8;
Integer newIdRaw = StatisticMappings.CUSTOM_STATS.get(name);
if (newIdRaw != null) {
newId = newIdRaw;
} else {
Via.getPlatform().getLogger().warning("Could not find 1.13 -> 1.12.2 statistic mapping for " + name);
}
} else if (split.length > 2) {
String category = split[1];
//TODO convert string ids (blocks, items, entities)
switch (category) {
case "mineBlock":
categoryId = 0;
break;
case "craftItem":
categoryId = 1;
break;
case "useItem":
categoryId = 2;
break;
case "breakItem":
categoryId = 3;
break;
case "pickup":
categoryId = 4;
break;
case "drop":
categoryId = 5;
break;
case "killEntity":
categoryId = 6;
break;
case "entityKilledBy":
categoryId = 7;
break;
}
}
if (newId != -1)
remappedStats.add(new StatisticData(categoryId, newId, value));
}
wrapper.write(Type.VAR_INT, remappedStats.size()); // size
for (StatisticData stat : remappedStats) {
wrapper.write(Type.VAR_INT, stat.getCategoryId()); // category id
wrapper.write(Type.VAR_INT, stat.getNewId()); // statistics id
wrapper.write(Type.VAR_INT, stat.getValue()); // value
}
});
componentRewriter.registerBossBar(ClientboundPackets1_12_1.BOSSBAR);
componentRewriter.registerComponentPacket(ClientboundPackets1_12_1.CHAT_MESSAGE);
registerClientbound(ClientboundPackets1_12_1.TAB_COMPLETE, wrapper -> {
wrapper.write(Type.VAR_INT, wrapper.user().get(TabCompleteTracker.class).getTransactionId());
String input = wrapper.user().get(TabCompleteTracker.class).getInput();
// Start & End
int index;
int length;
// If no input or new word (then it's the start)
if (input.endsWith(" ") || input.isEmpty()) {
index = input.length();
length = 0;
} else {
// Otherwise find the last space (+1 as we include it)
int lastSpace = input.lastIndexOf(' ') + 1;
index = lastSpace;
length = input.length() - lastSpace;
}
// Write index + length
wrapper.write(Type.VAR_INT, index);
wrapper.write(Type.VAR_INT, length);
int count = wrapper.passthrough(Type.VAR_INT);
for (int i = 0; i < count; i++) {
String suggestion = wrapper.read(Type.STRING);
// If we're at the start then handle removing slash
if (suggestion.startsWith("/") && index == 0) {
suggestion = suggestion.substring(1);
}
wrapper.write(Type.STRING, suggestion);
wrapper.write(Type.OPTIONAL_COMPONENT, null); // Tooltip
}
});
registerClientbound(ClientboundPackets1_12_1.OPEN_WINDOW, new PacketHandlers() {
@Override
public void register() {
map(Type.UNSIGNED_BYTE); // Id
map(Type.STRING); // Window type
handler(wrapper -> componentRewriter.processText(wrapper.passthrough(Type.COMPONENT))); // Title
}
});
registerClientbound(ClientboundPackets1_12_1.COOLDOWN, wrapper -> {
int item = wrapper.read(Type.VAR_INT);
int ticks = wrapper.read(Type.VAR_INT);
wrapper.cancel();
if (item == 383) { // Spawn egg
for (int i = 0; i < 44; i++) {
int newItem = MAPPINGS.getItemMappings().getNewId(item << 16 | i);
if (newItem != -1) {
PacketWrapper packet = wrapper.create(ClientboundPackets1_13.COOLDOWN);
packet.write(Type.VAR_INT, newItem);
packet.write(Type.VAR_INT, ticks);
packet.send(Protocol1_13To1_12_2.class);
} else {
break;
}
}
} else {
for (int i = 0; i < 16; i++) {
int newItem = MAPPINGS.getItemMappings().getNewId(item << 4 | i);
if (newItem != -1) {
PacketWrapper packet = wrapper.create(ClientboundPackets1_13.COOLDOWN);
packet.write(Type.VAR_INT, newItem);
packet.write(Type.VAR_INT, ticks);
packet.send(Protocol1_13To1_12_2.class);
} else {
break;
}
}
}
});
componentRewriter.registerComponentPacket(ClientboundPackets1_12_1.DISCONNECT);
registerClientbound(ClientboundPackets1_12_1.EFFECT, new PacketHandlers() {
@Override
public void register() {
map(Type.INT); // Effect Id
map(Type.POSITION1_8); // Location
map(Type.INT); // Data
handler(wrapper -> {
int id = wrapper.get(Type.INT, 0);
int data = wrapper.get(Type.INT, 1);
if (id == 1010) { // Play record
wrapper.set(Type.INT, 1, getMappingData().getItemMappings().getNewId(data << 4));
} else if (id == 2001) { // Block break + block break sound
int blockId = data & 0xFFF;
int blockData = data >> 12;
wrapper.set(Type.INT, 1, WorldPackets.toNewId(blockId << 4 | blockData));
}
});
}
});
registerClientbound(ClientboundPackets1_12_1.CRAFT_RECIPE_RESPONSE, new PacketHandlers() {
@Override
public void register() {
map(Type.BYTE);
handler(wrapper -> wrapper.write(Type.STRING, "viaversion:legacy/" + wrapper.read(Type.VAR_INT)));
}
});
componentRewriter.registerCombatEvent(ClientboundPackets1_12_1.COMBAT_EVENT);
registerClientbound(ClientboundPackets1_12_1.MAP_DATA, new PacketHandlers() {
@Override
public void register() {
map(Type.VAR_INT); // 0 - Map id
map(Type.BYTE); // 1 - Scale
map(Type.BOOLEAN); // 2 - Tracking Position
handler(wrapper -> {
int iconCount = wrapper.passthrough(Type.VAR_INT);
for (int i = 0; i < iconCount; i++) {
byte directionAndType = wrapper.read(Type.BYTE);
int type = (directionAndType & 0xF0) >> 4;
wrapper.write(Type.VAR_INT, type);
wrapper.passthrough(Type.BYTE); // Icon X
wrapper.passthrough(Type.BYTE); // Icon Z
byte direction = (byte) (directionAndType & 0x0F);
wrapper.write(Type.BYTE, direction);
wrapper.write(Type.OPTIONAL_COMPONENT, null); // Display Name
}
});
}
});
registerClientbound(ClientboundPackets1_12_1.UNLOCK_RECIPES, new PacketHandlers() {
@Override
public void register() {
map(Type.VAR_INT); // action
map(Type.BOOLEAN); // crafting book open
map(Type.BOOLEAN); // crafting filter active
handler(wrapper -> {
wrapper.write(Type.BOOLEAN, false); // smelting book open
wrapper.write(Type.BOOLEAN, false); // smelting filter active
});
handler(wrapper -> {
int action = wrapper.get(Type.VAR_INT, 0);
for (int i = 0; i < (action == 0 ? 2 : 1); i++) {
int[] ids = wrapper.read(Type.VAR_INT_ARRAY_PRIMITIVE);
String[] stringIds = new String[ids.length];
for (int j = 0; j < ids.length; j++) {
stringIds[j] = "viaversion:legacy/" + ids[j];
}
wrapper.write(Type.STRING_ARRAY, stringIds);
}
if (action == 0) {
wrapper.create(ClientboundPackets1_13.DECLARE_RECIPES, w -> writeDeclareRecipes(w)).send(Protocol1_13To1_12_2.class);
}
});
}
});
registerClientbound(ClientboundPackets1_12_1.RESPAWN, new PacketHandlers() {
@Override
public void register() {
map(Type.INT); // 0 - Dimension ID
handler(wrapper -> {
ClientWorld clientWorld = wrapper.user().get(ClientWorld.class);
int dimensionId = wrapper.get(Type.INT, 0);
clientWorld.setEnvironment(dimensionId);
if (Via.getConfig().isServersideBlockConnections()) {
ConnectionData.clearBlockStorage(wrapper.user());
}
});
handler(SEND_DECLARE_COMMANDS_AND_TAGS);
}
});
registerClientbound(ClientboundPackets1_12_1.SCOREBOARD_OBJECTIVE, new PacketHandlers() {
@Override
public void register() {
map(Type.STRING); // 0 - Objective name
map(Type.BYTE); // 1 - Mode
handler(wrapper -> {
byte mode = wrapper.get(Type.BYTE, 0);
// On create or update
if (mode == 0 || mode == 2) {
String value = wrapper.read(Type.STRING); // Value
wrapper.write(Type.COMPONENT, ComponentUtil.legacyToJson(value));
String type = wrapper.read(Type.STRING);
// integer or hearts
wrapper.write(Type.VAR_INT, type.equals("integer") ? 0 : 1);
}
});
}
});
registerClientbound(ClientboundPackets1_12_1.TEAMS, new PacketHandlers() {
@Override
public void register() {
map(Type.STRING); // 0 - Team Name
map(Type.BYTE); // 1 - Mode
handler(wrapper -> {
byte action = wrapper.get(Type.BYTE, 0);
if (action == 0 || action == 2) {
String displayName = wrapper.read(Type.STRING); // Display Name
wrapper.write(Type.COMPONENT, ComponentUtil.legacyToJson(displayName));
String prefix = wrapper.read(Type.STRING); // Prefix moved
String suffix = wrapper.read(Type.STRING); // Suffix moved
wrapper.passthrough(Type.BYTE); // Flags
wrapper.passthrough(Type.STRING); // Name Tag Visibility
wrapper.passthrough(Type.STRING); // Collision rule
// Handle new colors
int colour = wrapper.read(Type.BYTE).intValue();
if (colour == -1) {
colour = 21; // -1 changed to 21
}
if (Via.getConfig().is1_13TeamColourFix()) {
char lastColorChar = getLastColorChar(prefix);
colour = ChatColorUtil.getColorOrdinal(lastColorChar);
suffix = ChatColorUtil.COLOR_CHAR + Character.toString(lastColorChar) + suffix;
}
wrapper.write(Type.VAR_INT, colour);
wrapper.write(Type.COMPONENT, ComponentUtil.legacyToJson(prefix)); // Prefix
wrapper.write(Type.COMPONENT, ComponentUtil.legacyToJson(suffix)); // Suffix
}
if (action == 0 || action == 3 || action == 4) {
String[] names = wrapper.read(Type.STRING_ARRAY); // Entities
for (int i = 0; i < names.length; i++) {
names[i] = rewriteTeamMemberName(names[i]);
}
wrapper.write(Type.STRING_ARRAY, names);
}
});
}
});
registerClientbound(ClientboundPackets1_12_1.UPDATE_SCORE, wrapper -> {
String displayName = wrapper.read(Type.STRING); // Display Name
displayName = rewriteTeamMemberName(displayName);
wrapper.write(Type.STRING, displayName);
});
componentRewriter.registerTitle(ClientboundPackets1_12_1.TITLE);
// New 0x4C - Stop Sound
new SoundRewriter<>(this).registerSound(ClientboundPackets1_12_1.SOUND);
registerClientbound(ClientboundPackets1_12_1.TAB_LIST, wrapper -> {
componentRewriter.processText(wrapper.passthrough(Type.COMPONENT));
componentRewriter.processText(wrapper.passthrough(Type.COMPONENT));
});
registerClientbound(ClientboundPackets1_12_1.ADVANCEMENTS, wrapper -> {
wrapper.passthrough(Type.BOOLEAN); // Reset/clear
int size = wrapper.passthrough(Type.VAR_INT); // Mapping size
for (int i = 0; i < size; i++) {
wrapper.passthrough(Type.STRING); // Identifier
// Parent
if (wrapper.passthrough(Type.BOOLEAN))
wrapper.passthrough(Type.STRING);
// Display data
if (wrapper.passthrough(Type.BOOLEAN)) {
componentRewriter.processText(wrapper.passthrough(Type.COMPONENT)); // Title
componentRewriter.processText(wrapper.passthrough(Type.COMPONENT)); // Description
Item icon = wrapper.read(Type.ITEM1_8);
itemRewriter.handleItemToClient(icon);
wrapper.write(Type.ITEM1_13, icon); // Translate item to flat item
wrapper.passthrough(Type.VAR_INT); // Frame type
int flags = wrapper.passthrough(Type.INT); // Flags
if ((flags & 1) != 0) {
wrapper.passthrough(Type.STRING); // Background texture
}
wrapper.passthrough(Type.FLOAT); // X
wrapper.passthrough(Type.FLOAT); // Y
}
wrapper.passthrough(Type.STRING_ARRAY); // Criteria
int arrayLength = wrapper.passthrough(Type.VAR_INT);
for (int array = 0; array < arrayLength; array++) {
wrapper.passthrough(Type.STRING_ARRAY); // String array
}
}
});
// Incoming packets
// New packet 0x02 - Login Plugin Message
cancelServerbound(State.LOGIN, 0x02);
// New 0x01 - Query Block NBT
cancelServerbound(ServerboundPackets1_13.QUERY_BLOCK_NBT);
// Tab-Complete
registerServerbound(ServerboundPackets1_13.TAB_COMPLETE, new PacketHandlers() {
@Override
public void register() {
handler(wrapper -> {
// Disable auto-complete if configured
if (Via.getConfig().isDisable1_13AutoComplete()) {
wrapper.cancel();
}
int tid = wrapper.read(Type.VAR_INT);
// Save transaction id
wrapper.user().get(TabCompleteTracker.class).setTransactionId(tid);
});
// Prepend /
map(Type.STRING, new ValueTransformer<String, String>(Type.STRING) {
@Override
public String transform(PacketWrapper wrapper, String inputValue) {
wrapper.user().get(TabCompleteTracker.class).setInput(inputValue);
return "/" + inputValue;
}
});
// Fake the end of the packet
handler(wrapper -> {
wrapper.write(Type.BOOLEAN, false);
final Position playerLookTarget = Via.getManager().getProviders().get(PlayerLookTargetProvider.class).getPlayerLookTarget(wrapper.user());
wrapper.write(Type.OPTIONAL_POSITION1_8, playerLookTarget);
if (!wrapper.isCancelled() && Via.getConfig().get1_13TabCompleteDelay() > 0) {
TabCompleteTracker tracker = wrapper.user().get(TabCompleteTracker.class);
wrapper.cancel();
tracker.setTimeToSend(System.currentTimeMillis() + Via.getConfig().get1_13TabCompleteDelay() * 50L);
tracker.setLastTabComplete(wrapper.get(Type.STRING, 0));
}
});
}
});
// New 0x0A - Edit book -> Plugin Message
registerServerbound(ServerboundPackets1_13.EDIT_BOOK, ServerboundPackets1_12_1.PLUGIN_MESSAGE, wrapper -> {
Item item = wrapper.read(Type.ITEM1_13);
boolean isSigning = wrapper.read(Type.BOOLEAN);
itemRewriter.handleItemToServer(item);
wrapper.write(Type.STRING, isSigning ? "MC|BSign" : "MC|BEdit"); // Channel
wrapper.write(Type.ITEM1_8, item);
});
// New 0x0C - Query Entity NBT
cancelServerbound(ServerboundPackets1_13.ENTITY_NBT_REQUEST);
// New 0x15 - Pick Item -> Plugin Message
registerServerbound(ServerboundPackets1_13.PICK_ITEM, ServerboundPackets1_12_1.PLUGIN_MESSAGE, wrapper -> {
wrapper.write(Type.STRING, "MC|PickItem"); // Channel
});
registerServerbound(ServerboundPackets1_13.CRAFT_RECIPE_REQUEST, new PacketHandlers() {
@Override
public void register() {
map(Type.BYTE); // Window id
handler(wrapper -> {
String s = wrapper.read(Type.STRING);
Integer id;
if (s.length() < 19 || (id = Ints.tryParse(s.substring(18))) == null) {
wrapper.cancel();
return;
}
wrapper.write(Type.VAR_INT, id);
});
}
});
registerServerbound(ServerboundPackets1_13.RECIPE_BOOK_DATA, new PacketHandlers() {
@Override
public void register() {
map(Type.VAR_INT); // 0 - Type
handler(wrapper -> {
int type = wrapper.get(Type.VAR_INT, 0);
if (type == 0) {
String s = wrapper.read(Type.STRING);
Integer id;
// Custom recipes
if (s.length() < 19 || (id = Ints.tryParse(s.substring(18))) == null) {
wrapper.cancel();
return;
}
wrapper.write(Type.INT, id);
}
if (type == 1) {
wrapper.passthrough(Type.BOOLEAN); // Crafting Recipe Book Open
wrapper.passthrough(Type.BOOLEAN); // Crafting Recipe Filter Active
wrapper.read(Type.BOOLEAN); // Smelting Recipe Book Open | IGNORE NEW 1.13 FIELD
wrapper.read(Type.BOOLEAN); // Smelting Recipe Filter Active | IGNORE NEW 1.13 FIELD
}
});
}
});
// New 0x1C - Name Item -> Plugin Message
registerServerbound(ServerboundPackets1_13.RENAME_ITEM, ServerboundPackets1_12_1.PLUGIN_MESSAGE, wrapper -> {
wrapper.write(Type.STRING, "MC|ItemName"); // Channel
});
// New 0x1F - Select Trade -> Plugin Message
registerServerbound(ServerboundPackets1_13.SELECT_TRADE, ServerboundPackets1_12_1.PLUGIN_MESSAGE, new PacketHandlers() {
@Override
public void register() {
handler(wrapper -> {
wrapper.write(Type.STRING, "MC|TrSel"); // Channel
});
map(Type.VAR_INT, Type.INT); // Slot
}
});
// New 0x20 - Set Beacon Effect -> Plugin Message
registerServerbound(ServerboundPackets1_13.SET_BEACON_EFFECT, ServerboundPackets1_12_1.PLUGIN_MESSAGE, new PacketHandlers() {
@Override
public void register() {
handler(wrapper -> {
wrapper.write(Type.STRING, "MC|Beacon"); // Channel
});
map(Type.VAR_INT, Type.INT); // Primary Effect
map(Type.VAR_INT, Type.INT); // Secondary Effect
}
});
// New 0x22 - Update Command Block -> Plugin Message
registerServerbound(ServerboundPackets1_13.UPDATE_COMMAND_BLOCK, ServerboundPackets1_12_1.PLUGIN_MESSAGE, new PacketHandlers() {
@Override
public void register() {
handler(wrapper -> wrapper.write(Type.STRING, "MC|AutoCmd"));
handler(POS_TO_3_INT);
map(Type.STRING); // Command
handler(wrapper -> {
int mode = wrapper.read(Type.VAR_INT);
byte flags = wrapper.read(Type.BYTE);
String stringMode = mode == 0 ? "SEQUENCE"
: mode == 1 ? "AUTO"
: "REDSTONE";
wrapper.write(Type.BOOLEAN, (flags & 0x1) != 0); // Track output
wrapper.write(Type.STRING, stringMode);
wrapper.write(Type.BOOLEAN, (flags & 0x2) != 0); // Is conditional
wrapper.write(Type.BOOLEAN, (flags & 0x4) != 0); // Automatic
});
}
});
// New 0x23 - Update Command Block Minecart -> Plugin Message
registerServerbound(ServerboundPackets1_13.UPDATE_COMMAND_BLOCK_MINECART, ServerboundPackets1_12_1.PLUGIN_MESSAGE, new PacketHandlers() {
@Override
public void register() {
handler(wrapper -> {
wrapper.write(Type.STRING, "MC|AdvCmd");
wrapper.write(Type.BYTE, (byte) 1); // Type 1 for Entity
});
map(Type.VAR_INT, Type.INT); // Entity Id
}
});
// 0x1B -> 0x24 in InventoryPackets
// New 0x25 - Update Structure Block -> Message Channel
registerServerbound(ServerboundPackets1_13.UPDATE_STRUCTURE_BLOCK, ServerboundPackets1_12_1.PLUGIN_MESSAGE, new PacketHandlers() {
@Override
public void register() {
handler(wrapper -> {
wrapper.write(Type.STRING, "MC|Struct"); // Channel
});
handler(POS_TO_3_INT);
map(Type.VAR_INT, new ValueTransformer<Integer, Byte>(Type.BYTE) { // Action
@Override
public Byte transform(PacketWrapper wrapper, Integer action) throws Exception {
return (byte) (action + 1);
}
}); // Action
map(Type.VAR_INT, new ValueTransformer<Integer, String>(Type.STRING) {
@Override
public String transform(PacketWrapper wrapper, Integer mode) throws Exception {
return mode == 0 ? "SAVE"
: mode == 1 ? "LOAD"
: mode == 2 ? "CORNER"
: "DATA";
}
});
map(Type.STRING); // Name
map(Type.BYTE, Type.INT); // Offset X
map(Type.BYTE, Type.INT); // Offset Y
map(Type.BYTE, Type.INT); // Offset Z
map(Type.BYTE, Type.INT); // Size X
map(Type.BYTE, Type.INT); // Size Y
map(Type.BYTE, Type.INT); // Size Z
map(Type.VAR_INT, new ValueTransformer<Integer, String>(Type.STRING) { // Mirror
@Override
public String transform(PacketWrapper wrapper, Integer mirror) throws Exception {
return mirror == 0 ? "NONE"
: mirror == 1 ? "LEFT_RIGHT"
: "FRONT_BACK";
}
});
map(Type.VAR_INT, new ValueTransformer<Integer, String>(Type.STRING) { // Rotation
@Override
public String transform(PacketWrapper wrapper, Integer rotation) throws Exception {
return rotation == 0 ? "NONE"
: rotation == 1 ? "CLOCKWISE_90"
: rotation == 2 ? "CLOCKWISE_180"
: "COUNTERCLOCKWISE_90";
}
});
map(Type.STRING);
handler(wrapper -> {
float integrity = wrapper.read(Type.FLOAT);
long seed = wrapper.read(Type.VAR_LONG);
byte flags = wrapper.read(Type.BYTE);
wrapper.write(Type.BOOLEAN, (flags & 0x1) != 0); // Ignore Entities
wrapper.write(Type.BOOLEAN, (flags & 0x2) != 0); // Show air
wrapper.write(Type.BOOLEAN, (flags & 0x4) != 0); // Show bounding box
wrapper.write(Type.FLOAT, integrity);
wrapper.write(Type.VAR_LONG, seed);
});
}
});
}
private void writeDeclareRecipes(PacketWrapper recipesPacket) {
recipesPacket.write(Type.VAR_INT, RecipeData.recipes.size());
for (Map.Entry<String, RecipeData.Recipe> entry : RecipeData.recipes.entrySet()) {
recipesPacket.write(Type.STRING, entry.getKey()); // Id
recipesPacket.write(Type.STRING, entry.getValue().getType());
switch (entry.getValue().getType()) {
case "crafting_shapeless": {
recipesPacket.write(Type.STRING, entry.getValue().getGroup());
recipesPacket.write(Type.VAR_INT, entry.getValue().getIngredients().length);
for (Item[] ingredient : entry.getValue().getIngredients()) {
Item[] clone = ingredient.clone(); // Clone because array and item is mutable
for (int i = 0; i < clone.length; i++) {
if (clone[i] == null) continue;
clone[i] = new DataItem(clone[i]);
}
recipesPacket.write(Type.ITEM1_13_ARRAY, clone);
}
recipesPacket.write(Type.ITEM1_13, new DataItem(entry.getValue().getResult()));
break;
}
case "crafting_shaped": {
recipesPacket.write(Type.VAR_INT, entry.getValue().getWidth());
recipesPacket.write(Type.VAR_INT, entry.getValue().getHeight());
recipesPacket.write(Type.STRING, entry.getValue().getGroup());
for (Item[] ingredient : entry.getValue().getIngredients()) {
Item[] clone = ingredient.clone(); // Clone because array and item is mutable
for (int i = 0; i < clone.length; i++) {
if (clone[i] == null) continue;
clone[i] = new DataItem(clone[i]);
}
recipesPacket.write(Type.ITEM1_13_ARRAY, clone);
}
recipesPacket.write(Type.ITEM1_13, new DataItem(entry.getValue().getResult()));
break;
}
case "smelting": {
recipesPacket.write(Type.STRING, entry.getValue().getGroup());
Item[] clone = entry.getValue().getIngredient().clone(); // Clone because array and item is mutable
for (int i = 0; i < clone.length; i++) {
if (clone[i] == null) continue;
clone[i] = new DataItem(clone[i]);
}
recipesPacket.write(Type.ITEM1_13_ARRAY, clone);
recipesPacket.write(Type.ITEM1_13, new DataItem(entry.getValue().getResult()));
recipesPacket.write(Type.FLOAT, entry.getValue().getExperience());
recipesPacket.write(Type.VAR_INT, entry.getValue().getCookingTime());
break;
}
}
}
}
@Override
protected void onMappingDataLoaded() {
ConnectionData.init();
RecipeData.init();
BlockIdData.init();
Types1_13.PARTICLE.filler(this)
.reader(3, ParticleType.Readers.BLOCK)
.reader(20, ParticleType.Readers.DUST)
.reader(11, ParticleType.Readers.DUST)
.reader(27, ParticleType.Readers.ITEM1_13);
if (Via.getConfig().isServersideBlockConnections() && Via.getManager().getProviders().get(BlockConnectionProvider.class) instanceof PacketBlockConnectionProvider) {
BlockConnectionStorage.init();
}
}
@Override
public void init(UserConnection userConnection) {
userConnection.addEntityTracker(this.getClass(), new EntityTrackerBase(userConnection, EntityTypes1_13.EntityType.PLAYER));
userConnection.put(new TabCompleteTracker());
if (!userConnection.has(ClientWorld.class))
userConnection.put(new ClientWorld());
userConnection.put(new BlockStorage());
if (Via.getConfig().isServersideBlockConnections()) {
if (Via.getManager().getProviders().get(BlockConnectionProvider.class) instanceof PacketBlockConnectionProvider) {
userConnection.put(new BlockConnectionStorage());
}
}
}
@Override
public void register(ViaProviders providers) {
providers.register(BlockEntityProvider.class, new BlockEntityProvider());
providers.register(PaintingProvider.class, new PaintingProvider());
providers.register(PlayerLookTargetProvider.class, new PlayerLookTargetProvider());
}
// Based on method from https://github.com/Bukkit/Bukkit/blob/master/src/main/java/org/bukkit/ChatColor.java
public char getLastColorChar(String input) {
int length = input.length();
for (int index = length - 1; index > -1; index--) {
char section = input.charAt(index);
if (section == ChatColorUtil.COLOR_CHAR && index < length - 1) {
char c = input.charAt(index + 1);
if (ChatColorUtil.isColorCode(c) && !FORMATTING_CODES.contains(c)) {
return c;
}
}
}
return 'r';
}
protected String rewriteTeamMemberName(String name) {
// The Display Name is just colours which overwrites the suffix
// It also overwrites for ANY colour in name but most plugins
// will just send colour as 'invisible' character
if (ChatColorUtil.stripColor(name).isEmpty()) {
StringBuilder newName = new StringBuilder();
for (int i = 1; i < name.length(); i += 2) {
char colorChar = name.charAt(i);
Character rewrite = SCOREBOARD_TEAM_NAME_REWRITE.get(colorChar);
if (rewrite == null) {
rewrite = colorChar;
}
newName.append(ChatColorUtil.COLOR_CHAR).append(rewrite);
}
name = newName.toString();
}
return name;
}
@Override
public MappingData getMappingData() {
return MAPPINGS;
}
@Override
public MetadataRewriter1_13To1_12_2 getEntityRewriter() {
return entityRewriter;
}
@Override
public InventoryPackets getItemRewriter() {
return itemRewriter;
}
public ComponentRewriter1_13 getComponentRewriter() {
return componentRewriter;
}
}