diff --git a/SpongeAPI b/SpongeAPI index 1f58ca7d3f0..656efea1273 160000 --- a/SpongeAPI +++ b/SpongeAPI @@ -1 +1 @@ -Subproject commit 1f58ca7d3f0e4e940f54dd59d56cf876aad6795d +Subproject commit 656efea127396c6f7f336f342fdfb6e9d0e3aea6 diff --git a/src/main/java/org/spongepowered/common/data/provider/item/stack/ArmorItemStackData.java b/src/main/java/org/spongepowered/common/data/provider/item/stack/ArmorItemStackData.java index c39fe7b1e24..9c6cacd5697 100644 --- a/src/main/java/org/spongepowered/common/data/provider/item/stack/ArmorItemStackData.java +++ b/src/main/java/org/spongepowered/common/data/provider/item/stack/ArmorItemStackData.java @@ -24,22 +24,32 @@ */ package org.spongepowered.common.data.provider.item.stack; +import net.minecraft.core.Holder; +import net.minecraft.core.HolderSet; import net.minecraft.core.component.DataComponents; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.Identifier; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.component.ItemAttributeModifiers; +import net.minecraft.world.item.equipment.EquipmentAsset; +import net.minecraft.world.item.equipment.EquipmentAssets; import net.minecraft.world.item.equipment.Equippable; -import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.api.ResourceKey; import org.spongepowered.api.data.Keys; +import org.spongepowered.api.effect.sound.SoundType; +import org.spongepowered.api.entity.EntityType; import org.spongepowered.api.item.inventory.equipment.EquipmentType; import org.spongepowered.api.item.recipe.smithing.ArmorTrim; import org.spongepowered.api.registry.RegistryEntry; import org.spongepowered.api.registry.RegistryTypes; import org.spongepowered.common.data.provider.DataProviderRegistrator; -import java.util.function.Function; +import java.util.Optional; +import java.util.stream.Collectors; public final class ArmorItemStackData { @@ -50,6 +60,49 @@ private ArmorItemStackData() { public static void register(final DataProviderRegistrator registrator) { registrator .asMutable(ItemStack.class) + .create(Keys.ALLOWED_ENTITIES) + .get(h -> { + final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); + if (equippable == null || equippable.allowedEntities().isEmpty()) { + return null; + } + return equippable.allowedEntities().get().stream() + .map(Holder::value) + .map(e -> (EntityType) e) + .collect(Collectors.toSet()); + }) + .setAnd((h, v) -> { + final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); + if (equippable == null) { + return false; + } + final HolderSet> holderSet = HolderSet.direct( + e -> BuiltInRegistries.ENTITY_TYPE.wrapAsHolder((net.minecraft.world.entity.EntityType) e), + v + ); + h.set(DataComponents.EQUIPPABLE, ArmorItemStackData.asBuilder(equippable).setAllowedEntities(holderSet).build()); + return true; + }) + .deleteAnd(h -> { + final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); + if (equippable == null) { + return false; + } + h.set(DataComponents.EQUIPPABLE, new Equippable( + equippable.slot(), + equippable.equipSound(), + equippable.assetId(), + equippable.cameraOverlay(), + Optional.empty(), + equippable.dispensable(), + equippable.swappable(), + equippable.damageOnHurt(), + equippable.equipOnInteract(), + equippable.canBeSheared(), + equippable.shearingSound() + )); + return true; + }) .create(Keys.ARMOR_MATERIAL) .get(h -> { final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); @@ -62,7 +115,37 @@ public static void register(final DataProviderRegistrator registrator) { .map(RegistryEntry::value) .orElse(null); }) - .supports(isArmorItem()) + .setAnd((h, v) -> { + final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); + if (equippable == null) { + return false; + } + final ResourceKey key = RegistryTypes.ARMOR_MATERIAL.get().valueKey(v); + final net.minecraft.resources.ResourceKey assetKey = + net.minecraft.resources.ResourceKey.create(EquipmentAssets.ROOT_ID, (Identifier) (Object) key); + h.set(DataComponents.EQUIPPABLE, ArmorItemStackData.asBuilder(equippable).setAsset(assetKey).build()); + return true; + }) + .deleteAnd(h -> { + final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); + if (equippable == null) { + return false; + } + h.set(DataComponents.EQUIPPABLE, new Equippable( + equippable.slot(), + equippable.equipSound(), + Optional.empty(), + equippable.cameraOverlay(), + equippable.allowedEntities(), + equippable.dispensable(), + equippable.swappable(), + equippable.damageOnHurt(), + equippable.equipOnInteract(), + equippable.canBeSheared(), + equippable.shearingSound() + )); + return true; + }) .create(Keys.ARMOR_TRIM) .get(h -> { final net.minecraft.world.item.equipment.trim.@Nullable ArmorTrim trim = h.get(DataComponents.TRIM); @@ -79,7 +162,58 @@ public static void register(final DataProviderRegistrator registrator) { h.set(DataComponents.TRIM, (net.minecraft.world.item.equipment.trim.ArmorTrim) (Object) v); }) .delete(h -> h.remove(DataComponents.TRIM)) - .supports(isArmorItem()) + .create(Keys.CAMERA_OVERLAY) + .get(h -> { + final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); + if (equippable == null || equippable.cameraOverlay().isEmpty()) { + return null; + } + return (ResourceKey) (Object) equippable.cameraOverlay().get(); + }) + .setAnd((h, v) -> { + final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); + if (equippable == null) { + return false; + } + h.set(DataComponents.EQUIPPABLE, ArmorItemStackData.asBuilder(equippable).setCameraOverlay((Identifier) (Object) v).build()); + return true; + }) + .deleteAnd(h -> { + final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); + if (equippable == null) { + return false; + } + h.set(DataComponents.EQUIPPABLE, new Equippable( + equippable.slot(), + equippable.equipSound(), + equippable.assetId(), + Optional.empty(), + equippable.allowedEntities(), + equippable.dispensable(), + equippable.swappable(), + equippable.damageOnHurt(), + equippable.equipOnInteract(), + equippable.canBeSheared(), + equippable.shearingSound() + )); + return true; + }) + .create(Keys.CAN_BE_SHEARED) + .get(h -> { + final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); + if (equippable == null) { + return null; + } + return equippable.canBeSheared(); + }) + .setAnd((h, v) -> { + final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); + if (equippable == null) { + return false; + } + h.set(DataComponents.EQUIPPABLE, ArmorItemStackData.asBuilder(equippable).setCanBeSheared(v).build()); + return true; + }) .create(Keys.DAMAGE_ABSORPTION) .get(h -> { final @Nullable ItemAttributeModifiers modifiersContainer = h.get(DataComponents.ATTRIBUTE_MODIFIERS); @@ -92,32 +226,148 @@ public static void register(final DataProviderRegistrator registrator) { .map(e -> e.modifier().amount()) .orElse(null); }) - .supports(isArmorItem()) + .create(Keys.DAMAGE_ON_HURT) + .get(h -> { + final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); + if (equippable == null) { + return null; + } + return equippable.damageOnHurt(); + }) + .setAnd((h, v) -> { + final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); + if (equippable == null) { + return false; + } + h.set(DataComponents.EQUIPPABLE, ArmorItemStackData.asBuilder(equippable).setDamageOnHurt(v).build()); + return true; + }) + .create(Keys.EQUIP_ON_INTERACT) + .get(h -> { + final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); + if (equippable == null) { + return null; + } + return equippable.equipOnInteract(); + }) + .setAnd((h, v) -> { + final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); + if (equippable == null) { + return false; + } + h.set(DataComponents.EQUIPPABLE, ArmorItemStackData.asBuilder(equippable).setEquipOnInteract(v).build()); + return true; + }) + .create(Keys.EQUIP_SOUND) + .get(h -> { + final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); + if (equippable == null) { + return null; + } + return (SoundType) (Object) equippable.equipSound().value(); + }) + .setAnd((h, v) -> { + final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); + if (equippable == null) { + return false; + } + h.set(DataComponents.EQUIPPABLE, ArmorItemStackData.asBuilder(equippable).setEquipSound(Holder.direct((SoundEvent) (Object) v)).build()); + return true; + }) .create(Keys.EQUIPMENT_TYPE) .get(h -> { final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); if (equippable == null) { return null; } - return (EquipmentType) (Object) equippable.slot(); }) - .supports(isArmorItem()); + .set((h, v) -> { + final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); + final EquipmentSlot slot = (EquipmentSlot) (Object) v; + if (equippable == null) { + h.set(DataComponents.EQUIPPABLE, Equippable.builder(slot).build()); + return; + } + h.set(DataComponents.EQUIPPABLE, new Equippable( + slot, + equippable.equipSound(), + equippable.assetId(), + equippable.cameraOverlay(), + equippable.allowedEntities(), + equippable.dispensable(), + equippable.swappable(), + equippable.damageOnHurt(), + equippable.equipOnInteract(), + equippable.canBeSheared(), + equippable.shearingSound() + )); + }) + .delete(h -> h.remove(DataComponents.EQUIPPABLE)) + .create(Keys.IS_DISPENSABLE) + .get(h -> { + final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); + if (equippable == null) { + return null; + } + return equippable.dispensable(); + }) + .setAnd((h, v) -> { + final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); + if (equippable == null) { + return false; + } + h.set(DataComponents.EQUIPPABLE, ArmorItemStackData.asBuilder(equippable).setDispensable(v).build()); + return true; + }) + .create(Keys.IS_SWAPPABLE) + .get(h -> { + final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); + if (equippable == null) { + return null; + } + return equippable.swappable(); + }) + .setAnd((h, v) -> { + final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); + if (equippable == null) { + return false; + } + h.set(DataComponents.EQUIPPABLE, ArmorItemStackData.asBuilder(equippable).setSwappable(v).build()); + return true; + }) + .create(Keys.SHEARING_SOUND) + .get(h -> { + final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); + if (equippable == null) { + return null; + } + return (SoundType) (Object) equippable.shearingSound().value(); + }) + .setAnd((h, v) -> { + final @Nullable Equippable equippable = h.get(DataComponents.EQUIPPABLE); + if (equippable == null) { + return false; + } + h.set(DataComponents.EQUIPPABLE, ArmorItemStackData.asBuilder(equippable).setShearingSound(Holder.direct((SoundEvent) (Object) v)).build()); + return true; + }); } // @formatter:on - private static @NonNull Function isArmorItem() { - return h -> { - final var components = h.getItem().components(); - final @Nullable Integer stackSize = components.get(DataComponents.MAX_STACK_SIZE); - if (stackSize == null) { - return false; - } - return components.has(DataComponents.EQUIPPABLE) - && components.has(DataComponents.ENCHANTABLE) - && components.has(DataComponents.MAX_DAMAGE) - && components.has(DataComponents.MAX_STACK_SIZE) - && (1 == stackSize); - }; + private static Equippable.Builder asBuilder(final Equippable equippable) { + final Equippable.Builder builder = Equippable.builder(equippable.slot()) + .setEquipSound(equippable.equipSound()) + .setDispensable(equippable.dispensable()) + .setSwappable(equippable.swappable()) + .setDamageOnHurt(equippable.damageOnHurt()) + .setEquipOnInteract(equippable.equipOnInteract()) + .setCanBeSheared(equippable.canBeSheared()) + .setShearingSound(equippable.shearingSound()); + equippable.assetId().ifPresent(builder::setAsset); + equippable.cameraOverlay().ifPresent(builder::setCameraOverlay); + equippable.allowedEntities().ifPresent(builder::setAllowedEntities); + return builder; } + } diff --git a/src/test/java/org/spongepowered/common/data/key/KeysTest.java b/src/test/java/org/spongepowered/common/data/key/KeysTest.java index 5a7f8a4ac9f..2d9883e1099 100644 --- a/src/test/java/org/spongepowered/common/data/key/KeysTest.java +++ b/src/test/java/org/spongepowered/common/data/key/KeysTest.java @@ -25,11 +25,13 @@ package org.spongepowered.common.data.key; import io.leangen.geantyref.GenericTypeReflector; +import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.spongepowered.api.ResourceKey; import org.spongepowered.api.ResourceKeyed; import org.spongepowered.api.data.Key; import org.spongepowered.api.data.Keys; @@ -37,13 +39,16 @@ import org.spongepowered.api.data.type.ShieldItemDamageFunction; import org.spongepowered.api.data.value.Value; import org.spongepowered.api.effect.sound.SoundTypes; +import org.spongepowered.api.entity.EntityTypes; import org.spongepowered.api.event.cause.entity.damage.DamageTypes; import org.spongepowered.api.item.ItemTypes; import org.spongepowered.api.item.inventory.ItemStack; +import org.spongepowered.api.item.inventory.equipment.EquipmentTypes; import org.spongepowered.api.tag.DamageTypeTags; import org.spongepowered.api.util.Ticks; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.function.Function; import java.util.stream.Stream; @@ -52,35 +57,45 @@ public class KeysTest { private static Stream testSingleKeys() { return Stream.of( - Arguments.of(Keys.WEAPON_DAMAGE_PER_ATTACK, 5), - Arguments.of(Keys.DISABLE_SHIELD_TICKS, Ticks.of(10)), - Arguments.of(Keys.SHIELD_DEPLOY_TICKS, Ticks.of(15)), - Arguments.of(Keys.DISABLE_SHIELD_TICKS_SCALE, 2.5), + Arguments.of(Keys.WEAPON_DAMAGE_PER_ATTACK, 5, null), + Arguments.of(Keys.DISABLE_SHIELD_TICKS, Ticks.of(10), null), + Arguments.of(Keys.SHIELD_DEPLOY_TICKS, Ticks.of(15), null), + Arguments.of(Keys.DISABLE_SHIELD_TICKS_SCALE, 2.5, null), Arguments.of(Keys.SHIELD_DAMAGE_REDUCTIONS, List.of(ShieldDamageReduction.of(ShieldDamageReduction.MultiplyAdd.builder() .horizontalBlockingAngle(45) .constantReduction(2) .fractionalReduction(0.5) .damageTypes(Set.of(DamageTypes.ARROW.get(), DamageTypes.PLAYER_ATTACK.get())) - .build()))), + .build())), null), Arguments.of(Keys.SHIELD_ITEM_DAMAGE_FUNCTION, ShieldItemDamageFunction.of(ShieldItemDamageFunction.MultiplyAdd.builder() .constantDamage(5) .fractionalDamage(2) .minAttackDamage(2.5) - .build())), - Arguments.of(Keys.SHIELD_BLOCK_SOUND, SoundTypes.ENTITY_SHULKER_HURT.get()), - Arguments.of(Keys.SHIELD_DISABLE_SOUND, SoundTypes.ENTITY_ENDER_DRAGON_DEATH.get()) + .build()), null), + Arguments.of(Keys.SHIELD_BLOCK_SOUND, SoundTypes.ENTITY_SHULKER_HURT.get(), null), + Arguments.of(Keys.SHIELD_DISABLE_SOUND, SoundTypes.ENTITY_ENDER_DRAGON_DEATH.get(), null), + Arguments.of(Keys.ALLOWED_ENTITIES, Set.of(EntityTypes.PLAYER.get(), EntityTypes.ZOMBIE.get()), ItemStack.of(ItemTypes.DIAMOND_CHESTPLATE)), + Arguments.of(Keys.CAMERA_OVERLAY, ResourceKey.minecraft("textures/misc/pumpkinblur.png"), ItemStack.of(ItemTypes.DIAMOND_CHESTPLATE)), + Arguments.of(Keys.CAN_BE_SHEARED, true, ItemStack.of(ItemTypes.DIAMOND_CHESTPLATE)), + Arguments.of(Keys.DAMAGE_ON_HURT, false, ItemStack.of(ItemTypes.DIAMOND_CHESTPLATE)), + Arguments.of(Keys.EQUIP_ON_INTERACT, true, ItemStack.of(ItemTypes.DIAMOND_CHESTPLATE)), + Arguments.of(Keys.EQUIP_SOUND, SoundTypes.ITEM_ARMOR_EQUIP_DIAMOND.get(), ItemStack.of(ItemTypes.DIAMOND_CHESTPLATE)), + Arguments.of(Keys.EQUIPMENT_TYPE, EquipmentTypes.HEAD.get(), null), + Arguments.of(Keys.IS_DISPENSABLE, false, ItemStack.of(ItemTypes.DIAMOND_CHESTPLATE)), + Arguments.of(Keys.IS_SWAPPABLE, false, ItemStack.of(ItemTypes.DIAMOND_CHESTPLATE)), + Arguments.of(Keys.SHEARING_SOUND, SoundTypes.ENTITY_SHEEP_SHEAR.get(), ItemStack.of(ItemTypes.DIAMOND_CHESTPLATE)) ); } @MethodSource @ParameterizedTest - void testSingleKeys(Key k, Object value) { - testSingleKeyUnchecked(k, value); + void testSingleKeys(Key k, Object value, @Nullable ItemStack baseStack) { + testSingleKeyUnchecked(k, value, baseStack); } @Test void testBypassDamageTag() { - testSingleKey(Keys.BYPASS_DAMAGE_TAG, DamageTypeTags.BYPASSES_ARMOR, ResourceKeyed::key); + testSingleKey(Keys.BYPASS_DAMAGE_TAG, DamageTypeTags.BYPASSES_ARMOR, ResourceKeyed::key, null); } @Test @@ -104,19 +119,17 @@ void testAllWeaponKeys() { } @SuppressWarnings("unchecked") - private static > void testSingleKeyUnchecked(Key k, Object value) { + private static > void testSingleKeyUnchecked(Key k, Object value, @Nullable ItemStack baseStack) { if (!GenericTypeReflector.isSuperType(k.elementType(), value.getClass())) { throw new IllegalArgumentException("Invalid value type for key " + k.key() + ": " + value.getClass().getName()); } - testSingleKey((Key) k, (T) value, a -> a); + testSingleKey((Key) k, (T) value, a -> a, baseStack); } - private static > void testSingleKey(Key k, T value, Function equalityExtractor) { - final var stack = ItemStack.builder() - .itemType(ItemTypes.DIAMOND_SWORD) - .add(k, value) - .build(); + private static > void testSingleKey(Key k, T value, Function equalityExtractor, @Nullable ItemStack baseStack) { + final ItemStack stack = Objects.requireNonNullElseGet(baseStack, () -> ItemStack.of(ItemTypes.DIAMOND_SWORD)); + stack.offer(k, value); Assertions.assertEquals(equalityExtractor.apply(value), equalityExtractor.apply(stack.require(k)), () -> "retrieved value is not equal " + "to the original for " + k.key().asString());