Skip to content

Commit

Permalink
Update custom item translator for 1.21.4, implement custom model data
Browse files Browse the repository at this point in the history
  • Loading branch information
eclipseisoffline committed Dec 6, 2024
1 parent 0d32bbe commit b8fe886
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,18 @@
import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition;
import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate;
import org.geysermc.geyser.item.GeyserCustomMappingData;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.components.WearableSlot;
import org.geysermc.geyser.item.type.ArmorItem;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.registry.mappings.MappingsConfigReader;
import org.geysermc.geyser.registry.type.GeyserMappingItem;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ConsumeEffect;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.Equippable;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.FoodProperties;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.UseCooldown;
import org.geysermc.mcprotocollib.protocol.data.game.level.sound.BuiltinSound;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand Down Expand Up @@ -181,7 +176,7 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD

boolean canDestroyInCreative = true;
if (vanillaMapping.getToolType() != null) { // This is not using the isTool boolean because it is not just a render type here.
canDestroyInCreative = computeToolProperties(vanillaMapping.getToolType(), itemProperties, componentBuilder, vanillaJavaItem.attackDamage());
canDestroyInCreative = computeToolProperties(vanillaMapping.getToolType(), itemProperties, componentBuilder, vanillaJavaItem.defaultAttackDamage());
}
itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative);

Expand Down Expand Up @@ -498,60 +493,9 @@ private static NbtMap xyzToScaleList(float x, float y, float z) {
return NbtMap.builder().putList("scale", NbtType.FLOAT, List.of(x, y, z)).build();
}

// TODO this needs to be a simpler method once we just load default vanilla components from mappings or something
// TODO is this right?
private static DataComponents patchDataComponents(Item javaItem, CustomItemDefinition definition) {
DataComponents components = new DataComponents(new HashMap<>()); // TODO faster map ?

components.put(DataComponentType.MAX_STACK_SIZE, javaItem.maxStackSize());
components.put(DataComponentType.MAX_DAMAGE, javaItem.maxDamage());

Consumable consumable = getItemConsumable(javaItem);
if (consumable != null) {
components.put(DataComponentType.CONSUMABLE, consumable);
}

if (canAlwaysEat(javaItem)) {
components.put(DataComponentType.FOOD, new FoodProperties(0, 0, true));
}

if (javaItem.glint()) {
components.put(DataComponentType.ENCHANTMENT_GLINT_OVERRIDE, true);
}

if (javaItem instanceof ArmorItem armor) { // TODO equippable
}

components.put(DataComponentType.RARITY, javaItem.rarity().ordinal());

components.getDataComponents().putAll(definition.components().getDataComponents());
return components;
}

private static Consumable getItemConsumable(Item item) {
if (item == Items.APPLE || item == Items.BAKED_POTATO || item == Items.BEETROOT || item == Items.BEETROOT_SOUP || item == Items.BREAD
|| item == Items.CARROT || item == Items.CHORUS_FRUIT || item == Items.COOKED_CHICKEN || item == Items.COOKED_COD
|| item == Items.COOKED_MUTTON || item == Items.COOKED_PORKCHOP || item == Items.COOKED_RABBIT || item == Items.COOKED_SALMON
|| item == Items.COOKIE || item == Items.ENCHANTED_GOLDEN_APPLE || item == Items.GOLDEN_APPLE || item == Items.GLOW_BERRIES
|| item == Items.GOLDEN_CARROT || item == Items.MELON_SLICE || item == Items.MUSHROOM_STEW || item == Items.POISONOUS_POTATO
|| item == Items.POTATO || item == Items.PUFFERFISH || item == Items.PUMPKIN_PIE || item == Items.RABBIT_STEW
|| item == Items.BEEF || item == Items.CHICKEN || item == Items.COD || item == Items.MUTTON || item == Items.PORKCHOP
|| item == Items.RABBIT || item == Items.ROTTEN_FLESH || item == Items.SPIDER_EYE || item == Items.COOKED_BEEF
|| item == Items.SUSPICIOUS_STEW || item == Items.SWEET_BERRIES || item == Items.TROPICAL_FISH) {
return Consumables.DEFAULT_FOOD;
} else if (item == Items.POTION) {
return Consumables.DEFAULT_DRINK;
} else if (item == Items.HONEY_BOTTLE) {
return Consumables.HONEY_BOTTLE;
} else if (item == Items.OMINOUS_BOTTLE) {
return Consumables.OMINOUS_BOTTLE;
} else if (item == Items.DRIED_KELP) {
return Consumables.DRIED_KELP;
}
return null;
}

