DataComponent API

Exposes the data component logic used by vanilla ItemStack to API
consumers as a version-specific API.
The types and methods introduced by this patch do not follow the general
API deprecation contracts and will be adapted to each new minecraft
release without backwards compatibility measures.

== AT ==
public net/minecraft/world/item/component/ItemContainerContents MAX_SIZE
public net/minecraft/world/item/component/ItemContainerContents items
This commit is contained in:
Owen1212055 2024-04-28 19:53:01 -04:00
parent 2a20fde332
commit 25c25923e9
64 changed files with 4397 additions and 29 deletions

View file

@ -0,0 +1,36 @@
package io.papermc.paper.datacomponent;
import java.util.function.Function;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.util.NullOps;
import net.minecraft.util.Unit;
import org.bukkit.craftbukkit.CraftRegistry;
public record DataComponentAdapter<NMS, API>(
DataComponentType<NMS> type,
Function<API, NMS> apiToVanilla,
Function<NMS, API> vanillaToApi,
boolean codecValidation
) {
static final Function<Void, Unit> API_TO_UNIT_CONVERTER = $ -> Unit.INSTANCE;
public boolean isValued() {
return this.apiToVanilla != API_TO_UNIT_CONVERTER;
}
public NMS toVanilla(final API value) {
final NMS nms = this.apiToVanilla.apply(value);
if (this.codecValidation) {
this.type.codecOrThrow().encodeStart(CraftRegistry.getMinecraftRegistry().createSerializationContext(NullOps.INSTANCE), nms).ifError(error -> {
throw new IllegalArgumentException("Failed to encode data component %s (%s)".formatted(BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(this.type), error.message()));
});
}
return nms;
}
public API fromVanilla(final NMS value) {
return this.vanillaToApi.apply(value);
}
}

View file

@ -0,0 +1,170 @@
package io.papermc.paper.datacomponent;
import io.papermc.paper.adventure.PaperAdventure;
import io.papermc.paper.datacomponent.item.PaperBannerPatternLayers;
import io.papermc.paper.datacomponent.item.PaperBlockItemDataProperties;
import io.papermc.paper.datacomponent.item.PaperBundleContents;
import io.papermc.paper.datacomponent.item.PaperChargedProjectiles;
import io.papermc.paper.datacomponent.item.PaperConsumable;
import io.papermc.paper.datacomponent.item.PaperCustomModelData;
import io.papermc.paper.datacomponent.item.PaperDamageResistant;
import io.papermc.paper.datacomponent.item.PaperDeathProtection;
import io.papermc.paper.datacomponent.item.PaperDyedItemColor;
import io.papermc.paper.datacomponent.item.PaperEnchantable;
import io.papermc.paper.datacomponent.item.PaperEquippable;
import io.papermc.paper.datacomponent.item.PaperFireworks;
import io.papermc.paper.datacomponent.item.PaperFoodProperties;
import io.papermc.paper.datacomponent.item.PaperItemAdventurePredicate;
import io.papermc.paper.datacomponent.item.PaperItemArmorTrim;
import io.papermc.paper.datacomponent.item.PaperItemAttributeModifiers;
import io.papermc.paper.datacomponent.item.PaperItemContainerContents;
import io.papermc.paper.datacomponent.item.PaperItemEnchantments;
import io.papermc.paper.datacomponent.item.PaperItemLore;
import io.papermc.paper.datacomponent.item.PaperItemTool;
import io.papermc.paper.datacomponent.item.PaperJukeboxPlayable;
import io.papermc.paper.datacomponent.item.PaperLodestoneTracker;
import io.papermc.paper.datacomponent.item.PaperMapDecorations;
import io.papermc.paper.datacomponent.item.PaperMapId;
import io.papermc.paper.datacomponent.item.PaperMapItemColor;
import io.papermc.paper.datacomponent.item.PaperOminousBottleAmplifier;
import io.papermc.paper.datacomponent.item.PaperPotDecorations;
import io.papermc.paper.datacomponent.item.PaperPotionContents;
import io.papermc.paper.datacomponent.item.PaperRepairable;
import io.papermc.paper.datacomponent.item.PaperResolvableProfile;
import io.papermc.paper.datacomponent.item.PaperSeededContainerLoot;
import io.papermc.paper.datacomponent.item.PaperSuspiciousStewEffects;
import io.papermc.paper.datacomponent.item.PaperUnbreakable;
import io.papermc.paper.datacomponent.item.PaperUseCooldown;
import io.papermc.paper.datacomponent.item.PaperUseRemainder;
import io.papermc.paper.datacomponent.item.PaperWritableBookContent;
import io.papermc.paper.datacomponent.item.PaperWrittenBookContent;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.util.Unit;
import net.minecraft.world.item.Rarity;
import net.minecraft.world.item.component.MapPostProcessing;
import org.bukkit.DyeColor;
import org.bukkit.craftbukkit.CraftMusicInstrument;
import org.bukkit.craftbukkit.inventory.CraftMetaFirework;
import org.bukkit.craftbukkit.util.Handleable;
import org.bukkit.inventory.ItemRarity;
import static io.papermc.paper.util.MCUtil.transformUnmodifiable;
public final class DataComponentAdapters {
static final Function<Unit, Void> UNIT_TO_API_CONVERTER = $ -> {
throw new UnsupportedOperationException("Cannot convert the Unit type to an API value");
};
static final Map<ResourceKey<DataComponentType<?>>, DataComponentAdapter<?, ?>> ADAPTERS = new HashMap<>();
public static void bootstrap() {
registerIdentity(DataComponents.MAX_STACK_SIZE);
registerIdentity(DataComponents.MAX_DAMAGE);
registerIdentity(DataComponents.DAMAGE);
register(DataComponents.UNBREAKABLE, PaperUnbreakable::new);
register(DataComponents.CUSTOM_NAME, PaperAdventure::asAdventure, PaperAdventure::asVanilla);
register(DataComponents.ITEM_NAME, PaperAdventure::asAdventure, PaperAdventure::asVanilla);
register(DataComponents.ITEM_MODEL, PaperAdventure::asAdventure, PaperAdventure::asVanilla);
register(DataComponents.LORE, PaperItemLore::new);
register(DataComponents.RARITY, nms -> ItemRarity.valueOf(nms.name()), api -> Rarity.valueOf(api.name()));
register(DataComponents.ENCHANTMENTS, PaperItemEnchantments::new);
register(DataComponents.CAN_PLACE_ON, PaperItemAdventurePredicate::new);
register(DataComponents.CAN_BREAK, PaperItemAdventurePredicate::new);
register(DataComponents.ATTRIBUTE_MODIFIERS, PaperItemAttributeModifiers::new);
register(DataComponents.CUSTOM_MODEL_DATA, PaperCustomModelData::new);
registerUntyped(DataComponents.HIDE_ADDITIONAL_TOOLTIP);
registerUntyped(DataComponents.HIDE_TOOLTIP);
registerIdentity(DataComponents.REPAIR_COST);
// registerUntyped(DataComponents.CREATIVE_SLOT_LOCK);
registerIdentity(DataComponents.ENCHANTMENT_GLINT_OVERRIDE);
registerUntyped(DataComponents.INTANGIBLE_PROJECTILE);
register(DataComponents.FOOD, PaperFoodProperties::new);
register(DataComponents.CONSUMABLE, PaperConsumable::new);
register(DataComponents.USE_REMAINDER, PaperUseRemainder::new);
register(DataComponents.USE_COOLDOWN, PaperUseCooldown::new);
register(DataComponents.DAMAGE_RESISTANT, PaperDamageResistant::new);
register(DataComponents.TOOL, PaperItemTool::new);
register(DataComponents.ENCHANTABLE, PaperEnchantable::new);
register(DataComponents.EQUIPPABLE, PaperEquippable::new);
register(DataComponents.REPAIRABLE, PaperRepairable::new);
registerUntyped(DataComponents.GLIDER);
register(DataComponents.TOOLTIP_STYLE, PaperAdventure::asAdventure, PaperAdventure::asVanilla);
register(DataComponents.DEATH_PROTECTION, PaperDeathProtection::new);
register(DataComponents.STORED_ENCHANTMENTS, PaperItemEnchantments::new);
register(DataComponents.DYED_COLOR, PaperDyedItemColor::new);
register(DataComponents.MAP_COLOR, PaperMapItemColor::new);
register(DataComponents.MAP_ID, PaperMapId::new);
register(DataComponents.MAP_DECORATIONS, PaperMapDecorations::new);
register(DataComponents.MAP_POST_PROCESSING, nms -> io.papermc.paper.item.MapPostProcessing.valueOf(nms.name()), api -> MapPostProcessing.valueOf(api.name()));
register(DataComponents.CHARGED_PROJECTILES, PaperChargedProjectiles::new);
register(DataComponents.BUNDLE_CONTENTS, PaperBundleContents::new);
register(DataComponents.POTION_CONTENTS, PaperPotionContents::new);
register(DataComponents.SUSPICIOUS_STEW_EFFECTS, PaperSuspiciousStewEffects::new);
register(DataComponents.WRITTEN_BOOK_CONTENT, PaperWrittenBookContent::new);
register(DataComponents.WRITABLE_BOOK_CONTENT, PaperWritableBookContent::new);
register(DataComponents.TRIM, PaperItemArmorTrim::new);
// debug stick state
// entity data
// bucket entity data
// block entity data
register(DataComponents.INSTRUMENT, CraftMusicInstrument::minecraftHolderToBukkit, CraftMusicInstrument::bukkitToMinecraftHolder);
register(DataComponents.OMINOUS_BOTTLE_AMPLIFIER, PaperOminousBottleAmplifier::new);
register(DataComponents.JUKEBOX_PLAYABLE, PaperJukeboxPlayable::new);
register(DataComponents.RECIPES,
nms -> transformUnmodifiable(nms, PaperAdventure::asAdventureKey),
api -> transformUnmodifiable(api, key -> PaperAdventure.asVanilla(Registries.RECIPE, key))
);
register(DataComponents.LODESTONE_TRACKER, PaperLodestoneTracker::new);
register(DataComponents.FIREWORK_EXPLOSION, CraftMetaFirework::getEffect, CraftMetaFirework::getExplosion);
register(DataComponents.FIREWORKS, PaperFireworks::new);
register(DataComponents.PROFILE, PaperResolvableProfile::new);
register(DataComponents.NOTE_BLOCK_SOUND, PaperAdventure::asAdventure, PaperAdventure::asVanilla);
register(DataComponents.BANNER_PATTERNS, PaperBannerPatternLayers::new);
register(DataComponents.BASE_COLOR, nms -> DyeColor.getByWoolData((byte) nms.getId()), api -> net.minecraft.world.item.DyeColor.byId(api.getWoolData()));
register(DataComponents.POT_DECORATIONS, PaperPotDecorations::new);
register(DataComponents.CONTAINER, PaperItemContainerContents::new);
register(DataComponents.BLOCK_STATE, PaperBlockItemDataProperties::new);
// bees
// register(DataComponents.LOCK, PaperLockCode::new);
register(DataComponents.CONTAINER_LOOT, PaperSeededContainerLoot::new);
// TODO: REMOVE THIS... we want to build the PR... so lets just make things UNTYPED!
for (final Map.Entry<ResourceKey<DataComponentType<?>>, DataComponentType<?>> componentType : BuiltInRegistries.DATA_COMPONENT_TYPE.entrySet()) {
if (!ADAPTERS.containsKey(componentType.getKey())) {
registerUntyped((DataComponentType<Unit>) componentType.getValue());
}
}
}
public static void registerUntyped(final DataComponentType<Unit> type) {
registerInternal(type, UNIT_TO_API_CONVERTER, DataComponentAdapter.API_TO_UNIT_CONVERTER, false);
}
private static <COMMON> void registerIdentity(final DataComponentType<COMMON> type) {
registerInternal(type, Function.identity(), Function.identity(), true);
}
private static <NMS, API extends Handleable<NMS>> void register(final DataComponentType<NMS> type, final Function<NMS, API> vanillaToApi) {
registerInternal(type, vanillaToApi, Handleable::getHandle, false);
}
private static <NMS, API> void register(final DataComponentType<NMS> type, final Function<NMS, API> vanillaToApi, final Function<API, NMS> apiToVanilla) {
registerInternal(type, vanillaToApi, apiToVanilla, false);
}
private static <NMS, API> void registerInternal(final DataComponentType<NMS> type, final Function<NMS, API> vanillaToApi, final Function<API, NMS> apiToVanilla, final boolean codecValidation) {
final ResourceKey<DataComponentType<?>> key = BuiltInRegistries.DATA_COMPONENT_TYPE.getResourceKey(type).orElseThrow();
if (ADAPTERS.containsKey(key)) {
throw new IllegalStateException("Duplicate adapter registration for " + key);
}
ADAPTERS.put(key, new DataComponentAdapter<>(type, apiToVanilla, vanillaToApi, codecValidation && !type.isTransient()));
}
}

View file

@ -0,0 +1,109 @@
package io.papermc.paper.datacomponent;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import net.minecraft.core.component.DataComponentMap;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.craftbukkit.CraftRegistry;
import org.bukkit.craftbukkit.util.Handleable;
import org.jspecify.annotations.Nullable;
public abstract class PaperDataComponentType<T, NMS> implements DataComponentType, Handleable<net.minecraft.core.component.DataComponentType<NMS>> {
static {
DataComponentAdapters.bootstrap();
}
public static <T> net.minecraft.core.component.DataComponentType<T> bukkitToMinecraft(final DataComponentType type) {
return CraftRegistry.bukkitToMinecraft(type);
}
public static DataComponentType minecraftToBukkit(final net.minecraft.core.component.DataComponentType<?> type) {
return CraftRegistry.minecraftToBukkit(type, Registries.DATA_COMPONENT_TYPE, Registry.DATA_COMPONENT_TYPE);
}
public static Set<DataComponentType> minecraftToBukkit(final Set<net.minecraft.core.component.DataComponentType<?>> nmsTypes) {
final Set<DataComponentType> types = new HashSet<>(nmsTypes.size());
for (final net.minecraft.core.component.DataComponentType<?> nmsType : nmsTypes) {
types.add(PaperDataComponentType.minecraftToBukkit(nmsType));
}
return Collections.unmodifiableSet(types);
}
public static <B, M> @Nullable B convertDataComponentValue(final DataComponentMap map, final PaperDataComponentType.ValuedImpl<B, M> type) {
final net.minecraft.core.component.DataComponentType<M> nms = bukkitToMinecraft(type);
final M nmsValue = map.get(nms);
if (nmsValue == null) {
return null;
}
return type.getAdapter().fromVanilla(nmsValue);
}
private final NamespacedKey key;
private final net.minecraft.core.component.DataComponentType<NMS> type;
private final DataComponentAdapter<NMS, T> adapter;
public PaperDataComponentType(final NamespacedKey key, final net.minecraft.core.component.DataComponentType<NMS> type, final DataComponentAdapter<NMS, T> adapter) {
this.key = key;
this.type = type;
this.adapter = adapter;
}
@Override
public NamespacedKey getKey() {
return this.key;
}
@Override
public boolean isPersistent() {
return !this.type.isTransient();
}
public DataComponentAdapter<NMS, T> getAdapter() {
return this.adapter;
}
@Override
public net.minecraft.core.component.DataComponentType<NMS> getHandle() {
return this.type;
}
@SuppressWarnings("unchecked")
public static <NMS> DataComponentType of(final NamespacedKey key, final net.minecraft.core.component.DataComponentType<NMS> type) {
final DataComponentAdapter<NMS, ?> adapter = (DataComponentAdapter<NMS, ?>) DataComponentAdapters.ADAPTERS.get(BuiltInRegistries.DATA_COMPONENT_TYPE.getResourceKey(type).orElseThrow());
if (adapter == null) {
throw new IllegalArgumentException("No adapter found for " + key);
}
if (adapter.isValued()) {
return new ValuedImpl<>(key, type, adapter);
} else {
return new NonValuedImpl<>(key, type, adapter);
}
}
public static final class NonValuedImpl<T, NMS> extends PaperDataComponentType<T, NMS> implements NonValued {
NonValuedImpl(
final NamespacedKey key,
final net.minecraft.core.component.DataComponentType<NMS> type,
final DataComponentAdapter<NMS, T> adapter
) {
super(key, type, adapter);
}
}
public static final class ValuedImpl<T, NMS> extends PaperDataComponentType<T, NMS> implements Valued<T> {
ValuedImpl(
final NamespacedKey key,
final net.minecraft.core.component.DataComponentType<NMS> type,
final DataComponentAdapter<NMS, T> adapter
) {
super(key, type, adapter);
}
}
}

View file

@ -0,0 +1,239 @@
package io.papermc.paper.datacomponent.item;
import com.destroystokyo.paper.profile.PlayerProfile;
import com.google.common.base.Preconditions;
import io.papermc.paper.registry.PaperRegistries;
import io.papermc.paper.registry.set.PaperRegistrySets;
import io.papermc.paper.registry.set.RegistryKeySet;
import io.papermc.paper.registry.tag.TagKey;
import io.papermc.paper.text.Filtered;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.util.TriState;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.item.component.OminousBottleAmplifier;
import org.bukkit.JukeboxSong;
import org.bukkit.block.BlockType;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.damage.DamageType;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.ItemType;
import org.bukkit.inventory.meta.trim.ArmorTrim;
import org.bukkit.map.MapCursor;
import org.jspecify.annotations.Nullable;
public final class ItemComponentTypesBridgesImpl implements ItemComponentTypesBridge {
@Override
public ChargedProjectiles.Builder chargedProjectiles() {
return new PaperChargedProjectiles.BuilderImpl();
}
@Override
public PotDecorations.Builder potDecorations() {
return new PaperPotDecorations.BuilderImpl();
}
@Override
public Unbreakable.Builder unbreakable() {
return new PaperUnbreakable.BuilderImpl();
}
@Override
public ItemLore.Builder lore() {
return new PaperItemLore.BuilderImpl();
}
@Override
public ItemEnchantments.Builder enchantments() {
return new PaperItemEnchantments.BuilderImpl();
}
@Override
public ItemAttributeModifiers.Builder modifiers() {
return new PaperItemAttributeModifiers.BuilderImpl();
}
@Override
public FoodProperties.Builder food() {
return new PaperFoodProperties.BuilderImpl();
}
@Override
public DyedItemColor.Builder dyedItemColor() {
return new PaperDyedItemColor.BuilderImpl();
}
@Override
public PotionContents.Builder potionContents() {
return new PaperPotionContents.BuilderImpl();
}
@Override
public BundleContents.Builder bundleContents() {
return new PaperBundleContents.BuilderImpl();
}
@Override
public SuspiciousStewEffects.Builder suspiciousStewEffects() {
return new PaperSuspiciousStewEffects.BuilderImpl();
}
@Override
public MapItemColor.Builder mapItemColor() {
return new PaperMapItemColor.BuilderImpl();
}
@Override
public MapDecorations.Builder mapDecorations() {
return new PaperMapDecorations.BuilderImpl();
}
@Override
public MapDecorations.DecorationEntry decorationEntry(final MapCursor.Type type, final double x, final double z, final float rotation) {
return PaperMapDecorations.PaperDecorationEntry.toApi(type, x, z, rotation);
}
@Override
public SeededContainerLoot.Builder seededContainerLoot(final Key lootTableKey) {
return new PaperSeededContainerLoot.BuilderImpl(lootTableKey);
}
@Override
public ItemContainerContents.Builder itemContainerContents() {
return new PaperItemContainerContents.BuilderImpl();
}
@Override
public JukeboxPlayable.Builder jukeboxPlayable(final JukeboxSong song) {
return new PaperJukeboxPlayable.BuilderImpl(song);
}
@Override
public Tool.Builder tool() {
return new PaperItemTool.BuilderImpl();
}
@Override
public Tool.Rule rule(final RegistryKeySet<BlockType> blocks, final @Nullable Float speed, final TriState correctForDrops) {
return PaperItemTool.PaperRule.fromUnsafe(blocks, speed, correctForDrops);
}
@Override
public ItemAdventurePredicate.Builder itemAdventurePredicate() {
return new PaperItemAdventurePredicate.BuilderImpl();
}
@Override
public WrittenBookContent.Builder writtenBookContent(final Filtered<String> title, final String author) {
return new PaperWrittenBookContent.BuilderImpl(title, author);
}
@Override
public WritableBookContent.Builder writeableBookContent() {
return new PaperWritableBookContent.BuilderImpl();
}
@Override
public ItemArmorTrim.Builder itemArmorTrim(final ArmorTrim armorTrim) {
return new PaperItemArmorTrim.BuilderImpl(armorTrim);
}
@Override
public LodestoneTracker.Builder lodestoneTracker() {
return new PaperLodestoneTracker.BuilderImpl();
}
@Override
public Fireworks.Builder fireworks() {
return new PaperFireworks.BuilderImpl();
}
@Override
public ResolvableProfile.Builder resolvableProfile() {
return new PaperResolvableProfile.BuilderImpl();
}
@Override
public ResolvableProfile resolvableProfile(final PlayerProfile profile) {
return PaperResolvableProfile.toApi(profile);
}
@Override
public BannerPatternLayers.Builder bannerPatternLayers() {
return new PaperBannerPatternLayers.BuilderImpl();
}
@Override
public BlockItemDataProperties.Builder blockItemStateProperties() {
return new PaperBlockItemDataProperties.BuilderImpl();
}
@Override
public MapId mapId(final int id) {
return new PaperMapId(new net.minecraft.world.level.saveddata.maps.MapId(id));
}
@Override
public UseRemainder useRemainder(final ItemStack itemStack) {
Preconditions.checkArgument(itemStack != null, "Item cannot be null");
Preconditions.checkArgument(!itemStack.isEmpty(), "Remaining item cannot be empty!");
return new PaperUseRemainder(
new net.minecraft.world.item.component.UseRemainder(CraftItemStack.asNMSCopy(itemStack))
);
}
@Override
public Consumable.Builder consumable() {
return new PaperConsumable.BuilderImpl();
}
@Override
public UseCooldown.Builder useCooldown(final float seconds) {
Preconditions.checkArgument(seconds > 0, "seconds must be positive, was %s", seconds);
return new PaperUseCooldown.BuilderImpl(seconds);
}
@Override
public DamageResistant damageResistant(final TagKey<DamageType> types) {
return new PaperDamageResistant(new net.minecraft.world.item.component.DamageResistant(PaperRegistries.toNms(types)));
}
@Override
public Enchantable enchantable(final int level) {
return new PaperEnchantable(new net.minecraft.world.item.enchantment.Enchantable(level));
}
@Override
public Repairable repairable(final RegistryKeySet<ItemType> types) {
return new PaperRepairable(new net.minecraft.world.item.enchantment.Repairable(
PaperRegistrySets.convertToNms(Registries.ITEM, BuiltInRegistries.BUILT_IN_CONVERSIONS.lookup(), types)
));
}
@Override
public Equippable.Builder equippable(EquipmentSlot slot) {
return new PaperEquippable.BuilderImpl(slot);
}
@Override
public DeathProtection.Builder deathProtection() {
return new PaperDeathProtection.BuilderImpl();
}
@Override
public CustomModelData.Builder customModelData() {
return new PaperCustomModelData.BuilderImpl();
}
@Override
public PaperOminousBottleAmplifier ominousBottleAmplifier(final int amplifier) {
Preconditions.checkArgument(OminousBottleAmplifier.MIN_AMPLIFIER <= amplifier && amplifier <= OminousBottleAmplifier.MAX_AMPLIFIER,
"amplifier must be between %s-%s, was %s", OminousBottleAmplifier.MIN_AMPLIFIER, OminousBottleAmplifier.MAX_AMPLIFIER, amplifier
);
return new PaperOminousBottleAmplifier(
new OminousBottleAmplifier(amplifier)
);
}
}

