Compare commits

...
Author SHA1 Message Date
Doc b2427de904 We forget the tests
- Add missing Javadocs for ResolvableFloat and ResolvableInt
- Remove ignored components because they are implemented now
2026-09-19 14:53:07 -03:00
Doc 54bca1973c 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...
2026-09-19 14:53:07 -03:00
MiniDigger | Martin ec466edd5f Add Cooking and Brewing Fuel Item Components 2026-09-19 14:53:07 -03:00
20 changed files with 573 additions and 91 deletions

No files matched your search

@@ -6,9 +6,11 @@ import io.papermc.paper.datacomponent.item.AttackRange;
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<SignText> SIGN_TEXT_BACK = valued("sign_text_back");
public static final DataComponentType.NonValued WAXED = unvalued("waxed");
public static final DataComponentType.Valued<DyeColor> 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<CookingFuel> COOKING_FUEL = valued("cooking_fuel");
/**
* Describes an item that can be used as fuel for a brewing stand.
*/
public static final DataComponentType.Valued<BrewingFuel> 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));
@@ -0,0 +1,56 @@
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;
/**
* Describes an item that can be used as fuel for a brewing stand.
*
* @see io.papermc.paper.datacomponent.DataComponentTypes#BREWING_FUEL
*/
@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<BrewingFuel> {
/**
* @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);
}
}
@@ -0,0 +1,56 @@
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;
/**
* Describes an item that can be used as fuel for a furnace, smoker or blast furnace.
*
* @see io.papermc.paper.datacomponent.DataComponentTypes#COOKING_FUEL
*/
@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<CookingFuel> {
/**
* @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);
}
}
@@ -131,4 +131,8 @@ interface ItemComponentTypesBridge {
VillagerFood villagerFood(int nutrition);
SignText.Builder signText();
BrewingFuel.Builder brewingFuel();
CookingFuel.Builder cookingFuel();
}
@@ -0,0 +1,37 @@
package io.papermc.paper.loot.number;
import org.bukkit.loot.LootContext;
/**
* Represents a floating point number that can be resolved against a {@link LootContext}.
*/
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();
/**
* {@inheritDoc}
*/
@Override
default float resolve(LootContext context, float defaultValue) {
return this.getValue();
}
}
}
@@ -0,0 +1,37 @@
package io.papermc.paper.loot.number;
import org.bukkit.loot.LootContext;
/**
* Represents an integer number that can be resolved against a {@link LootContext}.
*/
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();
/**
* {@inheritDoc}
*/
@Override
default int resolve(LootContext context, int defaultValue) {
return this.getValue();
}
}
}
@@ -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;
@@ -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<ContextKey<?>> missingFromAllParamsOptional = Sets.difference(allParams, ALL_PARAMS_OPTIONAL.allowed());
+ if (!missingFromAllParamsOptional.isEmpty()) {
+ throw new IllegalStateException("Missing parameters from 'all_params_optional': " + missingFromAllParamsOptional);
+ }
+ // Paper end
}
}
@@ -6,9 +6,11 @@ import io.papermc.paper.datacomponent.item.PaperAttackRange;
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 final class DataComponentAdapters {
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<DataComponentType<?>> key : BuiltInRegistries.DATA_COMPONENT_TYPE.registryKeySet()) {
if (!ADAPTERS.containsKey(key)) {
@@ -307,4 +307,13 @@ public final class ItemComponentTypesBridgesImpl implements ItemComponentTypesBr
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();
}
}
@@ -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<net.minecraft.world.item.component.BrewingFuel> {
@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)
)
);
}
}
}
@@ -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<net.minecraft.world.item.component.CookingFuel> {
@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)
)
);
}
}
}
@@ -0,0 +1,41 @@
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.CraftLootContext;
import org.bukkit.craftbukkit.util.Handleable;
import org.bukkit.loot.LootContext;
public record PaperResolvableFloat(
net.minecraft.world.level.storage.loot.providers.number.floats.ResolvableFloat impl
) implements ResolvableFloat, Handleable<net.minecraft.world.level.storage.loot.providers.number.floats.ResolvableFloat> {
@Override
public float resolve(final LootContext context, final float defaultValue) {
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.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(
float value
)) {
return new PaperResolvableFloat.Constant(value);
}
return new PaperResolvableFloat(vanilla);
}
record Constant(float value) implements ResolvableFloat.Constant {
@Override
public float getValue() {
return this.value;
}
}
}
@@ -0,0 +1,41 @@
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.CraftLootContext;
import org.bukkit.craftbukkit.util.Handleable;
import org.bukkit.loot.LootContext;
public record PaperResolvableInt(
net.minecraft.world.level.storage.loot.providers.number.ints.ResolvableInt impl
) implements ResolvableInt, Handleable<net.minecraft.world.level.storage.loot.providers.number.ints.ResolvableInt> {
@Override
public int resolve(final LootContext context, final int defaultValue) {
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.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(
int value
)) {
return new Constant(value);
}
return new PaperResolvableInt(vanilla);
}
public record Constant(int value) implements ResolvableInt.Constant {
@Override
public int getValue() {
return this.value;
}
}
}
@@ -0,0 +1,4 @@
@NullMarked
package io.papermc.paper.loot.number;
import org.jspecify.annotations.NullMarked;
@@ -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 <T> void setMaybe(final LootParams.Builder builder, final ContextKeySet paramSet, final ContextKey<T> 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");
}
}
@@ -8,24 +8,13 @@ import java.util.Random;
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.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.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;
@@ -69,7 +58,7 @@ public class CraftLootTable implements org.bukkit.loot.LootTable {
@Override
public Collection<ItemStack> populateLoot(Random random, LootContext context) {
Preconditions.checkArgument(context != null, "LootContext cannot be null");
LootParams nmsContext = this.convertContext(context);
LootParams nmsContext = CraftLootContext.createLootParams(context, this.getHandle().getParamSet(), this.getHandle() == LootTable.EMPTY);
List<net.minecraft.world.item.ItemStack> nmsItems = this.handle.getRandomItems(nmsContext, random == null ? null : new RandomSourceWrapper(random));
Collection<ItemStack> bukkit = new ArrayList<>(nmsItems.size());
@@ -87,7 +76,7 @@ public class CraftLootTable implements org.bukkit.loot.LootTable {
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 = CraftLootContext.createLootParams(context, this.getHandle().getParamSet(), this.getHandle() == LootTable.EMPTY);
CraftInventory craftInventory = (CraftInventory) inventory;
Container handle = craftInventory.getInventory();
@@ -100,77 +89,6 @@ public class CraftLootTable implements org.bukkit.loot.LootTable {
return this.key;
}
private LootParams convertContext(LootContext context) {
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) {
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());
}
if (context.getKiller() != null) {
Player nmsKiller = ((CraftHumanEntity) context.getKiller()).getHandle();
this.setMaybe(builder, 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
}
}
// SPIGOT-5603 - Avoid IllegalArgumentException in ContextKeySet.Builder#create
ContextKeySet.Builder nmsBuilder = new ContextKeySet.Builder();
for (ContextKey<?> param : this.getHandle().getParamSet().required()) {
nmsBuilder.required(param);
}
for (ContextKey<?> param : this.getHandle().getParamSet().allowed()) {
if (!this.getHandle().getParamSet().required().contains(param)) {
nmsBuilder.optional(param);
}
}
return builder.create(this.getHandle().getParamSet());
}
private <T> void setMaybe(LootParams.Builder builder, ContextKey<T> param, T value) {
if (this.getHandle().getParamSet().required().contains(param) || this.getHandle().getParamSet().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();
@@ -98,7 +98,7 @@ import org.bukkit.craftbukkit.CraftChunk;
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 class CraftEventFactory {
Entity entity = lootInfo.getOptional(LootContextParams.THIS_ENTITY);
List<org.bukkit.inventory.ItemStack> 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;
}
@@ -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 class CraftInventoryFurnace extends CraftInventory implements FurnaceInve
@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
@@ -59,9 +59,7 @@ public class RegistryConstantsTest {
DataComponents.CREATIVE_SLOT_LOCK,
DataComponents.ADDITIONAL_TRADE_COST,
DataComponents.BLOCK_TRANSFORMER,
DataComponents.COMPOSTABLE,
DataComponents.COOKING_FUEL,
DataComponents.BREWING_FUEL
DataComponents.COMPOSTABLE
));
}