private static boolean canAlwaysEat(Item item) {
return item == Items.CHORUS_FRUIT || item == Items.ENCHANTED_GOLDEN_APPLE || item == Items.GOLDEN_APPLE || item == Items.HONEY_BOTTLE || item == Items.SUSPICIOUS_STEW;
return javaItem.gatherComponents(definition.components());
}

@SuppressWarnings("unchecked")
Expand All @@ -568,13 +512,4 @@ private static void addItemTag(NbtMapBuilder builder, String tag) {
}
}
}

private static final class Consumables {
private static final Consumable DEFAULT_FOOD = new Consumable(1.6F, Consumable.ItemUseAnimation.EAT, BuiltinSound.ENTITY_GENERIC_EAT, true, List.of());
private static final Consumable DEFAULT_DRINK = new Consumable(1.6F, Consumable.ItemUseAnimation.DRINK, BuiltinSound.ENTITY_GENERIC_DRINK, false, List.of());
private static final Consumable HONEY_BOTTLE = new Consumable(2.0F, Consumable.ItemUseAnimation.DRINK, BuiltinSound.ITEM_HONEY_BOTTLE_DRINK, false, List.of());
private static final Consumable OMINOUS_BOTTLE = new Consumable(2.0F, Consumable.ItemUseAnimation.DRINK, BuiltinSound.ITEM_HONEY_BOTTLE_DRINK,
false, List.of(new ConsumeEffect.PlaySound(BuiltinSound.ITEM_OMINOUS_BOTTLE_DISPOSE)));
private static final Consumable DRIED_KELP = new Consumable(0.8F, Consumable.ItemUseAnimation.EAT, BuiltinSound.ENTITY_GENERIC_EAT, false, List.of());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
package org.geysermc.geyser.translator.item;

import com.google.common.collect.Multimap;
import lombok.extern.slf4j.Slf4j;
import net.kyori.adventure.key.Key;
import org.cloudburstmc.protocol.bedrock.data.TrimMaterial;
import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition;
Expand All @@ -34,6 +35,7 @@
import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicate;
import org.geysermc.geyser.api.item.custom.v2.predicate.match.ChargeType;
import org.geysermc.geyser.api.item.custom.v2.predicate.MatchPredicate;
import org.geysermc.geyser.api.item.custom.v2.predicate.match.CustomModelDataString;
import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.level.JavaDimension;
Expand All @@ -42,6 +44,7 @@
import org.geysermc.geyser.util.MinecraftKey;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ArmorTrim;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.CustomModelData;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
import it.unimi.dsi.fastutil.Pair;
Expand All @@ -51,10 +54,12 @@

import java.util.Collection;
import java.util.List;
import java.util.function.Function;

/**
* This is only a separate class for testing purposes so we don't have to load in GeyserImpl in ItemTranslator.
*/
@Slf4j
public final class CustomItemTranslator {

@Nullable
Expand All @@ -68,13 +73,13 @@ public static ItemDefinition getCustomItem(GeyserSession session, int stackSize,
return null;
}

Key itemModel = components.getOrDefault(DataComponentType.ITEM_MODEL, MinecraftKey.key("air")); // TODO fallback onto default item model (when thats done by chris)
Key itemModel = components.getOrDefault(DataComponentType.ITEM_MODEL, MinecraftKey.key("air"));
System.out.println(itemModel + " is the model!");
Collection<Pair<CustomItemDefinition, ItemDefinition>> customItems = allCustomItems.get(itemModel);
if (customItems.isEmpty()) {
return null;
}

// TODO check if definitions/predicates are in the correct order
for (Pair<CustomItemDefinition, ItemDefinition> customModel : customItems) {
if (customModel.first().model().equals(itemModel)) {
boolean allMatch = true;
Expand All @@ -97,9 +102,9 @@ private static boolean predicateMatches(GeyserSession session, CustomItemPredica
return switch (condition.property()) {
case BROKEN -> nextDamageWillBreak(components);
case DAMAGED -> isDamaged(components);
case CUSTOM_MODEL_DATA -> false; // TODO 1.21.4
};
} else if (predicate instanceof MatchPredicate<?> match) { // TODO not much of a fun of the casts here, find a solution for the types?
case CUSTOM_MODEL_DATA -> getCustomBoolean(components, condition.index());
} == condition.expected();
} else if (predicate instanceof MatchPredicate<?> match) { // TODO not much of a fan of the casts here, find a solution for the types?
if (match.property() == MatchPredicateProperty.CHARGE_TYPE) {
ChargeType expected = (ChargeType) match.data();
List<ItemStack> charged = components.get(DataComponentType.CHARGED_PROJECTILES);
Expand Down Expand Up @@ -127,8 +132,8 @@ private static boolean predicateMatches(GeyserSession session, CustomItemPredica
RegistryEntryData<JavaDimension> registered = session.getRegistryCache().dimensions().entryByValue(session.getDimensionType());
return registered != null && dimension.equals(registered.key());
} else if (match.property() == MatchPredicateProperty.CUSTOM_MODEL_DATA) {
// TODO 1.21.4
return false;
CustomModelDataString expected = (CustomModelDataString) match.data();
return expected.value().equals(getSafeCustomModelData(components, CustomModelData::strings, expected.index()));
}
} else if (predicate instanceof RangeDispatchPredicate rangeDispatch) {
double propertyValue = switch (rangeDispatch.property()) {
Expand All @@ -145,14 +150,36 @@ private static boolean predicateMatches(GeyserSession session, CustomItemPredica
}
case DAMAGE -> tryNormalize(rangeDispatch, components.get(DataComponentType.DAMAGE), components.get(DataComponentType.MAX_DAMAGE));
case COUNT -> tryNormalize(rangeDispatch, stackSize, components.get(DataComponentType.MAX_STACK_SIZE));
case CUSTOM_MODEL_DATA -> 0.0; // TODO 1.21.4
case CUSTOM_MODEL_DATA -> getCustomFloat(components, rangeDispatch.index());
} * rangeDispatch.scale();
return propertyValue >= rangeDispatch.threshold();
}

throw new IllegalStateException("Unimplemented predicate type");
}

private static boolean getCustomBoolean(DataComponents components, int index) {
Boolean b = getSafeCustomModelData(components, CustomModelData::flags, index);
return b != null && b;
}

private static float getCustomFloat(DataComponents components, int index) {
Float f = getSafeCustomModelData(components, CustomModelData::floats, index);
return f == null ? 0.0F : f;
}

private static <T> T getSafeCustomModelData(DataComponents components, Function<CustomModelData, List<T>> listGetter, int index) {
CustomModelData modelData = components.get(DataComponentType.CUSTOM_MODEL_DATA);
if (modelData == null || index < 0) {
return null;
}
List<T> list = listGetter.apply(modelData);
if (index < list.size()) {
return list.get(index);
}
return null;
}

private static double tryNormalize(RangeDispatchPredicate predicate, @Nullable Integer value, @Nullable Integer max) {
if (value == null) {
return 0.0;
Expand Down

0 comments on commit b8fe886

Please sign in to comment.