View file

@ -0,0 +1,62 @@
package io.papermc.paper.datacomponent.item;
import io.papermc.paper.registry.RegistryAccess;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.util.MCUtil;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.bukkit.DyeColor;
import org.bukkit.block.banner.Pattern;
import org.bukkit.block.banner.PatternType;
import org.bukkit.craftbukkit.CraftRegistry;
import org.bukkit.craftbukkit.block.banner.CraftPatternType;
import org.bukkit.craftbukkit.util.Handleable;
import org.jetbrains.annotations.Unmodifiable;
public record PaperBannerPatternLayers(
net.minecraft.world.level.block.entity.BannerPatternLayers impl
) implements BannerPatternLayers, Handleable<net.minecraft.world.level.block.entity.BannerPatternLayers> {
private static List<Pattern> convert(final net.minecraft.world.level.block.entity.BannerPatternLayers nmsPatterns) {
return MCUtil.transformUnmodifiable(nmsPatterns.layers(), input -> {
final Optional<PatternType> type = CraftRegistry.unwrapAndConvertHolder(RegistryAccess.registryAccess().getRegistry(RegistryKey.BANNER_PATTERN), input.pattern());
return new Pattern(Objects.requireNonNull(DyeColor.getByWoolData((byte) input.color().getId())), type.orElseThrow(() -> new IllegalStateException("Inlined banner patterns are not supported yet in the API!")));
});
}
@Override
public net.minecraft.world.level.block.entity.BannerPatternLayers getHandle() {
return this.impl;
}
@Override
public @Unmodifiable List<Pattern> patterns() {
return convert(impl);
}
static final class BuilderImpl implements BannerPatternLayers.Builder {
private final net.minecraft.world.level.block.entity.BannerPatternLayers.Builder builder = new net.minecraft.world.level.block.entity.BannerPatternLayers.Builder();
@Override
public BannerPatternLayers.Builder add(final Pattern pattern) {
this.builder.add(
CraftPatternType.bukkitToMinecraftHolder(pattern.getPattern()),
net.minecraft.world.item.DyeColor.byId(pattern.getColor().getWoolData())
);
return this;
}
@Override
public BannerPatternLayers.Builder addAll(final List<Pattern> patterns) {
patterns.forEach(this::add);
return this;
}
@Override
public BannerPatternLayers build() {
return new PaperBannerPatternLayers(this.builder.build());
}
}
}

View file

@ -0,0 +1,50 @@
package io.papermc.paper.datacomponent.item;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import java.util.Map;
import net.minecraft.world.item.component.BlockItemStateProperties;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.block.BlockType;
import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.block.CraftBlockType;
import org.bukkit.craftbukkit.block.data.CraftBlockData;
import org.bukkit.craftbukkit.util.Handleable;
public record PaperBlockItemDataProperties(
BlockItemStateProperties impl
) implements BlockItemDataProperties, Handleable<BlockItemStateProperties> {
@Override
public BlockData createBlockData(final BlockType blockType) {
final Block block = CraftBlockType.bukkitToMinecraftNew(blockType);
final BlockState defaultState = block.defaultBlockState();
return this.impl.apply(defaultState).createCraftBlockData();
}
@Override
public BlockData applyTo(final BlockData blockData) {
final BlockState state = ((CraftBlockData) blockData).getState();
return this.impl.apply(state).createCraftBlockData();
}
@Override
public BlockItemStateProperties getHandle() {
return this.impl;
}
static final class BuilderImpl implements BlockItemDataProperties.Builder {
private final Map<String, String> properties = new Object2ObjectOpenHashMap<>();
// TODO when BlockProperty API is merged
@Override
public BlockItemDataProperties build() {
if (this.properties.isEmpty()) {
return new PaperBlockItemDataProperties(BlockItemStateProperties.EMPTY);
}
return new PaperBlockItemDataProperties(new BlockItemStateProperties(new Object2ObjectOpenHashMap<>(this.properties)));
}
}
}

View file

@ -0,0 +1,51 @@
package io.papermc.paper.datacomponent.item;
import com.google.common.base.Preconditions;
import io.papermc.paper.util.MCUtil;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.List;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.craftbukkit.util.Handleable;
import org.bukkit.inventory.ItemStack;
public record PaperBundleContents(
net.minecraft.world.item.component.BundleContents impl
) implements BundleContents, Handleable<net.minecraft.world.item.component.BundleContents> {
@Override
public net.minecraft.world.item.component.BundleContents getHandle() {
return this.impl;
}
@Override
public List<ItemStack> contents() {
return MCUtil.transformUnmodifiable((List<net.minecraft.world.item.ItemStack>) this.impl.items(), CraftItemStack::asBukkitCopy);
}
static final class BuilderImpl implements BundleContents.Builder {
private final List<net.minecraft.world.item.ItemStack> items = new ObjectArrayList<>();
@Override
public BundleContents.Builder add(final ItemStack stack) {
Preconditions.checkArgument(stack != null, "stack cannot be null");
Preconditions.checkArgument(!stack.isEmpty(), "stack cannot be empty");
this.items.add(CraftItemStack.asNMSCopy(stack));
return this;
}
@Override
public BundleContents.Builder addAll(final List<ItemStack> stacks) {
stacks.forEach(this::add);
return this;
}
@Override
public BundleContents build() {
if (this.items.isEmpty()) {
return new PaperBundleContents(net.minecraft.world.item.component.BundleContents.EMPTY);
}
return new PaperBundleContents(new net.minecraft.world.item.component.BundleContents(new ObjectArrayList<>(this.items)));
}
}
}

View file

@ -0,0 +1,51 @@
package io.papermc.paper.datacomponent.item;
import com.google.common.base.Preconditions;
import io.papermc.paper.util.MCUtil;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.craftbukkit.util.Handleable;
import org.bukkit.inventory.ItemStack;
public record PaperChargedProjectiles(
net.minecraft.world.item.component.ChargedProjectiles impl
) implements ChargedProjectiles, Handleable<net.minecraft.world.item.component.ChargedProjectiles> {
@Override
public net.minecraft.world.item.component.ChargedProjectiles getHandle() {
return this.impl;
}
@Override
public List<ItemStack> projectiles() {
return MCUtil.transformUnmodifiable(this.impl.getItems() /*makes copies internally*/, CraftItemStack::asCraftMirror);
}
static final class BuilderImpl implements ChargedProjectiles.Builder {
private final List<net.minecraft.world.item.ItemStack> items = new ArrayList<>();
@Override
public ChargedProjectiles.Builder add(final ItemStack stack) {
Preconditions.checkArgument(stack != null, "stack cannot be null");
Preconditions.checkArgument(!stack.isEmpty(), "stack cannot be empty");
this.items.add(CraftItemStack.asNMSCopy(stack));
return this;
}
@Override
public ChargedProjectiles.Builder addAll(final List<ItemStack> stacks) {
stacks.forEach(this::add);
return this;
}
@Override
public ChargedProjectiles build() {
if (this.items.isEmpty()) {
return new PaperChargedProjectiles(net.minecraft.world.item.component.ChargedProjectiles.EMPTY);
}
return new PaperChargedProjectiles(net.minecraft.world.item.component.ChargedProjectiles.of(this.items));
}
}
}

View file

@ -0,0 +1,126 @@
package io.papermc.paper.datacomponent.item;
import com.google.common.base.Preconditions;
import io.papermc.paper.adventure.PaperAdventure;
import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect;
import io.papermc.paper.datacomponent.item.consumable.ItemUseAnimation;
import io.papermc.paper.datacomponent.item.consumable.PaperConsumableEffects;
import io.papermc.paper.util.MCUtil;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.List;
import net.kyori.adventure.key.Key;
import net.minecraft.core.Holder;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import org.bukkit.craftbukkit.util.Handleable;
import org.checkerframework.checker.index.qual.NonNegative;
import org.jetbrains.annotations.Unmodifiable;
public record PaperConsumable(
net.minecraft.world.item.component.Consumable impl
) implements Consumable, Handleable<net.minecraft.world.item.component.Consumable> {
private static final ItemUseAnimation[] VALUES = ItemUseAnimation.values();
@Override
public net.minecraft.world.item.component.Consumable getHandle() {
return this.impl;
}
@Override
public @NonNegative float consumeSeconds() {
return this.impl.consumeSeconds();
}
@Override
public ItemUseAnimation animation() {
return VALUES[this.impl.animation().ordinal()];
}
@Override
public Key sound() {
return PaperAdventure.asAdventure(this.impl.sound().value().location());
}
@Override
public boolean hasConsumeParticles() {
return this.impl.hasConsumeParticles();
}
@Override
public @Unmodifiable List<ConsumeEffect> consumeEffects() {
return MCUtil.transformUnmodifiable(this.impl.onConsumeEffects(), PaperConsumableEffects::fromNms);
}
@Override
public Consumable.Builder toBuilder() {
return new BuilderImpl()
.consumeSeconds(this.consumeSeconds())
.animation(this.animation())
.sound(this.sound())
.addEffects(this.consumeEffects());
}
static final class BuilderImpl implements Builder {
private static final net.minecraft.world.item.ItemUseAnimation[] VALUES = net.minecraft.world.item.ItemUseAnimation.values();
private float consumeSeconds = net.minecraft.world.item.component.Consumable.DEFAULT_CONSUME_SECONDS;
private net.minecraft.world.item.ItemUseAnimation consumeAnimation = net.minecraft.world.item.ItemUseAnimation.EAT;
private Holder<SoundEvent> eatSound = SoundEvents.GENERIC_EAT;
private boolean hasConsumeParticles = true;
private final List<net.minecraft.world.item.consume_effects.ConsumeEffect> effects = new ObjectArrayList<>();
@Override
public Builder consumeSeconds(final @NonNegative float consumeSeconds) {
Preconditions.checkArgument(consumeSeconds >= 0, "consumeSeconds must be non-negative, was %s", consumeSeconds);
this.consumeSeconds = consumeSeconds;
return this;
}
@Override
public Builder animation(final ItemUseAnimation animation) {
this.consumeAnimation = VALUES[animation.ordinal()];
return this;
}
@Override
public Builder sound(final Key sound) {
this.eatSound = PaperAdventure.resolveSound(sound);
return this;
}
@Override
public Builder hasConsumeParticles(final boolean hasConsumeParticles) {
this.hasConsumeParticles = hasConsumeParticles;
return this;
}
@Override
public Builder addEffect(final ConsumeEffect effect) {
this.effects.add(PaperConsumableEffects.toNms(effect));
return this;
}
@Override
public Builder addEffects(final List<ConsumeEffect> effects) {
for (final ConsumeEffect effect : effects) {
this.effects.add(PaperConsumableEffects.toNms(effect));
}
return this;
}
@Override
public Consumable build() {
return new PaperConsumable(
new net.minecraft.world.item.component.Consumable(
this.consumeSeconds,
this.consumeAnimation,
this.eatSound,
this.hasConsumeParticles,
new ObjectArrayList<>(this.effects)
)
);
}
}
}

View file

@ -0,0 +1,121 @@
package io.papermc.paper.datacomponent.item;
import com.google.common.base.Preconditions;
import it.unimi.dsi.fastutil.booleans.BooleanArrayList;
import it.unimi.dsi.fastutil.booleans.BooleanList;
import it.unimi.dsi.fastutil.floats.FloatArrayList;
import it.unimi.dsi.fastutil.floats.FloatList;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.Collections;
import java.util.List;
import io.papermc.paper.util.MCUtil;
import org.bukkit.Color;
import org.bukkit.craftbukkit.util.Handleable;
public record PaperCustomModelData(
net.minecraft.world.item.component.CustomModelData impl
) implements CustomModelData, Handleable<net.minecraft.world.item.component.CustomModelData> {
@Override
public net.minecraft.world.item.component.CustomModelData getHandle() {
return this.impl;
}
@Override
public List<Float> floats() {
return Collections.unmodifiableList(this.impl.floats());
}
@Override
public List<Boolean> flags() {
return Collections.unmodifiableList(this.impl.flags());
}
@Override
public List<String> strings() {
return Collections.unmodifiableList(this.impl.strings());
}
@Override
public List<Color> colors() {
return MCUtil.transformUnmodifiable(this.impl.colors(), Color::fromRGB);
}
static final class BuilderImpl implements CustomModelData.Builder {
private final FloatList floats = new FloatArrayList();
private final BooleanList flags = new BooleanArrayList();
private final List<String> strings = new ObjectArrayList<>();
private final IntList colors = new IntArrayList();
@Override
public Builder addFloat(final float f) {
this.floats.add(f);
return this;
}
@Override
public Builder addFloats(final List<Float> floats) {
for (Float f : floats) {
Preconditions.checkArgument(f != null, "Float cannot be null");
}
this.floats.addAll(floats);
return this;
}
@Override
public Builder addFlag(final boolean flag) {
this.flags.add(flag);
return this;
}
@Override
public Builder addFlags(final List<Boolean> flags) {
for (Boolean flag : flags) {
Preconditions.checkArgument(flag != null, "Flag cannot be null");
}
this.flags.addAll(flags);
return this;
}
@Override
public Builder addString(final String string) {
Preconditions.checkArgument(string != null, "String cannot be null");
this.strings.add(string);
return this;
}
@Override
public Builder addStrings(final List<String> strings) {
strings.forEach(this::addString);
return this;
}
@Override
public Builder addColor(final Color color) {
Preconditions.checkArgument(color != null, "Color cannot be null");
this.colors.add(color.asRGB());
return this;
}
@Override
public Builder addColors(final List<Color> colors) {
colors.forEach(this::addColor);
return this;
}
@Override
public CustomModelData build() {
return new PaperCustomModelData(
new net.minecraft.world.item.component.CustomModelData(
new FloatArrayList(this.floats),
new BooleanArrayList(this.flags),
new ObjectArrayList<>(this.strings),
new IntArrayList(this.colors)
)
);
}
}
}

View file

@ -0,0 +1,21 @@
package io.papermc.paper.datacomponent.item;
import io.papermc.paper.registry.PaperRegistries;
import io.papermc.paper.registry.tag.TagKey;
import org.bukkit.craftbukkit.util.Handleable;
import org.bukkit.damage.DamageType;
public record PaperDamageResistant(
net.minecraft.world.item.component.DamageResistant impl
) implements DamageResistant, Handleable<net.minecraft.world.item.component.DamageResistant> {
@Override
public net.minecraft.world.item.component.DamageResistant getHandle() {
return this.impl;
}
@Override
public TagKey<DamageType> types() {
return PaperRegistries.fromNms(this.impl.types());
}
}

View file

@ -0,0 +1,50 @@
package io.papermc.paper.datacomponent.item;
import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect;
import io.papermc.paper.datacomponent.item.consumable.PaperConsumableEffects;
import io.papermc.paper.util.MCUtil;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.craftbukkit.util.Handleable;
import org.jetbrains.annotations.Unmodifiable;
public record PaperDeathProtection(
net.minecraft.world.item.component.DeathProtection impl
) implements DeathProtection, Handleable<net.minecraft.world.item.component.DeathProtection> {
@Override
public net.minecraft.world.item.component.DeathProtection getHandle() {
return this.impl;
}
@Override
public @Unmodifiable List<ConsumeEffect> deathEffects() {
return MCUtil.transformUnmodifiable(this.impl.deathEffects(), PaperConsumableEffects::fromNms);
}
static final class BuilderImpl implements Builder {
private final List<net.minecraft.world.item.consume_effects.ConsumeEffect> effects = new ArrayList<>();
@Override
public Builder addEffect(final ConsumeEffect effect) {
this.effects.add(PaperConsumableEffects.toNms(effect));
return this;
}
@Override
public Builder addEffects(final List<ConsumeEffect> effects) {
for (final ConsumeEffect effect : effects) {
this.effects.add(PaperConsumableEffects.toNms(effect));
}
return this;
}
@Override
public DeathProtection build() {
return new PaperDeathProtection(
new net.minecraft.world.item.component.DeathProtection(this.effects)
);
}
}
}

View file

@ -0,0 +1,52 @@
package io.papermc.paper.datacomponent.item;
import org.bukkit.Color;
import org.bukkit.craftbukkit.util.Handleable;
public record PaperDyedItemColor(
net.minecraft.world.item.component.DyedItemColor impl
) implements DyedItemColor, Handleable<net.minecraft.world.item.component.DyedItemColor> {
@Override
public net.minecraft.world.item.component.DyedItemColor getHandle() {
return this.impl;
}
@Override
public Color color() {
return Color.fromRGB(this.impl.rgb() & 0x00FFFFFF); // skip alpha channel
}
@Override
public boolean showInTooltip() {
return this.impl.showInTooltip();
}
@Override
public DyedItemColor showInTooltip(final boolean showInTooltip) {
return new PaperDyedItemColor(this.impl.withTooltip(showInTooltip));
}
static final class BuilderImpl implements DyedItemColor.Builder {
private Color color = Color.WHITE;
private boolean showInToolTip = true;
@Override
public DyedItemColor.Builder color(final Color color) {
this.color = color;
return this;
}
@Override
public DyedItemColor.Builder showInTooltip(final boolean showInTooltip) {
this.showInToolTip = showInTooltip;
return this;
}
@Override
public DyedItemColor build() {
return new PaperDyedItemColor(new net.minecraft.world.item.component.DyedItemColor(this.color.asRGB(), this.showInToolTip));
}
}
}

View file

@ -0,0 +1,18 @@
package io.papermc.paper.datacomponent.item;
import org.bukkit.craftbukkit.util.Handleable;
public record PaperEnchantable(
net.minecraft.world.item.enchantment.Enchantable impl
) implements Enchantable, Handleable<net.minecraft.world.item.enchantment.Enchantable> {
@Override
public net.minecraft.world.item.enchantment.Enchantable getHandle() {
return this.impl;
}
@Override
public int value() {
return this.impl.value();
}
}

View file

@ -0,0 +1,174 @@
package io.papermc.paper.datacomponent.item;
import io.papermc.paper.adventure.PaperAdventure;
import io.papermc.paper.registry.PaperRegistries;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.set.PaperRegistrySets;
import io.papermc.paper.registry.set.RegistryKeySet;
import java.util.Optional;
import java.util.function.Function;
import net.kyori.adventure.key.Key;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.util.datafix.fixes.EquippableAssetRenameFix;
import net.minecraft.world.item.equipment.EquipmentAsset;
import net.minecraft.world.item.equipment.EquipmentAssets;
import org.bukkit.craftbukkit.CraftEquipmentSlot;
import org.bukkit.craftbukkit.util.Handleable;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.EquipmentSlot;
import org.checkerframework.checker.nullness.qual.Nullable;
public record PaperEquippable(
net.minecraft.world.item.equipment.Equippable impl
) implements Equippable, Handleable<net.minecraft.world.item.equipment.Equippable> {
@Override
public net.minecraft.world.item.equipment.Equippable getHandle() {
return this.impl;
}
@Override
public EquipmentSlot slot() {
return CraftEquipmentSlot.getSlot(this.impl.slot());
}
@Override
public Key equipSound() {
return PaperAdventure.asAdventure(this.impl.equipSound().value().location());
}
@Override
public @Nullable Key assetId() {
return this.impl.assetId()
.map(PaperAdventure::asAdventureKey)
.orElse(null);
}
@Override
public @Nullable Key cameraOverlay() {
return this.impl.cameraOverlay()
.map(PaperAdventure::asAdventure)
.orElse(null);
}
@Override
public @Nullable RegistryKeySet<EntityType> allowedEntities() {
return this.impl.allowedEntities()
.map((set) -> PaperRegistrySets.convertToApi(RegistryKey.ENTITY_TYPE, set))
.orElse(null);
}
@Override
public boolean dispensable() {
return this.impl.dispensable();
}
@Override
public boolean swappable() {
return this.impl.swappable();
}
@Override
public boolean damageOnHurt() {
return this.impl.damageOnHurt();
}
@Override
public Builder toBuilder() {
return new BuilderImpl(this.slot())
.equipSound(this.equipSound())
.assetId(this.assetId())
.cameraOverlay(this.cameraOverlay())
.allowedEntities(this.allowedEntities())
.dispensable(this.dispensable())
.swappable(this.swappable())
.damageOnHurt(this.damageOnHurt());
}
static final class BuilderImpl implements Builder {
private final net.minecraft.world.entity.EquipmentSlot equipmentSlot;
private Holder<SoundEvent> equipSound = SoundEvents.ARMOR_EQUIP_GENERIC;
private Optional<ResourceKey<EquipmentAsset>> assetId = Optional.empty();
private Optional<ResourceLocation> cameraOverlay = Optional.empty();
private Optional<HolderSet<net.minecraft.world.entity.EntityType<?>>> allowedEntities = Optional.empty();
private boolean dispensable = true;
private boolean swappable = true;
private boolean damageOnHurt = true;
BuilderImpl(final EquipmentSlot equipmentSlot) {
this.equipmentSlot = CraftEquipmentSlot.getNMS(equipmentSlot);
}
@Override
public Builder equipSound(final Key sound) {
this.equipSound = PaperAdventure.resolveSound(sound);
return this;
}
@Override
public Builder assetId(final @Nullable Key assetId) {
this.assetId = Optional.ofNullable(assetId)
.map(key -> PaperAdventure.asVanilla(EquipmentAssets.ROOT_ID, key));
return this;
}
@Override
public Builder cameraOverlay(@Nullable final Key cameraOverlay) {
this.cameraOverlay = Optional.ofNullable(cameraOverlay)
.map(PaperAdventure::asVanilla);
return this;
}
@Override
public Builder allowedEntities(final @Nullable RegistryKeySet<EntityType> allowedEntities) {
this.allowedEntities = Optional.ofNullable(allowedEntities)
.map((set) -> PaperRegistrySets.convertToNms(Registries.ENTITY_TYPE, BuiltInRegistries.BUILT_IN_CONVERSIONS.lookup(), set));
return this;
}
@Override
public Builder dispensable(final boolean dispensable) {
this.dispensable = dispensable;
return this;
}
@Override
public Builder swappable(final boolean swappable) {
this.swappable = swappable;
return this;
}
@Override
public Builder damageOnHurt(final boolean damageOnHurt) {
this.damageOnHurt = damageOnHurt;
return this;
}
@Override
public Equippable build() {
return new PaperEquippable(
new net.minecraft.world.item.equipment.Equippable(
this.equipmentSlot,
this.equipSound,
this.assetId,
this.cameraOverlay,
this.allowedEntities,
this.dispensable,
this.swappable,
this.damageOnHurt
)
);
}
}
}

View file

@ -0,0 +1,73 @@
package io.papermc.paper.datacomponent.item;
import com.google.common.base.Preconditions;
import io.papermc.paper.util.MCUtil;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.List;
import net.minecraft.world.item.component.FireworkExplosion;
import org.bukkit.FireworkEffect;
import org.bukkit.craftbukkit.inventory.CraftMetaFirework;
import org.bukkit.craftbukkit.util.Handleable;
import org.jetbrains.annotations.Unmodifiable;
public record PaperFireworks(
net.minecraft.world.item.component.Fireworks impl
) implements Fireworks, Handleable<net.minecraft.world.item.component.Fireworks> {
@Override
public net.minecraft.world.item.component.Fireworks getHandle() {
return this.impl;
}
@Override
public @Unmodifiable List<FireworkEffect> effects() {
return MCUtil.transformUnmodifiable(this.impl.explosions(), CraftMetaFirework::getEffect);
}
@Override
public int flightDuration() {
return this.impl.flightDuration();
}
static final class BuilderImpl implements Fireworks.Builder {
private final List<FireworkExplosion> effects = new ObjectArrayList<>();
private int duration = 0; // default set from nms Fireworks component
@Override
public Fireworks.Builder flightDuration(final int duration) {
Preconditions.checkArgument(duration >= 0 && duration <= 0xFF, "duration must be an unsigned byte ([%s, %s]), was %s", 0, 0xFF, duration);
this.duration = duration;
return this;
}
@Override
public Fireworks.Builder addEffect(final FireworkEffect effect) {
Preconditions.checkArgument(
this.effects.size() + 1 <= net.minecraft.world.item.component.Fireworks.MAX_EXPLOSIONS,
"Cannot have more than %s effects, had %s",
net.minecraft.world.item.component.Fireworks.MAX_EXPLOSIONS,
this.effects.size() + 1
);
this.effects.add(CraftMetaFirework.getExplosion(effect));
return this;
}
@Override
public Fireworks.Builder addEffects(final List<FireworkEffect> effects) {
Preconditions.checkArgument(
this.effects.size() + effects.size() <= net.minecraft.world.item.component.Fireworks.MAX_EXPLOSIONS,
"Cannot have more than %s effects, had %s",
net.minecraft.world.item.component.Fireworks.MAX_EXPLOSIONS,
this.effects.size() + effects.size()
);
MCUtil.addAndConvert(this.effects, effects, CraftMetaFirework::getExplosion);
return this;
}
@Override
public Fireworks build() {
return new PaperFireworks(new net.minecraft.world.item.component.Fireworks(this.duration, new ObjectArrayList<>(this.effects)));
}
}
}

View file

@ -0,0 +1,72 @@
package io.papermc.paper.datacomponent.item;
import com.google.common.base.Preconditions;
import org.bukkit.craftbukkit.util.Handleable;
public record PaperFoodProperties(
net.minecraft.world.food.FoodProperties impl
) implements FoodProperties, Handleable<net.minecraft.world.food.FoodProperties> {
@Override
public int nutrition() {
return this.impl.nutrition();
}
@Override
public float saturation() {
return this.impl.saturation();
}
@Override
public boolean canAlwaysEat() {
return this.impl.canAlwaysEat();
}
@Override
public FoodProperties.Builder toBuilder() {
return new BuilderImpl()
.nutrition(this.nutrition())
.saturation(this.saturation())
.canAlwaysEat(this.canAlwaysEat());
}
@Override
public net.minecraft.world.food.FoodProperties getHandle() {
return this.impl;
}
static final class BuilderImpl implements FoodProperties.Builder {
private boolean canAlwaysEat = false;
private float saturation = 0;
private int nutrition = 0;
@Override
public FoodProperties.Builder canAlwaysEat(final boolean canAlwaysEat) {
this.canAlwaysEat = canAlwaysEat;
return this;
}
@Override
public FoodProperties.Builder saturation(final float saturation) {
this.saturation = saturation;
return this;
}
@Override
public FoodProperties.Builder nutrition(final int nutrition) {
Preconditions.checkArgument(nutrition >= 0, "nutrition must be non-negative, was %s", nutrition);
this.nutrition = nutrition;
return this;
}
@Override
public FoodProperties build() {
return new PaperFoodProperties(new net.minecraft.world.food.FoodProperties(
this.nutrition,
this.saturation,
this.canAlwaysEat
));
}
}
}

View file

@ -0,0 +1,75 @@
package io.papermc.paper.datacomponent.item;
import io.papermc.paper.block.BlockPredicate;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.set.PaperRegistrySets;
import io.papermc.paper.util.MCUtil;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.List;
import java.util.Optional;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import org.bukkit.craftbukkit.util.Handleable;
public record PaperItemAdventurePredicate(
net.minecraft.world.item.AdventureModePredicate impl
) implements ItemAdventurePredicate, Handleable<net.minecraft.world.item.AdventureModePredicate> {
private static List<BlockPredicate> convert(final net.minecraft.world.item.AdventureModePredicate nmsModifiers) {
return MCUtil.transformUnmodifiable(nmsModifiers.predicates, nms -> BlockPredicate.predicate()
.blocks(nms.blocks().map(blocks -> PaperRegistrySets.convertToApi(RegistryKey.BLOCK, blocks)).orElse(null)).build());
}
@Override
public net.minecraft.world.item.AdventureModePredicate getHandle() {
return this.impl;
}
@Override
public boolean showInTooltip() {
return this.impl.showInTooltip();
}
@Override
public PaperItemAdventurePredicate showInTooltip(final boolean showInTooltip) {
return new PaperItemAdventurePredicate(this.impl.withTooltip(showInTooltip));
}
@Override
public List<BlockPredicate> predicates() {
return convert(this.impl);
}
static final class BuilderImpl implements ItemAdventurePredicate.Builder {
private final List<net.minecraft.advancements.critereon.BlockPredicate> predicates = new ObjectArrayList<>();
private boolean showInTooltip = true;
@Override
public ItemAdventurePredicate.Builder addPredicate(final BlockPredicate predicate) {
this.predicates.add(new net.minecraft.advancements.critereon.BlockPredicate(Optional.ofNullable(predicate.blocks()).map(
blocks -> PaperRegistrySets.convertToNms(Registries.BLOCK, BuiltInRegistries.BUILT_IN_CONVERSIONS.lookup(), blocks)
), Optional.empty(), Optional.empty()));
return this;
}
@Override
public Builder addPredicates(final List<BlockPredicate> predicates) {
for (final BlockPredicate predicate : predicates) {
this.addPredicate(predicate);
}
return this;
}
@Override
public ItemAdventurePredicate.Builder showInTooltip(final boolean showInTooltip) {
this.showInTooltip = showInTooltip;
return this;
}
@Override
public ItemAdventurePredicate build() {
return new PaperItemAdventurePredicate(new net.minecraft.world.item.AdventureModePredicate(new ObjectArrayList<>(this.predicates), this.showInTooltip));
}
}
}

View file

@ -0,0 +1,62 @@
package io.papermc.paper.datacomponent.item;
import org.bukkit.craftbukkit.inventory.trim.CraftTrimMaterial;
import org.bukkit.craftbukkit.inventory.trim.CraftTrimPattern;
import org.bukkit.craftbukkit.util.Handleable;
import org.bukkit.inventory.meta.trim.ArmorTrim;
public record PaperItemArmorTrim(
net.minecraft.world.item.equipment.trim.ArmorTrim impl
) implements ItemArmorTrim, Handleable<net.minecraft.world.item.equipment.trim.ArmorTrim> {
@Override
public net.minecraft.world.item.equipment.trim.ArmorTrim getHandle() {
return this.impl;
}
@Override
public boolean showInTooltip() {
return this.impl.showInTooltip();
}
@Override
public ItemArmorTrim showInTooltip(final boolean showInTooltip) {
return new PaperItemArmorTrim(this.impl.withTooltip(showInTooltip));
}
@Override
public ArmorTrim armorTrim() {
return new ArmorTrim(CraftTrimMaterial.minecraftHolderToBukkit(this.impl.material()), CraftTrimPattern.minecraftHolderToBukkit(this.impl.pattern()));
}
static final class BuilderImpl implements ItemArmorTrim.Builder {
private ArmorTrim armorTrim;
private boolean showInTooltip = true;
BuilderImpl(final ArmorTrim armorTrim) {
this.armorTrim = armorTrim;
}
@Override
public ItemArmorTrim.Builder showInTooltip(final boolean showInTooltip) {
this.showInTooltip = showInTooltip;
return this;
}
@Override
public ItemArmorTrim.Builder armorTrim(final ArmorTrim armorTrim) {
this.armorTrim = armorTrim;
return this;
}
@Override
public ItemArmorTrim build() {
return new PaperItemArmorTrim(new net.minecraft.world.item.equipment.trim.ArmorTrim(
CraftTrimMaterial.bukkitToMinecraftHolder(this.armorTrim.getMaterial()),
CraftTrimPattern.bukkitToMinecraftHolder(this.armorTrim.getPattern()),
this.showInTooltip
));
}
}
}

View file

@ -0,0 +1,97 @@
package io.papermc.paper.datacomponent.item;
import com.google.common.base.Preconditions;
import io.papermc.paper.util.MCUtil;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.List;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeModifier;
import org.bukkit.craftbukkit.CraftEquipmentSlot;
import org.bukkit.craftbukkit.attribute.CraftAttribute;
import org.bukkit.craftbukkit.attribute.CraftAttributeInstance;
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
import org.bukkit.craftbukkit.util.Handleable;
import org.bukkit.inventory.EquipmentSlotGroup;
import org.jetbrains.annotations.Unmodifiable;
public record PaperItemAttributeModifiers(
net.minecraft.world.item.component.ItemAttributeModifiers impl
) implements ItemAttributeModifiers, Handleable<net.minecraft.world.item.component.ItemAttributeModifiers> {
private static List<Entry> convert(final net.minecraft.world.item.component.ItemAttributeModifiers nmsModifiers) {
return MCUtil.transformUnmodifiable(nmsModifiers.modifiers(), nms -> new PaperEntry(
CraftAttribute.minecraftHolderToBukkit(nms.attribute()),
CraftAttributeInstance.convert(nms.modifier(), nms.slot())
));
}
@Override
public net.minecraft.world.item.component.ItemAttributeModifiers getHandle() {
return this.impl;
}
@Override
public boolean showInTooltip() {
return this.impl.showInTooltip();
}
@Override
public ItemAttributeModifiers showInTooltip(final boolean showInTooltip) {
return new PaperItemAttributeModifiers(this.impl.withTooltip(showInTooltip));
}
@Override
public @Unmodifiable List<Entry> modifiers() {
return convert(this.impl);
}
public record PaperEntry(Attribute attribute, AttributeModifier modifier) implements ItemAttributeModifiers.Entry {
}
static final class BuilderImpl implements ItemAttributeModifiers.Builder {
private final List<net.minecraft.world.item.component.ItemAttributeModifiers.Entry> entries = new ObjectArrayList<>();
private boolean showInTooltip = net.minecraft.world.item.component.ItemAttributeModifiers.EMPTY.showInTooltip();
@Override
public Builder addModifier(final Attribute attribute, final AttributeModifier modifier) {
return this.addModifier(attribute, modifier, modifier.getSlotGroup());
}
@Override
public ItemAttributeModifiers.Builder addModifier(final Attribute attribute, final AttributeModifier modifier, final EquipmentSlotGroup equipmentSlotGroup) {
Preconditions.checkArgument(
this.entries.stream().noneMatch(e ->
e.modifier().id().equals(CraftNamespacedKey.toMinecraft(modifier.getKey())) && e.attribute().is(CraftNamespacedKey.toMinecraft(attribute.getKey()))
),
"Cannot add 2 modifiers with identical keys on the same attribute (modifier %s for attribute %s)",
modifier.getKey(), attribute.getKey()
);
this.entries.add(new net.minecraft.world.item.component.ItemAttributeModifiers.Entry(
CraftAttribute.bukkitToMinecraftHolder(attribute),
CraftAttributeInstance.convert(modifier),
CraftEquipmentSlot.getNMSGroup(equipmentSlotGroup)
));
return this;
}
@Override
public ItemAttributeModifiers.Builder showInTooltip(final boolean showInTooltip) {
this.showInTooltip = showInTooltip;
return this;
}
@Override
public ItemAttributeModifiers build() {
if (this.entries.isEmpty()) {
return new PaperItemAttributeModifiers(net.minecraft.world.item.component.ItemAttributeModifiers.EMPTY.withTooltip(this.showInTooltip));
}
return new PaperItemAttributeModifiers(new net.minecraft.world.item.component.ItemAttributeModifiers(
new ObjectArrayList<>(this.entries),
this.showInTooltip
));
}
}
}

View file

@ -0,0 +1,65 @@
package io.papermc.paper.datacomponent.item;
import com.google.common.base.Preconditions;
import io.papermc.paper.util.MCUtil;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.List;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.craftbukkit.util.Handleable;
import org.bukkit.inventory.ItemStack;
public record PaperItemContainerContents(
net.minecraft.world.item.component.ItemContainerContents impl
) implements ItemContainerContents, Handleable<net.minecraft.world.item.component.ItemContainerContents> {
@Override
public net.minecraft.world.item.component.ItemContainerContents getHandle() {
return this.impl;
}
@Override
public List<ItemStack> contents() {
return MCUtil.transformUnmodifiable(this.impl.items, CraftItemStack::asBukkitCopy);
}
static final class BuilderImpl implements ItemContainerContents.Builder {
private final List<net.minecraft.world.item.ItemStack> items = new ObjectArrayList<>();
@Override
public ItemContainerContents.Builder add(final ItemStack stack) {
Preconditions.checkArgument(stack != null, "Item cannot be null");
Preconditions.checkArgument(
this.items.size() + 1 <= net.minecraft.world.item.component.ItemContainerContents.MAX_SIZE,
"Cannot have more than %s items, had %s",
net.minecraft.world.item.component.ItemContainerContents.MAX_SIZE,
this.items.size() + 1
);
this.items.add(CraftItemStack.asNMSCopy(stack));
return this;
}
@Override
public ItemContainerContents.Builder addAll(final List<ItemStack> stacks) {
Preconditions.checkArgument(
this.items.size() + stacks.size() <= net.minecraft.world.item.component.ItemContainerContents.MAX_SIZE,
"Cannot have more than %s items, had %s",
net.minecraft.world.item.component.ItemContainerContents.MAX_SIZE,
this.items.size() + stacks.size()
);
MCUtil.addAndConvert(this.items, stacks, stack -> {
Preconditions.checkArgument(stack != null, "Cannot pass null itemstacks!");
return CraftItemStack.asNMSCopy(stack);
});
return this;
}
@Override
public ItemContainerContents build() {
if (this.items.isEmpty()) {
return new PaperItemContainerContents(net.minecraft.world.item.component.ItemContainerContents.EMPTY);
}
return new PaperItemContainerContents(net.minecraft.world.item.component.ItemContainerContents.fromItems(this.items));
}
}
}

