mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-12-29 15:49:09 +01:00
Fix behavior of matching custom item predicates
Huge thanks to Kastle for helping me disect this behavior. - The Unbreakable NBT tag is not the only source for determining if an item should be treated as unbreakable. The damage NBT is also taken into account. - Custom item options must be processed in an ascending order. - Multiple conditions may be necessary for an item to be selected. - Conditions do not have to be exact. See the comments in CustomItemTranslator for an explanation. - Added a test so we don't break this behavior in the future.
This commit is contained in:
parent
f4b810534b
commit
f59e33d749
7 changed files with 293 additions and 160 deletions
|
@ -30,7 +30,6 @@ import com.nukkitx.nbt.NbtMapBuilder;
|
|||
import com.nukkitx.nbt.NbtType;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.ComponentItemData;
|
||||
import com.nukkitx.protocol.bedrock.packet.StartGamePacket;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMaps;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.item.custom.CustomItemData;
|
||||
import org.geysermc.geyser.api.item.custom.CustomRenderOffsets;
|
||||
|
@ -43,6 +42,7 @@ import org.geysermc.geyser.registry.type.ItemMapping;
|
|||
import org.geysermc.geyser.registry.type.NonVanillaItemRegistration;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.OptionalInt;
|
||||
|
@ -85,7 +85,7 @@ public class CustomItemRegistryPopulator {
|
|||
.maxDamage(customItemData.maxDamage())
|
||||
.repairMaterials(customItemData.repairMaterials())
|
||||
.hasSuspiciousStewEffect(false)
|
||||
.customItemOptions(Object2IntMaps.emptyMap())
|
||||
.customItemOptions(Collections.emptyList())
|
||||
.build();
|
||||
|
||||
NbtMapBuilder builder = createComponentNbt(customItemData, customItemData.identifier(), customItemId,
|
||||
|
|
|
@ -95,7 +95,10 @@ public class ItemRegistryPopulator {
|
|||
|
||||
boolean customItemsAllowed = GeyserImpl.getInstance().getConfig().isAddNonBedrockItems();
|
||||
|
||||
Multimap<String, CustomItemData> customItems = MultimapBuilder.hashKeys().hashSetValues().build();
|
||||
// List values here is important compared to HashSet - we need to preserve the order of what's given to us
|
||||
// (as of 1.19.2 Java) to replicate some edge cases in Java predicate behavior where it checks from the bottom
|
||||
// of the list first, then ascends.
|
||||
Multimap<String, CustomItemData> customItems = MultimapBuilder.hashKeys().arrayListValues().build();
|
||||
List<NonVanillaCustomItemData> nonVanillaCustomItems;
|
||||
|
||||
MappingsConfigReader mappingsConfigReader = new MappingsConfigReader();
|
||||
|
@ -468,10 +471,10 @@ public class ItemRegistryPopulator {
|
|||
}
|
||||
|
||||
// Add the custom item properties, if applicable
|
||||
Object2IntMap<CustomItemOptions> customItemOptions;
|
||||
List<ObjectIntPair<CustomItemOptions>> customItemOptions;
|
||||
Collection<CustomItemData> customItemsToLoad = customItems.get(javaIdentifier);
|
||||
if (customItemsAllowed && !customItemsToLoad.isEmpty()) {
|
||||
customItemOptions = new Object2IntOpenHashMap<>(customItemsToLoad.size());
|
||||
customItemOptions = new ObjectArrayList<>(customItemsToLoad.size());
|
||||
|
||||
for (CustomItemData customItem : customItemsToLoad) {
|
||||
int customProtocolId = nextFreeBedrockId++;
|
||||
|
@ -491,12 +494,15 @@ public class ItemRegistryPopulator {
|
|||
entries.put(customMapping.stringId(), customMapping.startGamePacketItemEntry());
|
||||
// ComponentItemData - used to register some custom properties
|
||||
componentItemData.add(customMapping.componentItemData());
|
||||
customItemOptions.put(customItem.customItemOptions(), customProtocolId);
|
||||
customItemOptions.add(ObjectIntPair.of(customItem.customItemOptions(), customProtocolId));
|
||||
|
||||
customIdMappings.put(customMapping.integerId(), customMapping.stringId());
|
||||
}
|
||||
|
||||
// Important for later to find the best match and accurately replicate Java behavior
|
||||
Collections.reverse(customItemOptions);
|
||||
} else {
|
||||
customItemOptions = Object2IntMaps.emptyMap();
|
||||
customItemOptions = Collections.emptyList();
|
||||
}
|
||||
mappingBuilder.customItemOptions(customItemOptions);
|
||||
|
||||
|
@ -550,7 +556,7 @@ public class ItemRegistryPopulator {
|
|||
.bedrockData(0)
|
||||
.bedrockBlockId(-1)
|
||||
.stackSize(1)
|
||||
.customItemOptions(Object2IntMaps.emptyMap())
|
||||
.customItemOptions(Collections.emptyList())
|
||||
.build();
|
||||
|
||||
if (customItemsAllowed) {
|
||||
|
@ -567,7 +573,7 @@ public class ItemRegistryPopulator {
|
|||
.bedrockData(0)
|
||||
.bedrockBlockId(-1)
|
||||
.stackSize(1)
|
||||
.customItemOptions(Object2IntMaps.emptyMap()) // TODO check for custom items with furnace minecart
|
||||
.customItemOptions(Collections.emptyList()) // TODO check for custom items with furnace minecart
|
||||
.build());
|
||||
|
||||
creativeItems.add(ItemData.builder()
|
||||
|
|
|
@ -25,15 +25,15 @@
|
|||
|
||||
package org.geysermc.geyser.registry.type;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMaps;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectIntPair;
|
||||
import lombok.Builder;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Value
|
||||
|
@ -41,8 +41,8 @@ import java.util.Set;
|
|||
@EqualsAndHashCode
|
||||
public class ItemMapping {
|
||||
public static final ItemMapping AIR = new ItemMapping("minecraft:air", "minecraft:air", 0, 0, 0,
|
||||
BlockRegistries.BLOCKS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getBedrockAirId(),
|
||||
64, null, null, null, Object2IntMaps.emptyMap(), 0, null, false);
|
||||
0, // Air is never sent in full over the network for this to serialize.
|
||||
64, null, null, null, Collections.emptyList(), 0, null, false);
|
||||
|
||||
String javaIdentifier;
|
||||
String bedrockIdentifier;
|
||||
|
@ -62,7 +62,8 @@ public class ItemMapping {
|
|||
|
||||
String translationString;
|
||||
|
||||
Object2IntMap<CustomItemOptions> customItemOptions;
|
||||
@NonNull
|
||||
List<ObjectIntPair<CustomItemOptions>> customItemOptions;
|
||||
|
||||
int maxDamage;
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
package org.geysermc.geyser.text;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrays;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
|
||||
|
@ -173,6 +174,16 @@ public class GeyserLocale {
|
|||
return localeProp.isEmpty() ? null : locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a formatted language string with the default locale for Geyser
|
||||
*
|
||||
* @param key Language string to translate
|
||||
* @return Translated string or the original message if it was not found in the given locale
|
||||
*/
|
||||
public static String getLocaleStringLog(String key) {
|
||||
return getLocaleStringLog(key, ObjectArrays.EMPTY_ARRAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a formatted language string with the default locale for Geyser
|
||||
*
|
||||
|
@ -184,6 +195,17 @@ public class GeyserLocale {
|
|||
return getPlayerLocaleString(key, getDefaultLocale(), values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a formatted language string with the given locale for Geyser
|
||||
*
|
||||
* @param key Language string to translate
|
||||
* @param locale Locale to translate to
|
||||
* @return Translated string or the original message if it was not found in the given locale
|
||||
*/
|
||||
public static String getPlayerLocaleString(String key, String locale) {
|
||||
return getPlayerLocaleString(key, locale, ObjectArrays.EMPTY_ARRAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a formatted language string with the given locale for Geyser
|
||||
*
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 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.inventory.item;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.IntTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectIntPair;
|
||||
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.OptionalInt;
|
||||
|
||||
/**
|
||||
* This is only a separate class for testing purposes so we don't have to load in GeyserImpl in ItemTranslator.
|
||||
*/
|
||||
final class CustomItemTranslator {
|
||||
|
||||
static int getCustomItem(CompoundTag nbt, ItemMapping mapping) {
|
||||
if (nbt == null) {
|
||||
return -1;
|
||||
}
|
||||
List<ObjectIntPair<CustomItemOptions>> customMappings = mapping.getCustomItemOptions();
|
||||
if (customMappings.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int customModelData = nbt.get("CustomModelData") instanceof IntTag customModelDataTag ? customModelDataTag.getValue() : 0;
|
||||
int damage = nbt.get("Damage") instanceof IntTag damageTag ? damageTag.getValue() : 0;
|
||||
boolean unbreakable = !isDamaged(mapping, nbt, damage);
|
||||
|
||||
for (ObjectIntPair<CustomItemOptions> mappingTypes : customMappings) {
|
||||
CustomItemOptions options = mappingTypes.key();
|
||||
|
||||
// Code note: there may be two or more conditions that a custom item must follow, hence the "continues"
|
||||
// here with the return at the end.
|
||||
|
||||
// Implementation details: Java's predicate system works exclusively on comparing float numbers.
|
||||
// A value doesn't necessarily have to match 100%; it just has to be the first to meet all predicate conditions.
|
||||
// This is also why the order of iteration is important as the first to match will be the chosen display item.
|
||||
// For example, if CustomModelData is set to 2f as the requirement, then the NBT can be any number greater or equal (2, 3, 4...)
|
||||
// The same behavior exists for Damage (in fraction form instead of whole numbers),
|
||||
// and Damaged/Unbreakable handles no damage as 0f and damaged as 1f.
|
||||
|
||||
if (unbreakable && options.unbreakable() != TriState.TRUE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
OptionalInt customModelDataOption = options.customModelData();
|
||||
if (customModelDataOption.isPresent() && customModelData < customModelDataOption.getAsInt()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
OptionalInt damagePredicate = options.damagePredicate();
|
||||
if (damagePredicate.isPresent() && damage < damagePredicate.getAsInt()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return mappingTypes.valueInt();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* These two functions are based off their Mojmap equivalents from 1.19.2 */
|
||||
|
||||
private static boolean isDamaged(ItemMapping mapping, CompoundTag nbt, int damage) {
|
||||
return isDamagableItem(mapping, nbt) && damage > 0;
|
||||
}
|
||||
|
||||
private static boolean isDamagableItem(ItemMapping mapping, CompoundTag nbt) {
|
||||
if (mapping.getMaxDamage() > 0) {
|
||||
Tag unbreakableTag = nbt.get("Unbreakable");
|
||||
return unbreakableTag != null && unbreakableTag.getValue() instanceof Number number && number.byteValue() == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private CustomItemTranslator() {
|
||||
}
|
||||
}
|
|
@ -34,12 +34,9 @@ import com.nukkitx.nbt.NbtType;
|
|||
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
|
@ -173,18 +170,18 @@ public abstract class ItemTranslator {
|
|||
builder.blockRuntimeId(bedrockItem.getBedrockBlockId());
|
||||
}
|
||||
|
||||
translateCustomItem(nbt, builder, bedrockItem);
|
||||
|
||||
if (nbt != null) {
|
||||
// Translate the canDestroy and canPlaceOn Java NBT
|
||||
ListTag canDestroy = nbt.get("CanDestroy");
|
||||
String[] canBreak = new String[0];
|
||||
ListTag canPlaceOn = nbt.get("CanPlaceOn");
|
||||
String[] canPlace = new String[0];
|
||||
canBreak = getCanModify(canDestroy, canBreak);
|
||||
canPlace = getCanModify(canPlaceOn, canPlace);
|
||||
builder.canBreak(canBreak);
|
||||
builder.canPlace(canPlace);
|
||||
String[] canBreak = getCanModify(canDestroy);
|
||||
String[] canPlace = getCanModify(canPlaceOn);
|
||||
if (canBreak != null) {
|
||||
builder.canBreak(canBreak);
|
||||
}
|
||||
if (canPlace != null) {
|
||||
builder.canPlace(canPlace);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
|
@ -246,12 +243,11 @@ public abstract class ItemTranslator {
|
|||
* In Java, this is treated as normal NBT, but in Bedrock, these arguments are extra parts of the item data itself.
|
||||
*
|
||||
* @param canModifyJava the list of items in Java
|
||||
* @param canModifyBedrock the empty list of items in Bedrock
|
||||
* @return the new list of items in Bedrock
|
||||
*/
|
||||
private static String[] getCanModify(ListTag canModifyJava, String[] canModifyBedrock) {
|
||||
private static String[] getCanModify(ListTag canModifyJava) {
|
||||
if (canModifyJava != null && canModifyJava.size() > 0) {
|
||||
canModifyBedrock = new String[canModifyJava.size()];
|
||||
String[] canModifyBedrock = new String[canModifyJava.size()];
|
||||
for (int i = 0; i < canModifyBedrock.length; i++) {
|
||||
// Get the Java identifier of the block that can be placed
|
||||
String block = ((StringTag) canModifyJava.get(i)).getValue();
|
||||
|
@ -261,8 +257,9 @@ public abstract class ItemTranslator {
|
|||
// This will unfortunately be limited - for example, beds and banners will be translated weirdly
|
||||
canModifyBedrock[i] = BlockRegistries.JAVA_TO_BEDROCK_IDENTIFIERS.getOrDefault(block, block).replace("minecraft:", "");
|
||||
}
|
||||
return canModifyBedrock;
|
||||
}
|
||||
return canModifyBedrock;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -276,7 +273,7 @@ public abstract class ItemTranslator {
|
|||
ItemMapping mapping = ITEM_STACK_TRANSLATORS.getOrDefault(javaId, DEFAULT_TRANSLATOR)
|
||||
.getItemMapping(javaId, itemStack.getNbt(), session.getItemMappings());
|
||||
|
||||
int customItemId = getCustomItem(itemStack.getNbt(), mapping);
|
||||
int customItemId = CustomItemTranslator.getCustomItem(itemStack.getNbt(), mapping);
|
||||
if (customItemId == -1) {
|
||||
// No custom item
|
||||
return mapping.getBedrockId();
|
||||
|
@ -329,66 +326,26 @@ public abstract class ItemTranslator {
|
|||
}
|
||||
|
||||
protected NbtMap translateNbtToBedrock(CompoundTag tag) {
|
||||
NbtMapBuilder builder = NbtMap.builder();
|
||||
if (tag.getValue() != null && !tag.getValue().isEmpty()) {
|
||||
for (String str : tag.getValue().keySet()) {
|
||||
Tag javaTag = tag.get(str);
|
||||
if (!tag.getValue().isEmpty()) {
|
||||
NbtMapBuilder builder = NbtMap.builder();
|
||||
for (Tag javaTag : tag.values()) {
|
||||
Object translatedTag = translateToBedrockNBT(javaTag);
|
||||
if (translatedTag == null)
|
||||
continue;
|
||||
|
||||
builder.put(javaTag.getName(), translatedTag);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
return builder.build();
|
||||
return NbtMap.EMPTY;
|
||||
}
|
||||
|
||||
private Object translateToBedrockNBT(Tag tag) {
|
||||
if (tag instanceof ByteArrayTag) {
|
||||
return ((ByteArrayTag) tag).getValue();
|
||||
}
|
||||
|
||||
if (tag instanceof ByteTag) {
|
||||
return ((ByteTag) tag).getValue();
|
||||
}
|
||||
|
||||
if (tag instanceof DoubleTag) {
|
||||
return ((DoubleTag) tag).getValue();
|
||||
}
|
||||
|
||||
if (tag instanceof FloatTag) {
|
||||
return ((FloatTag) tag).getValue();
|
||||
}
|
||||
|
||||
if (tag instanceof IntArrayTag) {
|
||||
return ((IntArrayTag) tag).getValue();
|
||||
}
|
||||
|
||||
if (tag instanceof IntTag) {
|
||||
return ((IntTag) tag).getValue();
|
||||
}
|
||||
|
||||
if (tag instanceof LongArrayTag) {
|
||||
//Long array tag does not exist in BE
|
||||
//LongArrayTag longArrayTag = (LongArrayTag) tag;
|
||||
//return new com.nukkitx.nbt.tag.LongArrayTag(longArrayTag.getName(), longArrayTag.getValue());
|
||||
return null;
|
||||
}
|
||||
|
||||
if (tag instanceof LongTag) {
|
||||
return ((LongTag) tag).getValue();
|
||||
}
|
||||
|
||||
if (tag instanceof ShortTag) {
|
||||
return ((ShortTag) tag).getValue();
|
||||
}
|
||||
|
||||
if (tag instanceof StringTag) {
|
||||
return ((StringTag) tag).getValue();
|
||||
if (tag instanceof CompoundTag compoundTag) {
|
||||
return translateNbtToBedrock(compoundTag);
|
||||
}
|
||||
|
||||
if (tag instanceof ListTag listTag) {
|
||||
|
||||
List<Object> tagList = new ArrayList<>();
|
||||
for (Tag value : listTag) {
|
||||
tagList.add(translateToBedrockNBT(value));
|
||||
|
@ -400,11 +357,14 @@ public abstract class ItemTranslator {
|
|||
return new NbtList(type, tagList);
|
||||
}
|
||||
|
||||
if (tag instanceof CompoundTag compoundTag) {
|
||||
return translateNbtToBedrock(compoundTag);
|
||||
if (tag instanceof LongArrayTag) {
|
||||
//Long array tag does not exist in BE
|
||||
//LongArrayTag longArrayTag = (LongArrayTag) tag;
|
||||
//return new com.nukkitx.nbt.tag.LongArrayTag(longArrayTag.getName(), longArrayTag.getValue());
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
return tag.getValue();
|
||||
}
|
||||
|
||||
private CompoundTag translateToJavaNBT(String name, NbtMap tag) {
|
||||
|
@ -544,87 +504,10 @@ public abstract class ItemTranslator {
|
|||
* Translates the custom model data of an item
|
||||
*/
|
||||
private static void translateCustomItem(CompoundTag nbt, ItemData.Builder builder, ItemMapping mapping) {
|
||||
int bedrockId = getCustomItem(nbt, mapping);
|
||||
int bedrockId = CustomItemTranslator.getCustomItem(nbt, mapping);
|
||||
if (bedrockId != -1) {
|
||||
builder.id(bedrockId);
|
||||
}
|
||||
}
|
||||
|
||||
private static int getCustomItem(CompoundTag nbt, ItemMapping mapping) {
|
||||
if (nbt == null) {
|
||||
return -1;
|
||||
}
|
||||
Object2IntMap<CustomItemOptions> customMappings = mapping.getCustomItemOptions();
|
||||
if (customMappings.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
int customModelData = nbt.get("CustomModelData") instanceof IntTag customModelDataTag ? customModelDataTag.getValue() : 0;
|
||||
TriState unbreakable = TriState.fromBoolean(nbt.get("Unbreakable") instanceof ByteTag unbreakableTag && unbreakableTag.getValue() == 1);
|
||||
int damage = nbt.get("Damage") instanceof IntTag damageTag ? damageTag.getValue() : 0;
|
||||
for (Object2IntMap.Entry<CustomItemOptions> mappingTypes : customMappings.object2IntEntrySet()) {
|
||||
CustomItemOptions options = mappingTypes.getKey();
|
||||
|
||||
TriState unbreakableOption = options.unbreakable();
|
||||
if (unbreakableOption == unbreakable) { // Implementation note: if the option is NOT_SET then this comparison will always be false because of how the item unbreaking TriState is created
|
||||
return mappingTypes.getIntValue();
|
||||
}
|
||||
|
||||
OptionalInt customModelDataOption = options.customModelData();
|
||||
if (customModelDataOption.isPresent() && customModelDataOption.getAsInt() == customModelData) {
|
||||
return mappingTypes.getIntValue();
|
||||
}
|
||||
|
||||
OptionalInt damagePredicate = options.damagePredicate();
|
||||
if (damagePredicate.isPresent() && damagePredicate.getAsInt() == damage) {
|
||||
return mappingTypes.getIntValue();
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an {@link ItemStack} is equal to another item stack
|
||||
*
|
||||
* @param itemStack the item stack to check
|
||||
* @param equalsItemStack the item stack to check if equal to
|
||||
* @param checkAmount if the amount should be taken into account
|
||||
* @param trueIfAmountIsGreater if this should return true if the amount of the
|
||||
* first item stack is greater than that of the second
|
||||
* @param checkNbt if NBT data should be checked
|
||||
* @return if an item stack is equal to another item stack
|
||||
*/
|
||||
public boolean equals(ItemStack itemStack, ItemStack equalsItemStack, boolean checkAmount, boolean trueIfAmountIsGreater, boolean checkNbt) {
|
||||
if (itemStack.getId() != equalsItemStack.getId()) {
|
||||
return false;
|
||||
}
|
||||
if (checkAmount) {
|
||||
if (trueIfAmountIsGreater) {
|
||||
if (itemStack.getAmount() < equalsItemStack.getAmount()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (itemStack.getAmount() != equalsItemStack.getAmount()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!checkNbt) {
|
||||
return true;
|
||||
}
|
||||
if ((itemStack.getNbt() == null || itemStack.getNbt().isEmpty()) && (equalsItemStack.getNbt() != null && !equalsItemStack.getNbt().isEmpty())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((itemStack.getNbt() != null && !itemStack.getNbt().isEmpty() && (equalsItemStack.getNbt() == null || !equalsItemStack.getNbt().isEmpty()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (itemStack.getNbt() != null && equalsItemStack.getNbt() != null) {
|
||||
return itemStack.getNbt().equals(equalsItemStack.getNbt());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 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.inventory.item;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.ByteTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.IntTag;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectIntPair;
|
||||
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
|
||||
import org.geysermc.geyser.api.util.TriState;
|
||||
import org.geysermc.geyser.item.GeyserCustomItemOptions;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.OptionalInt;
|
||||
|
||||
public class CustomItemsTest {
|
||||
private ItemMapping testMappingWithDamage;
|
||||
private Object2IntMap<CompoundTag> tagToCustomItemWithDamage;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
CustomItemOptions a = new GeyserCustomItemOptions(TriState.TRUE, OptionalInt.of(2), OptionalInt.empty());
|
||||
CustomItemOptions b = new GeyserCustomItemOptions(TriState.FALSE, OptionalInt.of(5), OptionalInt.empty());
|
||||
CustomItemOptions c = new GeyserCustomItemOptions(TriState.FALSE, OptionalInt.empty(), OptionalInt.of(3));
|
||||
CustomItemOptions d = new GeyserCustomItemOptions(TriState.TRUE, OptionalInt.empty(), OptionalInt.of(8));
|
||||
CustomItemOptions e = new GeyserCustomItemOptions(TriState.FALSE, OptionalInt.empty(), OptionalInt.of(12));
|
||||
CustomItemOptions f = new GeyserCustomItemOptions(TriState.FALSE, OptionalInt.of(8), OptionalInt.of(6));
|
||||
|
||||
Object2IntMap<CustomItemOptions> optionsToId = new Object2IntArrayMap<>();
|
||||
// Order here is important, hence why we're using an array map
|
||||
optionsToId.put(f, 6);
|
||||
optionsToId.put(e, 5);
|
||||
optionsToId.put(d, 4);
|
||||
optionsToId.put(c, 3);
|
||||
optionsToId.put(b, 2);
|
||||
optionsToId.put(a, 1);
|
||||
|
||||
tagToCustomItemWithDamage = new Object2IntOpenHashMap<>();
|
||||
|
||||
CompoundTag tag = new CompoundTag("");
|
||||
tag.put(new IntTag("CustomModelData", 6));
|
||||
// Test item with no damage should be treated as unbreakable
|
||||
tagToCustomItemWithDamage.put(tag, optionsToId.getInt(a));
|
||||
|
||||
tag = new CompoundTag("");
|
||||
tag.put(new IntTag("CustomModelData", 3));
|
||||
tag.put(new ByteTag("Unbreakable", (byte) 1));
|
||||
tagToCustomItemWithDamage.put(tag, optionsToId.getInt(a));
|
||||
|
||||
tag = new CompoundTag("");
|
||||
tag.put(new IntTag("Damage", 16));
|
||||
tag.put(new ByteTag("Unbreakable", (byte) 0));
|
||||
tagToCustomItemWithDamage.put(tag, optionsToId.getInt(e));
|
||||
|
||||
tag = new CompoundTag("");
|
||||
tag.put(new IntTag("CustomModelData", 7));
|
||||
tag.put(new IntTag("Damage", 6));
|
||||
tag.put(new ByteTag("Unbreakable", (byte) 0));
|
||||
tagToCustomItemWithDamage.put(tag, optionsToId.getInt(c));
|
||||
|
||||
tag = new CompoundTag("");
|
||||
tag.put(new IntTag("CustomModelData", 8));
|
||||
tag.put(new IntTag("Damage", 6));
|
||||
tag.put(new ByteTag("Unbreakable", (byte) 1));
|
||||
tagToCustomItemWithDamage.put(tag, optionsToId.getInt(a));
|
||||
|
||||
tag = new CompoundTag("");
|
||||
tag.put(new IntTag("CustomModelData", 9));
|
||||
tag.put(new IntTag("Damage", 6));
|
||||
tag.put(new ByteTag("Unbreakable", (byte) 0));
|
||||
tagToCustomItemWithDamage.put(tag, optionsToId.getInt(f));
|
||||
|
||||
testMappingWithDamage = ItemMapping.builder()
|
||||
.customItemOptions(optionsToId.object2IntEntrySet().stream().map(entry -> ObjectIntPair.of(entry.getKey(), entry.getIntValue())).toList())
|
||||
.maxDamage(100)
|
||||
.build();
|
||||
// Later, possibly add a condition with a mapping with no damage
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomItems() {
|
||||
for (Object2IntMap.Entry<CompoundTag> entry : this.tagToCustomItemWithDamage.object2IntEntrySet()) {
|
||||
int id = CustomItemTranslator.getCustomItem(entry.getKey(), this.testMappingWithDamage);
|
||||
Assert.assertEquals(entry.getKey() + " did not produce the correct custom item", entry.getIntValue(), id);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue