From ee96e278201636096a237ea40d542dcafcf38f06 Mon Sep 17 00:00:00 2001 From: MiniDigger | Martin Date: Fri, 11 Sep 2026 20:31:42 +0200 Subject: [PATCH 1/3] Add Cooking and Brewing Fuel Item Components --- .../datacomponent/DataComponentTypes.java | 10 ++++ .../paper/datacomponent/item/BrewingFuel.java | 58 +++++++++++++++++++ .../paper/datacomponent/item/CookingFuel.java | 58 +++++++++++++++++++ .../item/ItemComponentTypesBridge.java | 4 ++ .../paper/loot/number/ResolvableFloat.java | 35 +++++++++++ .../paper/loot/number/ResolvableInt.java | 35 +++++++++++ .../LootContextParamSets.java.patch | 42 ++++++++++++++ .../number/floats/ResolvableFloat.java.patch | 15 +++++ .../number/ints/ResolvableInt.java.patch | 15 +++++ .../datacomponent/DataComponentAdapters.java | 4 ++ .../item/ItemComponentTypesBridgesImpl.java | 9 +++ .../datacomponent/item/PaperBrewingFuel.java | 55 ++++++++++++++++++ .../datacomponent/item/PaperCookingFuel.java | 55 ++++++++++++++++++ .../loot/number/PaperResolvableFloat.java | 40 +++++++++++++ .../paper/loot/number/PaperResolvableInt.java | 40 +++++++++++++ .../bukkit/craftbukkit/CraftLootTable.java | 46 +++++++++------ .../inventory/CraftInventoryFurnace.java | 4 +- 17 files changed, 505 insertions(+), 20 deletions(-) create mode 100644 paper-api/src/main/java/io/papermc/paper/datacomponent/item/BrewingFuel.java create mode 100644 paper-api/src/main/java/io/papermc/paper/datacomponent/item/CookingFuel.java create mode 100644 paper-api/src/main/java/io/papermc/paper/loot/number/ResolvableFloat.java create mode 100644 paper-api/src/main/java/io/papermc/paper/loot/number/ResolvableInt.java create mode 100644 paper-server/patches/sources/net/minecraft/world/level/storage/loot/parameters/LootContextParamSets.java.patch create mode 100644 paper-server/patches/sources/net/minecraft/world/level/storage/loot/providers/number/floats/ResolvableFloat.java.patch create mode 100644 paper-server/patches/sources/net/minecraft/world/level/storage/loot/providers/number/ints/ResolvableInt.java.patch create mode 100644 paper-server/src/main/java/io/papermc/paper/datacomponent/item/PaperBrewingFuel.java create mode 100644 paper-server/src/main/java/io/papermc/paper/datacomponent/item/PaperCookingFuel.java create mode 100644 paper-server/src/main/java/io/papermc/paper/loot/number/PaperResolvableFloat.java create mode 100644 paper-server/src/main/java/io/papermc/paper/loot/number/PaperResolvableInt.java diff --git a/paper-api/src/main/java/io/papermc/paper/datacomponent/DataComponentTypes.java b/paper-api/src/main/java/io/papermc/paper/datacomponent/DataComponentTypes.java index 6b0c37203bb6..a5411d9f3a83 100644 --- a/paper-api/src/main/java/io/papermc/paper/datacomponent/DataComponentTypes.java +++ b/paper-api/src/main/java/io/papermc/paper/datacomponent/DataComponentTypes.java @@ -6,9 +6,11 @@ import io.papermc.paper.datacomponent.item.BannerPatternLayers; import io.papermc.paper.datacomponent.item.BlockItemDataProperties; import io.papermc.paper.datacomponent.item.BlocksAttacks; +import io.papermc.paper.datacomponent.item.BrewingFuel; import io.papermc.paper.datacomponent.item.BundleContents; import io.papermc.paper.datacomponent.item.ChargedProjectiles; import io.papermc.paper.datacomponent.item.Consumable; +import io.papermc.paper.datacomponent.item.CookingFuel; import io.papermc.paper.datacomponent.item.CustomModelData; import io.papermc.paper.datacomponent.item.DamageResistant; import io.papermc.paper.datacomponent.item.DeathProtection; @@ -401,6 +403,14 @@ public final class DataComponentTypes { public static final DataComponentType.Valued SIGN_TEXT_BACK = valued("sign_text_back"); public static final DataComponentType.NonValued WAXED = unvalued("waxed"); public static final DataComponentType.Valued CUSHION_COLOR = valued("cushion/color"); + /** + * Describes an item that can be used as fuel for a furnace, smoker or blast furnace. + */ + public static final DataComponentType.Valued COOKING_FUEL = valued("cooking_fuel"); + /** + * Describes an item that can be used as fuel for a brewing stand. + */ + public static final DataComponentType.Valued BREWING_FUEL = valued("brewing_fuel"); private static DataComponentType.NonValued unvalued(@KeyPattern.Value final String key) { final DataComponentType dataComponentType = Registry.DATA_COMPONENT_TYPE.getOrThrow(Key.key(Key.MINECRAFT_NAMESPACE, key)); diff --git a/paper-api/src/main/java/io/papermc/paper/datacomponent/item/BrewingFuel.java b/paper-api/src/main/java/io/papermc/paper/datacomponent/item/BrewingFuel.java new file mode 100644 index 000000000000..aa8d3d912467 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/datacomponent/item/BrewingFuel.java @@ -0,0 +1,58 @@ +package io.papermc.paper.datacomponent.item; + +import io.papermc.paper.datacomponent.DataComponentBuilder; +import io.papermc.paper.loot.number.ResolvableFloat; +import io.papermc.paper.loot.number.ResolvableInt; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jspecify.annotations.NullMarked; + +/** + * Describes an item that can be used as fuel for a brewing stand. + * + * @see io.papermc.paper.datacomponent.DataComponentTypes#BREWING_FUEL + */ +@NullMarked +@ApiStatus.NonExtendable +public interface BrewingFuel { + + @Contract(value = "-> new", pure = true) + static BrewingFuel.Builder brewingFuel() { + return ItemComponentTypesBridge.bridge().brewingFuel(); + } + + /** + * @return the number of times this fuel will brew before being consumed + */ + @Contract(pure = true) + ResolvableInt uses(); + + /** + * @return the speed of the brewing + */ + @Contract(pure = true) + ResolvableFloat speedMultiplier(); + + /** + * Builder for {@link BrewingFuel}. + */ + @ApiStatus.NonExtendable + interface Builder extends DataComponentBuilder { + + /** + * @param uses the number of times this fuel will brew before being consumed + * @return the builder for chaining + * @see #uses() + */ + @Contract(value = "_ -> this", mutates = "this") + Builder uses(int uses); + + /** + * @param speedMultiplier the speed of the brewing + * @return the builder for chaining + * @see #speedMultiplier() + */ + @Contract(value = "_ -> this", mutates = "this") + Builder speedMultiplier(float speedMultiplier); + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/datacomponent/item/CookingFuel.java b/paper-api/src/main/java/io/papermc/paper/datacomponent/item/CookingFuel.java new file mode 100644 index 000000000000..2882bc681516 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/datacomponent/item/CookingFuel.java @@ -0,0 +1,58 @@ +package io.papermc.paper.datacomponent.item; + +import io.papermc.paper.datacomponent.DataComponentBuilder; +import io.papermc.paper.loot.number.ResolvableFloat; +import io.papermc.paper.loot.number.ResolvableInt; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jspecify.annotations.NullMarked; + +/** + * Describes an item that can be used as fuel for a furnace, smoker or blast furnace. + * + * @see io.papermc.paper.datacomponent.DataComponentTypes#COOKING_FUEL + */ +@NullMarked +@ApiStatus.NonExtendable +public interface CookingFuel { + + @Contract(value = "-> new", pure = true) + static CookingFuel.Builder cookingFuel() { + return ItemComponentTypesBridge.bridge().cookingFuel(); + } + + /** + * @return the time, in ticks, for which this fuel will burn + */ + @Contract(pure = true) + ResolvableInt burnTime(); + + /** + * @return the speed of the cooking/smelting + */ + @Contract(pure = true) + ResolvableFloat speedMultiplier(); + + /** + * Builder for {@link CookingFuel}. + */ + @ApiStatus.NonExtendable + interface Builder extends DataComponentBuilder { + + /** + * @param speedMultiplier the speed of the cooking/smelting + * @return the builder for chaining + * @see #speedMultiplier() + */ + @Contract(value = "_ -> this", mutates = "this") + Builder speedMultiplier(float speedMultiplier); + + /** + * @param burnTime the time, in ticks, for which this fuel will burn + * @return the builder for chaining + * @see #speedMultiplier() + */ + @Contract(value = "_ -> this", mutates = "this") + Builder burnTime(int burnTime); + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/datacomponent/item/ItemComponentTypesBridge.java b/paper-api/src/main/java/io/papermc/paper/datacomponent/item/ItemComponentTypesBridge.java index c64f9084f6cb..b432c7e3fbe5 100644 --- a/paper-api/src/main/java/io/papermc/paper/datacomponent/item/ItemComponentTypesBridge.java +++ b/paper-api/src/main/java/io/papermc/paper/datacomponent/item/ItemComponentTypesBridge.java @@ -131,4 +131,8 @@ static ItemComponentTypesBridge bridge() { VillagerFood villagerFood(int nutrition); SignText.Builder signText(); + + BrewingFuel.Builder brewingFuel(); + + CookingFuel.Builder cookingFuel(); } diff --git a/paper-api/src/main/java/io/papermc/paper/loot/number/ResolvableFloat.java b/paper-api/src/main/java/io/papermc/paper/loot/number/ResolvableFloat.java new file mode 100644 index 000000000000..6c3355cb9ae2 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/loot/number/ResolvableFloat.java @@ -0,0 +1,35 @@ +package io.papermc.paper.loot.number; + +import org.bukkit.loot.LootContext; +import org.jetbrains.annotations.ApiStatus; + +/** + * Represents a floating point number that can be resolved against a {@link LootContext}. + */ +@ApiStatus.Experimental +public interface ResolvableFloat { + + /** + * Resolves this floating point number against the given loot context. + * + * @param context the loot context to resolve against. + * @param defaultValue the default value + * @return the resolved number + */ + float resolve(LootContext context, float defaultValue); + + /** + * Represents a constant {@link ResolvableFloat}. + */ + interface Constant extends ResolvableFloat { + + /** + * @return the constant value + */ + float getValue(); + + default float resolve(LootContext context, float defaultValue) { + return getValue(); + } + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/loot/number/ResolvableInt.java b/paper-api/src/main/java/io/papermc/paper/loot/number/ResolvableInt.java new file mode 100644 index 000000000000..826532042992 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/loot/number/ResolvableInt.java @@ -0,0 +1,35 @@ +package io.papermc.paper.loot.number; + +import org.bukkit.loot.LootContext; +import org.jetbrains.annotations.ApiStatus; + +/** + * Represents an integer number that can be resolved against a {@link LootContext}. + */ +@ApiStatus.Experimental +public interface ResolvableInt { + + /** + * Resolves this integer number against the given loot context. + * + * @param context the loot context to resolve against. + * @param defaultValue the default value + * @return the resolved number + */ + int resolve(LootContext context, int defaultValue); + + /** + * Represents a constant {@link ResolvableInt}. + */ + interface Constant extends ResolvableInt { + + /** + * @return the constant value + */ + int getValue(); + + default int resolve(LootContext context, int defaultValue) { + return getValue(); + } + } +} diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/loot/parameters/LootContextParamSets.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/parameters/LootContextParamSets.java.patch new file mode 100644 index 000000000000..cca6e2460165 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/parameters/LootContextParamSets.java.patch @@ -0,0 +1,42 @@ +--- a/net/minecraft/world/level/storage/loot/parameters/LootContextParamSets.java ++++ b/net/minecraft/world/level/storage/loot/parameters/LootContextParamSets.java +@@ -35,6 +_,27 @@ + .required(LootContextParams.ENCHANTMENT_ACTIVE) + .required(LootContextParams.ENCHANTMENT_LEVEL) + ); ++ // Paper start ++ public static final ContextKeySet ALL_PARAMS_OPTIONAL = register( ++ "generic_optional", ++ builder -> builder.optional(LootContextParams.THIS_ENTITY) ++ .optional(LootContextParams.LAST_DAMAGE_PLAYER) ++ .optional(LootContextParams.DAMAGE_SOURCE) ++ .optional(LootContextParams.ATTACKING_ENTITY) ++ .optional(LootContextParams.DIRECT_ATTACKING_ENTITY) ++ .optional(LootContextParams.ORIGIN) ++ .optional(LootContextParams.BLOCK_STATE) ++ .optional(LootContextParams.BLOCK_ENTITY) ++ .optional(LootContextParams.TOOL) ++ .optional(LootContextParams.EXPLOSION_RADIUS) ++ .optional(LootContextParams.ADDITIONAL_COST_COMPONENT_ALLOWED) ++ .optional(LootContextParams.CONTAINER) ++ .optional(LootContextParams.INTERACTING_ENTITY) ++ .optional(LootContextParams.TARGET_ENTITY) ++ .optional(LootContextParams.ENCHANTMENT_ACTIVE) ++ .optional(LootContextParams.ENCHANTMENT_LEVEL) ++ ); ++ // Paper end + public static final ContextKeySet CHEST = register("chest", builder -> builder.required(LootContextParams.ORIGIN).optional(LootContextParams.THIS_ENTITY)); + public static final ContextKeySet COMMAND = register( + "command", builder -> builder.required(LootContextParams.ORIGIN).optional(LootContextParams.THIS_ENTITY) +@@ -194,5 +_,11 @@ + if (!missingFromAllParams.isEmpty()) { + throw new IllegalStateException("Missing parameters from 'all_params': " + missingFromAllParams); + } ++ // Paper start ++ Set> missingFromAllParamsOptional = Sets.difference(allParams, ALL_PARAMS_OPTIONAL.allowed()); ++ if (!missingFromAllParamsOptional.isEmpty()) { ++ throw new IllegalStateException("Missing parameters from 'all_params_optional': " + missingFromAllParamsOptional); ++ } ++ // Paper end + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/loot/providers/number/floats/ResolvableFloat.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/providers/number/floats/ResolvableFloat.java.patch new file mode 100644 index 000000000000..35f386fdfc6c --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/providers/number/floats/ResolvableFloat.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/world/level/storage/loot/providers/number/floats/ResolvableFloat.java ++++ b/net/minecraft/world/level/storage/loot/providers/number/floats/ResolvableFloat.java +@@ -53,6 +_,12 @@ + public float get(final LootContext context, final float defaultValue) { + return this.value; + } ++ ++ // Paper start - convenience ++ public float getValue() { ++ return this.value; ++ } ++ // Paper end + } + + record Reference(ResourceKey key) implements ResolvableFloat { diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/loot/providers/number/ints/ResolvableInt.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/providers/number/ints/ResolvableInt.java.patch new file mode 100644 index 000000000000..84991f47dda3 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/providers/number/ints/ResolvableInt.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/world/level/storage/loot/providers/number/ints/ResolvableInt.java ++++ b/net/minecraft/world/level/storage/loot/providers/number/ints/ResolvableInt.java +@@ -53,6 +_,12 @@ + public int get(final LootContext context, final int defaultValue) { + return this.value; + } ++ ++ // Paper start - convenience ++ public int getValue() { ++ return this.value; ++ } ++ // Paper end + } + + record Reference(ResourceKey key) implements ResolvableInt { diff --git a/paper-server/src/main/java/io/papermc/paper/datacomponent/DataComponentAdapters.java b/paper-server/src/main/java/io/papermc/paper/datacomponent/DataComponentAdapters.java index 5e4c51049e03..4c45b2404611 100644 --- a/paper-server/src/main/java/io/papermc/paper/datacomponent/DataComponentAdapters.java +++ b/paper-server/src/main/java/io/papermc/paper/datacomponent/DataComponentAdapters.java @@ -6,9 +6,11 @@ import io.papermc.paper.datacomponent.item.PaperBannerPatternLayers; import io.papermc.paper.datacomponent.item.PaperBlockItemDataProperties; import io.papermc.paper.datacomponent.item.PaperBlocksAttacks; +import io.papermc.paper.datacomponent.item.PaperBrewingFuel; 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.PaperCookingFuel; import io.papermc.paper.datacomponent.item.PaperCustomModelData; import io.papermc.paper.datacomponent.item.PaperDamageResistant; import io.papermc.paper.datacomponent.item.PaperDeathProtection; @@ -226,6 +228,8 @@ public static void bootstrap() { register(DataComponents.SIGN_TEXT_BACK, PaperSignText::new); registerUntyped(DataComponents.WAXED); register(DataComponents.CUSHION_COLOR, nms -> DyeColor.getByWoolData((byte) nms.getId()), api -> net.minecraft.world.item.DyeColor.byId(api.getWoolData())); + register(DataComponents.COOKING_FUEL, PaperCookingFuel::new); + register(DataComponents.BREWING_FUEL, PaperBrewingFuel::new); for (final ResourceKey> key : BuiltInRegistries.DATA_COMPONENT_TYPE.registryKeySet()) { if (!ADAPTERS.containsKey(key)) { diff --git a/paper-server/src/main/java/io/papermc/paper/datacomponent/item/ItemComponentTypesBridgesImpl.java b/paper-server/src/main/java/io/papermc/paper/datacomponent/item/ItemComponentTypesBridgesImpl.java index 96f638ecbb42..f6dda389f71d 100644 --- a/paper-server/src/main/java/io/papermc/paper/datacomponent/item/ItemComponentTypesBridgesImpl.java +++ b/paper-server/src/main/java/io/papermc/paper/datacomponent/item/ItemComponentTypesBridgesImpl.java @@ -307,4 +307,13 @@ public VillagerFood villagerFood(final int nutrition) { public SignText.Builder signText() { return new PaperSignText.BuilderImpl(); } + @Override + public BrewingFuel.Builder brewingFuel() { + return new PaperBrewingFuel.BuilderImpl(); + } + + @Override + public CookingFuel.Builder cookingFuel() { + return new PaperCookingFuel.BuilderImpl(); + } } diff --git a/paper-server/src/main/java/io/papermc/paper/datacomponent/item/PaperBrewingFuel.java b/paper-server/src/main/java/io/papermc/paper/datacomponent/item/PaperBrewingFuel.java new file mode 100644 index 000000000000..2433ab179f6a --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/datacomponent/item/PaperBrewingFuel.java @@ -0,0 +1,55 @@ +package io.papermc.paper.datacomponent.item; + +import io.papermc.paper.loot.number.PaperResolvableFloat; +import io.papermc.paper.loot.number.PaperResolvableInt; +import net.minecraft.world.level.storage.loot.providers.number.floats.ResolvableFloat; +import net.minecraft.world.level.storage.loot.providers.number.ints.ResolvableInt; +import org.bukkit.craftbukkit.util.Handleable; + +public record PaperBrewingFuel( + net.minecraft.world.item.component.BrewingFuel impl +) implements BrewingFuel, Handleable { + + @Override + public net.minecraft.world.item.component.BrewingFuel getHandle() { + return this.impl; + } + + @Override + public io.papermc.paper.loot.number.ResolvableInt uses() { + return PaperResolvableInt.fromVanilla(this.impl.uses()); + } + + @Override + public io.papermc.paper.loot.number.ResolvableFloat speedMultiplier() { + return PaperResolvableFloat.fromVanilla(this.impl.speedMultiplier()); + } + + static final class BuilderImpl implements BrewingFuel.Builder { + + private int uses = 0; + private float speedMultiplier = 1.0F; + + @Override + public Builder uses(int uses) { + this.uses = uses; + return this; + } + + @Override + public Builder speedMultiplier(float speedMultiplier) { + this.speedMultiplier = speedMultiplier; + return this; + } + + @Override + public BrewingFuel build() { + return new PaperBrewingFuel( + new net.minecraft.world.item.component.BrewingFuel( + new ResolvableInt.Constant(this.uses), + new ResolvableFloat.Constant(this.speedMultiplier) + ) + ); + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/datacomponent/item/PaperCookingFuel.java b/paper-server/src/main/java/io/papermc/paper/datacomponent/item/PaperCookingFuel.java new file mode 100644 index 000000000000..b03e0b4b7fa5 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/datacomponent/item/PaperCookingFuel.java @@ -0,0 +1,55 @@ +package io.papermc.paper.datacomponent.item; + +import io.papermc.paper.loot.number.PaperResolvableFloat; +import io.papermc.paper.loot.number.PaperResolvableInt; +import net.minecraft.world.level.storage.loot.providers.number.floats.ResolvableFloat; +import net.minecraft.world.level.storage.loot.providers.number.ints.ResolvableInt; +import org.bukkit.craftbukkit.util.Handleable; + +public record PaperCookingFuel( + net.minecraft.world.item.component.CookingFuel impl +) implements CookingFuel, Handleable { + + @Override + public net.minecraft.world.item.component.CookingFuel getHandle() { + return this.impl; + } + + @Override + public io.papermc.paper.loot.number.ResolvableInt burnTime() { + return PaperResolvableInt.fromVanilla(this.impl.burnTime()); + } + + @Override + public io.papermc.paper.loot.number.ResolvableFloat speedMultiplier() { + return PaperResolvableFloat.fromVanilla(this.impl.speedMultiplier()); + } + + static final class BuilderImpl implements CookingFuel.Builder { + + private int burnTime = 0; + private float speedMultiplier = 1.0F; + + @Override + public Builder burnTime(final int burnTime) { + this.burnTime = burnTime; + return this; + } + + @Override + public Builder speedMultiplier(final float speedMultiplier) { + this.speedMultiplier = speedMultiplier; + return this; + } + + @Override + public CookingFuel build() { + return new PaperCookingFuel( + new net.minecraft.world.item.component.CookingFuel( + new ResolvableInt.Constant(this.burnTime), + new ResolvableFloat.Constant(this.speedMultiplier) + ) + ); + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/loot/number/PaperResolvableFloat.java b/paper-server/src/main/java/io/papermc/paper/loot/number/PaperResolvableFloat.java new file mode 100644 index 000000000000..88059fd013e9 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/loot/number/PaperResolvableFloat.java @@ -0,0 +1,40 @@ +package io.papermc.paper.loot.number; + +import java.util.Optional; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; +import org.bukkit.craftbukkit.CraftLootTable; +import org.bukkit.craftbukkit.util.Handleable; +import org.bukkit.loot.LootContext; +import org.jspecify.annotations.NonNull; + +public record PaperResolvableFloat( + net.minecraft.world.level.storage.loot.providers.number.floats.ResolvableFloat impl +) implements ResolvableFloat, Handleable { + + @Override + public float resolve(final LootContext context, final float defaultValue) { + LootParams lootParams = CraftLootTable.convertContext(context, LootContextParamSets.ALL_PARAMS_OPTIONAL, false); + net.minecraft.world.level.storage.loot.LootContext vanillaContext = new net.minecraft.world.level.storage.loot.LootContext.Builder(lootParams).create(Optional.empty()); + return this.impl.get(vanillaContext, defaultValue); + } + + @Override + public net.minecraft.world.level.storage.loot.providers.number.floats.@NonNull ResolvableFloat getHandle() { + return this.impl; + } + + public static ResolvableFloat fromVanilla(net.minecraft.world.level.storage.loot.providers.number.floats.ResolvableFloat vanilla) { + if (vanilla instanceof net.minecraft.world.level.storage.loot.providers.number.floats.ResolvableFloat.Constant constant) { + return new PaperResolvableFloat.Constant(constant.getValue()); + } + return new PaperResolvableFloat(vanilla); + } + + record Constant(float value) implements ResolvableFloat.Constant { + @Override + public float getValue() { + return this.value; + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/loot/number/PaperResolvableInt.java b/paper-server/src/main/java/io/papermc/paper/loot/number/PaperResolvableInt.java new file mode 100644 index 000000000000..df8a52b84b58 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/loot/number/PaperResolvableInt.java @@ -0,0 +1,40 @@ +package io.papermc.paper.loot.number; + +import java.util.Optional; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; +import org.bukkit.craftbukkit.CraftLootTable; +import org.bukkit.craftbukkit.util.Handleable; +import org.bukkit.loot.LootContext; +import org.jspecify.annotations.NonNull; + +public record PaperResolvableInt( + net.minecraft.world.level.storage.loot.providers.number.ints.ResolvableInt impl +) implements ResolvableInt, Handleable { + + @Override + public int resolve(final LootContext context, final int defaultValue) { + LootParams lootParams = CraftLootTable.convertContext(context, LootContextParamSets.ALL_PARAMS_OPTIONAL, false); + net.minecraft.world.level.storage.loot.LootContext vanillaContext = new net.minecraft.world.level.storage.loot.LootContext.Builder(lootParams).create(Optional.empty()); + return this.impl.get(vanillaContext, defaultValue); + } + + @Override + public net.minecraft.world.level.storage.loot.providers.number.ints.@NonNull ResolvableInt getHandle() { + return this.impl; + } + + public static ResolvableInt fromVanilla(net.minecraft.world.level.storage.loot.providers.number.ints.ResolvableInt vanilla) { + if (vanilla instanceof net.minecraft.world.level.storage.loot.providers.number.ints.ResolvableInt.Constant constant) { + return new Constant(constant.getValue()); + } + return new PaperResolvableInt(vanilla); + } + + public record Constant(int value) implements ResolvableInt.Constant { + @Override + public int getValue() { + return this.value; + } + } +} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java index 6dfa847c7fcc..1dad72008caa 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java @@ -5,6 +5,7 @@ import java.util.Collection; import java.util.List; import java.util.Random; +import net.minecraft.core.BlockPos; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.Identifier; @@ -14,6 +15,7 @@ import net.minecraft.world.Container; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.storage.loot.LootParams; import net.minecraft.world.level.storage.loot.LootTable; import net.minecraft.world.level.storage.loot.parameters.LootContextParams; @@ -21,6 +23,7 @@ import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.NamespacedKey; +import org.bukkit.craftbukkit.block.CraftBlockState; import org.bukkit.craftbukkit.entity.CraftEntity; import org.bukkit.craftbukkit.entity.CraftHumanEntity; import org.bukkit.craftbukkit.inventory.CraftInventory; @@ -69,7 +72,7 @@ public LootTable getHandle() { @Override public Collection populateLoot(Random random, LootContext context) { Preconditions.checkArgument(context != null, "LootContext cannot be null"); - LootParams nmsContext = this.convertContext(context); + LootParams nmsContext = convertContext(context, this.getHandle().getParamSet(), this.getHandle() == LootTable.EMPTY); List nmsItems = this.handle.getRandomItems(nmsContext, random == null ? null : new RandomSourceWrapper(random)); Collection bukkit = new ArrayList<>(nmsItems.size()); @@ -87,7 +90,7 @@ public Collection populateLoot(Random random, LootContext context) { public void fillInventory(Inventory inventory, Random random, LootContext context) { Preconditions.checkArgument(inventory != null, "Inventory cannot be null"); Preconditions.checkArgument(context != null, "LootContext cannot be null"); - LootParams nmsContext = this.convertContext(context); + LootParams nmsContext = convertContext(context, this.getHandle().getParamSet(), this.getHandle() == LootTable.EMPTY); CraftInventory craftInventory = (CraftInventory) inventory; Container handle = craftInventory.getInventory(); @@ -100,50 +103,57 @@ public NamespacedKey getKey() { return this.key; } - private LootParams convertContext(LootContext context) { + public static LootParams convertContext(LootContext context, ContextKeySet paramSet, boolean empty) { Preconditions.checkArgument(context != null, "LootContext cannot be null"); Location loc = context.getLocation(); Preconditions.checkArgument(loc.getWorld() != null, "LootContext.getLocation#getWorld cannot be null"); ServerLevel handle = ((CraftWorld) loc.getWorld()).getHandle(); LootParams.Builder builder = new LootParams.Builder(handle); - this.setMaybe(builder, LootContextParams.ORIGIN, CraftLocation.toVec3(loc)); - if (this.getHandle() != LootTable.EMPTY) { + setMaybe(builder, paramSet, LootContextParams.ORIGIN, CraftLocation.toVec3(loc)); + + BlockState blockState = ((CraftBlockState) loc.getBlock().getState()).getHandle(); + setMaybe(builder, paramSet, LootContextParams.BLOCK_STATE, blockState); + if (blockState.hasBlockEntity()) { + setMaybe(builder, paramSet, LootContextParams.BLOCK_ENTITY, handle.getBlockEntity(new BlockPos(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()))); + } + + if (!empty) { builder.withLuck(context.getLuck()); if (context.getLootedEntity() != null) { Entity nmsLootedEntity = ((CraftEntity) context.getLootedEntity()).getHandle(); - this.setMaybe(builder, LootContextParams.THIS_ENTITY, nmsLootedEntity); - this.setMaybe(builder, LootContextParams.DAMAGE_SOURCE, handle.damageSources().generic()); - this.setMaybe(builder, LootContextParams.ORIGIN, nmsLootedEntity.position()); + setMaybe(builder, paramSet, LootContextParams.THIS_ENTITY, nmsLootedEntity); + setMaybe(builder, paramSet, LootContextParams.DAMAGE_SOURCE, handle.damageSources().generic()); + setMaybe(builder, paramSet, LootContextParams.ORIGIN, nmsLootedEntity.position()); } if (context.getKiller() != null) { Player nmsKiller = ((CraftHumanEntity) context.getKiller()).getHandle(); - this.setMaybe(builder, LootContextParams.ATTACKING_ENTITY, nmsKiller); + setMaybe(builder, paramSet, LootContextParams.ATTACKING_ENTITY, nmsKiller); // If there is a player killer, damage source should reflect that in case loot tables use that information - this.setMaybe(builder, LootContextParams.DAMAGE_SOURCE, handle.damageSources().playerAttack(nmsKiller)); - this.setMaybe(builder, LootContextParams.LAST_DAMAGE_PLAYER, nmsKiller); // SPIGOT-5603 - Set minecraft:killed_by_player - this.setMaybe(builder, LootContextParams.TOOL, nmsKiller.getUseItem()); // SPIGOT-6925 - Set minecraft:match_tool + setMaybe(builder, paramSet, LootContextParams.DAMAGE_SOURCE, handle.damageSources().playerAttack(nmsKiller)); + setMaybe(builder, paramSet, LootContextParams.LAST_DAMAGE_PLAYER, nmsKiller); // SPIGOT-5603 - Set minecraft:killed_by_player + setMaybe(builder, paramSet, LootContextParams.TOOL, nmsKiller.getUseItem()); // SPIGOT-6925 - Set minecraft:match_tool } } // SPIGOT-5603 - Avoid IllegalArgumentException in ContextKeySet.Builder#create ContextKeySet.Builder nmsBuilder = new ContextKeySet.Builder(); - for (ContextKey param : this.getHandle().getParamSet().required()) { + for (ContextKey param : paramSet.required()) { nmsBuilder.required(param); } - for (ContextKey param : this.getHandle().getParamSet().allowed()) { - if (!this.getHandle().getParamSet().required().contains(param)) { + for (ContextKey param : paramSet.allowed()) { + if (!paramSet.required().contains(param)) { nmsBuilder.optional(param); } } - return builder.create(this.getHandle().getParamSet()); + return builder.create(paramSet); } - private void setMaybe(LootParams.Builder builder, ContextKey param, T value) { - if (this.getHandle().getParamSet().required().contains(param) || this.getHandle().getParamSet().allowed().contains(param)) { + private static void setMaybe(LootParams.Builder builder, ContextKeySet paramSet, ContextKey param, T value) { + if (paramSet.required().contains(param) || paramSet.allowed().contains(param)) { builder.withParameter(param, value); } } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java index 45f1191f7681..188488b36abf 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java @@ -1,6 +1,6 @@ package org.bukkit.craftbukkit.inventory; -import net.minecraft.core.component.DataComponents; +import io.papermc.paper.datacomponent.DataComponentTypes; import net.minecraft.world.item.crafting.SingleRecipeInput; import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity; import org.bukkit.block.Furnace; @@ -44,7 +44,7 @@ public void setSmelting(ItemStack stack) { @Override public boolean isFuel(ItemStack stack) { - return stack != null && !stack.isEmpty() && CraftItemStack.asNMSCopy(stack).has(DataComponents.COOKING_FUEL); + return stack != null && !stack.isEmpty() && stack.hasData(DataComponentTypes.COOKING_FUEL); } @Override From 772fdac635942e473641d4da82fa350029ee5292 Mon Sep 17 00:00:00 2001 From: Doc Date: Sat, 12 Sep 2026 12:28:34 -0300 Subject: [PATCH 2/3] Improvements - Remove reduntant annotations from components - Move annotations to package-info.java (and create missing) - Add NullMarked where missing - Remove convenience methods for ResolvableFloat/Int and improvement the pattern match - Move the logic for lootcontext to a proper Craft Class (mostly for visual... this is going to be changed when https://github.com/PaperMC/Paper/pull/12778) and a few changes because I don't like yellow marks... --- .../paper/datacomponent/item/BrewingFuel.java | 2 - .../paper/datacomponent/item/CookingFuel.java | 2 - .../paper/loot/number/ResolvableFloat.java | 4 +- .../paper/loot/number/ResolvableInt.java | 4 +- .../paper/loot/number/package-info.java | 9 ++ .../number/floats/ResolvableFloat.java.patch | 15 --- .../number/ints/ResolvableInt.java.patch | 15 --- .../loot/number/PaperResolvableFloat.java | 13 ++- .../paper/loot/number/PaperResolvableInt.java | 13 ++- .../paper/loot/number/package-info.java | 4 + .../bukkit/craftbukkit/CraftLootContext.java | 106 ++++++++++++++++++ .../bukkit/craftbukkit/CraftLootTable.java | 96 +--------------- .../craftbukkit/event/CraftEventFactory.java | 4 +- 13 files changed, 139 insertions(+), 148 deletions(-) create mode 100644 paper-api/src/main/java/io/papermc/paper/loot/number/package-info.java delete mode 100644 paper-server/patches/sources/net/minecraft/world/level/storage/loot/providers/number/floats/ResolvableFloat.java.patch delete mode 100644 paper-server/patches/sources/net/minecraft/world/level/storage/loot/providers/number/ints/ResolvableInt.java.patch create mode 100644 paper-server/src/main/java/io/papermc/paper/loot/number/package-info.java create mode 100644 paper-server/src/main/java/org/bukkit/craftbukkit/CraftLootContext.java diff --git a/paper-api/src/main/java/io/papermc/paper/datacomponent/item/BrewingFuel.java b/paper-api/src/main/java/io/papermc/paper/datacomponent/item/BrewingFuel.java index aa8d3d912467..c612673c11c5 100644 --- a/paper-api/src/main/java/io/papermc/paper/datacomponent/item/BrewingFuel.java +++ b/paper-api/src/main/java/io/papermc/paper/datacomponent/item/BrewingFuel.java @@ -5,14 +5,12 @@ import io.papermc.paper.loot.number.ResolvableInt; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Contract; -import org.jspecify.annotations.NullMarked; /** * Describes an item that can be used as fuel for a brewing stand. * * @see io.papermc.paper.datacomponent.DataComponentTypes#BREWING_FUEL */ -@NullMarked @ApiStatus.NonExtendable public interface BrewingFuel { diff --git a/paper-api/src/main/java/io/papermc/paper/datacomponent/item/CookingFuel.java b/paper-api/src/main/java/io/papermc/paper/datacomponent/item/CookingFuel.java index 2882bc681516..e1692df0b682 100644 --- a/paper-api/src/main/java/io/papermc/paper/datacomponent/item/CookingFuel.java +++ b/paper-api/src/main/java/io/papermc/paper/datacomponent/item/CookingFuel.java @@ -5,14 +5,12 @@ import io.papermc.paper.loot.number.ResolvableInt; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Contract; -import org.jspecify.annotations.NullMarked; /** * Describes an item that can be used as fuel for a furnace, smoker or blast furnace. * * @see io.papermc.paper.datacomponent.DataComponentTypes#COOKING_FUEL */ -@NullMarked @ApiStatus.NonExtendable public interface CookingFuel { diff --git a/paper-api/src/main/java/io/papermc/paper/loot/number/ResolvableFloat.java b/paper-api/src/main/java/io/papermc/paper/loot/number/ResolvableFloat.java index 6c3355cb9ae2..93b56a43c75d 100644 --- a/paper-api/src/main/java/io/papermc/paper/loot/number/ResolvableFloat.java +++ b/paper-api/src/main/java/io/papermc/paper/loot/number/ResolvableFloat.java @@ -1,12 +1,10 @@ package io.papermc.paper.loot.number; import org.bukkit.loot.LootContext; -import org.jetbrains.annotations.ApiStatus; /** * Represents a floating point number that can be resolved against a {@link LootContext}. */ -@ApiStatus.Experimental public interface ResolvableFloat { /** @@ -29,7 +27,7 @@ interface Constant extends ResolvableFloat { float getValue(); default float resolve(LootContext context, float defaultValue) { - return getValue(); + return this.getValue(); } } } diff --git a/paper-api/src/main/java/io/papermc/paper/loot/number/ResolvableInt.java b/paper-api/src/main/java/io/papermc/paper/loot/number/ResolvableInt.java index 826532042992..8bcbe19cca73 100644 --- a/paper-api/src/main/java/io/papermc/paper/loot/number/ResolvableInt.java +++ b/paper-api/src/main/java/io/papermc/paper/loot/number/ResolvableInt.java @@ -1,12 +1,10 @@ package io.papermc.paper.loot.number; import org.bukkit.loot.LootContext; -import org.jetbrains.annotations.ApiStatus; /** * Represents an integer number that can be resolved against a {@link LootContext}. */ -@ApiStatus.Experimental public interface ResolvableInt { /** @@ -29,7 +27,7 @@ interface Constant extends ResolvableInt { int getValue(); default int resolve(LootContext context, int defaultValue) { - return getValue(); + return this.getValue(); } } } diff --git a/paper-api/src/main/java/io/papermc/paper/loot/number/package-info.java b/paper-api/src/main/java/io/papermc/paper/loot/number/package-info.java new file mode 100644 index 000000000000..9ba7efccbe91 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/loot/number/package-info.java @@ -0,0 +1,9 @@ +/** + * Package containing maths-related API, including position API. + */ +@NullMarked +@ApiStatus.Experimental +package io.papermc.paper.loot.number; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/loot/providers/number/floats/ResolvableFloat.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/providers/number/floats/ResolvableFloat.java.patch deleted file mode 100644 index 35f386fdfc6c..000000000000 --- a/paper-server/patches/sources/net/minecraft/world/level/storage/loot/providers/number/floats/ResolvableFloat.java.patch +++ /dev/null @@ -1,15 +0,0 @@ ---- a/net/minecraft/world/level/storage/loot/providers/number/floats/ResolvableFloat.java -+++ b/net/minecraft/world/level/storage/loot/providers/number/floats/ResolvableFloat.java -@@ -53,6 +_,12 @@ - public float get(final LootContext context, final float defaultValue) { - return this.value; - } -+ -+ // Paper start - convenience -+ public float getValue() { -+ return this.value; -+ } -+ // Paper end - } - - record Reference(ResourceKey key) implements ResolvableFloat { diff --git a/paper-server/patches/sources/net/minecraft/world/level/storage/loot/providers/number/ints/ResolvableInt.java.patch b/paper-server/patches/sources/net/minecraft/world/level/storage/loot/providers/number/ints/ResolvableInt.java.patch deleted file mode 100644 index 84991f47dda3..000000000000 --- a/paper-server/patches/sources/net/minecraft/world/level/storage/loot/providers/number/ints/ResolvableInt.java.patch +++ /dev/null @@ -1,15 +0,0 @@ ---- a/net/minecraft/world/level/storage/loot/providers/number/ints/ResolvableInt.java -+++ b/net/minecraft/world/level/storage/loot/providers/number/ints/ResolvableInt.java -@@ -53,6 +_,12 @@ - public int get(final LootContext context, final int defaultValue) { - return this.value; - } -+ -+ // Paper start - convenience -+ public int getValue() { -+ return this.value; -+ } -+ // Paper end - } - - record Reference(ResourceKey key) implements ResolvableInt { diff --git a/paper-server/src/main/java/io/papermc/paper/loot/number/PaperResolvableFloat.java b/paper-server/src/main/java/io/papermc/paper/loot/number/PaperResolvableFloat.java index 88059fd013e9..f65c299939ff 100644 --- a/paper-server/src/main/java/io/papermc/paper/loot/number/PaperResolvableFloat.java +++ b/paper-server/src/main/java/io/papermc/paper/loot/number/PaperResolvableFloat.java @@ -3,10 +3,9 @@ import java.util.Optional; import net.minecraft.world.level.storage.loot.LootParams; import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; -import org.bukkit.craftbukkit.CraftLootTable; +import org.bukkit.craftbukkit.CraftLootContext; import org.bukkit.craftbukkit.util.Handleable; import org.bukkit.loot.LootContext; -import org.jspecify.annotations.NonNull; public record PaperResolvableFloat( net.minecraft.world.level.storage.loot.providers.number.floats.ResolvableFloat impl @@ -14,19 +13,21 @@ public record PaperResolvableFloat( @Override public float resolve(final LootContext context, final float defaultValue) { - LootParams lootParams = CraftLootTable.convertContext(context, LootContextParamSets.ALL_PARAMS_OPTIONAL, false); + LootParams lootParams = CraftLootContext.createLootParams(context, LootContextParamSets.ALL_PARAMS_OPTIONAL, false); net.minecraft.world.level.storage.loot.LootContext vanillaContext = new net.minecraft.world.level.storage.loot.LootContext.Builder(lootParams).create(Optional.empty()); return this.impl.get(vanillaContext, defaultValue); } @Override - public net.minecraft.world.level.storage.loot.providers.number.floats.@NonNull ResolvableFloat getHandle() { + public net.minecraft.world.level.storage.loot.providers.number.floats.ResolvableFloat getHandle() { return this.impl; } public static ResolvableFloat fromVanilla(net.minecraft.world.level.storage.loot.providers.number.floats.ResolvableFloat vanilla) { - if (vanilla instanceof net.minecraft.world.level.storage.loot.providers.number.floats.ResolvableFloat.Constant constant) { - return new PaperResolvableFloat.Constant(constant.getValue()); + if (vanilla instanceof net.minecraft.world.level.storage.loot.providers.number.floats.ResolvableFloat.Constant( + float value + )) { + return new PaperResolvableFloat.Constant(value); } return new PaperResolvableFloat(vanilla); } diff --git a/paper-server/src/main/java/io/papermc/paper/loot/number/PaperResolvableInt.java b/paper-server/src/main/java/io/papermc/paper/loot/number/PaperResolvableInt.java index df8a52b84b58..5994cb5e2f56 100644 --- a/paper-server/src/main/java/io/papermc/paper/loot/number/PaperResolvableInt.java +++ b/paper-server/src/main/java/io/papermc/paper/loot/number/PaperResolvableInt.java @@ -3,10 +3,9 @@ import java.util.Optional; import net.minecraft.world.level.storage.loot.LootParams; import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; -import org.bukkit.craftbukkit.CraftLootTable; +import org.bukkit.craftbukkit.CraftLootContext; import org.bukkit.craftbukkit.util.Handleable; import org.bukkit.loot.LootContext; -import org.jspecify.annotations.NonNull; public record PaperResolvableInt( net.minecraft.world.level.storage.loot.providers.number.ints.ResolvableInt impl @@ -14,19 +13,21 @@ public record PaperResolvableInt( @Override public int resolve(final LootContext context, final int defaultValue) { - LootParams lootParams = CraftLootTable.convertContext(context, LootContextParamSets.ALL_PARAMS_OPTIONAL, false); + LootParams lootParams = CraftLootContext.createLootParams(context, LootContextParamSets.ALL_PARAMS_OPTIONAL, false); net.minecraft.world.level.storage.loot.LootContext vanillaContext = new net.minecraft.world.level.storage.loot.LootContext.Builder(lootParams).create(Optional.empty()); return this.impl.get(vanillaContext, defaultValue); } @Override - public net.minecraft.world.level.storage.loot.providers.number.ints.@NonNull ResolvableInt getHandle() { + public net.minecraft.world.level.storage.loot.providers.number.ints.ResolvableInt getHandle() { return this.impl; } public static ResolvableInt fromVanilla(net.minecraft.world.level.storage.loot.providers.number.ints.ResolvableInt vanilla) { - if (vanilla instanceof net.minecraft.world.level.storage.loot.providers.number.ints.ResolvableInt.Constant constant) { - return new Constant(constant.getValue()); + if (vanilla instanceof net.minecraft.world.level.storage.loot.providers.number.ints.ResolvableInt.Constant( + int value + )) { + return new Constant(value); } return new PaperResolvableInt(vanilla); } diff --git a/paper-server/src/main/java/io/papermc/paper/loot/number/package-info.java b/paper-server/src/main/java/io/papermc/paper/loot/number/package-info.java new file mode 100644 index 000000000000..3d8b61c221e8 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/loot/number/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package io.papermc.paper.loot.number; + +import org.jspecify.annotations.NullMarked; diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftLootContext.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftLootContext.java new file mode 100644 index 000000000000..c5a5c7c0c07c --- /dev/null +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftLootContext.java @@ -0,0 +1,106 @@ +package org.bukkit.craftbukkit; + +import com.google.common.base.Preconditions; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.context.ContextKey; +import net.minecraft.util.context.ContextKeySet; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; +import net.minecraft.world.phys.Vec3; +import org.bukkit.Location; +import org.bukkit.craftbukkit.block.CraftBlockState; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.entity.CraftHumanEntity; +import org.bukkit.craftbukkit.util.CraftLocation; +import org.bukkit.loot.LootContext; + +public final class CraftLootContext { + + public static LootParams createLootParams(final LootContext context, final ContextKeySet paramSet, final boolean empty) { + Preconditions.checkArgument(context != null, "LootContext cannot be null"); + final Location loc = context.getLocation(); + Preconditions.checkArgument(loc.getWorld() != null, "LootContext.getLocation#getWorld cannot be null"); + final ServerLevel handle = ((CraftWorld) loc.getWorld()).getHandle(); + + final LootParams.Builder builder = new LootParams.Builder(handle); + setMaybe(builder, paramSet, LootContextParams.ORIGIN, CraftLocation.toVec3(loc)); + + final BlockState blockState = ((CraftBlockState) loc.getBlock().getState()).getHandle(); + setMaybe(builder, paramSet, LootContextParams.BLOCK_STATE, blockState); + if (blockState.hasBlockEntity()) { + setMaybe(builder, paramSet, LootContextParams.BLOCK_ENTITY, handle.getBlockEntity(CraftLocation.toBlockPos(loc))); + } + + if (!empty) { + builder.withLuck(context.getLuck()); + + if (context.getLootedEntity() instanceof CraftEntity craftLootedEntity) { + Entity nmsLootedEntity = craftLootedEntity.getHandle(); + setMaybe(builder, paramSet, LootContextParams.THIS_ENTITY, nmsLootedEntity); + setMaybe(builder, paramSet, LootContextParams.DAMAGE_SOURCE, handle.damageSources().generic()); + setMaybe(builder, paramSet, LootContextParams.ORIGIN, nmsLootedEntity.position()); + } + + if (context.getKiller() instanceof CraftHumanEntity craftHumanKiller) { + Player nmsKiller = craftHumanKiller.getHandle(); + setMaybe(builder, paramSet, LootContextParams.ATTACKING_ENTITY, nmsKiller); + // If there is a player killer, damage source should reflect that in case loot tables use that information + setMaybe(builder, paramSet, LootContextParams.DAMAGE_SOURCE, handle.damageSources().playerAttack(nmsKiller)); + setMaybe(builder, paramSet, LootContextParams.LAST_DAMAGE_PLAYER, nmsKiller); // SPIGOT-5603 - Set minecraft:killed_by_player + setMaybe(builder, paramSet, LootContextParams.TOOL, nmsKiller.getUseItem()); // SPIGOT-6925 - Set minecraft:match_tool + } + } + + // SPIGOT-5603 - Avoid IllegalArgumentException in ContextKeySet.Builder#create + final ContextKeySet.Builder nmsBuilder = new ContextKeySet.Builder(); + for (final ContextKey param : paramSet.required()) { + nmsBuilder.required(param); + } + for (final ContextKey param : paramSet.allowed()) { + if (!paramSet.required().contains(param)) { + nmsBuilder.optional(param); + } + } + + return builder.create(paramSet); + } + + private static void setMaybe(final LootParams.Builder builder, final ContextKeySet paramSet, final ContextKey param, final T value) { + if (paramSet.required().contains(param) || paramSet.allowed().contains(param)) { + builder.withParameter(param, value); + } + } + + public static LootContext toBukkit(final net.minecraft.world.level.storage.loot.LootContext info) { + final Entity entity = info.getOptional(LootContextParams.THIS_ENTITY); + + Vec3 position = info.getOptional(LootContextParams.ORIGIN); + // Every vanilla context has origin or this_entity, see LootContextParamSets but can be Optional so need to check for null + if (position == null && entity != null) { + position = entity.position(); + } + final Location location = CraftLocation.toBukkit(position, info.getLevel()); + + final LootContext.Builder contextBuilder = new LootContext.Builder(location); + + final Entity killer = info.getOptional(LootContextParams.ATTACKING_ENTITY); + if (killer != null && killer.getBukkitEntity() instanceof CraftHumanEntity craftHumanKiller) { + contextBuilder.killer(craftHumanKiller); + } + + if (entity != null) { + contextBuilder.lootedEntity(entity.getBukkitEntity()); + } + + contextBuilder.luck(info.getLuck()); + return contextBuilder.build(); + } + + private CraftLootContext() { + throw new UnsupportedOperationException("Cannot instantiate"); + } + +} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java index 1dad72008caa..5c210d355076 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java @@ -5,30 +5,16 @@ import java.util.Collection; import java.util.List; import java.util.Random; -import net.minecraft.core.BlockPos; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.Identifier; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.util.context.ContextKey; -import net.minecraft.util.context.ContextKeySet; import net.minecraft.world.Container; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.storage.loot.LootParams; import net.minecraft.world.level.storage.loot.LootTable; -import net.minecraft.world.level.storage.loot.parameters.LootContextParams; -import net.minecraft.world.phys.Vec3; import org.bukkit.Bukkit; -import org.bukkit.Location; import org.bukkit.NamespacedKey; -import org.bukkit.craftbukkit.block.CraftBlockState; -import org.bukkit.craftbukkit.entity.CraftEntity; -import org.bukkit.craftbukkit.entity.CraftHumanEntity; import org.bukkit.craftbukkit.inventory.CraftInventory; import org.bukkit.craftbukkit.inventory.CraftItemStack; -import org.bukkit.craftbukkit.util.CraftLocation; import org.bukkit.craftbukkit.util.CraftNamespacedKey; import org.bukkit.craftbukkit.util.RandomSourceWrapper; import org.bukkit.inventory.Inventory; @@ -72,7 +58,7 @@ public LootTable getHandle() { @Override public Collection populateLoot(Random random, LootContext context) { Preconditions.checkArgument(context != null, "LootContext cannot be null"); - LootParams nmsContext = convertContext(context, this.getHandle().getParamSet(), this.getHandle() == LootTable.EMPTY); + LootParams nmsContext = CraftLootContext.createLootParams(context, this.getHandle().getParamSet(), this.getHandle() == LootTable.EMPTY); List nmsItems = this.handle.getRandomItems(nmsContext, random == null ? null : new RandomSourceWrapper(random)); Collection bukkit = new ArrayList<>(nmsItems.size()); @@ -90,7 +76,7 @@ public Collection populateLoot(Random random, LootContext context) { public void fillInventory(Inventory inventory, Random random, LootContext context) { Preconditions.checkArgument(inventory != null, "Inventory cannot be null"); Preconditions.checkArgument(context != null, "LootContext cannot be null"); - LootParams nmsContext = convertContext(context, this.getHandle().getParamSet(), this.getHandle() == LootTable.EMPTY); + LootParams nmsContext = CraftLootContext.createLootParams(context, this.getHandle().getParamSet(), this.getHandle() == LootTable.EMPTY); CraftInventory craftInventory = (CraftInventory) inventory; Container handle = craftInventory.getInventory(); @@ -103,84 +89,6 @@ public NamespacedKey getKey() { return this.key; } - public static LootParams convertContext(LootContext context, ContextKeySet paramSet, boolean empty) { - Preconditions.checkArgument(context != null, "LootContext cannot be null"); - Location loc = context.getLocation(); - Preconditions.checkArgument(loc.getWorld() != null, "LootContext.getLocation#getWorld cannot be null"); - ServerLevel handle = ((CraftWorld) loc.getWorld()).getHandle(); - - LootParams.Builder builder = new LootParams.Builder(handle); - setMaybe(builder, paramSet, LootContextParams.ORIGIN, CraftLocation.toVec3(loc)); - - BlockState blockState = ((CraftBlockState) loc.getBlock().getState()).getHandle(); - setMaybe(builder, paramSet, LootContextParams.BLOCK_STATE, blockState); - if (blockState.hasBlockEntity()) { - setMaybe(builder, paramSet, LootContextParams.BLOCK_ENTITY, handle.getBlockEntity(new BlockPos(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()))); - } - - if (!empty) { - builder.withLuck(context.getLuck()); - - if (context.getLootedEntity() != null) { - Entity nmsLootedEntity = ((CraftEntity) context.getLootedEntity()).getHandle(); - setMaybe(builder, paramSet, LootContextParams.THIS_ENTITY, nmsLootedEntity); - setMaybe(builder, paramSet, LootContextParams.DAMAGE_SOURCE, handle.damageSources().generic()); - setMaybe(builder, paramSet, LootContextParams.ORIGIN, nmsLootedEntity.position()); - } - - if (context.getKiller() != null) { - Player nmsKiller = ((CraftHumanEntity) context.getKiller()).getHandle(); - setMaybe(builder, paramSet, LootContextParams.ATTACKING_ENTITY, nmsKiller); - // If there is a player killer, damage source should reflect that in case loot tables use that information - setMaybe(builder, paramSet, LootContextParams.DAMAGE_SOURCE, handle.damageSources().playerAttack(nmsKiller)); - setMaybe(builder, paramSet, LootContextParams.LAST_DAMAGE_PLAYER, nmsKiller); // SPIGOT-5603 - Set minecraft:killed_by_player - setMaybe(builder, paramSet, LootContextParams.TOOL, nmsKiller.getUseItem()); // SPIGOT-6925 - Set minecraft:match_tool - } - } - - // SPIGOT-5603 - Avoid IllegalArgumentException in ContextKeySet.Builder#create - ContextKeySet.Builder nmsBuilder = new ContextKeySet.Builder(); - for (ContextKey param : paramSet.required()) { - nmsBuilder.required(param); - } - for (ContextKey param : paramSet.allowed()) { - if (!paramSet.required().contains(param)) { - nmsBuilder.optional(param); - } - } - - return builder.create(paramSet); - } - - private static void setMaybe(LootParams.Builder builder, ContextKeySet paramSet, ContextKey param, T value) { - if (paramSet.required().contains(param) || paramSet.allowed().contains(param)) { - builder.withParameter(param, value); - } - } - - public static LootContext convertContext(net.minecraft.world.level.storage.loot.LootContext info) { - Vec3 position = info.getOptional(LootContextParams.ORIGIN); - if (position == null) { - position = info.getOptional(LootContextParams.THIS_ENTITY).position(); // Every vanilla context has origin or this_entity, see LootContextParamSets - } - Location location = CraftLocation.toBukkit(position, info.getLevel()); - LootContext.Builder contextBuilder = new LootContext.Builder(location); - - if (info.hasParameter(LootContextParams.ATTACKING_ENTITY)) { - CraftEntity killer = info.getOptional(LootContextParams.ATTACKING_ENTITY).getBukkitEntity(); - if (killer instanceof CraftHumanEntity) { - contextBuilder.killer((CraftHumanEntity) killer); - } - } - - if (info.hasParameter(LootContextParams.THIS_ENTITY)) { - contextBuilder.lootedEntity(info.getOptional(LootContextParams.THIS_ENTITY).getBukkitEntity()); - } - - contextBuilder.luck(info.getLuck()); - return contextBuilder.build(); - } - @Override public String toString() { return this.key.toString(); diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index 52b11c40b1f8..4ca4e155cf8f 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -98,7 +98,7 @@ import org.bukkit.craftbukkit.CraftEquipmentSlot; import org.bukkit.craftbukkit.CraftExplosionResult; import org.bukkit.craftbukkit.CraftGameRule; -import org.bukkit.craftbukkit.CraftLootTable; +import org.bukkit.craftbukkit.CraftLootContext; import org.bukkit.craftbukkit.CraftRaid; import org.bukkit.craftbukkit.CraftServer; import org.bukkit.craftbukkit.CraftStatistic; @@ -1996,7 +1996,7 @@ public static LootGenerateEvent callLootGenerateEvent(Container inventory, LootT Entity entity = lootInfo.getOptional(LootContextParams.THIS_ENTITY); List bukkitLoot = loot.stream().map(CraftItemStack::asBukkitMirror).collect(Collectors.toCollection(ArrayList::new)); - LootGenerateEvent event = new LootGenerateEvent(world, (entity != null ? entity.getBukkitEntity() : null), inventory.getOwner(), lootTable.craftLootTable, CraftLootTable.convertContext(lootInfo), bukkitLoot, plugin); + LootGenerateEvent event = new LootGenerateEvent(world, (entity != null ? entity.getBukkitEntity() : null), inventory.getOwner(), lootTable.craftLootTable, CraftLootContext.toBukkit(lootInfo), bukkitLoot, plugin); Bukkit.getPluginManager().callEvent(event); return event; } From faa2805b9bda2edc51833bedd9c0ef519a947e64 Mon Sep 17 00:00:00 2001 From: Doc Date: Sat, 12 Sep 2026 12:38:24 -0300 Subject: [PATCH 3/3] We forget the tests - Add missing Javadocs for ResolvableFloat and ResolvableInt - Remove ignored components because they are implemented now --- .../java/io/papermc/paper/loot/number/ResolvableFloat.java | 4 ++++ .../main/java/io/papermc/paper/loot/number/ResolvableInt.java | 4 ++++ .../test/java/org/bukkit/registry/RegistryConstantsTest.java | 4 +--- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/paper-api/src/main/java/io/papermc/paper/loot/number/ResolvableFloat.java b/paper-api/src/main/java/io/papermc/paper/loot/number/ResolvableFloat.java index 93b56a43c75d..6548c815e81b 100644 --- a/paper-api/src/main/java/io/papermc/paper/loot/number/ResolvableFloat.java +++ b/paper-api/src/main/java/io/papermc/paper/loot/number/ResolvableFloat.java @@ -26,6 +26,10 @@ interface Constant extends ResolvableFloat { */ float getValue(); + /** + * {@inheritDoc} + */ + @Override default float resolve(LootContext context, float defaultValue) { return this.getValue(); } diff --git a/paper-api/src/main/java/io/papermc/paper/loot/number/ResolvableInt.java b/paper-api/src/main/java/io/papermc/paper/loot/number/ResolvableInt.java index 8bcbe19cca73..78e8df8c2640 100644 --- a/paper-api/src/main/java/io/papermc/paper/loot/number/ResolvableInt.java +++ b/paper-api/src/main/java/io/papermc/paper/loot/number/ResolvableInt.java @@ -26,6 +26,10 @@ interface Constant extends ResolvableInt { */ int getValue(); + /** + * {@inheritDoc} + */ + @Override default int resolve(LootContext context, int defaultValue) { return this.getValue(); } diff --git a/paper-server/src/test/java/org/bukkit/registry/RegistryConstantsTest.java b/paper-server/src/test/java/org/bukkit/registry/RegistryConstantsTest.java index 2ebcb1f1d66e..5a3d4d891e88 100644 --- a/paper-server/src/test/java/org/bukkit/registry/RegistryConstantsTest.java +++ b/paper-server/src/test/java/org/bukkit/registry/RegistryConstantsTest.java @@ -59,9 +59,7 @@ public static void populateIgnored() { DataComponents.CREATIVE_SLOT_LOCK, DataComponents.ADDITIONAL_TRADE_COST, DataComponents.BLOCK_TRANSFORMER, - DataComponents.COMPOSTABLE, - DataComponents.COOKING_FUEL, - DataComponents.BREWING_FUEL + DataComponents.COMPOSTABLE )); }