View file

@ -0,0 +1,92 @@
package io.papermc.paper.datacomponent.item;
import com.google.common.base.Preconditions;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import net.minecraft.core.Holder;
import org.bukkit.craftbukkit.enchantments.CraftEnchantment;
import org.bukkit.craftbukkit.util.Handleable;
import org.bukkit.enchantments.Enchantment;
public record PaperItemEnchantments(
net.minecraft.world.item.enchantment.ItemEnchantments impl,
Map<Enchantment, Integer> enchantments // API values are stored externally as the concept of a lazy key transformer map does not make much sense
) implements ItemEnchantments, Handleable<net.minecraft.world.item.enchantment.ItemEnchantments> {
public PaperItemEnchantments(final net.minecraft.world.item.enchantment.ItemEnchantments itemEnchantments) {
this(itemEnchantments, convert(itemEnchantments));
}
private static Map<Enchantment, Integer> convert(final net.minecraft.world.item.enchantment.ItemEnchantments itemEnchantments) {
if (itemEnchantments.isEmpty()) {
return Collections.emptyMap();
}
final Map<Enchantment, Integer> map = new HashMap<>(itemEnchantments.size());
for (final Object2IntMap.Entry<Holder<net.minecraft.world.item.enchantment.Enchantment>> entry : itemEnchantments.entrySet()) {
map.put(CraftEnchantment.minecraftHolderToBukkit(entry.getKey()), entry.getIntValue());
}
return Collections.unmodifiableMap(map); // TODO look into making a "transforming" map maybe?
}
@Override
public boolean showInTooltip() {
return this.impl.showInTooltip;
}
@Override
public ItemEnchantments showInTooltip(final boolean showInTooltip) {
return new PaperItemEnchantments(this.impl.withTooltip(showInTooltip), this.enchantments);
}
@Override
public net.minecraft.world.item.enchantment.ItemEnchantments getHandle() {
return this.impl;
}
static final class BuilderImpl implements ItemEnchantments.Builder {
private final Map<Enchantment, Integer> enchantments = new Object2ObjectOpenHashMap<>();
private boolean showInTooltip = true;
@Override
public ItemEnchantments.Builder add(final Enchantment enchantment, final int level) {
Preconditions.checkArgument(
level >= 1 && level <= net.minecraft.world.item.enchantment.Enchantment.MAX_LEVEL,
"level must be between %s and %s, was %s",
1, net.minecraft.world.item.enchantment.Enchantment.MAX_LEVEL,
level
);
this.enchantments.put(enchantment, level);
return this;
}
@Override
public ItemEnchantments.Builder addAll(final Map<Enchantment, Integer> enchantments) {
enchantments.forEach(this::add);
return this;
}
@Override
public ItemEnchantments.Builder showInTooltip(final boolean showInTooltip) {
this.showInTooltip = showInTooltip;
return this;
}
@Override
public ItemEnchantments build() {
final net.minecraft.world.item.enchantment.ItemEnchantments initialEnchantments = net.minecraft.world.item.enchantment.ItemEnchantments.EMPTY.withTooltip(this.showInTooltip);
if (this.enchantments.isEmpty()) {
return new PaperItemEnchantments(initialEnchantments);
}
final net.minecraft.world.item.enchantment.ItemEnchantments.Mutable mutable = new net.minecraft.world.item.enchantment.ItemEnchantments.Mutable(initialEnchantments);
this.enchantments.forEach((enchantment, level) ->
mutable.set(CraftEnchantment.bukkitToMinecraftHolder(enchantment), level)
);
return new PaperItemEnchantments(mutable.toImmutable());
}
}
}

View file

@ -0,0 +1,77 @@
package io.papermc.paper.datacomponent.item;
import com.google.common.base.Preconditions;
import io.papermc.paper.adventure.PaperAdventure;
import io.papermc.paper.util.MCUtil;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.ArrayList;
import java.util.List;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import org.bukkit.craftbukkit.util.Handleable;
import org.jetbrains.annotations.Unmodifiable;
public record PaperItemLore(
net.minecraft.world.item.component.ItemLore impl
) implements ItemLore, Handleable<net.minecraft.world.item.component.ItemLore> {
@Override
public net.minecraft.world.item.component.ItemLore getHandle() {
return this.impl;
}
@Override
public @Unmodifiable List<Component> lines() {
return MCUtil.transformUnmodifiable(this.impl.lines(), PaperAdventure::asAdventure);
}
@Override
public @Unmodifiable List<Component> styledLines() {
return MCUtil.transformUnmodifiable(this.impl.styledLines(), PaperAdventure::asAdventure);
}
static final class BuilderImpl implements ItemLore.Builder {
private List<Component> lines = new ObjectArrayList<>();
private static void validateLineCount(final int current, final int add) {
final int newSize = current + add;
Preconditions.checkArgument(
newSize <= net.minecraft.world.item.component.ItemLore.MAX_LINES,
"Cannot have more than %s lines, had %s",
net.minecraft.world.item.component.ItemLore.MAX_LINES,
newSize
);
}
@Override
public ItemLore.Builder lines(final List<? extends ComponentLike> lines) {
validateLineCount(0, lines.size());
this.lines = new ArrayList<>(ComponentLike.asComponents(lines));
return this;
}
@Override
public ItemLore.Builder addLine(final ComponentLike line) {
validateLineCount(this.lines.size(), 1);
this.lines.add(line.asComponent());
return this;
}
@Override
public ItemLore.Builder addLines(final List<? extends ComponentLike> lines) {
validateLineCount(this.lines.size(), lines.size());
this.lines.addAll(ComponentLike.asComponents(lines));
return this;
}
@Override
public ItemLore build() {
if (this.lines.isEmpty()) {
return new PaperItemLore(net.minecraft.world.item.component.ItemLore.EMPTY);
}
return new PaperItemLore(new net.minecraft.world.item.component.ItemLore(PaperAdventure.asVanilla(this.lines))); // asVanilla does a list clone
}
}
}

View file

@ -0,0 +1,100 @@
package io.papermc.paper.datacomponent.item;
import com.google.common.base.Preconditions;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.set.PaperRegistrySets;
import io.papermc.paper.registry.set.RegistryKeySet;
import io.papermc.paper.util.MCUtil;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import net.kyori.adventure.util.TriState;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import org.bukkit.block.BlockType;
import org.bukkit.craftbukkit.util.Handleable;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.Unmodifiable;
public record PaperItemTool(
net.minecraft.world.item.component.Tool impl
) implements Tool, Handleable<net.minecraft.world.item.component.Tool> {
private static List<Tool.Rule> convert(final List<net.minecraft.world.item.component.Tool.Rule> tool) {
return MCUtil.transformUnmodifiable(tool, nms -> new PaperRule(
PaperRegistrySets.convertToApi(RegistryKey.BLOCK, nms.blocks()),
nms.speed().orElse(null),
TriState.byBoolean(nms.correctForDrops().orElse(null))
));
}
@Override
public net.minecraft.world.item.component.Tool getHandle() {
return this.impl;
}
@Override
public @Unmodifiable List<Rule> rules() {
return convert(this.impl.rules());
}
@Override
public float defaultMiningSpeed() {
return this.impl.defaultMiningSpeed();
}
@Override
public int damagePerBlock() {
return this.impl.damagePerBlock();
}
record PaperRule(RegistryKeySet<BlockType> blocks, @Nullable Float speed, TriState correctForDrops) implements Rule {
public static PaperRule fromUnsafe(final RegistryKeySet<BlockType> blocks, final @Nullable Float speed, final TriState correctForDrops) {
Preconditions.checkArgument(speed == null || speed > 0, "speed must be positive");
return new PaperRule(blocks, speed, correctForDrops);
}
}
static final class BuilderImpl implements Builder {
private final List<net.minecraft.world.item.component.Tool.Rule> rules = new ObjectArrayList<>();
private int damage = 1;
private float miningSpeed = 1.0F;
@Override
public Builder damagePerBlock(final int damage) {
Preconditions.checkArgument(damage >= 0, "damage must be non-negative, was %s", damage);
this.damage = damage;
return this;
}
@Override
public Builder defaultMiningSpeed(final float miningSpeed) {
this.miningSpeed = miningSpeed;
return this;
}
@Override
public Builder addRule(final Rule rule) {
this.rules.add(new net.minecraft.world.item.component.Tool.Rule(
PaperRegistrySets.convertToNms(Registries.BLOCK, BuiltInRegistries.BUILT_IN_CONVERSIONS.lookup(), rule.blocks()),
Optional.ofNullable(rule.speed()),
Optional.ofNullable(rule.correctForDrops().toBoolean())
));
return this;
}
@Override
public Builder addRules(final Collection<Rule> rules) {
rules.forEach(this::addRule);
return this;
}
@Override
public Tool build() {
return new PaperItemTool(new net.minecraft.world.item.component.Tool(new ObjectArrayList<>(this.rules), this.miningSpeed, this.damage));
}
}
}

View file

@ -0,0 +1,62 @@
package io.papermc.paper.datacomponent.item;
import net.minecraft.world.item.EitherHolder;
import org.bukkit.JukeboxSong;
import org.bukkit.craftbukkit.CraftJukeboxSong;
import org.bukkit.craftbukkit.CraftRegistry;
import org.bukkit.craftbukkit.util.Handleable;
public record PaperJukeboxPlayable(
net.minecraft.world.item.JukeboxPlayable impl
) implements JukeboxPlayable, Handleable<net.minecraft.world.item.JukeboxPlayable> {
@Override
public net.minecraft.world.item.JukeboxPlayable getHandle() {
return this.impl;
}
@Override
public boolean showInTooltip() {
return this.impl.showInTooltip();
}
@Override
public PaperJukeboxPlayable showInTooltip(final boolean showInTooltip) {
return new PaperJukeboxPlayable(this.impl.withTooltip(showInTooltip));
}
@Override
public JukeboxSong jukeboxSong() {
return this.impl.song()
.unwrap(CraftRegistry.getMinecraftRegistry())
.map(CraftJukeboxSong::minecraftHolderToBukkit)
.orElseThrow();
}
static final class BuilderImpl implements JukeboxPlayable.Builder {
private JukeboxSong song;
private boolean showInTooltip = true;
BuilderImpl(final JukeboxSong song) {
this.song = song;
}
@Override
public JukeboxPlayable.Builder showInTooltip(final boolean showInTooltip) {
this.showInTooltip = showInTooltip;
return this;
}
@Override
public JukeboxPlayable.Builder jukeboxSong(final JukeboxSong song) {
this.song = song;
return this;
}
@Override
public JukeboxPlayable build() {
return new PaperJukeboxPlayable(new net.minecraft.world.item.JukeboxPlayable(new EitherHolder<>(CraftJukeboxSong.bukkitToMinecraftHolder(this.song)), this.showInTooltip));
}
}
}

View file

@ -0,0 +1,53 @@
package io.papermc.paper.datacomponent.item;
import java.util.Optional;
import org.bukkit.Location;
import org.bukkit.craftbukkit.util.CraftLocation;
import org.bukkit.craftbukkit.util.Handleable;
import org.jspecify.annotations.Nullable;
public record PaperLodestoneTracker(
net.minecraft.world.item.component.LodestoneTracker impl
) implements LodestoneTracker, Handleable<net.minecraft.world.item.component.LodestoneTracker> {
@Override
public net.minecraft.world.item.component.LodestoneTracker getHandle() {
return this.impl;
}
@Override
public @Nullable Location location() {
return this.impl.target().map(CraftLocation::fromGlobalPos).orElse(null);
}
@Override
public boolean tracked() {
return this.impl.tracked();
}
static final class BuilderImpl implements LodestoneTracker.Builder {
private @Nullable Location location;
private boolean tracked = true;
@Override
public LodestoneTracker.Builder location(final @Nullable Location location) {
this.location = location;
return this;
}
@Override
public LodestoneTracker.Builder tracked(final boolean tracked) {
this.tracked = tracked;
return this;
}
@Override
public LodestoneTracker build() {
return new PaperLodestoneTracker(new net.minecraft.world.item.component.LodestoneTracker(
Optional.ofNullable(this.location).map(CraftLocation::toGlobalPos),
this.tracked
));
}
}
}

View file

@ -0,0 +1,97 @@
package io.papermc.paper.datacomponent.item;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import org.bukkit.craftbukkit.map.CraftMapCursor;
import org.bukkit.craftbukkit.util.Handleable;
import org.bukkit.map.MapCursor;
import org.jspecify.annotations.Nullable;
public record PaperMapDecorations(
net.minecraft.world.item.component.MapDecorations impl
) implements MapDecorations, Handleable<net.minecraft.world.item.component.MapDecorations> {
@Override
public net.minecraft.world.item.component.MapDecorations getHandle() {
return this.impl;
}
@Override
public @Nullable DecorationEntry decoration(final String id) {
final net.minecraft.world.item.component.MapDecorations.Entry decoration = this.impl.decorations().get(id);
if (decoration == null) {
return null;
}
return new PaperDecorationEntry(decoration);
}
@Override
public Map<String, DecorationEntry> decorations() {
if (this.impl.decorations().isEmpty()) {
return Collections.emptyMap();
}
final Set<Map.Entry<String, net.minecraft.world.item.component.MapDecorations.Entry>> entries = this.impl.decorations().entrySet();
final Map<String, DecorationEntry> decorations = new Object2ObjectOpenHashMap<>(entries.size());
for (final Map.Entry<String, net.minecraft.world.item.component.MapDecorations.Entry> entry : entries) {
decorations.put(entry.getKey(), new PaperDecorationEntry(entry.getValue()));
}
return Collections.unmodifiableMap(decorations);
}
public record PaperDecorationEntry(net.minecraft.world.item.component.MapDecorations.Entry entry) implements DecorationEntry {
public static DecorationEntry toApi(final MapCursor.Type type, final double x, final double z, final float rotation) {
return new PaperDecorationEntry(new net.minecraft.world.item.component.MapDecorations.Entry(CraftMapCursor.CraftType.bukkitToMinecraftHolder(type), x, z, rotation));
}
@Override
public MapCursor.Type type() {
return CraftMapCursor.CraftType.minecraftHolderToBukkit(this.entry.type());
}
@Override
public double x() {
return this.entry.x();
}
@Override
public double z() {
return this.entry.z();
}
@Override
public float rotation() {
return this.entry.rotation();
}
}
static final class BuilderImpl implements Builder {
private final Map<String, net.minecraft.world.item.component.MapDecorations.Entry> entries = new Object2ObjectOpenHashMap<>();
@Override
public MapDecorations.Builder put(final String id, final DecorationEntry entry) {
this.entries.put(id, new net.minecraft.world.item.component.MapDecorations.Entry(CraftMapCursor.CraftType.bukkitToMinecraftHolder(entry.type()), entry.x(), entry.z(), entry.rotation()));
return this;
}
@Override
public Builder putAll(final Map<String, DecorationEntry> entries) {
entries.forEach(this::put);
return this;
}
@Override
public MapDecorations build() {
if (this.entries.isEmpty()) {
return new PaperMapDecorations(net.minecraft.world.item.component.MapDecorations.EMPTY);
}
return new PaperMapDecorations(new net.minecraft.world.item.component.MapDecorations(new Object2ObjectOpenHashMap<>(this.entries)));
}
}
}

View file

@ -0,0 +1,19 @@
package io.papermc.paper.datacomponent.item;
import org.bukkit.craftbukkit.util.Handleable;
public record PaperMapId(
net.minecraft.world.level.saveddata.maps.MapId impl
) implements MapId, Handleable<net.minecraft.world.level.saveddata.maps.MapId> {
@Override
public net.minecraft.world.level.saveddata.maps.MapId getHandle() {
return this.impl;
}
@Override
public int id() {
return this.impl.id();
}
}

View file

@ -0,0 +1,35 @@
package io.papermc.paper.datacomponent.item;
import org.bukkit.Color;
import org.bukkit.craftbukkit.util.Handleable;
public record PaperMapItemColor(
net.minecraft.world.item.component.MapItemColor impl
) implements MapItemColor, Handleable<net.minecraft.world.item.component.MapItemColor> {
@Override
public net.minecraft.world.item.component.MapItemColor getHandle() {
return this.impl;
}
@Override
public Color color() {
return Color.fromRGB(this.impl.rgb() & 0x00FFFFFF); // skip alpha channel
}
static final class BuilderImpl implements Builder {
private Color color = Color.fromRGB(net.minecraft.world.item.component.MapItemColor.DEFAULT.rgb());
@Override
public Builder color(final Color color) {
this.color = color;
return this;
}
@Override
public MapItemColor build() {
return new PaperMapItemColor(new net.minecraft.world.item.component.MapItemColor(this.color.asRGB()));
}
}
}

View file

@ -0,0 +1,18 @@
package io.papermc.paper.datacomponent.item;
import org.bukkit.craftbukkit.util.Handleable;
public record PaperOminousBottleAmplifier(
net.minecraft.world.item.component.OminousBottleAmplifier impl
) implements OminousBottleAmplifier, Handleable<net.minecraft.world.item.component.OminousBottleAmplifier> {
@Override
public net.minecraft.world.item.component.OminousBottleAmplifier getHandle() {
return this.impl;
}
@Override
public int amplifier() {
return this.impl.value();
}
}

View file

@ -0,0 +1,83 @@
package io.papermc.paper.datacomponent.item;
import java.util.Optional;
import org.bukkit.craftbukkit.inventory.CraftItemType;
import org.bukkit.craftbukkit.util.Handleable;
import org.bukkit.inventory.ItemType;
import org.jspecify.annotations.Nullable;
public record PaperPotDecorations(
net.minecraft.world.level.block.entity.PotDecorations impl
) implements PotDecorations, Handleable<net.minecraft.world.level.block.entity.PotDecorations> {
@Override
public @Nullable ItemType back() {
return this.impl.back().map(CraftItemType::minecraftToBukkitNew).orElse(null);
}
@Override
public @Nullable ItemType left() {
return this.impl.left().map(CraftItemType::minecraftToBukkitNew).orElse(null);
}
@Override
public @Nullable ItemType right() {
return this.impl.right().map(CraftItemType::minecraftToBukkitNew).orElse(null);
}
@Override
public @Nullable ItemType front() {
return this.impl.front().map(CraftItemType::minecraftToBukkitNew).orElse(null);
}
@Override
public net.minecraft.world.level.block.entity.PotDecorations getHandle() {
return this.impl;
}
static final class BuilderImpl implements PotDecorations.Builder {
private @Nullable ItemType back;
private @Nullable ItemType left;
private @Nullable ItemType right;
private @Nullable ItemType front;
@Override
public PotDecorations.Builder back(final @Nullable ItemType back) {
this.back = back;
return this;
}
@Override
public PotDecorations.Builder left(final @Nullable ItemType left) {
this.left = left;
return this;
}
@Override
public PotDecorations.Builder right(final @Nullable ItemType right) {
this.right = right;
return this;
}
@Override
public PotDecorations.Builder front(final @Nullable ItemType front) {
this.front = front;
return this;
}
@Override
public PotDecorations build() {
if (this.back == null && this.left == null && this.right == null && this.front == null) {
return new PaperPotDecorations(net.minecraft.world.level.block.entity.PotDecorations.EMPTY);
}
return new PaperPotDecorations(new net.minecraft.world.level.block.entity.PotDecorations(
Optional.ofNullable(this.back).map(CraftItemType::bukkitToMinecraftNew),
Optional.ofNullable(this.left).map(CraftItemType::bukkitToMinecraftNew),
Optional.ofNullable(this.right).map(CraftItemType::bukkitToMinecraftNew),
Optional.ofNullable(this.front).map(CraftItemType::bukkitToMinecraftNew)
));
}
}
}

View file

