Fix trade list handling

This commit is contained in:
Nassim Jahnke 2024-04-07 13:36:02 +02:00
parent 06394c1f74
commit 1fe23e4aeb
No known key found for this signature in database
GPG Key ID: EF6771C01F6EF02F
13 changed files with 182 additions and 60 deletions

View File

@ -41,6 +41,13 @@ public final class StructuredDataContainer {
this.data = data;
}
public StructuredDataContainer(final StructuredData<?>[] dataArray) {
this(new Reference2ObjectOpenHashMap<>(dataArray.length));
for (final StructuredData<?> data : dataArray) {
add(data);
}
}
public StructuredDataContainer() {
this(new Reference2ObjectOpenHashMap<>());
}
@ -152,6 +159,10 @@ public final class StructuredDataContainer {
return data;
}
private <T> void add(final StructuredData<T> data) {
set(data.key(), data.value());
}
@Override
public String toString() {
return "StructuredDataContainer{" +

View File

@ -80,7 +80,8 @@ public interface Item {
*
* @return item tag
*/
@Nullable CompoundTag tag();
@Nullable
CompoundTag tag();
/**
* Sets the item compound tag.
@ -97,4 +98,13 @@ public interface Item {
* @return copy of the item
*/
Item copy();
/**
* Returns true if the item is empty.
*
* @return true if the item is empty
*/
default boolean isEmpty() {
return identifier() == 0 || amount() <= 0;
}
}

View File

@ -31,8 +31,8 @@ public class StructuredItem implements Item {
private int identifier;
private int amount;
public StructuredItem() {
this(0, 0, new StructuredDataContainer());
public StructuredItem(final int identifier, final int amount) {
this(identifier, amount, new StructuredDataContainer());
}
public StructuredItem(final int identifier, final int amount, final StructuredDataContainer data) {

View File

@ -0,0 +1,64 @@
/*
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
* Copyright (C) 2016-2024 ViaVersion and contributors
*
* 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.
*/
package com.viaversion.viaversion.api.type.types.item;
import com.viaversion.viaversion.api.minecraft.data.StructuredData;
import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer;
import com.viaversion.viaversion.api.minecraft.item.Item;
import com.viaversion.viaversion.api.minecraft.item.StructuredItem;
import com.viaversion.viaversion.api.type.OptionalType;
import com.viaversion.viaversion.api.type.Type;
import io.netty.buffer.ByteBuf;
// Very similar to normal items (and just results in an item), except it allows non-positive amounts and has id/amount swapped because ???
public final class ItemCostType1_20_5 extends Type<Item> {
private final Type<StructuredData<?>[]> dataArrayType;
public ItemCostType1_20_5(final Type<StructuredData<?>[]> dataArrayType) {
super(Item.class);
this.dataArrayType = dataArrayType;
}
@Override
public Item read(final ByteBuf buffer) throws Exception {
final int id = Type.VAR_INT.readPrimitive(buffer);
final int amount = Type.VAR_INT.readPrimitive(buffer);
final StructuredData<?>[] dataArray = dataArrayType.read(buffer);
return new StructuredItem(id, amount, new StructuredDataContainer(dataArray));
}
@Override
public void write(final ByteBuf buffer, final Item object) throws Exception {
Type.VAR_INT.writePrimitive(buffer, object.identifier());
Type.VAR_INT.writePrimitive(buffer, object.amount());
dataArrayType.write(buffer, object.structuredData().data().values().toArray(new StructuredData[0]));
}
public static final class OptionalItemCostType extends OptionalType<Item> {
public OptionalItemCostType(final Type<Item> type) {
super(type);
}
}
}

View File

@ -23,11 +23,13 @@
package com.viaversion.viaversion.api.type.types.version;
import com.viaversion.viaversion.api.minecraft.Particle;
import com.viaversion.viaversion.api.minecraft.data.StructuredData;
import com.viaversion.viaversion.api.minecraft.item.Item;
import com.viaversion.viaversion.api.minecraft.metadata.Metadata;
import com.viaversion.viaversion.api.minecraft.metadata.types.MetaTypes1_20_5;
import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.api.type.types.ArrayType;
import com.viaversion.viaversion.api.type.types.item.ItemCostType1_20_5;
import com.viaversion.viaversion.api.type.types.item.ItemType1_20_5;
import com.viaversion.viaversion.api.type.types.item.StructuredDataType;
import com.viaversion.viaversion.api.type.types.metadata.MetaListType;
@ -35,14 +37,18 @@ import com.viaversion.viaversion.api.type.types.metadata.MetadataType;
import com.viaversion.viaversion.api.type.types.misc.ParticleType;
import java.util.List;
// Most of these are only safe to use after protocol loading
public final class Types1_20_5 {
// Most of these are only safe to use after protocol loading
public static final ParticleType PARTICLE = new ParticleType();
public static final ArrayType<Particle> PARTICLES = new ArrayType<>(PARTICLE);
public static final StructuredDataType STRUCTURED_DATA = new StructuredDataType();
public static final Type<StructuredData<?>[]> STRUCTURED_DATA_ARRAY = new ArrayType<>(STRUCTURED_DATA);
public static final Type<Item> ITEM = new ItemType1_20_5(STRUCTURED_DATA);
public static final Type<Item[]> ITEM_ARRAY = new ArrayType<>(ITEM);
public static final Type<Item> ITEM_COST = new ItemCostType1_20_5(STRUCTURED_DATA_ARRAY);
public static final Type<Item> OPTIONAL_ITEM_COST = new ItemCostType1_20_5.OptionalItemCostType(ITEM_COST);
public static final ParticleType PARTICLE = new ParticleType();
public static final ArrayType<Particle> PARTICLES = new ArrayType<>(PARTICLE);
public static final MetaTypes1_20_5 META_TYPES = new MetaTypes1_20_5(PARTICLE, PARTICLES);
public static final Type<Metadata> METADATA = new MetadataType(META_TYPES);
public static final Type<List<Metadata>> METADATA_LIST = new MetaListType(METADATA);

View File

@ -52,10 +52,7 @@ public class Protocol1_13_2To1_13_1 extends AbstractProtocol<ClientboundPackets1
for (int i = 0; i < size; i++) {
wrapper.passthrough(Type.STRING); // Identifier
// Parent
if (wrapper.passthrough(Type.BOOLEAN))
wrapper.passthrough(Type.STRING);
wrapper.passthrough(Type.OPTIONAL_STRING); // Parent
// Display data
if (wrapper.passthrough(Type.BOOLEAN)) {

View File

@ -520,10 +520,7 @@ public class Protocol1_13To1_12_2 extends AbstractProtocol<ClientboundPackets1_1
for (int i = 0; i < size; i++) {
wrapper.passthrough(Type.STRING); // Identifier
// Parent
if (wrapper.passthrough(Type.BOOLEAN))
wrapper.passthrough(Type.STRING);
wrapper.passthrough(Type.OPTIONAL_STRING); // Parent
// Display data
if (wrapper.passthrough(Type.BOOLEAN)) {

View File

@ -143,10 +143,7 @@ public final class BlockItemPacketRewriter1_20_2 extends ItemRewriter<Clientboun
final 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);
wrapper.passthrough(Type.OPTIONAL_STRING); // Parent
// Display data
if (wrapper.passthrough(Type.BOOLEAN)) {

View File

@ -128,11 +128,7 @@ public final class Protocol1_20_3To1_20_2 extends AbstractProtocol<ClientboundPa
final 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);
}
wrapper.passthrough(Type.OPTIONAL_STRING); // Parent
// Display data
if (wrapper.passthrough(Type.BOOLEAN)) {

View File

@ -141,12 +141,44 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
registerSetCooldown(ClientboundPackets1_20_3.COOLDOWN);
registerWindowItems1_17_1(ClientboundPackets1_20_3.WINDOW_ITEMS);
registerSetSlot1_17_1(ClientboundPackets1_20_3.SET_SLOT);
registerAdvancements1_20_3(ClientboundPackets1_20_3.ADVANCEMENTS);
registerEntityEquipmentArray(ClientboundPackets1_20_3.ENTITY_EQUIPMENT);
registerClickWindow1_17_1(ServerboundPackets1_20_5.CLICK_WINDOW);
registerCreativeInvAction(ServerboundPackets1_20_5.CREATIVE_INVENTORY_ACTION);
registerWindowPropertyEnchantmentHandler(ClientboundPackets1_20_3.WINDOW_PROPERTY);
protocol.registerClientbound(ClientboundPackets1_20_3.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
wrapper.passthrough(Type.OPTIONAL_STRING); // Parent
// Display data
if (wrapper.passthrough(Type.BOOLEAN)) {
wrapper.passthrough(Type.TAG); // Title
wrapper.passthrough(Type.TAG); // Description
Item item = handleNonNullItemToClient(wrapper.read(itemType()));
wrapper.write(mappedItemType(), 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
}
int requirements = wrapper.passthrough(Type.VAR_INT);
for (int array = 0; array < requirements; array++) {
wrapper.passthrough(Type.STRING_ARRAY);
}
wrapper.passthrough(Type.BOOLEAN); // Send telemetry
}
});
protocol.registerClientbound(ClientboundPackets1_20_3.SPAWN_PARTICLE, wrapper -> {
final int particleId = wrapper.read(Type.VAR_INT);
@ -167,7 +199,7 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
final int blockStateId = wrapper.read(Type.VAR_INT);
particle.add(Type.VAR_INT, protocol.getMappingData().getNewBlockStateId(blockStateId));
} else if (mappings.isItemParticle(particleId)) {
final Item item = handleItemToClient(wrapper.read(Type.ITEM1_20_2));
final Item item = handleNonNullItemToClient(wrapper.read(Type.ITEM1_20_2));
particle.add(Types1_20_5.ITEM, item);
}
@ -201,21 +233,21 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
final int size = wrapper.passthrough(Type.VAR_INT);
for (int i = 0; i < size; i++) {
final Item input = handleItemToClient(wrapper.read(Type.ITEM1_20_2));
final Item output = handleItemToClient(wrapper.read(Type.ITEM1_20_2));
final Item secondItem = handleItemToClient(wrapper.read(Type.ITEM1_20_2));
wrapper.write(Types1_20_5.ITEM, input);
wrapper.write(Types1_20_5.ITEM, output);
wrapper.write(Types1_20_5.ITEM, secondItem);
wrapper.write(Types1_20_5.ITEM_COST, input);
wrapper.passthrough(Type.BOOLEAN); // Trade disabled
wrapper.passthrough(Type.INT); // Number of tools uses
final Item output = handleNonNullItemToClient(wrapper.read(Type.ITEM1_20_2));
wrapper.write(Types1_20_5.ITEM, output);
final Item secondInput = handleItemToClient(wrapper.read(Type.ITEM1_20_2));
wrapper.write(Types1_20_5.OPTIONAL_ITEM_COST, secondInput);
wrapper.passthrough(Type.BOOLEAN); // Out of stock
wrapper.passthrough(Type.INT); // Number of trade uses
wrapper.passthrough(Type.INT); // Maximum number of trade uses
wrapper.passthrough(Type.INT); // XP
wrapper.passthrough(Type.INT); // Special price
wrapper.passthrough(Type.FLOAT); // Price multiplier
wrapper.passthrough(Type.INT); // Demand
wrapper.write(Type.BOOLEAN, false); // Ignore tags
}
});
@ -233,6 +265,15 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
});
}
public Item handleNonNullItemToClient(@Nullable Item item) {
item = handleItemToClient(item);
// Items are no longer nullable in a few places
if (item == null || item.isEmpty()) {
return new StructuredItem(1, 1);
}
return item;
}
@Override
public @Nullable Item handleItemToClient(@Nullable final Item item) {
if (item == null) return null;
@ -391,9 +432,9 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
updateMobTags(data, tag);
updateItemList(data, tag, "ChargedProjectiles", StructuredDataKey.CHARGED_PROJECTILES);
updateItemList(data, tag, "ChargedProjectiles", StructuredDataKey.CHARGED_PROJECTILES, false);
if (old.identifier() == 927) {
updateItemList(data, tag, "Items", StructuredDataKey.BUNDLE_CONTENTS);
updateItemList(data, tag, "Items", StructuredDataKey.BUNDLE_CONTENTS, false);
}
updateEnchantments(data, tag, "Enchantments", StructuredDataKey.ENCHANTMENTS, (hideFlagsValue & StructuredDataConverter.HIDE_ENCHANTMENTS) == 0);
@ -884,10 +925,15 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
data.set(StructuredDataKey.WRITTEN_BOOK_CONTENT, writtenBook);
}
private void updateItemList(final StructuredDataContainer data, final CompoundTag tag, final String key, final StructuredDataKey<Item[]> dataKey) {
private void updateItemList(final StructuredDataContainer data, final CompoundTag tag, final String key, final StructuredDataKey<Item[]> dataKey, final boolean allowEmpty) {
final ListTag<CompoundTag> itemsTag = tag.getListTag(key, CompoundTag.class);
if (itemsTag != null) {
final Item[] items = itemsTag.stream().limit(256).map(this::itemFromTag).filter(Objects::nonNull).toArray(Item[]::new);
final Item[] items = itemsTag.stream()
.limit(256)
.map(this::itemFromTag)
.filter(Objects::nonNull)
.filter(item -> allowEmpty || !item.isEmpty())
.toArray(Item[]::new);
data.set(dataKey, items);
}
}
@ -1111,7 +1157,7 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
data.set(StructuredDataKey.BASE_COLOR, ((NumberTag) baseColorTag).asInt());
}
updateItemList(data, tag, "Items", StructuredDataKey.CONTAINER);
updateItemList(data, tag, "Items", StructuredDataKey.CONTAINER, true);
}
final Tag skullOwnerTag = tag.remove("SkullOwner");

View File

@ -66,10 +66,7 @@ public final class InventoryPackets extends ItemRewriter<ClientboundPackets1_19_
int size = wrapper.passthrough(Type.VAR_INT); // Mapping size
for (int i = 0; i < size; i++) {
wrapper.passthrough(Type.STRING); // Identifier
if (wrapper.passthrough(Type.BOOLEAN)) {
wrapper.passthrough(Type.STRING); // Parent
}
wrapper.passthrough(Type.OPTIONAL_STRING); // Parent
// Display data
if (wrapper.passthrough(Type.BOOLEAN)) {

View File

@ -279,24 +279,32 @@ public class ItemRewriter<C extends ClientboundPacketType, S extends Serverbound
});
}
public void registerTradeList1_20_5(C packetType) {
// Hopefully the item cost weirdness is temporary
public void registerTradeList1_20_5(
final C packetType,
final Type<Item> costType, final Type<Item> mappedCostType,
final Type<Item> optionalCostType, final Type<Item> mappedOptionalCostType
) {
protocol.registerClientbound(packetType, wrapper -> {
wrapper.passthrough(Type.VAR_INT); // Container id
int size = wrapper.passthrough(Type.VAR_INT);
for (int i = 0; i < size; i++) {
handleClientboundItem(wrapper); // Input
handleClientboundItem(wrapper); // Output
handleClientboundItem(wrapper); // Second item
final Item input = wrapper.read(costType);
wrapper.write(mappedCostType, handleItemToClient(input));
wrapper.passthrough(Type.BOOLEAN); // Trade disabled
wrapper.passthrough(Type.INT); // Number of tools uses
handleClientboundItem(wrapper); // Result
final Item secondInput = wrapper.read(optionalCostType);
wrapper.write(mappedOptionalCostType, handleItemToClient(secondInput));
wrapper.passthrough(Type.BOOLEAN); // Out of stock
wrapper.passthrough(Type.INT); // Number of trade uses
wrapper.passthrough(Type.INT); // Maximum number of trade uses
wrapper.passthrough(Type.INT); // XP
wrapper.passthrough(Type.INT); // Special price
wrapper.passthrough(Type.FLOAT); // Price multiplier
wrapper.passthrough(Type.INT); // Demand
wrapper.passthrough(Type.BOOLEAN); // Ignore tags
}
});
}
@ -307,10 +315,7 @@ public class ItemRewriter<C extends ClientboundPacketType, S extends Serverbound
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);
wrapper.passthrough(Type.OPTIONAL_STRING); // Parent
// Display data
if (wrapper.passthrough(Type.BOOLEAN)) {
@ -350,11 +355,7 @@ public class ItemRewriter<C extends ClientboundPacketType, S extends Serverbound
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);
}
wrapper.passthrough(Type.OPTIONAL_STRING); // Parent
// Display data
if (wrapper.passthrough(Type.BOOLEAN)) {

View File

@ -59,7 +59,7 @@ public final class BlockItemPacketRewriter1_99 extends StructuredItemRewriter<Cl
registerAdvancements1_20_3(ClientboundPackets1_20_5.ADVANCEMENTS);
registerEntityEquipmentArray(ClientboundPackets1_20_5.ENTITY_EQUIPMENT);
registerClickWindow1_17_1(ServerboundPackets1_20_5.CLICK_WINDOW);
registerTradeList1_20_5(ClientboundPackets1_20_5.TRADE_LIST);
registerTradeList1_20_5(ClientboundPackets1_20_5.TRADE_LIST, Types1_20_5.ITEM_COST, Types1_20_5.ITEM_COST, Types1_20_5.OPTIONAL_ITEM_COST, Types1_20_5.OPTIONAL_ITEM_COST);
registerCreativeInvAction(ServerboundPackets1_20_5.CREATIVE_INVENTORY_ACTION);
registerWindowPropertyEnchantmentHandler(ClientboundPackets1_20_5.WINDOW_PROPERTY);
registerSpawnParticle1_20_5(ClientboundPackets1_20_5.SPAWN_PARTICLE, Types1_20_5.PARTICLE, Types1_20_5.PARTICLE);