From f1dbed072cc5bf0f3b4f37980c1bd4f6599bdacb Mon Sep 17 00:00:00 2001 From: Pedro <3602279+Doc94@users.noreply.github.com> Date: Sun, 18 May 2025 10:31:34 -0400 Subject: [PATCH] Implement BlocksAttack DamageReduction and ItemDamage (#12538) --- .../datacomponent/item/BlocksAttacks.java | 71 ++++++++++++--- .../blocksattacks/BlocksAttacksBridge.java | 21 +++++ .../item/blocksattacks/DamageReduction.java | 78 +++++++++++++++++ .../blocksattacks/ItemDamageFunction.java | 71 +++++++++++++++ .../item/PaperBlocksAttacks.java | 51 +++++++---- .../BlocksAttacksBridgeImpl.java | 19 ++++ .../blocksattacks/PaperDamageReduction.java | 87 +++++++++++++++++++ .../PaperItemDamageFunction.java | 70 +++++++++++++++ .../item/blocksattacks/package-info.java | 7 ++ ...ent.item.blocksattacks.BlocksAttacksBridge | 1 + 10 files changed, 448 insertions(+), 28 deletions(-) create mode 100644 paper-api/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/BlocksAttacksBridge.java create mode 100644 paper-api/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/DamageReduction.java create mode 100644 paper-api/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/ItemDamageFunction.java create mode 100644 paper-server/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/BlocksAttacksBridgeImpl.java create mode 100644 paper-server/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/PaperDamageReduction.java create mode 100644 paper-server/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/PaperItemDamageFunction.java create mode 100644 paper-server/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/package-info.java create mode 100644 paper-server/src/main/resources/META-INF/services/io.papermc.paper.datacomponent.item.blocksattacks.BlocksAttacksBridge diff --git a/paper-api/src/main/java/io/papermc/paper/datacomponent/item/BlocksAttacks.java b/paper-api/src/main/java/io/papermc/paper/datacomponent/item/BlocksAttacks.java index bf53d0e0b0..d7372673ac 100644 --- a/paper-api/src/main/java/io/papermc/paper/datacomponent/item/BlocksAttacks.java +++ b/paper-api/src/main/java/io/papermc/paper/datacomponent/item/BlocksAttacks.java @@ -1,6 +1,8 @@ package io.papermc.paper.datacomponent.item; import io.papermc.paper.datacomponent.DataComponentBuilder; +import io.papermc.paper.datacomponent.item.blocksattacks.DamageReduction; +import io.papermc.paper.datacomponent.item.blocksattacks.ItemDamageFunction; import io.papermc.paper.registry.tag.TagKey; import net.kyori.adventure.key.Key; import org.bukkit.damage.DamageType; @@ -8,8 +10,13 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Contract; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; +import java.util.List; -// TODO +/** + * Holds block attacks to the holding player like Shield. + * + * @see io.papermc.paper.datacomponent.DataComponentTypes#BLOCKS_ATTACKS + */ @NullMarked @ApiStatus.Experimental @ApiStatus.NonExtendable @@ -20,19 +27,59 @@ public interface BlocksAttacks { return ItemComponentTypesBridge.bridge().blocksAttacks(); } + /** + * Gets the amount of time (in seconds) that use must be held before successfully blocking attacks. + * + * @return the delay in seconds + */ float blockDelaySeconds(); + /** + * Gets the multiplier applied to the cooldown time for the item when attacked by a disabling attack (the multiplier for {@link Weapon#disableBlockingForSeconds()}). + *
+ * If set to 0, this item can never be disabled by attacks. + * + * @return the multiplier for the cooldown time + */ float disableCooldownScale(); - //List damageReductions(); + /** + * Gets a list of {@link DamageReduction} of how much damage should be blocked in a given attack. + * + * @return a list of damage reductions + */ + List damageReductions(); - //ItemDamageFunction itemDamage(); + /** + * Gets how much damage should be applied to the item from a given attack. + * + * @return the damage function + */ + ItemDamageFunction itemDamage(); - @Nullable TagKey bypassedBy(); + /** + * Gets the DamageType that can bypass the blocking. + * + * @return a damage type tag key, or null if there is no such tag key + */ + @Nullable + TagKey bypassedBy(); - @Nullable Key blockSound(); + /** + * Gets the key sound to play when an attack is successfully blocked. + * + * @return a key of the sound + */ + @Nullable + Key blockSound(); - @Nullable Key disableSound(); + /** + * Gets the key sound to play when the item goes on its disabled cooldown due to an attack. + * + * @return a key of the sound + */ + @Nullable + Key disableSound(); /** * Builder for {@link BlocksAttacks}. @@ -47,14 +94,14 @@ public interface BlocksAttacks { @Contract(value = "_ -> this", mutates = "this") Builder disableCooldownScale(float scale); - //@Contract(value = "_ -> this", mutates = "this") - //Builder addDamageReduction(DamageReduction reduction); + @Contract(value = "_ -> this", mutates = "this") + Builder addDamageReduction(DamageReduction reduction); - //@Contract(value = "_ -> this", mutates = "this") - //Builder damageReductions(List reductions); + @Contract(value = "_ -> this", mutates = "this") + Builder damageReductions(List reductions); - //@Contract(value = "_ -> this", mutates = "this") - //Builder itemDamage(ItemDamageFunction function); + @Contract(value = "_ -> this", mutates = "this") + Builder itemDamage(ItemDamageFunction function); @Contract(value = "_ -> this", mutates = "this") Builder bypassedBy(@Nullable TagKey bypassedBy); diff --git a/paper-api/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/BlocksAttacksBridge.java b/paper-api/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/BlocksAttacksBridge.java new file mode 100644 index 0000000000..aafc085c91 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/BlocksAttacksBridge.java @@ -0,0 +1,21 @@ +package io.papermc.paper.datacomponent.item.blocksattacks; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import java.util.Optional; +import java.util.ServiceLoader; + +@NullMarked +@ApiStatus.Internal +interface BlocksAttacksBridge { + + Optional BRIDGE = ServiceLoader.load(BlocksAttacksBridge.class).findFirst(); + + static BlocksAttacksBridge bridge() { + return BRIDGE.orElseThrow(); + } + + DamageReduction.Builder blocksAttacksDamageReduction(); + + ItemDamageFunction.Builder blocksAttacksItemDamageFunction(); +} diff --git a/paper-api/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/DamageReduction.java b/paper-api/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/DamageReduction.java new file mode 100644 index 0000000000..fbf4a03782 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/DamageReduction.java @@ -0,0 +1,78 @@ +package io.papermc.paper.datacomponent.item.blocksattacks; + +import io.papermc.paper.datacomponent.DataComponentBuilder; +import io.papermc.paper.registry.set.RegistryKeySet; +import org.bukkit.damage.DamageType; +import org.checkerframework.checker.index.qual.Positive; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** + * Hold how much damage should be blocked in a given attack. + * + * @see io.papermc.paper.datacomponent.DataComponentTypes#BLOCKS_ATTACKS + * @see io.papermc.paper.datacomponent.item.BlocksAttacks#damageReductions() + */ +@NullMarked +@ApiStatus.Experimental +@ApiStatus.NonExtendable +public interface DamageReduction { + + @Contract(value = "-> new", pure = true) + static DamageReduction.Builder damageReduction() { + return BlocksAttacksBridge.bridge().blocksAttacksDamageReduction(); + } + + /** + * The damage types to block. + * + * @return the set of damage type + */ + @Nullable + RegistryKeySet type(); + + /** + * Get the maximum angle between the users facing direction and the direction of the incoming attack to be blocked. + * + * @return the angle + */ + @Positive + float horizontalBlockingAngle(); + + /** + * Get the constant amount of damage to be blocked. + * + * @return the base + */ + float base(); + + /** + * Get the fraction of the dealt damage to be blocked. + * + * @return the factor + */ + float factor(); + + /** + * Builder for {@link DamageReduction}. + */ + @ApiStatus.Experimental + @ApiStatus.NonExtendable + interface Builder extends DataComponentBuilder { + + @Contract(value = "_ -> this", mutates = "this") + DamageReduction.Builder type(RegistryKeySet type); + + @Contract(value = "_ -> this", mutates = "this") + DamageReduction.Builder horizontalBlockingAngle(@Positive float horizontalBlockingAngle); + + @Contract(value = "_ -> this", mutates = "this") + DamageReduction.Builder base(float base); + + @Contract(value = "_ -> this", mutates = "this") + DamageReduction.Builder factor(float factor); + } + +} diff --git a/paper-api/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/ItemDamageFunction.java b/paper-api/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/ItemDamageFunction.java new file mode 100644 index 0000000000..965b12fc66 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/ItemDamageFunction.java @@ -0,0 +1,71 @@ +package io.papermc.paper.datacomponent.item.blocksattacks; + +import io.papermc.paper.datacomponent.DataComponentBuilder; +import org.checkerframework.checker.index.qual.NonNegative; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jspecify.annotations.NullMarked; + +/** + * Hold how much damage should be applied to the item from a given attack. + * + * @see io.papermc.paper.datacomponent.DataComponentTypes#BLOCKS_ATTACKS + * @see io.papermc.paper.datacomponent.item.BlocksAttacks#itemDamage() + */ +@NullMarked +@ApiStatus.Experimental +@ApiStatus.NonExtendable +public interface ItemDamageFunction { + + @Contract(value = "-> new", pure = true) + static ItemDamageFunction.Builder itemDamageFunction() { + return BlocksAttacksBridge.bridge().blocksAttacksItemDamageFunction(); + } + + /** + * Get the minimum amount of damage dealt by the attack before item damage is applied to the item. + * + * @return the threshold + */ + @NonNegative + float threshold(); + + /** + * Get the constant amount of damage applied to the item, if threshold is passed. + * + * @return the base + */ + float base(); + + /** + * Get the fraction of the dealt damage that should be applied to the item, if threshold is passed. + * + * @return the base + */ + float factor(); + + /** + * Get the damage to apply for the item. + * + * @apiNote this doesn't apply enchantments like {@link org.bukkit.enchantments.Enchantment#UNBREAKING}} + * @return the damage to apply + */ + int damageToApply(float damage); + + /** + * Builder for {@link ItemDamageFunction}. + */ + @ApiStatus.Experimental + @ApiStatus.NonExtendable + interface Builder extends DataComponentBuilder { + + @Contract(value = "_ -> this", mutates = "this") + ItemDamageFunction.Builder threshold(@NonNegative final float threshold); + + @Contract(value = "_ -> this", mutates = "this") + ItemDamageFunction.Builder base(final float base); + + @Contract(value = "_ -> this", mutates = "this") + ItemDamageFunction.Builder factor(final float factor); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/datacomponent/item/PaperBlocksAttacks.java b/paper-server/src/main/java/io/papermc/paper/datacomponent/item/PaperBlocksAttacks.java index 1748518bbc..b4b9df9d85 100644 --- a/paper-server/src/main/java/io/papermc/paper/datacomponent/item/PaperBlocksAttacks.java +++ b/paper-server/src/main/java/io/papermc/paper/datacomponent/item/PaperBlocksAttacks.java @@ -2,8 +2,13 @@ package io.papermc.paper.datacomponent.item; import com.google.common.base.Preconditions; import io.papermc.paper.adventure.PaperAdventure; +import io.papermc.paper.datacomponent.item.blocksattacks.DamageReduction; +import io.papermc.paper.datacomponent.item.blocksattacks.ItemDamageFunction; +import io.papermc.paper.datacomponent.item.blocksattacks.PaperDamageReduction; +import io.papermc.paper.datacomponent.item.blocksattacks.PaperItemDamageFunction; import io.papermc.paper.registry.PaperRegistries; import io.papermc.paper.registry.tag.TagKey; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import net.kyori.adventure.key.Key; @@ -30,6 +35,16 @@ public record PaperBlocksAttacks( return this.impl.disableCooldownScale(); } + @Override + public List damageReductions() { + return this.impl.damageReductions().stream().map(PaperDamageReduction::new).map(paperDamageReduction -> ((DamageReduction) paperDamageReduction)).toList(); + } + + @Override + public ItemDamageFunction itemDamage() { + return new PaperItemDamageFunction(this.impl.itemDamage()); + } + @Override public @Nullable TagKey bypassedBy() { final Optional> tagKey = this.impl.bypassedBy().map(PaperRegistries::fromNms); @@ -50,8 +65,8 @@ public record PaperBlocksAttacks( private float blockDelaySeconds; private float disableCooldownScale = 1.0F; - //private List damageReductions = List.of(); - //private ItemDamageFunction itemDamage = ItemDamageFunction.DEFAULT; + private List damageReductions = new ArrayList<>(); + private ItemDamageFunction itemDamage = new PaperItemDamageFunction(net.minecraft.world.item.component.BlocksAttacks.ItemDamageFunction.DEFAULT); private @Nullable TagKey bypassedBy; private @Nullable Key blockSound; private @Nullable Key disableSound; @@ -70,15 +85,18 @@ public record PaperBlocksAttacks( return this; } - //@Override - //public Builder addDamageReduction(final DamageReduction reduction) { - // return null; - //} + @Override + public Builder addDamageReduction(final DamageReduction reduction) { + Preconditions.checkArgument(reduction.horizontalBlockingAngle() >= 0, "horizontalBlockingAngle must be non-negative, was %s", reduction.horizontalBlockingAngle()); + this.damageReductions.add(reduction); + return this; + } - //@Override - //public Builder itemDamage(final ItemDamageFunction function) { - // return null; - //} + @Override + public Builder itemDamage(final ItemDamageFunction function) { + this.itemDamage = function; + return this; + } @Override public Builder bypassedBy(@Nullable final TagKey bypassedBy) { @@ -98,18 +116,19 @@ public record PaperBlocksAttacks( return this; } - //@Override - //public Builder damageReductions(final List reductions) { - // return null; - //} + @Override + public Builder damageReductions(final List reductions) { + this.damageReductions = new ArrayList<>(reductions); + return this; + } @Override public BlocksAttacks build() { return new PaperBlocksAttacks(new net.minecraft.world.item.component.BlocksAttacks( this.blockDelaySeconds, this.disableCooldownScale, - List.of(), // TODO - net.minecraft.world.item.component.BlocksAttacks.ItemDamageFunction.DEFAULT, // TODO + this.damageReductions.stream().map(damageReduction -> ((PaperDamageReduction) damageReduction).getHandle()).toList(), + ((PaperItemDamageFunction) itemDamage).getHandle(), Optional.ofNullable(this.bypassedBy).map(PaperRegistries::toNms), Optional.ofNullable(this.blockSound).map(PaperAdventure::resolveSound), Optional.ofNullable(this.disableSound).map(PaperAdventure::resolveSound) diff --git a/paper-server/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/BlocksAttacksBridgeImpl.java b/paper-server/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/BlocksAttacksBridgeImpl.java new file mode 100644 index 0000000000..865563be36 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/BlocksAttacksBridgeImpl.java @@ -0,0 +1,19 @@ +package io.papermc.paper.datacomponent.item.blocksattacks; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +@ApiStatus.Internal +@NullMarked +public class BlocksAttacksBridgeImpl implements BlocksAttacksBridge { + + @Override + public DamageReduction.Builder blocksAttacksDamageReduction() { + return new PaperDamageReduction.BuilderImpl(); + } + + @Override + public ItemDamageFunction.Builder blocksAttacksItemDamageFunction() { + return new PaperItemDamageFunction.BuilderImpl(); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/PaperDamageReduction.java b/paper-server/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/PaperDamageReduction.java new file mode 100644 index 0000000000..7da287938e --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/PaperDamageReduction.java @@ -0,0 +1,87 @@ +package io.papermc.paper.datacomponent.item.blocksattacks; + +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 net.minecraft.core.HolderSet; +import net.minecraft.core.registries.Registries; +import org.bukkit.craftbukkit.util.Handleable; +import org.bukkit.damage.DamageType; +import org.checkerframework.checker.index.qual.Positive; +import org.jetbrains.annotations.Nullable; +import java.util.Optional; + +public record PaperDamageReduction( + net.minecraft.world.item.component.BlocksAttacks.DamageReduction impl +) implements DamageReduction, Handleable { + + @Override + public net.minecraft.world.item.component.BlocksAttacks.DamageReduction getHandle() { + return this.impl; + } + + @Override + public @Nullable RegistryKeySet type() { + return this.impl.type().map((set) -> PaperRegistrySets.convertToApi(RegistryKey.DAMAGE_TYPE, set)).orElse(null); + } + + @Override + public @Positive float horizontalBlockingAngle() { + return this.impl.horizontalBlockingAngle(); + } + + @Override + public float base() { + return this.impl.base(); + } + + @Override + public float factor() { + return this.impl.factor(); + } + + static final class BuilderImpl implements Builder { + + private Optional> type = Optional.empty(); + private float horizontalBlockingAngle = 90f; + private float base = 0; + private float factor = 0; + + @Override + public Builder type(final @Nullable RegistryKeySet type) { + this.type = Optional.ofNullable(type) + .map((set) -> PaperRegistrySets.convertToNms(Registries.DAMAGE_TYPE, net.minecraft.server.MinecraftServer.getServer().registryAccess().createSerializationContext(net.minecraft.nbt.NbtOps.INSTANCE).lookupProvider, set)); + return this; + } + + @Override + public Builder horizontalBlockingAngle(@Positive final float horizontalBlockingAngle) { + Preconditions.checkArgument(horizontalBlockingAngle > 0, "horizontalBlockingAngle must be positive and not zero, was %s", horizontalBlockingAngle); + this.horizontalBlockingAngle = horizontalBlockingAngle; + return this; + } + + @Override + public Builder base(final float base) { + this.base = base; + return this; + } + + @Override + public Builder factor(final float factor) { + this.factor = factor; + return this; + } + + @Override + public DamageReduction build() { + return new PaperDamageReduction(new net.minecraft.world.item.component.BlocksAttacks.DamageReduction( + this.horizontalBlockingAngle, + this.type, + this.base, + this.factor + )); + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/PaperItemDamageFunction.java b/paper-server/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/PaperItemDamageFunction.java new file mode 100644 index 0000000000..515eec1599 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/PaperItemDamageFunction.java @@ -0,0 +1,70 @@ +package io.papermc.paper.datacomponent.item.blocksattacks; + +import com.google.common.base.Preconditions; +import org.bukkit.craftbukkit.util.Handleable; +import org.checkerframework.checker.index.qual.NonNegative; + +public record PaperItemDamageFunction( + net.minecraft.world.item.component.BlocksAttacks.ItemDamageFunction impl +) implements ItemDamageFunction, Handleable { + + @Override + public net.minecraft.world.item.component.BlocksAttacks.ItemDamageFunction getHandle() { + return this.impl; + } + + @Override + public @NonNegative float threshold() { + return this.impl.threshold(); + } + + @Override + public float base() { + return this.impl.base(); + } + + @Override + public float factor() { + return this.impl.factor(); + } + + @Override + public int damageToApply(final float damage) { + return this.impl.apply(damage); + } + + static final class BuilderImpl implements Builder { + + private float threshold; + private float base; + private float factor; + + @Override + public Builder threshold(@NonNegative final float threshold) { + Preconditions.checkArgument(threshold >= 0, "threshold must be non-negative, was %s", threshold); + this.threshold = threshold; + return this; + } + + @Override + public Builder base(final float base) { + this.base = base; + return this; + } + + @Override + public Builder factor(final float factor) { + this.factor = factor; + return this; + } + + @Override + public ItemDamageFunction build() { + return new PaperItemDamageFunction(new net.minecraft.world.item.component.BlocksAttacks.ItemDamageFunction( + this.threshold, + this.base, + this.factor + )); + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/package-info.java b/paper-server/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/package-info.java new file mode 100644 index 0000000000..d0fca5341f --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/package-info.java @@ -0,0 +1,7 @@ +/** + * Relating to block attacks for components. + */ +@NullMarked +package io.papermc.paper.datacomponent.item.blocksattacks; + +import org.jspecify.annotations.NullMarked; diff --git a/paper-server/src/main/resources/META-INF/services/io.papermc.paper.datacomponent.item.blocksattacks.BlocksAttacksBridge b/paper-server/src/main/resources/META-INF/services/io.papermc.paper.datacomponent.item.blocksattacks.BlocksAttacksBridge new file mode 100644 index 0000000000..02e0ef0649 --- /dev/null +++ b/paper-server/src/main/resources/META-INF/services/io.papermc.paper.datacomponent.item.blocksattacks.BlocksAttacksBridge @@ -0,0 +1 @@ +io.papermc.paper.datacomponent.item.blocksattacks.BlocksAttacksBridgeImpl