@ -0,0 +1,103 @@
package io.papermc.paper.datacomponent.item;
import com.google.common.base.Preconditions;
import io.papermc.paper.util.MCUtil;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.List;
import java.util.Optional;
import net.minecraft.world.effect.MobEffectInstance;
import org.bukkit.Color;
import org.bukkit.craftbukkit.potion.CraftPotionType;
import org.bukkit.craftbukkit.potion.CraftPotionUtil;
import org.bukkit.craftbukkit.util.Handleable;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionType;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.Unmodifiable;
public record PaperPotionContents(
net.minecraft.world.item.alchemy.PotionContents impl
) implements PotionContents, Handleable<net.minecraft.world.item.alchemy.PotionContents> {
@Override
public net.minecraft.world.item.alchemy.PotionContents getHandle() {
return this.impl;
}
@Override
public @Unmodifiable List<PotionEffect> customEffects() {
return MCUtil.transformUnmodifiable(this.impl.customEffects(), CraftPotionUtil::toBukkit);
}
@Override
public @Nullable PotionType potion() {
return this.impl.potion()
.map(CraftPotionType::minecraftHolderToBukkit)
.orElse(null);
}
@Override
public @Nullable Color customColor() {
return this.impl.customColor()
.map(Color::fromARGB) // alpha channel is supported for tipped arrows, so let's just leave it in
.orElse(null);
}
@Override
public @Nullable String customName() {
return this.impl.customName().orElse(null);
}
static final class BuilderImpl implements PotionContents.Builder {
private final List<MobEffectInstance> customEffects = new ObjectArrayList<>();
private @Nullable PotionType type;
private @Nullable Color color;
private @Nullable String customName;
@Override
public PotionContents.Builder potion(final @Nullable PotionType type) {
this.type = type;
return this;
}
@Override
public PotionContents.Builder customColor(final @Nullable Color color) {
this.color = color;
return this;
}
@Override
public Builder customName(final @Nullable String name) {
Preconditions.checkArgument(name == null || name.length() <= Short.MAX_VALUE, "Custom name is longer than %s characters", Short.MAX_VALUE);
this.customName = name;
return this;
}
@Override
public PotionContents.Builder addCustomEffect(final PotionEffect effect) {
this.customEffects.add(CraftPotionUtil.fromBukkit(effect));
return this;
}
@Override
public PotionContents.Builder addCustomEffects(final List<PotionEffect> effects) {
effects.forEach(this::addCustomEffect);
return this;
}
@Override
public PotionContents build() {
if (this.type == null && this.color == null && this.customEffects.isEmpty() && this.customName == null) {
return new PaperPotionContents(net.minecraft.world.item.alchemy.PotionContents.EMPTY);
}
return new PaperPotionContents(new net.minecraft.world.item.alchemy.PotionContents(
Optional.ofNullable(this.type).map(CraftPotionType::bukkitToMinecraftHolder),
Optional.ofNullable(this.color).map(Color::asARGB),
new ObjectArrayList<>(this.customEffects),
Optional.ofNullable(this.customName)
));
}
}
}

View file

@ -0,0 +1,22 @@
package io.papermc.paper.datacomponent.item;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.set.PaperRegistrySets;
import io.papermc.paper.registry.set.RegistryKeySet;
import org.bukkit.craftbukkit.util.Handleable;
import org.bukkit.inventory.ItemType;
public record PaperRepairable(
net.minecraft.world.item.enchantment.Repairable impl
) implements Repairable, Handleable<net.minecraft.world.item.enchantment.Repairable> {
@Override
public net.minecraft.world.item.enchantment.Repairable getHandle() {
return this.impl;
}
@Override
public RegistryKeySet<ItemType> types() {
return PaperRegistrySets.convertToApi(RegistryKey.ITEM, this.impl.items());
}
}

View file

@ -0,0 +1,105 @@
package io.papermc.paper.datacomponent.item;
import com.destroystokyo.paper.profile.CraftPlayerProfile;
import com.destroystokyo.paper.profile.PlayerProfile;
import com.destroystokyo.paper.profile.ProfileProperty;
import com.google.common.base.Preconditions;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
import io.papermc.paper.util.MCUtil;
import java.util.Collection;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import net.minecraft.util.StringUtil;
import org.bukkit.craftbukkit.util.Handleable;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.Unmodifiable;
public record PaperResolvableProfile(
net.minecraft.world.item.component.ResolvableProfile impl
) implements ResolvableProfile, Handleable<net.minecraft.world.item.component.ResolvableProfile> {
static PaperResolvableProfile toApi(final PlayerProfile profile) {
return new PaperResolvableProfile(new net.minecraft.world.item.component.ResolvableProfile(CraftPlayerProfile.asAuthlibCopy(profile)));
}
@Override
public net.minecraft.world.item.component.ResolvableProfile getHandle() {
return this.impl;
}
@Override
public @Nullable UUID uuid() {
return this.impl.id().orElse(null);
}
@Override
public @Nullable String name() {
return this.impl.name().orElse(null);
}
@Override
public @Unmodifiable Collection<ProfileProperty> properties() {
return MCUtil.transformUnmodifiable(this.impl.properties().values(), input -> new ProfileProperty(input.name(), input.value(), input.signature()));
}
@Override
public CompletableFuture<PlayerProfile> resolve() {
return this.impl.resolve().thenApply(resolvableProfile -> CraftPlayerProfile.asBukkitCopy(resolvableProfile.gameProfile()));
}
static final class BuilderImpl implements ResolvableProfile.Builder {
private final PropertyMap propertyMap = new PropertyMap();
private @Nullable String name;
private @Nullable UUID uuid;
@Override
public ResolvableProfile.Builder name(final @Nullable String name) {
if (name != null) {
Preconditions.checkArgument(name.length() <= 16, "name cannot be more than 16 characters, was %s", name.length());
Preconditions.checkArgument(StringUtil.isValidPlayerName(name), "name cannot include invalid characters, was %s", name);
}
this.name = name;
return this;
}
@Override
public ResolvableProfile.Builder uuid(final @Nullable UUID uuid) {
this.uuid = uuid;
return this;
}
@Override
public ResolvableProfile.Builder addProperty(final ProfileProperty property) {
// ProfileProperty constructor already has specific validations
final Property newProperty = new Property(property.getName(), property.getValue(), property.getSignature());
if (!this.propertyMap.containsEntry(property.getName(), newProperty)) { // underlying map is a multimap that doesn't allow duplicate key-value pair
final int newSize = this.propertyMap.size() + 1;
Preconditions.checkArgument(newSize <= 16, "Cannot have more than 16 properties, was %s", newSize);
}
this.propertyMap.put(property.getName(), newProperty);
return this;
}
@Override
public ResolvableProfile.Builder addProperties(final Collection<ProfileProperty> properties) {
properties.forEach(this::addProperty);
return this;
}
@Override
public ResolvableProfile build() {
final PropertyMap shallowCopy = new PropertyMap();
shallowCopy.putAll(this.propertyMap);
return new PaperResolvableProfile(new net.minecraft.world.item.component.ResolvableProfile(
Optional.ofNullable(this.name),
Optional.ofNullable(this.uuid),
shallowCopy
));
}
}
}

View file

@ -0,0 +1,59 @@
package io.papermc.paper.datacomponent.item;
import io.papermc.paper.adventure.PaperAdventure;
import net.kyori.adventure.key.Key;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.storage.loot.LootTable;
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
import org.bukkit.craftbukkit.util.Handleable;
public record PaperSeededContainerLoot(
net.minecraft.world.item.component.SeededContainerLoot impl
) implements SeededContainerLoot, Handleable<net.minecraft.world.item.component.SeededContainerLoot> {
@Override
public net.minecraft.world.item.component.SeededContainerLoot getHandle() {
return this.impl;
}
@Override
public Key lootTable() {
return CraftNamespacedKey.fromMinecraft(this.impl.lootTable().location());
}
@Override
public long seed() {
return this.impl.seed();
}
static final class BuilderImpl implements SeededContainerLoot.Builder {
private long seed = LootTable.RANDOMIZE_SEED;
private Key key;
BuilderImpl(final Key key) {
this.key = key;
}
@Override
public SeededContainerLoot.Builder lootTable(final Key key) {
this.key = key;
return this;
}
@Override
public SeededContainerLoot.Builder seed(final long seed) {
this.seed = seed;
return this;
}
@Override
public SeededContainerLoot build() {
return new PaperSeededContainerLoot(new net.minecraft.world.item.component.SeededContainerLoot(
ResourceKey.create(Registries.LOOT_TABLE, PaperAdventure.asVanilla(this.key)),
this.seed
));
}
}
}

View file

@ -0,0 +1,58 @@
package io.papermc.paper.datacomponent.item;
import io.papermc.paper.potion.SuspiciousEffectEntry;
import io.papermc.paper.util.MCUtil;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.Collection;
import java.util.List;
import org.bukkit.craftbukkit.potion.CraftPotionEffectType;
import org.bukkit.craftbukkit.util.Handleable;
import org.jetbrains.annotations.Unmodifiable;
import static io.papermc.paper.potion.SuspiciousEffectEntry.create;
public record PaperSuspiciousStewEffects(
net.minecraft.world.item.component.SuspiciousStewEffects impl
) implements SuspiciousStewEffects, Handleable<net.minecraft.world.item.component.SuspiciousStewEffects> {
@Override
public net.minecraft.world.item.component.SuspiciousStewEffects getHandle() {
return this.impl;
}
@Override
public @Unmodifiable List<SuspiciousEffectEntry> effects() {
return MCUtil.transformUnmodifiable(this.impl.effects(), entry -> create(CraftPotionEffectType.minecraftHolderToBukkit(entry.effect()), entry.duration()));
}
static final class BuilderImpl implements Builder {
private final List<net.minecraft.world.item.component.SuspiciousStewEffects.Entry> effects = new ObjectArrayList<>();
@Override
public Builder add(final SuspiciousEffectEntry entry) {
this.effects.add(new net.minecraft.world.item.component.SuspiciousStewEffects.Entry(
org.bukkit.craftbukkit.potion.CraftPotionEffectType.bukkitToMinecraftHolder(entry.effect()),
entry.duration()
));
return this;
}
@Override
public Builder addAll(final Collection<SuspiciousEffectEntry> entries) {
entries.forEach(this::add);
return this;
}
@Override
public SuspiciousStewEffects build() {
if (this.effects.isEmpty()) {
return new PaperSuspiciousStewEffects(net.minecraft.world.item.component.SuspiciousStewEffects.EMPTY);
}
return new PaperSuspiciousStewEffects(
new net.minecraft.world.item.component.SuspiciousStewEffects(new ObjectArrayList<>(this.effects))
);
}
}
}

View file

@ -0,0 +1,39 @@
package io.papermc.paper.datacomponent.item;
import org.bukkit.craftbukkit.util.Handleable;
public record PaperUnbreakable(
net.minecraft.world.item.component.Unbreakable impl
) implements Unbreakable, Handleable<net.minecraft.world.item.component.Unbreakable> {
@Override
public boolean showInTooltip() {
return this.impl.showInTooltip();
}
@Override
public Unbreakable showInTooltip(final boolean showInTooltip) {
return new PaperUnbreakable(this.impl.withTooltip(showInTooltip));
}
@Override
public net.minecraft.world.item.component.Unbreakable getHandle() {
return this.impl;
}
static final class BuilderImpl implements Unbreakable.Builder {
private boolean showInTooltip = true;
@Override
public Unbreakable.Builder showInTooltip(final boolean showInTooltip) {
this.showInTooltip = showInTooltip;
return this;
}
@Override
public Unbreakable build() {
return new PaperUnbreakable(new net.minecraft.world.item.component.Unbreakable(this.showInTooltip));
}
}
}

View file

@ -0,0 +1,56 @@
package io.papermc.paper.datacomponent.item;
import io.papermc.paper.adventure.PaperAdventure;
import java.util.Optional;
import net.kyori.adventure.key.Key;
import net.minecraft.resources.ResourceLocation;
import org.bukkit.craftbukkit.util.Handleable;
import org.jspecify.annotations.Nullable;
public record PaperUseCooldown(
net.minecraft.world.item.component.UseCooldown impl
) implements UseCooldown, Handleable<net.minecraft.world.item.component.UseCooldown> {
@Override
public net.minecraft.world.item.component.UseCooldown getHandle() {
return this.impl;
}
@Override
public float seconds() {
return this.impl.seconds();
}
@Override
public @Nullable Key cooldownGroup() {
return this.impl.cooldownGroup()
.map(PaperAdventure::asAdventure)
.orElse(null);
}
static final class BuilderImpl implements Builder {
private final float seconds;
private Optional<ResourceLocation> cooldownGroup = Optional.empty();
BuilderImpl(final float seconds) {
this.seconds = seconds;
}
@Override
public Builder cooldownGroup(@Nullable final Key key) {
this.cooldownGroup = Optional.ofNullable(key)
.map(PaperAdventure::asVanilla);
return this;
}
@Override
public UseCooldown build() {
return new PaperUseCooldown(
new net.minecraft.world.item.component.UseCooldown(this.seconds, this.cooldownGroup)
);
}
}
}

View file

@ -0,0 +1,20 @@
package io.papermc.paper.datacomponent.item;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.craftbukkit.util.Handleable;
import org.bukkit.inventory.ItemStack;
public record PaperUseRemainder(
net.minecraft.world.item.component.UseRemainder impl
) implements UseRemainder, Handleable<net.minecraft.world.item.component.UseRemainder> {
@Override
public net.minecraft.world.item.component.UseRemainder getHandle() {
return this.impl;
}
@Override
public ItemStack transformInto() {
return CraftItemStack.asBukkitCopy(this.impl.convertInto());
}
}

View file

@ -0,0 +1,103 @@
package io.papermc.paper.datacomponent.item;
import com.google.common.base.Preconditions;
import io.papermc.paper.text.Filtered;
import io.papermc.paper.util.MCUtil;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.List;
import java.util.Optional;
import net.minecraft.server.network.Filterable;
import org.bukkit.craftbukkit.util.Handleable;
import org.jetbrains.annotations.Unmodifiable;
public record PaperWritableBookContent(
net.minecraft.world.item.component.WritableBookContent impl
) implements WritableBookContent, Handleable<net.minecraft.world.item.component.WritableBookContent> {
@Override
public net.minecraft.world.item.component.WritableBookContent getHandle() {
return this.impl;
}
@Override
public @Unmodifiable List<Filtered<String>> pages() {
return MCUtil.transformUnmodifiable(this.impl.pages(), input -> Filtered.of(input.raw(), input.filtered().orElse(null)));
}
static final class BuilderImpl implements WritableBookContent.Builder {
private final List<Filterable<String>> pages = new ObjectArrayList<>();
private static void validatePageLength(final String page) {
Preconditions.checkArgument(
page.length() <= net.minecraft.world.item.component.WritableBookContent.PAGE_EDIT_LENGTH,
"Cannot have page length more than %s, had %s",
net.minecraft.world.item.component.WritableBookContent.PAGE_EDIT_LENGTH,
page.length()
);
}
private static void validatePageCount(final int current, final int add) {
final int newSize = current + add;
Preconditions.checkArgument(
newSize <= net.minecraft.world.item.component.WritableBookContent.MAX_PAGES,
"Cannot have more than %s pages, had %s",
net.minecraft.world.item.component.WritableBookContent.MAX_PAGES,
newSize
);
}
@Override
public WritableBookContent.Builder addPage(final String page) {
validatePageLength(page);
validatePageCount(this.pages.size(), 1);
this.pages.add(Filterable.passThrough(page));
return this;
}
@Override
public WritableBookContent.Builder addPages(final List<String> pages) {
validatePageCount(this.pages.size(), pages.size());
for (final String page : pages) {
validatePageLength(page);
this.pages.add(Filterable.passThrough(page));
}
return this;
}
@Override
public WritableBookContent.Builder addFilteredPage(final Filtered<String> page) {
validatePageLength(page.raw());
if (page.filtered() != null) {
validatePageLength(page.filtered());
}
validatePageCount(this.pages.size(), 1);
this.pages.add(new Filterable<>(page.raw(), Optional.ofNullable(page.filtered())));
return this;
}
@Override
public WritableBookContent.Builder addFilteredPages(final List<Filtered<String>> pages) {
validatePageCount(this.pages.size(), pages.size());
for (final Filtered<String> page : pages) {
validatePageLength(page.raw());
if (page.filtered() != null) {
validatePageLength(page.filtered());
}
this.pages.add(new Filterable<>(page.raw(), Optional.ofNullable(page.filtered())));
}
return this;
}
@Override
public WritableBookContent build() {
if (this.pages.isEmpty()) {
return new PaperWritableBookContent(net.minecraft.world.item.component.WritableBookContent.EMPTY);
}
return new PaperWritableBookContent(
new net.minecraft.world.item.component.WritableBookContent(new ObjectArrayList<>(this.pages))
);
}
}
}

View file

@ -0,0 +1,183 @@
package io.papermc.paper.datacomponent.item;
import com.google.common.base.Preconditions;
import io.papermc.paper.adventure.PaperAdventure;
import io.papermc.paper.text.Filtered;
import io.papermc.paper.util.MCUtil;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.List;
import java.util.Optional;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minecraft.server.network.Filterable;
import net.minecraft.util.GsonHelper;
import org.bukkit.craftbukkit.util.Handleable;
import org.jetbrains.annotations.Unmodifiable;
import static io.papermc.paper.adventure.PaperAdventure.asAdventure;
import static io.papermc.paper.adventure.PaperAdventure.asVanilla;
public record PaperWrittenBookContent(
net.minecraft.world.item.component.WrittenBookContent impl
) implements WrittenBookContent, Handleable<net.minecraft.world.item.component.WrittenBookContent> {
@Override
public net.minecraft.world.item.component.WrittenBookContent getHandle() {
return this.impl;
}
@Override
public Filtered<String> title() {
return Filtered.of(this.impl.title().raw(), this.impl.title().filtered().orElse(null));
}
@Override
public String author() {
return this.impl.author();
}
@Override
public int generation() {
return this.impl.generation();
}
@Override
public @Unmodifiable List<Filtered<Component>> pages() {
return MCUtil.transformUnmodifiable(
this.impl.pages(),
page -> Filtered.of(asAdventure(page.raw()), page.filtered().map(PaperAdventure::asAdventure).orElse(null))
);
}
@Override
public boolean resolved() {
return this.impl.resolved();
}
static final class BuilderImpl implements WrittenBookContent.Builder {
private final List<Filterable<net.minecraft.network.chat.Component>> pages = new ObjectArrayList<>();
private Filterable<String> title;
private String author;
private int generation = 0;
private boolean resolved = false;
BuilderImpl(final Filtered<String> title, final String author) {
validateTitle(title.raw());
if (title.filtered() != null) {
validateTitle(title.filtered());
}
this.title = new Filterable<>(title.raw(), Optional.ofNullable(title.filtered()));
this.author = author;
}
private static void validateTitle(final String title) {
Preconditions.checkArgument(
title.length() <= net.minecraft.world.item.component.WrittenBookContent.TITLE_MAX_LENGTH,
"Title cannot be longer than %s, was %s",
net.minecraft.world.item.component.WrittenBookContent.TITLE_MAX_LENGTH,
title.length()
);
}
private static void validatePageLength(final Component page) {
final String flagPage = GsonHelper.toStableString(GsonComponentSerializer.gson().serializeToTree(page));
Preconditions.checkArgument(
flagPage.length() <= net.minecraft.world.item.component.WrittenBookContent.PAGE_LENGTH,
"Cannot have page length more than %s, had %s",
net.minecraft.world.item.component.WrittenBookContent.PAGE_LENGTH,
flagPage.length()
);
}
@Override
public WrittenBookContent.Builder title(final String title) {
validateTitle(title);
this.title = Filterable.passThrough(title);
return this;
}
@Override
public WrittenBookContent.Builder filteredTitle(final Filtered<String> title) {
validateTitle(title.raw());
if (title.filtered() != null) {
validateTitle(title.filtered());
}
this.title = new Filterable<>(title.raw(), Optional.ofNullable(title.filtered()));
return this;
}
@Override
public WrittenBookContent.Builder author(final String author) {
this.author = author;
return this;
}
@Override
public WrittenBookContent.Builder generation(final int generation) {
Preconditions.checkArgument(
generation >= 0 && generation <= net.minecraft.world.item.component.WrittenBookContent.MAX_GENERATION,
"generation must be between %s and %s, was %s",
0, net.minecraft.world.item.component.WrittenBookContent.MAX_GENERATION,
generation
);
this.generation = generation;
return this;
}
@Override
public WrittenBookContent.Builder resolved(final boolean resolved) {
this.resolved = resolved;
return this;
}
@Override
public WrittenBookContent.Builder addPage(final ComponentLike page) {
final Component component = page.asComponent();
validatePageLength(component);
this.pages.add(Filterable.passThrough(asVanilla(component)));
return this;
}
@Override
public WrittenBookContent.Builder addPages(final List<? extends ComponentLike> pages) {
for (final ComponentLike page : pages) {
final Component component = page.asComponent();
validatePageLength(component);
this.pages.add(Filterable.passThrough(asVanilla(component)));
}
return this;
}
@Override
public WrittenBookContent.Builder addFilteredPage(final Filtered<? extends ComponentLike> page) {
final Component raw = page.raw().asComponent();
validatePageLength(raw);
Component filtered = null;
if (page.filtered() != null) {
filtered = page.filtered().asComponent();
validatePageLength(filtered);
}
this.pages.add(new Filterable<>(asVanilla(raw), Optional.ofNullable(filtered).map(PaperAdventure::asVanilla)));
return this;
}
@Override
public WrittenBookContent.Builder addFilteredPages(final List<Filtered<? extends ComponentLike>> pages) {
pages.forEach(this::addFilteredPage);
return this;
}
@Override
public WrittenBookContent build() {
return new PaperWrittenBookContent(new net.minecraft.world.item.component.WrittenBookContent(
this.title,
this.author,
this.generation,
new ObjectArrayList<>(this.pages),
this.resolved
));
}
}
}

View file

@ -0,0 +1,64 @@
package io.papermc.paper.datacomponent.item.consumable;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import io.papermc.paper.adventure.PaperAdventure;
import io.papermc.paper.registry.set.PaperRegistrySets;
import io.papermc.paper.registry.set.RegistryKeySet;
import java.util.ArrayList;
import java.util.List;
import net.kyori.adventure.key.Key;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import org.bukkit.craftbukkit.potion.CraftPotionUtil;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
@ApiStatus.Internal
@NullMarked
public class ConsumableTypesBridgeImpl implements ConsumableTypesBridge {
@Override
public ConsumeEffect.ApplyStatusEffects applyStatusEffects(final List<PotionEffect> effectList, final float probability) {
Preconditions.checkArgument(0 <= probability && probability <= 1, "probability must be between 0-1, was %s", probability);
return new PaperApplyStatusEffects(
new net.minecraft.world.item.consume_effects.ApplyStatusEffectsConsumeEffect(
new ArrayList<>(Lists.transform(effectList, CraftPotionUtil::fromBukkit)),
probability
)
);
}
@Override
public ConsumeEffect.RemoveStatusEffects removeStatusEffects(final RegistryKeySet<PotionEffectType> effectTypes) {
return new PaperRemoveStatusEffects(
new net.minecraft.world.item.consume_effects.RemoveStatusEffectsConsumeEffect(
PaperRegistrySets.convertToNms(Registries.MOB_EFFECT, BuiltInRegistries.BUILT_IN_CONVERSIONS.lookup(), effectTypes)
)
);
}
@Override
public ConsumeEffect.ClearAllStatusEffects clearAllStatusEffects() {
return new PaperClearAllStatusEffects(
new net.minecraft.world.item.consume_effects.ClearAllStatusEffectsConsumeEffect()
);
}
@Override
public ConsumeEffect.PlaySound playSoundEffect(final Key sound) {
return new PaperPlaySound(
new net.minecraft.world.item.consume_effects.PlaySoundConsumeEffect(PaperAdventure.resolveSound(sound))
);
}
@Override
public ConsumeEffect.TeleportRandomly teleportRandomlyEffect(final float diameter) {
Preconditions.checkArgument(diameter > 0, "diameter must be positive, was %s", diameter);
return new PaperTeleportRandomly(
new net.minecraft.world.item.consume_effects.TeleportRandomlyConsumeEffect(diameter)
);
}
}

View file

@ -0,0 +1,28 @@
package io.papermc.paper.datacomponent.item.consumable;
import java.util.List;
import net.minecraft.world.item.consume_effects.ApplyStatusEffectsConsumeEffect;
import org.bukkit.craftbukkit.potion.CraftPotionUtil;
import org.bukkit.potion.PotionEffect;
import static io.papermc.paper.util.MCUtil.transformUnmodifiable;
public record PaperApplyStatusEffects(
ApplyStatusEffectsConsumeEffect impl
) implements ConsumeEffect.ApplyStatusEffects, PaperConsumableEffectImpl<ApplyStatusEffectsConsumeEffect> {
@Override
public List<PotionEffect> effects() {
return transformUnmodifiable(this.impl().effects(), CraftPotionUtil::toBukkit);
}
@Override
public float probability() {
return this.impl.probability();
}
@Override
public ApplyStatusEffectsConsumeEffect getHandle() {
return this.impl;
}
}

View file

@ -0,0 +1,11 @@
package io.papermc.paper.datacomponent.item.consumable;
public record PaperClearAllStatusEffects(
net.minecraft.world.item.consume_effects.ClearAllStatusEffectsConsumeEffect impl
) implements ConsumeEffect.ClearAllStatusEffects, PaperConsumableEffectImpl<net.minecraft.world.item.consume_effects.ClearAllStatusEffectsConsumeEffect> {
@Override
public net.minecraft.world.item.consume_effects.ClearAllStatusEffectsConsumeEffect getHandle() {
return this.impl;
}
}

View file

@ -0,0 +1,7 @@
package io.papermc.paper.datacomponent.item.consumable;
import net.minecraft.world.item.consume_effects.ConsumeEffect;
import org.bukkit.craftbukkit.util.Handleable;
public interface PaperConsumableEffectImpl<T extends ConsumeEffect> extends Handleable<T> {
}

View file

@ -0,0 +1,32 @@
package io.papermc.paper.datacomponent.item.consumable;
import net.minecraft.world.item.consume_effects.ApplyStatusEffectsConsumeEffect;
import net.minecraft.world.item.consume_effects.ClearAllStatusEffectsConsumeEffect;
import net.minecraft.world.item.consume_effects.PlaySoundConsumeEffect;
import net.minecraft.world.item.consume_effects.RemoveStatusEffectsConsumeEffect;
import net.minecraft.world.item.consume_effects.TeleportRandomlyConsumeEffect;
public final class PaperConsumableEffects {
private PaperConsumableEffects() {
}
public static ConsumeEffect fromNms(net.minecraft.world.item.consume_effects.ConsumeEffect consumable) {
return switch (consumable) {
case ApplyStatusEffectsConsumeEffect effect -> new PaperApplyStatusEffects(effect);
case ClearAllStatusEffectsConsumeEffect effect -> new PaperClearAllStatusEffects(effect);
case PlaySoundConsumeEffect effect -> new PaperPlaySound(effect);
case RemoveStatusEffectsConsumeEffect effect -> new PaperRemoveStatusEffects(effect);
case TeleportRandomlyConsumeEffect effect -> new PaperTeleportRandomly(effect);
default -> throw new UnsupportedOperationException("Don't know how to convert " + consumable.getClass());
};
}
public static net.minecraft.world.item.consume_effects.ConsumeEffect toNms(ConsumeEffect effect) {
if (effect instanceof PaperConsumableEffectImpl<?> consumableEffect) {
return consumableEffect.getHandle();
} else {
throw new UnsupportedOperationException("Must implement handleable!");
}
}
}

View file

@ -0,0 +1,20 @@
package io.papermc.paper.datacomponent.item.consumable;
import io.papermc.paper.adventure.PaperAdventure;
import net.kyori.adventure.key.Key;
import net.minecraft.world.item.consume_effects.PlaySoundConsumeEffect;
public record PaperPlaySound(
PlaySoundConsumeEffect impl
) implements ConsumeEffect.PlaySound, PaperConsumableEffectImpl<PlaySoundConsumeEffect> {
@Override
public Key sound() {
return PaperAdventure.asAdventure(this.impl.sound().value().location());
}
@Override
public PlaySoundConsumeEffect getHandle() {
return this.impl;
}
}

View file

@ -0,0 +1,21 @@
package io.papermc.paper.datacomponent.item.consumable;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.set.PaperRegistrySets;
import io.papermc.paper.registry.set.RegistryKeySet;
import org.bukkit.potion.PotionEffectType;
public record PaperRemoveStatusEffects(
net.minecraft.world.item.consume_effects.RemoveStatusEffectsConsumeEffect impl
) implements ConsumeEffect.RemoveStatusEffects, PaperConsumableEffectImpl<net.minecraft.world.item.consume_effects.RemoveStatusEffectsConsumeEffect> {
@Override
public RegistryKeySet<PotionEffectType> removeEffects() {
return PaperRegistrySets.convertToApi(RegistryKey.MOB_EFFECT, this.impl.effects());
}
@Override
public net.minecraft.world.item.consume_effects.RemoveStatusEffectsConsumeEffect getHandle() {
return this.impl;
}
}

View file

@ -0,0 +1,15 @@
package io.papermc.paper.datacomponent.item.consumable;
public record PaperTeleportRandomly(
net.minecraft.world.item.consume_effects.TeleportRandomlyConsumeEffect impl
) implements ConsumeEffect.TeleportRandomly, PaperConsumableEffectImpl<net.minecraft.world.item.consume_effects.TeleportRandomlyConsumeEffect> {
@Override
public float diameter() {
return this.impl.diameter();
}
@Override
public net.minecraft.world.item.consume_effects.TeleportRandomlyConsumeEffect getHandle() {
return this.impl;
}
}

View file

@ -0,0 +1,7 @@
/**
* Relating to consumable effects for components.
*/
@NullMarked
package io.papermc.paper.datacomponent.item.consumable;
import org.jspecify.annotations.NullMarked;

View file

@ -0,0 +1,4 @@
@NullMarked
package io.papermc.paper.datacomponent.item;
import org.jspecify.annotations.NullMarked;

View file

@ -0,0 +1,4 @@
@NullMarked
package io.papermc.paper.datacomponent;
import org.jspecify.annotations.NullMarked;

View file

@ -2,6 +2,8 @@ package io.papermc.paper.registry;
import com.google.common.base.Preconditions;
import io.papermc.paper.adventure.PaperAdventure;
import io.papermc.paper.datacomponent.DataComponentTypes;
import io.papermc.paper.datacomponent.PaperDataComponentType;
import io.papermc.paper.registry.data.PaperEnchantmentRegistryEntry;
import io.papermc.paper.registry.data.PaperGameEventRegistryEntry;
import io.papermc.paper.registry.data.PaperPaintingVariantRegistryEntry;
@ -93,6 +95,7 @@ public final class PaperRegistries {
start(Registries.ATTRIBUTE, RegistryKey.ATTRIBUTE).craft(Attribute.class, CraftAttribute::new).build(),
start(Registries.FLUID, RegistryKey.FLUID).craft(Fluid.class, CraftFluid::new).build(),
start(Registries.SOUND_EVENT, RegistryKey.SOUND_EVENT).craft(Sound.class, CraftSound::new).build(),
start(Registries.DATA_COMPONENT_TYPE, RegistryKey.DATA_COMPONENT_TYPE).craft(DataComponentTypes.class, PaperDataComponentType::of).build(),
// data-drivens
start(Registries.BIOME, RegistryKey.BIOME).craft(Biome.class, CraftBiome::new).build().delayed(),

View file

@ -20,13 +20,11 @@ import net.minecraft.world.item.enchantment.ItemEnchantments;
import org.bukkit.Material;
import org.bukkit.configuration.serialization.DelegateDeserialization;
import org.bukkit.craftbukkit.enchantments.CraftEnchantment;
import org.bukkit.craftbukkit.util.CraftLegacy;
import org.bukkit.craftbukkit.util.CraftMagicNumbers;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.material.MaterialData;
import org.jetbrains.annotations.ApiStatus;
@DelegateDeserialization(ItemStack.class)
public final class CraftItemStack extends ItemStack {
@ -206,7 +204,7 @@ public final class CraftItemStack extends ItemStack {
this.adjustTagForItemMeta(oldType); // Paper
}
}
this.setData(null);
this.setData((MaterialData) null); // Paper
}
@Override
@ -245,7 +243,7 @@ public final class CraftItemStack extends ItemStack {
@Override
public int getMaxStackSize() {
return (this.handle == null) ? Material.AIR.getMaxStackSize() : this.handle.getMaxStackSize();
return (this.handle == null) ? Item.DEFAULT_MAX_STACK_SIZE : this.handle.getMaxStackSize(); // Paper - air stacks to 64
}
// Paper start
@ -267,12 +265,14 @@ public final class CraftItemStack extends ItemStack {
public void addUnsafeEnchantment(Enchantment ench, int level) {
Preconditions.checkArgument(ench != null, "Enchantment cannot be null");
// Paper start - Replace whole method
final ItemMeta itemMeta = this.getItemMeta();
if (itemMeta != null) {
itemMeta.addEnchant(ench, level, true);
this.setItemMeta(itemMeta);
// Paper start
if (this.handle == null) {
return;
}
EnchantmentHelper.updateEnchantments(this.handle, mutable -> { // data component api doesn't really support mutable things once already set yet
mutable.set(CraftEnchantment.bukkitToMinecraftHolder(ench), level);
});
// Paper end
}
@ -302,17 +302,28 @@ public final class CraftItemStack extends ItemStack {
public int removeEnchantment(Enchantment ench) {
Preconditions.checkArgument(ench != null, "Enchantment cannot be null");
// Paper start - replace entire method
int level = getEnchantmentLevel(ench);
if (level > 0) {
final ItemMeta itemMeta = this.getItemMeta();
if (itemMeta == null) return 0;
itemMeta.removeEnchant(ench);
this.setItemMeta(itemMeta);
// Paper start
if (this.handle == null) {
return 0;
}
// Paper end
return level;
ItemEnchantments itemEnchantments = this.handle.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY);
if (itemEnchantments.isEmpty()) {
return 0;
}
Holder<net.minecraft.world.item.enchantment.Enchantment> removedEnchantment = CraftEnchantment.bukkitToMinecraftHolder(ench);
if (itemEnchantments.keySet().contains(removedEnchantment)) {
int previousLevel = itemEnchantments.getLevel(removedEnchantment);
ItemEnchantments.Mutable mutable = new ItemEnchantments.Mutable(itemEnchantments); // data component api doesn't really support mutable things once already set yet
mutable.removeIf(enchantment -> enchantment.equals(removedEnchantment));
this.handle.set(DataComponents.ENCHANTMENTS, mutable.toImmutable());
return previousLevel;
}
return 0;
// Paper end
}
@Override
@ -324,7 +335,13 @@ public final class CraftItemStack extends ItemStack {
@Override
public Map<Enchantment, Integer> getEnchantments() {
return this.hasItemMeta() ? this.getItemMeta().getEnchants() : ImmutableMap.<Enchantment, Integer>of(); // Paper - use Item Meta
// Paper start
io.papermc.paper.datacomponent.item.ItemEnchantments itemEnchantments = this.getData(io.papermc.paper.datacomponent.DataComponentTypes.ENCHANTMENTS); // empty constant might be useful here
if (itemEnchantments == null) {
return java.util.Collections.emptyMap();
}
return itemEnchantments.enchantments();
// Paper end
}
static Map<Enchantment, Integer> getEnchantments(net.minecraft.world.item.ItemStack item) {
@ -526,4 +543,119 @@ public final class CraftItemStack extends ItemStack {
return this.pdcView;
}
// Paper end - pdc
// Paper start - data component API
@Override
public <T> T getData(final io.papermc.paper.datacomponent.DataComponentType.Valued<T> type) {
if (this.isEmpty()) {
return null;
}
return io.papermc.paper.datacomponent.PaperDataComponentType.convertDataComponentValue(this.handle.getComponents(), (io.papermc.paper.datacomponent.PaperDataComponentType.ValuedImpl<T, ?>) type);
}
@Override
public boolean hasData(final io.papermc.paper.datacomponent.DataComponentType type) {
if (this.isEmpty()) {
return false;
}
return this.handle.has(io.papermc.paper.datacomponent.PaperDataComponentType.bukkitToMinecraft(type));
}
@Override
public java.util.Set<io.papermc.paper.datacomponent.DataComponentType> getDataTypes() {
if (this.isEmpty()) {
return java.util.Collections.emptySet();
}
return io.papermc.paper.datacomponent.PaperDataComponentType.minecraftToBukkit(this.handle.getComponents().keySet());
}
@Override
public <T> void setData(final io.papermc.paper.datacomponent.DataComponentType.Valued<T> type, final T value) {
Preconditions.checkArgument(value != null, "value cannot be null");
if (this.isEmpty()) {
return;
}
this.setDataInternal((io.papermc.paper.datacomponent.PaperDataComponentType.ValuedImpl<T, ?>) type, value);
}
@Override
public void setData(final io.papermc.paper.datacomponent.DataComponentType.NonValued type) {
if (this.isEmpty()) {
return;
}
this.setDataInternal((io.papermc.paper.datacomponent.PaperDataComponentType.NonValuedImpl<?, ?>) type, null);
}
private <A, V> void setDataInternal(final io.papermc.paper.datacomponent.PaperDataComponentType<A, V> type, final A value) {
this.handle.set(type.getHandle(), type.getAdapter().toVanilla(value));
}
@Override
public void unsetData(final io.papermc.paper.datacomponent.DataComponentType type) {
if (this.isEmpty()) {
return;
}
this.handle.remove(io.papermc.paper.datacomponent.PaperDataComponentType.bukkitToMinecraft(type));
}
@Override
public void resetData(final io.papermc.paper.datacomponent.DataComponentType type) {
if (this.isEmpty()) {
return;
}
this.resetData((io.papermc.paper.datacomponent.PaperDataComponentType<?, ?>) type);
}
private <M> void resetData(final io.papermc.paper.datacomponent.PaperDataComponentType<?, M> type) {
final net.minecraft.core.component.DataComponentType<M> nms = io.papermc.paper.datacomponent.PaperDataComponentType.bukkitToMinecraft(type);
final M nmsValue = this.handle.getItem().components().get(nms);
// if nmsValue is null, it will clear any set patch
// if nmsValue is not null, it will still clear any set patch because it will equal the default value
this.handle.set(nms, nmsValue);
}
@Override
public boolean isDataOverridden(final io.papermc.paper.datacomponent.DataComponentType type) {
if (this.isEmpty()) {
return false;
}
final net.minecraft.core.component.DataComponentType<?> nms = io.papermc.paper.datacomponent.PaperDataComponentType.bukkitToMinecraft(type);
// maybe a more efficient way is to expose the "patch" map in PatchedDataComponentMap and just check if the type exists as a key
return !java.util.Objects.equals(this.handle.get(nms), this.handle.getPrototype().get(nms));
}
@Override
public boolean matchesWithoutData(final ItemStack item, final java.util.Set<io.papermc.paper.datacomponent.DataComponentType> exclude, final boolean ignoreCount) {
// Extracted from base equals
final CraftItemStack craftStack = getCraftStack(item);
if (this.handle == craftStack.handle) return true;
if (this.handle == null || craftStack.handle == null) return false;
if (this.handle.isEmpty() && craftStack.handle.isEmpty()) return true;
net.minecraft.world.item.ItemStack left = this.handle;
net.minecraft.world.item.ItemStack right = craftStack.handle;
if (!ignoreCount && left.getCount() != right.getCount()) {
return false;
}
if (!left.is(right.getItem())) {
return false;
}
// It can be assumed that the prototype is equal since the type is the same. This way all we need to check is the patch
// Fast path when excluded types is empty
if (exclude.isEmpty()) {
return left.getComponentsPatch().equals(right.getComponentsPatch());
}
// Collect all the NMS types into a set
java.util.Set<net.minecraft.core.component.DataComponentType<?>> skippingTypes = new java.util.HashSet<>(exclude.size());
for (io.papermc.paper.datacomponent.DataComponentType api : exclude) {
skippingTypes.add(io.papermc.paper.datacomponent.PaperDataComponentType.bukkitToMinecraft(api));
}
// Check the patch by first stripping excluded types and then compare the trimmed patches
return left.getComponentsPatch().forget(skippingTypes::contains).equals(right.getComponentsPatch().forget(skippingTypes::contains));
}
// Paper end - data component API
}

View file

@ -150,7 +150,7 @@ public class CraftItemType<M extends ItemMeta> implements ItemType.Typed<M>, Han
public int getMaxStackSize() {
// Based of the material enum air is only 0, in PerMaterialTest it is also set as special case
// the item info itself would return 64
if (this == AIR) {
if (false && this == AIR) { // Paper - air stacks to 64
return 0;
}
return this.item.components().getOrDefault(DataComponents.MAX_STACK_SIZE, 64);
@ -270,4 +270,20 @@ public class CraftItemType<M extends ItemMeta> implements ItemType.Typed<M>, Han
return rarity == null ? null : org.bukkit.inventory.ItemRarity.valueOf(rarity.name());
}
// Paper end - expand ItemRarity API
// Paper start - data component API
@Override
public <T> T getDefaultData(final io.papermc.paper.datacomponent.DataComponentType.Valued<T> type) {
return io.papermc.paper.datacomponent.PaperDataComponentType.convertDataComponentValue(this.item.components(), ((io.papermc.paper.datacomponent.PaperDataComponentType.ValuedImpl<T, ?>) type));
}
@Override
public boolean hasDefaultData(final io.papermc.paper.datacomponent.DataComponentType type) {
return this.item.components().has(io.papermc.paper.datacomponent.PaperDataComponentType.bukkitToMinecraft(type));
}
@Override
public java.util.Set<io.papermc.paper.datacomponent.DataComponentType> getDefaultDataTypes() {
return io.papermc.paper.datacomponent.PaperDataComponentType.minecraftToBukkit(this.item.components().keySet());
}
// Paper end - data component API
}

View file

@ -91,7 +91,7 @@ class CraftMetaFirework extends CraftMetaItem implements FireworkMeta {
this.safelyAddEffects(effects, false); // Paper - limit firework effects
}
static FireworkEffect getEffect(FireworkExplosion explosion) {
public static FireworkEffect getEffect(FireworkExplosion explosion) { // Paper
FireworkEffect.Builder effect = FireworkEffect.builder()
.flicker(explosion.hasTwinkle())
.trail(explosion.hasTrail())
@ -111,7 +111,7 @@ class CraftMetaFirework extends CraftMetaItem implements FireworkMeta {
return effect.build();
}
static FireworkExplosion getExplosion(FireworkEffect effect) {
public static FireworkExplosion getExplosion(FireworkEffect effect) { // Paper
IntList colors = CraftMetaFirework.addColors(effect.getColors());
IntList fadeColors = CraftMetaFirework.addColors(effect.getFadeColors());

View file

@ -0,0 +1 @@
io.papermc.paper.datacomponent.item.ItemComponentTypesBridgesImpl

View file

@ -0,0 +1 @@
io.papermc.paper.datacomponent.item.consumable.ConsumableTypesBridgeImpl

View file

@ -0,0 +1,58 @@
package io.papermc.paper.datacomponent;
import com.google.common.collect.Collections2;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
import org.bukkit.support.RegistryHelper;
import org.bukkit.support.environment.AllFeatures;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@AllFeatures
public class DataComponentTypesTest {
private static final Set<ResourceLocation> NOT_IN_API = Set.of(
ResourceLocation.parse("custom_data"),
ResourceLocation.parse("entity_data"),
ResourceLocation.parse("bees"),
ResourceLocation.parse("debug_stick_state"),
ResourceLocation.parse("block_entity_data"),
ResourceLocation.parse("bucket_entity_data"),
ResourceLocation.parse("lock"),
ResourceLocation.parse("creative_slot_lock")
);
@Test
public void testAllDataComponentsAreMapped() throws IllegalAccessException {
final Set<ResourceLocation> vanillaDataComponentTypes = new ObjectOpenHashSet<>(
RegistryHelper.getRegistry()
.lookupOrThrow(Registries.DATA_COMPONENT_TYPE)
.keySet()
);
for (final Field declaredField : DataComponentTypes.class.getDeclaredFields()) {
if (!DataComponentType.class.isAssignableFrom(declaredField.getType())) continue;
final DataComponentType dataComponentType = (DataComponentType) declaredField.get(null);
if (!vanillaDataComponentTypes.remove(CraftNamespacedKey.toMinecraft(dataComponentType.getKey()))) {
Assertions.fail("API defined component type " + dataComponentType.key().asMinimalString() + " is unknown to vanilla registry");
}
}
if (!vanillaDataComponentTypes.containsAll(NOT_IN_API)) {
Assertions.fail("API defined data components that were marked as not-yet-implemented: " + NOT_IN_API.stream().filter(Predicate.not(vanillaDataComponentTypes::contains)).map(ResourceLocation::toString).collect(Collectors.joining(", ")));
}
vanillaDataComponentTypes.removeAll(NOT_IN_API);
if (!vanillaDataComponentTypes.isEmpty()) {
Assertions.fail("API did not define following vanilla data component types: " + String.join(", ", Collections2.transform(vanillaDataComponentTypes, ResourceLocation::toString)));
}
}
}

View file

@ -0,0 +1,92 @@
package io.papermc.paper.item;
import io.papermc.paper.datacomponent.DataComponentTypes;
import java.util.Set;
import net.kyori.adventure.text.Component;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.support.environment.AllFeatures;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@AllFeatures
class ItemStackDataComponentEqualsTest {
@Test
public void testEqual() {
ItemStack item1 = ItemStack.of(Material.STONE, 1);
item1.setData(DataComponentTypes.MAX_STACK_SIZE, 32);
item1.setData(DataComponentTypes.ITEM_NAME, Component.text("HI"));
ItemStack item2 = ItemStack.of(Material.STONE, 1);
item2.setData(DataComponentTypes.MAX_STACK_SIZE, 32);
item2.setData(DataComponentTypes.ITEM_NAME, Component.text("HI"));
Assertions.assertTrue(item1.matchesWithoutData(item2, Set.of()));
}
@Test
public void testEqualIgnoreComponent() {
ItemStack item1 = ItemStack.of(Material.STONE, 2);
item1.setData(DataComponentTypes.MAX_STACK_SIZE, 1);
ItemStack item2 = ItemStack.of(Material.STONE, 1);
item2.setData(DataComponentTypes.MAX_STACK_SIZE, 2);
Assertions.assertFalse(item1.matchesWithoutData(item2, Set.of(DataComponentTypes.MAX_STACK_SIZE)));
}
@Test
public void testEqualIgnoreComponentAndSize() {
ItemStack item1 = ItemStack.of(Material.STONE, 2);
item1.setData(DataComponentTypes.MAX_STACK_SIZE, 1);
ItemStack item2 = ItemStack.of(Material.STONE, 1);
item2.setData(DataComponentTypes.MAX_STACK_SIZE, 2);
Assertions.assertTrue(item1.matchesWithoutData(item2, Set.of(DataComponentTypes.MAX_STACK_SIZE), true));
}
@Test
public void testEqualWithoutComponent() {
ItemStack item1 = ItemStack.of(Material.STONE, 1);
ItemStack item2 = ItemStack.of(Material.STONE, 1);
item2.setData(DataComponentTypes.MAX_STACK_SIZE, 2);
Assertions.assertFalse(item1.matchesWithoutData(item2, Set.of(DataComponentTypes.WRITTEN_BOOK_CONTENT)));
}
@Test
public void testEqualRemoveComponent() {
ItemStack item1 = ItemStack.of(Material.STONE, 1);
item1.unsetData(DataComponentTypes.MAX_STACK_SIZE);
ItemStack item2 = ItemStack.of(Material.STONE, 1);
item2.unsetData(DataComponentTypes.MAX_STACK_SIZE);
Assertions.assertTrue(item1.matchesWithoutData(item2, Set.of()));
}
@Test
public void testEqualIncludeComponentIgnoreSize() {
ItemStack item1 = ItemStack.of(Material.STONE, 2);
item1.setData(DataComponentTypes.MAX_STACK_SIZE, 1);
ItemStack item2 = ItemStack.of(Material.STONE, 1);
item2.setData(DataComponentTypes.MAX_STACK_SIZE, 1);
Assertions.assertTrue(item1.matchesWithoutData(item2, Set.of(), true));
}
@Test
public void testAdvancedExample() {
ItemStack oakLeaves = ItemStack.of(Material.OAK_LEAVES, 1);
oakLeaves.setData(DataComponentTypes.HIDE_TOOLTIP);
oakLeaves.setData(DataComponentTypes.MAX_STACK_SIZE, 1);
ItemStack otherOakLeavesItem = ItemStack.of(Material.OAK_LEAVES, 2);
Assertions.assertTrue(oakLeaves.matchesWithoutData(otherOakLeavesItem, Set.of(DataComponentTypes.HIDE_TOOLTIP, DataComponentTypes.MAX_STACK_SIZE), true));
}
}

View file

@ -0,0 +1,416 @@
package io.papermc.paper.item;
import io.papermc.paper.datacomponent.DataComponentType;
import io.papermc.paper.datacomponent.DataComponentTypes;
import io.papermc.paper.datacomponent.item.ChargedProjectiles;
import io.papermc.paper.datacomponent.item.CustomModelData;
import io.papermc.paper.datacomponent.item.DyedItemColor;
import io.papermc.paper.datacomponent.item.Fireworks;
import io.papermc.paper.datacomponent.item.FoodProperties;
import io.papermc.paper.datacomponent.item.ItemArmorTrim;
import io.papermc.paper.datacomponent.item.ItemAttributeModifiers;
import io.papermc.paper.datacomponent.item.ItemEnchantments;
import io.papermc.paper.datacomponent.item.ItemLore;
import io.papermc.paper.datacomponent.item.JukeboxPlayable;
import io.papermc.paper.datacomponent.item.MapId;
import io.papermc.paper.datacomponent.item.MapItemColor;
import io.papermc.paper.datacomponent.item.PotDecorations;
import io.papermc.paper.datacomponent.item.Tool;
import io.papermc.paper.datacomponent.item.Unbreakable;
import io.papermc.paper.registry.RegistryAccess;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.set.RegistrySet;
import io.papermc.paper.registry.tag.TagKey;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Function;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.util.TriState;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.item.EitherHolder;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.JukeboxSongs;
import org.bukkit.Color;
import org.bukkit.FireworkEffect;
import org.bukkit.JukeboxSong;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeModifier;
import org.bukkit.block.BlockState;
import org.bukkit.block.BlockType;
import org.bukkit.block.DecoratedPot;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.EquipmentSlotGroup;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemRarity;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.ItemType;
import org.bukkit.inventory.meta.ArmorMeta;
import org.bukkit.inventory.meta.BlockStateMeta;
import org.bukkit.inventory.meta.CrossbowMeta;
import org.bukkit.inventory.meta.Damageable;
import org.bukkit.inventory.meta.FireworkMeta;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.KnowledgeBookMeta;
import org.bukkit.inventory.meta.LeatherArmorMeta;
import org.bukkit.inventory.meta.MapMeta;
import org.bukkit.inventory.meta.Repairable;
import org.bukkit.inventory.meta.components.FoodComponent;
import org.bukkit.inventory.meta.components.JukeboxPlayableComponent;
import org.bukkit.inventory.meta.components.ToolComponent;
import org.bukkit.inventory.meta.trim.ArmorTrim;
import org.bukkit.inventory.meta.trim.TrimMaterial;
import org.bukkit.inventory.meta.trim.TrimPattern;
import org.bukkit.support.RegistryHelper;
import org.bukkit.support.environment.AllFeatures;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@AllFeatures
class ItemStackDataComponentTest {
@Test
void testMaxStackSize() {
testWithMeta(new ItemStack(Material.STONE), DataComponentTypes.MAX_STACK_SIZE, 32, ItemMeta.class, ItemMeta::getMaxStackSize, ItemMeta::setMaxStackSize);
}
@Test
void testMaxDamage() {
testWithMeta(new ItemStack(Material.STONE), DataComponentTypes.MAX_DAMAGE, 120, Damageable.class, Damageable::getMaxDamage, Damageable::setMaxDamage);
}
@Test
void testDamage() {
testWithMeta(new ItemStack(Material.STONE), DataComponentTypes.DAMAGE, 120, Damageable.class, Damageable::getDamage, Damageable::setDamage);
}
@Test
void testUnbreakable() {
final ItemStack stack = new ItemStack(Material.STONE);
stack.setData(DataComponentTypes.UNBREAKABLE, Unbreakable.unbreakable().showInTooltip(false).build());
Assertions.assertTrue(stack.getItemMeta().isUnbreakable());
Assertions.assertTrue(stack.getItemMeta().getItemFlags().contains(ItemFlag.HIDE_UNBREAKABLE));
stack.unsetData(DataComponentTypes.UNBREAKABLE);
Assertions.assertFalse(stack.getItemMeta().isUnbreakable());
}
@Test
void testHideAdditionalTooltip() {
final ItemStack stack = new ItemStack(Material.STONE);
stack.setData(DataComponentTypes.HIDE_ADDITIONAL_TOOLTIP);
Assertions.assertTrue(stack.getItemMeta().getItemFlags().contains(ItemFlag.HIDE_ADDITIONAL_TOOLTIP));
stack.unsetData(DataComponentTypes.HIDE_ADDITIONAL_TOOLTIP);
Assertions.assertFalse(stack.getItemMeta().getItemFlags().contains(ItemFlag.HIDE_ADDITIONAL_TOOLTIP));
}
@Test
void testHideTooltip() {
ItemStack stack = new ItemStack(Material.STONE);
stack.setData(DataComponentTypes.HIDE_TOOLTIP);
Assertions.assertEquals(stack.getItemMeta().isHideTooltip(), stack.hasData(DataComponentTypes.HIDE_TOOLTIP));
Assertions.assertTrue(stack.getItemMeta().isHideTooltip());
stack.unsetData(DataComponentTypes.HIDE_TOOLTIP);
Assertions.assertFalse(stack.getItemMeta().isHideTooltip());
stack = new ItemStack(Material.STONE);
stack.unsetData(DataComponentTypes.HIDE_TOOLTIP);
Assertions.assertFalse(stack.getItemMeta().isHideTooltip());
Assertions.assertEquals(stack.getItemMeta().isHideTooltip(), stack.hasData(DataComponentTypes.HIDE_TOOLTIP));
}
@Test
void testRepairCost() {
final ItemStack stack = new ItemStack(Material.STONE);
testWithMeta(stack, DataComponentTypes.REPAIR_COST, 120, Repairable.class, Repairable::getRepairCost, Repairable::setRepairCost);
}
@Test
void testCustomName() {
testWithMeta(new ItemStack(Material.STONE), DataComponentTypes.CUSTOM_NAME, Component.text("HELLO!!!!!!"), ItemMeta.class, ItemMeta::displayName, ItemMeta::displayName);
}
@Test
void testItemName() {
testWithMeta(new ItemStack(Material.STONE), DataComponentTypes.ITEM_NAME, Component.text("HELLO!!!!!! ITEM NAME"), ItemMeta.class, ItemMeta::itemName, ItemMeta::itemName);
}
@Test
void testItemLore() {
List<Component> list = List.of(Component.text("1"), Component.text("2"));
testWithMeta(new ItemStack(Material.STONE), DataComponentTypes.LORE, ItemLore.lore().lines(list).build(), ItemLore::lines, ItemMeta.class, ItemMeta::lore, ItemMeta::lore);
}
@Test
void testItemRarity() {
testWithMeta(new ItemStack(Material.STONE), DataComponentTypes.RARITY, ItemRarity.RARE, ItemMeta.class, ItemMeta::getRarity, ItemMeta::setRarity);
}
@Test
void testItemEnchantments() {
final ItemStack stack = new ItemStack(Material.STONE);
Map<Enchantment, Integer> enchantmentIntegerMap = Map.of(Enchantment.SOUL_SPEED, 1);
stack.setData(DataComponentTypes.ENCHANTMENTS, ItemEnchantments.itemEnchantments(enchantmentIntegerMap, false));
Assertions.assertTrue(stack.getItemMeta().hasItemFlag(ItemFlag.HIDE_ENCHANTS));
Assertions.assertEquals(1, stack.getItemMeta().getEnchantLevel(Enchantment.SOUL_SPEED));
Assertions.assertEquals(stack.getItemMeta().getEnchants(), enchantmentIntegerMap);
stack.unsetData(DataComponentTypes.ENCHANTMENTS);
Assertions.assertTrue(stack.getItemMeta().getEnchants().isEmpty());
}
@Test
void testItemAttributes() {
final ItemStack stack = new ItemStack(Material.STONE);
AttributeModifier modifier = new AttributeModifier(NamespacedKey.minecraft("test"), 5, AttributeModifier.Operation.ADD_NUMBER, EquipmentSlotGroup.ANY);
stack.setData(DataComponentTypes.ATTRIBUTE_MODIFIERS, ItemAttributeModifiers.itemAttributes().showInTooltip(false).addModifier(Attribute.ATTACK_DAMAGE, modifier).build());
Assertions.assertTrue(stack.getItemMeta().hasItemFlag(ItemFlag.HIDE_ATTRIBUTES));
Assertions.assertEquals(modifier, ((List<AttributeModifier>) stack.getItemMeta().getAttributeModifiers(Attribute.ATTACK_DAMAGE)).getFirst());
stack.unsetData(DataComponentTypes.ATTRIBUTE_MODIFIERS);
Assertions.assertNull(stack.getItemMeta().getAttributeModifiers());
}
@Test
void testLegacyCustomModelData() {
testWithMeta(new ItemStack(Material.STONE), DataComponentTypes.CUSTOM_MODEL_DATA, CustomModelData.customModelData().addFloat(1).build(), customModelData -> customModelData.floats().get(0).intValue(), ItemMeta.class, ItemMeta::getCustomModelData, ItemMeta::setCustomModelData);
}
@Test
void testEnchantmentGlintOverride() {
testWithMeta(new ItemStack(Material.STONE), DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, true, ItemMeta.class, ItemMeta::getEnchantmentGlintOverride, ItemMeta::setEnchantmentGlintOverride);
}
@Test
void testFood() {
FoodProperties properties = FoodProperties.food()
.canAlwaysEat(true)
.saturation(1.3F)
.nutrition(1)
.build();
final ItemStack stack = new ItemStack(Material.CROSSBOW);
stack.setData(DataComponentTypes.FOOD, properties);
ItemMeta meta = stack.getItemMeta();
FoodComponent component = meta.getFood();
Assertions.assertEquals(properties.canAlwaysEat(), component.canAlwaysEat());
Assertions.assertEquals(properties.saturation(), component.getSaturation());
Assertions.assertEquals(properties.nutrition(), component.getNutrition());
stack.unsetData(DataComponentTypes.FOOD);
meta = stack.getItemMeta();
Assertions.assertFalse(meta.hasFood());
}
@Test
void testTool() {
Tool properties = Tool.tool()
.damagePerBlock(1)
.defaultMiningSpeed(2F)
.addRules(List.of(
Tool.rule(
RegistrySet.keySetFromValues(RegistryKey.BLOCK, List.of(BlockType.STONE, BlockType.GRAVEL)),
2F,
TriState.TRUE
),
Tool.rule(
RegistryAccess.registryAccess().getRegistry(RegistryKey.BLOCK).getTag(TagKey.create(RegistryKey.BLOCK, NamespacedKey.minecraft("bamboo_blocks"))),
2F,
TriState.TRUE
)
))
.build();
final ItemStack stack = new ItemStack(Material.CROSSBOW);
stack.setData(DataComponentTypes.TOOL, properties);
ItemMeta meta = stack.getItemMeta();
ToolComponent component = meta.getTool();
Assertions.assertEquals(properties.damagePerBlock(), component.getDamagePerBlock());
Assertions.assertEquals(properties.defaultMiningSpeed(), component.getDefaultMiningSpeed());
int idx = 0;
for (ToolComponent.ToolRule effect : component.getRules()) {
Assertions.assertEquals(properties.rules().get(idx).speed(), effect.getSpeed());
Assertions.assertEquals(properties.rules().get(idx).correctForDrops().toBoolean(), effect.isCorrectForDrops());
Assertions.assertEquals(properties.rules().get(idx).blocks().resolve(Registry.BLOCK), effect.getBlocks().stream().map(Material::asBlockType).toList());
idx++;
}
stack.unsetData(DataComponentTypes.TOOL);
meta = stack.getItemMeta();
Assertions.assertFalse(meta.hasTool());
}
@Test
void testJukeboxPlayable() {
JukeboxPlayable properties = JukeboxPlayable.jukeboxPlayable(JukeboxSong.MALL).build();
final ItemStack stack = new ItemStack(Material.BEEF);
stack.setData(DataComponentTypes.JUKEBOX_PLAYABLE, properties);
ItemMeta meta = stack.getItemMeta();
JukeboxPlayableComponent component = meta.getJukeboxPlayable();
Assertions.assertEquals(properties.jukeboxSong(), component.getSong());
stack.unsetData(DataComponentTypes.JUKEBOX_PLAYABLE);
meta = stack.getItemMeta();
Assertions.assertFalse(meta.hasJukeboxPlayable());
}
@Test
void testDyedColor() {
final ItemStack stack = new ItemStack(Material.LEATHER_CHESTPLATE);
Color color = Color.BLUE;
stack.setData(DataComponentTypes.DYED_COLOR, DyedItemColor.dyedItemColor(color, false));
Assertions.assertTrue(stack.getItemMeta().hasItemFlag(ItemFlag.HIDE_DYE));
Assertions.assertEquals(color, ((LeatherArmorMeta) stack.getItemMeta()).getColor());
stack.unsetData(DataComponentTypes.DYED_COLOR);
Assertions.assertFalse(((LeatherArmorMeta) stack.getItemMeta()).isDyed());
}
@Test
void testMapColor() {
testWithMeta(new ItemStack(Material.FILLED_MAP), DataComponentTypes.MAP_COLOR, MapItemColor.mapItemColor().color(Color.BLUE).build(), MapItemColor::color, MapMeta.class, MapMeta::getColor, MapMeta::setColor);
}
@Test
void testMapId() {
testWithMeta(new ItemStack(Material.FILLED_MAP), DataComponentTypes.MAP_ID, MapId.mapId(1), MapId::id, MapMeta.class, MapMeta::getMapId, MapMeta::setMapId);
}
@Test
void testFireworks() {
testWithMeta(new ItemStack(Material.FIREWORK_ROCKET), DataComponentTypes.FIREWORKS, Fireworks.fireworks(List.of(FireworkEffect.builder().build()), 1), Fireworks::effects, FireworkMeta.class, FireworkMeta::getEffects, (fireworkMeta, effects) -> {
fireworkMeta.clearEffects();
fireworkMeta.addEffects(effects);
});
testWithMeta(new ItemStack(Material.FIREWORK_ROCKET), DataComponentTypes.FIREWORKS, Fireworks.fireworks(List.of(FireworkEffect.builder().build()), 1), Fireworks::flightDuration, FireworkMeta.class, FireworkMeta::getPower, FireworkMeta::setPower);
}
@Test
void testTrim() {
final ItemStack stack = new ItemStack(Material.LEATHER_CHESTPLATE);
ItemArmorTrim armorTrim = ItemArmorTrim.itemArmorTrim(new ArmorTrim(TrimMaterial.AMETHYST, TrimPattern.BOLT), false);
stack.setData(DataComponentTypes.TRIM, armorTrim);
Assertions.assertTrue(stack.getItemMeta().hasItemFlag(ItemFlag.HIDE_ARMOR_TRIM));
Assertions.assertEquals(armorTrim.armorTrim(), ((ArmorMeta) stack.getItemMeta()).getTrim());
stack.unsetData(DataComponentTypes.TRIM);
Assertions.assertFalse(stack.getItemMeta().hasItemFlag(ItemFlag.HIDE_ARMOR_TRIM));
Assertions.assertFalse(((ArmorMeta) stack.getItemMeta()).hasTrim());
}
@Test
void testChargedProjectiles() {
final ItemStack stack = new ItemStack(Material.CROSSBOW);
ItemStack projectile = new ItemStack(Material.FIREWORK_ROCKET);
stack.setData(DataComponentTypes.CHARGED_PROJECTILES, ChargedProjectiles.chargedProjectiles().add(projectile).build());
CrossbowMeta meta = (CrossbowMeta) stack.getItemMeta();
Assertions.assertEquals(meta.getChargedProjectiles().getFirst(), projectile);
stack.unsetData(DataComponentTypes.CHARGED_PROJECTILES);
meta = (CrossbowMeta) stack.getItemMeta();
Assertions.assertTrue(meta.getChargedProjectiles().isEmpty());
}
@Test
void testPot() {
final ItemStack stack = new ItemStack(Material.DECORATED_POT);
stack.setData(DataComponentTypes.POT_DECORATIONS, PotDecorations.potDecorations().back(ItemType.DANGER_POTTERY_SHERD).build());
BlockState state = ((BlockStateMeta) stack.getItemMeta()).getBlockState();
DecoratedPot decoratedPot = (DecoratedPot) state;
Assertions.assertEquals(decoratedPot.getSherd(DecoratedPot.Side.BACK), Material.DANGER_POTTERY_SHERD);
stack.unsetData(DataComponentTypes.POT_DECORATIONS);
decoratedPot = (DecoratedPot) ((BlockStateMeta) stack.getItemMeta()).getBlockState();
Assertions.assertTrue(decoratedPot.getSherds().values().stream().allMatch((m) -> m.asItemType() == ItemType.BRICK));
}
@Test
void testRecipes() {
final ItemStack stack = new ItemStack(Material.KNOWLEDGE_BOOK);
stack.setData(DataComponentTypes.RECIPES, List.of(Key.key("paper:fun_recipe")));
final ItemMeta itemMeta = stack.getItemMeta();
Assertions.assertInstanceOf(KnowledgeBookMeta.class, itemMeta);
final List<NamespacedKey> recipes = ((KnowledgeBookMeta) itemMeta).getRecipes();
Assertions.assertEquals(recipes, List.of(new NamespacedKey("paper", "fun_recipe")));
}
@Test
void testJukeboxWithEitherKey() {
final ItemStack apiStack = CraftItemStack.asBukkitCopy(new net.minecraft.world.item.ItemStack(Items.MUSIC_DISC_5));
final JukeboxPlayable data = apiStack.getData(DataComponentTypes.JUKEBOX_PLAYABLE);
Assertions.assertNotNull(data);
Assertions.assertEquals(JukeboxSong.FIVE, data.jukeboxSong());
}
@Test
void testJukeboxWithEitherHolder() {
final net.minecraft.world.item.ItemStack internalStack = new net.minecraft.world.item.ItemStack(Items.STONE);
internalStack.set(DataComponents.JUKEBOX_PLAYABLE, new net.minecraft.world.item.JukeboxPlayable(
new EitherHolder<>(RegistryHelper.getRegistry().lookupOrThrow(Registries.JUKEBOX_SONG).getOrThrow(JukeboxSongs.FIVE)),
true
));
final ItemStack apiStack = CraftItemStack.asBukkitCopy(internalStack);
final JukeboxPlayable data = apiStack.getData(DataComponentTypes.JUKEBOX_PLAYABLE);
Assertions.assertNotNull(data);
Assertions.assertEquals(JukeboxSong.FIVE, data.jukeboxSong());
}
private static <T, M extends ItemMeta> void testWithMeta(final ItemStack stack, final DataComponentType.Valued<T> type, final T value, final Class<M> metaType, final Function<M, T> metaGetter, final BiConsumer<M, T> metaSetter) {
testWithMeta(stack, type, value, Function.identity(), metaType, metaGetter, metaSetter);
}
private static <T, M extends ItemMeta, R> void testWithMeta(final ItemStack stack, final DataComponentType.Valued<T> type, final T value, Function<T, R> mapper, final Class<M> metaType, final Function<M, R> metaGetter, final BiConsumer<M, R> metaSetter) {
ItemStack original = stack.clone();
stack.setData(type, value);
Assertions.assertEquals(value, stack.getData(type));
final ItemMeta meta = stack.getItemMeta();
final M typedMeta = Assertions.assertInstanceOf(metaType, meta);
Assertions.assertEquals(metaGetter.apply(typedMeta), mapper.apply(value));
// SETTING
metaSetter.accept(typedMeta, mapper.apply(value));
original.setItemMeta(typedMeta);
Assertions.assertEquals(value, original.getData(type));
}
private static <M extends ItemMeta> void testWithMeta(final ItemStack stack, final DataComponentType.NonValued type, final boolean value, final Class<M> metaType, final Function<M, Boolean> metaGetter, final BiConsumer<M, Boolean> metaSetter) {
ItemStack original = stack.clone();
stack.setData(type);
Assertions.assertEquals(value, stack.hasData(type));
final ItemMeta meta = stack.getItemMeta();
final M typedMeta = Assertions.assertInstanceOf(metaType, meta);
Assertions.assertEquals(metaGetter.apply(typedMeta), value);
// SETTING
metaSetter.accept(typedMeta, value);
original.setItemMeta(typedMeta);
Assertions.assertEquals(value, original.hasData(type));
}
}

View file

@ -0,0 +1,281 @@
package io.papermc.paper.item;
import com.destroystokyo.paper.profile.CraftPlayerProfile;
import com.destroystokyo.paper.profile.PlayerProfile;
import java.util.UUID;
import java.util.function.Consumer;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Color;
import org.bukkit.Material;
import org.bukkit.craftbukkit.inventory.CraftItemFactory;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemFactory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.PotionMeta;
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.support.environment.AllFeatures;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
// TODO: This should technically be used to compare legacy meta vs the newly implemented
@AllFeatures
public class MetaComparisonTest {
private static final ItemFactory FACTORY = CraftItemFactory.instance();
@Test
public void testMetaApplication() {
ItemStack itemStack = new ItemStack(Material.STONE);
ItemMeta meta = itemStack.getItemMeta();
meta.setCustomModelData(1);
ItemMeta converted = FACTORY.asMetaFor(meta, Material.GOLD_INGOT);
Assertions.assertEquals(converted.getCustomModelData(), meta.getCustomModelData());
ItemMeta convertedAdvanced = FACTORY.asMetaFor(meta, Material.PLAYER_HEAD);
Assertions.assertEquals(convertedAdvanced.getCustomModelData(), meta.getCustomModelData());
}
@Test
public void testMetaApplicationDowngrading() {
ItemStack itemStack = new ItemStack(Material.PLAYER_HEAD);
PlayerProfile profile = Bukkit.createProfile("Owen1212055");
SkullMeta meta = (SkullMeta) itemStack.getItemMeta();
meta.setPlayerProfile(profile);
SkullMeta converted = (SkullMeta) FACTORY.asMetaFor(meta, Material.PLAYER_HEAD);
Assertions.assertEquals(converted.getPlayerProfile(), meta.getPlayerProfile());
SkullMeta downgraded = (SkullMeta) FACTORY.asMetaFor(FACTORY.asMetaFor(meta, Material.STONE), Material.PLAYER_HEAD);
Assertions.assertNull(downgraded.getPlayerProfile());
}
@Test
public void testMetaApplicationDowngradingPotion() {
ItemStack itemStack = new ItemStack(Material.POTION);
Color color = Color.BLUE;
PotionMeta meta = (PotionMeta) itemStack.getItemMeta();
meta.setColor(color);
PotionMeta converted = (PotionMeta) FACTORY.asMetaFor(meta, Material.POTION);
Assertions.assertEquals(converted.getColor(), color);
PotionMeta downgraded = (PotionMeta) FACTORY.asMetaFor(FACTORY.asMetaFor(meta, Material.STONE), Material.POTION);
Assertions.assertNull(downgraded.getColor());
}
@Test
public void testNullMeta() {
ItemStack itemStack = new ItemStack(Material.AIR);
Assertions.assertFalse(itemStack.hasItemMeta());
Assertions.assertNull(itemStack.getItemMeta());
}
@Test
public void testPotionMeta() {
PotionEffect potionEffect = new PotionEffect(PotionEffectType.SPEED, 10, 10, false);
ItemStack nmsItemStack = new ItemStack(Material.POTION, 1);
testSetAndGet(nmsItemStack,
(meta) -> ((PotionMeta) meta).addCustomEffect(potionEffect, true),
(meta) -> Assertions.assertEquals(potionEffect, ((PotionMeta) meta).getCustomEffects().getFirst())
);
}
@Test
public void testEnchantment() {
ItemStack stack = new ItemStack(Material.STICK, 1);
testSetAndGet(stack,
(meta) -> Assertions.assertTrue(meta.addEnchant(Enchantment.SHARPNESS, 1, true)),
(meta) -> Assertions.assertEquals(1, meta.getEnchantLevel(Enchantment.SHARPNESS))
);
}
@Test
@Disabled
public void testPlayerHead() {
PlayerProfile profile = new CraftPlayerProfile(UUID.randomUUID(), "Owen1212055");
ItemStack stack = new ItemStack(Material.PLAYER_HEAD, 1);
testSetAndGet(stack,
(meta) -> ((SkullMeta) meta).setPlayerProfile(profile),
(meta) -> {
Assertions.assertTrue(((SkullMeta) meta).hasOwner());
Assertions.assertEquals(profile, ((SkullMeta) meta).getPlayerProfile());
}
);
testSetAndGet(stack,
(meta) -> ((SkullMeta) meta).setOwner("Owen1212055"),
(meta) -> {
Assertions.assertTrue(((SkullMeta) meta).hasOwner());
Assertions.assertEquals("Owen1212055", ((SkullMeta) meta).getOwner());
}
);
}
@Test
public void testBookMetaAuthor() {
ItemStack stack = new ItemStack(Material.WRITTEN_BOOK, 1);
// Legacy string
testSetAndGet(stack,
(meta) -> ((BookMeta) meta).setAuthor("Owen1212055"),
(meta) -> Assertions.assertEquals("Owen1212055", ((BookMeta) meta).getAuthor())
);
// Component Colored
Component coloredName = Component.text("Owen1212055", NamedTextColor.DARK_GRAY);
testSetAndGet(stack,
(meta) -> ((BookMeta) meta).author(coloredName),
(meta) -> Assertions.assertEquals(coloredName, ((BookMeta) meta).author())
);
// Simple text
Component name = Component.text("Owen1212055");
testSetAndGet(stack,
(meta) -> ((BookMeta) meta).author(name),
(meta) -> Assertions.assertEquals(name, ((BookMeta) meta).author())
);
}
@Test
public void testBookMetaTitle() {
ItemStack stack = new ItemStack(Material.WRITTEN_BOOK, 1);
// Legacy string
testSetAndGet(stack,
(meta) -> ((BookMeta) meta).setTitle("Owen1212055"),
(meta) -> Assertions.assertEquals("Owen1212055", ((BookMeta) meta).getTitle())
);
// Component Colored
Component coloredName = Component.text("Owen1212055", NamedTextColor.DARK_GRAY);
testSetAndGet(stack,
(meta) -> ((BookMeta) meta).title(coloredName),
(meta) -> Assertions.assertEquals(coloredName, ((BookMeta) meta).title())
);
// Simple text
Component name = Component.text("Owen1212055");
testSetAndGet(stack,
(meta) -> ((BookMeta) meta).title(name),
(meta) -> Assertions.assertEquals(name, ((BookMeta) meta).title())
);
}
@Test
public void testWriteableBookPages() {
ItemStack stack = new ItemStack(Material.WRITABLE_BOOK, 1);
// Writeable books are serialized as plain text, but has weird legacy color support.
// So, we need to test to make sure that all works here.
// Legacy string
testSetAndGet(stack,
(meta) -> ((BookMeta) meta).addPage("Owen1212055"),
(meta) -> Assertions.assertEquals("Owen1212055", ((BookMeta) meta).getPage(1))
);
// Legacy string colored
String translatedLegacy = ChatColor.translateAlternateColorCodes('&', "&7Owen1212055");
testSetAndGet(stack,
(meta) -> ((BookMeta) meta).addPage(translatedLegacy),
(meta) -> Assertions.assertEquals(translatedLegacy, ((BookMeta) meta).getPage(1))
);
// Component Colored
Component coloredName = Component.text("Owen1212055", NamedTextColor.DARK_GRAY);
testSetAndGet(stack,
(meta) -> ((BookMeta) meta).addPages(coloredName),
(meta) -> Assertions.assertEquals(coloredName, ((BookMeta) meta).page(1))
);
// Simple text
Component name = Component.text("Owen1212055");
testSetAndGet(stack,
(meta) -> ((BookMeta) meta).addPages(name),
(meta) -> Assertions.assertEquals(name, ((BookMeta) meta).page(1))
);
// Simple text + hover... should NOT be saved
// As this is plain text
Component nameWithHover = Component.text("Owen1212055")
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, Component.text("Hover")));
testSetAndGet(stack,
(meta) -> ((BookMeta) meta).addPages(nameWithHover),
(meta) -> Assertions.assertEquals(name, ((BookMeta) meta).page(1))
);
}
@Test
public void testWrittenBookPages() {
ItemStack stack = new ItemStack(Material.WRITTEN_BOOK, 1);
// Writeable books are serialized as plain text, but has weird legacy color support.
// So, we need to test to make sure that all works here.
// Legacy string
testSetAndGet(stack,
(meta) -> ((BookMeta) meta).addPage("Owen1212055"),
(meta) -> Assertions.assertEquals("Owen1212055", ((BookMeta) meta).getPage(1))
);
// Legacy string colored
String translatedLegacy = ChatColor.translateAlternateColorCodes('&', "&7Owen1212055");
testSetAndGet(stack,
(meta) -> ((BookMeta) meta).addPage(translatedLegacy),
(meta) -> Assertions.assertEquals(translatedLegacy, ((BookMeta) meta).getPage(1))
);
// Component Colored
Component coloredName = Component.text("Owen1212055", NamedTextColor.DARK_GRAY);
testSetAndGet(stack,
(meta) -> ((BookMeta) meta).addPages(coloredName),
(meta) -> Assertions.assertEquals(coloredName, ((BookMeta) meta).page(1))
);
// Simple text
Component name = Component.text("Owen1212055");
testSetAndGet(stack,
(meta) -> ((BookMeta) meta).addPages(name),
(meta) -> Assertions.assertEquals(name, ((BookMeta) meta).page(1))
);
// Simple text + hover... should be saved
Component nameWithHover = Component.text("Owen1212055")
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, Component.text("Hover")));
testSetAndGet(stack,
(meta) -> ((BookMeta) meta).addPages(nameWithHover),
(meta) -> Assertions.assertEquals(nameWithHover, ((BookMeta) meta).page(1))
);
}
private void testSetAndGet(ItemStack itemStack, Consumer<ItemMeta> set, Consumer<ItemMeta> get) {
ItemMeta craftMeta = CraftItemStack.getItemMeta(CraftItemStack.asNMSCopy(itemStack)); // TODO: This should be converted to use the old meta when this is added.
ItemMeta paperMeta = CraftItemStack.getItemMeta(CraftItemStack.asNMSCopy(itemStack));
// Test craft meta
set.accept(craftMeta);
get.accept(craftMeta);
// Test paper meta
set.accept(paperMeta);
get.accept(paperMeta);
}
}

View file

@ -101,17 +101,13 @@ public class PerMaterialTest {
final ItemStack bukkit = new ItemStack(material);
final CraftItemStack craft = CraftItemStack.asCraftCopy(bukkit);
if (material == Material.AIR) {
final int MAX_AIR_STACK = 0 /* Why can't I hold all of these AIR? */;
assertThat(material.getMaxStackSize(), is(MAX_AIR_STACK));
assertThat(bukkit.getMaxStackSize(), is(MAX_AIR_STACK));
assertThat(craft.getMaxStackSize(), is(MAX_AIR_STACK));
} else {
// Paper - remove air exception
int max = CraftMagicNumbers.getItem(material).components().getOrDefault(DataComponents.MAX_STACK_SIZE, 64);
assertThat(material.getMaxStackSize(), is(max));
assertThat(bukkit.getMaxStackSize(), is(max));
assertThat(craft.getMaxStackSize(), is(max));
}
// Paper - remove air exception
}
@ParameterizedTest

View file

@ -100,6 +100,7 @@ public class RegistriesArgumentProvider implements ArgumentsProvider {
register(RegistryKey.MAP_DECORATION_TYPE, MapCursor.Type.class, Registries.MAP_DECORATION_TYPE, CraftMapCursor.CraftType.class, MapDecorationType.class);
register(RegistryKey.BANNER_PATTERN, PatternType.class, Registries.BANNER_PATTERN, CraftPatternType.class, BannerPattern.class);
register(RegistryKey.MENU, MenuType.class, Registries.MENU, CraftMenuType.class, net.minecraft.world.inventory.MenuType.class);
register(RegistryKey.DATA_COMPONENT_TYPE, io.papermc.paper.datacomponent.DataComponentType.class, Registries.DATA_COMPONENT_TYPE, io.papermc.paper.datacomponent.PaperDataComponentType.class, net.minecraft.core.component.DataComponentType.class);
}
private static void register(RegistryKey registryKey, Class bukkit, ResourceKey registry, Class craft, Class minecraft) { // Paper