Improve LootContext API

This commit is contained in:
Jake Potrebic 2022-03-22 22:17:42 -07:00
parent 61fe23cd08
commit ff6babece2
No known key found for this signature in database
GPG Key ID: ECE0B3C133C016C5
2 changed files with 629 additions and 0 deletions

View File

@ -0,0 +1,381 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Tue, 22 Mar 2022 22:14:10 -0700
Subject: [PATCH] Improve LootContext API
diff --git a/src/main/java/io/papermc/paper/loot/LootContextKey.java b/src/main/java/io/papermc/paper/loot/LootContextKey.java
new file mode 100644
index 0000000000000000000000000000000000000000..67f8d027d68c0af5c26ee23d625da6917f6f1642
--- /dev/null
+++ b/src/main/java/io/papermc/paper/loot/LootContextKey.java
@@ -0,0 +1,34 @@
+package io.papermc.paper.loot;
+
+import io.papermc.paper.math.Position;
+import net.kyori.adventure.key.Keyed;
+import org.bukkit.block.TileState;
+import org.bukkit.block.data.BlockData;
+import org.bukkit.damage.DamageSource;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+
+import static io.papermc.paper.loot.LootContextKeyImpl.create;
+
+/**
+ * A key to a possible value in a {@link org.bukkit.loot.LootContext}.
+ *
+ * @param <T> the value type
+ */
+@SuppressWarnings("unused")
+public interface LootContextKey<T> extends Keyed {
+
+ LootContextKey<Entity> THIS_ENTITY = create("this_entity");
+ LootContextKey<Player> LAST_DAMAGE_PLAYER = create("last_damage_player");
+ LootContextKey<DamageSource> DAMAGE_SOURCE = create("damage_source");
+ LootContextKey<Entity> ATTACKING_ENTITY = create("attacking_entity");
+ LootContextKey<Entity> DIRECT_ATTACKING_ENTITY = create("direct_attacking_entity");
+ LootContextKey<Position> ORIGIN = create("origin");
+ LootContextKey<BlockData> BLOCK_DATA = create("block_state");
+ LootContextKey<TileState> TILE_STATE = create("block_entity");
+ LootContextKey<ItemStack> TOOL = create("tool");
+ LootContextKey<Float> EXPLOSION_RADIUS = create("explosion_radius");
+ LootContextKey<Integer> ENCHANTMENT_LEVEL = create("enchantment_level");
+ LootContextKey<Boolean> ENCHANTMENT_ACTIVE = create("enchantment_active");
+}
diff --git a/src/main/java/io/papermc/paper/loot/LootContextKeyImpl.java b/src/main/java/io/papermc/paper/loot/LootContextKeyImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..941a490e1b8f865a6ec9fdfb57215f1674ce40ff
--- /dev/null
+++ b/src/main/java/io/papermc/paper/loot/LootContextKeyImpl.java
@@ -0,0 +1,24 @@
+package io.papermc.paper.loot;
+
+import java.util.HashSet;
+import java.util.Set;
+import net.kyori.adventure.key.Key;
+import net.kyori.adventure.key.KeyPattern;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+import org.jetbrains.annotations.ApiStatus;
+
+@ApiStatus.Internal
+@DefaultQualifier(NonNull.class)
+record LootContextKeyImpl<T>(Key key) implements LootContextKey<T> {
+
+ static final Set<LootContextKey<?>> KEYS = new HashSet<>();
+
+ static <T> LootContextKey<T> create(@KeyPattern final String name) {
+ final LootContextKeyImpl<T> key = new LootContextKeyImpl<>(Key.key(name));
+ if (!KEYS.add(key)) {
+ throw new IllegalStateException("Already registered " + name);
+ }
+ return key;
+ }
+}
diff --git a/src/main/java/org/bukkit/loot/LootContext.java b/src/main/java/org/bukkit/loot/LootContext.java
index 9c1ccaed727ec5e5dad93146bbfda798e3f536e7..43e7839157c37745a3623512b35b89fd6839ab3c 100644
--- a/src/main/java/org/bukkit/loot/LootContext.java
+++ b/src/main/java/org/bukkit/loot/LootContext.java
@@ -15,30 +15,124 @@ public final class LootContext {
public static final int DEFAULT_LOOT_MODIFIER = -1;
- private final Location location;
+ // Paper start - loot context overhaul
+ private final org.bukkit.World world;
private final float luck;
- private final int lootingModifier;
- private final Entity lootedEntity;
- private final HumanEntity killer;
-
- private LootContext(@NotNull Location location, float luck, int lootingModifier, @Nullable Entity lootedEntity, @Nullable HumanEntity killer) {
- Preconditions.checkArgument(location != null, "LootContext location cannot be null");
- Preconditions.checkArgument(location.getWorld() != null, "LootContext World cannot be null");
- this.location = location;
+ private final java.util.Random random;
+ private final java.util.Map<io.papermc.paper.loot.LootContextKey<?>, Object> contextMap;
+ // TODO dynamic drops API
+ @Deprecated
+ private @org.checkerframework.checker.nullness.qual.MonotonicNonNull Location legacyLocation;
+ @Deprecated // has no functionality
+ private int lootingModifier;
+ private final boolean isLegacy;
+
+ private LootContext(@NotNull org.bukkit.World world, float luck, @NotNull java.util.Random random, @NotNull java.util.Map<io.papermc.paper.loot.LootContextKey<?>, Object> contextMap, boolean isLegacy, @Deprecated int lootingModifier) {
+ this.world = world;
this.luck = luck;
+ this.random = random;
+ this.contextMap = java.util.Map.copyOf(contextMap);
+ this.isLegacy = isLegacy;
this.lootingModifier = lootingModifier;
- this.lootedEntity = lootedEntity;
- this.killer = killer;
+ }
+
+ @org.jetbrains.annotations.ApiStatus.Internal
+ public boolean isLegacy() {
+ return this.isLegacy;
+ }
+
+ /**
+ * Checks if this context contains a value for the key
+ *
+ * @param contextKey the key to check
+ * @return true if this context has a value for that key
+ */
+ public boolean hasKey(final io.papermc.paper.loot.@NotNull LootContextKey<?> contextKey) {
+ return this.contextMap.containsKey(contextKey);
+ }
+
+ /**
+ * Gets the value for a context key
+ *
+ * @param contextKey the key for the value
+ * @return the value or null if this context doesn't have a value for the key
+ * @param <T> value type
+ * @see #hasKey(io.papermc.paper.loot.LootContextKey)
+ * @see #getOrThrow(io.papermc.paper.loot.LootContextKey)
+ */
+ @SuppressWarnings("unchecked")
+ public <T> @Nullable T get(final io.papermc.paper.loot.@NotNull LootContextKey<T> contextKey) {
+ return (T) this.contextMap.get(contextKey);
}
+ /**
+ * Gets the value of a context key, throwing an exception
+ * if one isn't found
+ *
+ * @param contextKey the key for the value
+ * @return the value
+ * @param <T> value type
+ * @throws java.util.NoSuchElementException if no value is found for that key
+ */
+ public <T> @NotNull T getOrThrow(final io.papermc.paper.loot.@NotNull LootContextKey<T> contextKey) {
+ final T value = this.get(contextKey);
+ if (value == null) {
+ throw new java.util.NoSuchElementException("No value found for " + contextKey);
+ }
+ return value;
+ }
+
+ /**
+ * Gets the random instance used for this context.
+ *
+ * @return the random
+ */
+ public java.util.@NotNull Random getRandom() {
+ return this.random;
+ }
+
+ /**
+ * Gets the world for this context.
+ *
+ * @return the world
+ */
+ public org.bukkit.@NotNull World getWorld() {
+ return this.world;
+ }
+
+ /**
+ * Gets the context map for this loot context.
+ *
+ * @return an unmodifiable map
+ */
+
+ public java.util.@NotNull @org.jetbrains.annotations.Unmodifiable Map<io.papermc.paper.loot.LootContextKey<?>, Object> getContextMap() {
+ return this.contextMap;
+ }
+ // Paper end
+
/**
* The {@link Location} to store where the loot will be generated.
*
* @return the Location of where the loot will be generated
+ * @deprecated use {@link #get(io.papermc.paper.loot.LootContextKey)} methods
*/
@NotNull
+ @Deprecated // Paper
public Location getLocation() {
- return location;
+ // Paper start - fallback to legacy location
+ if (this.legacyLocation == null) {
+ if (contextMap.containsKey(io.papermc.paper.loot.LootContextKey.ORIGIN)) {
+ io.papermc.paper.math.Position pos = this.getOrThrow(io.papermc.paper.loot.LootContextKey.ORIGIN);
+ this.legacyLocation = new Location(this.world, pos.x(), pos.y(), pos.z());
+ } else if (contextMap.containsKey(io.papermc.paper.loot.LootContextKey.THIS_ENTITY)) {
+ this.legacyLocation = this.getOrThrow(io.papermc.paper.loot.LootContextKey.THIS_ENTITY).getLocation();
+ } else {
+ throw new IllegalStateException("All known context key sets require \"origin\" or \"this_entity\" and this one doesn't have either");
+ }
+ }
+ return this.legacyLocation;
+ // Paper end
}
/**
@@ -73,10 +167,12 @@ public final class LootContext {
* Get the {@link Entity} that was killed. Can be null.
*
* @return the looted entity or null
+ * @deprecated use {@link #get(io.papermc.paper.loot.LootContextKey)} methods
*/
@Nullable
+ @Deprecated // Paper
public Entity getLootedEntity() {
- return lootedEntity;
+ return this.get(io.papermc.paper.loot.LootContextKey.THIS_ENTITY); // Paper
}
/**
@@ -84,41 +180,89 @@ public final class LootContext {
* Can be null.
*
* @return the killer entity, or null.
+ * @deprecated use {@link #get(io.papermc.paper.loot.LootContextKey)} methods
*/
@Nullable
+ @Deprecated // Paper
public HumanEntity getKiller() {
- return killer;
+ return this.get(io.papermc.paper.loot.LootContextKey.ATTACKING_ENTITY) instanceof HumanEntity humanEntity ? humanEntity : null; // Paper
}
/**
* Utility class to make building {@link LootContext} easier. The only
- * required argument is {@link Location} with a valid (non-null)
- * {@link org.bukkit.World}.
+ * required argument is {@link org.bukkit.World}.
*/
public static class Builder {
- private final Location location;
+ private final org.bukkit.World world; // Paper
private float luck;
+ @Deprecated // Paper - not functional
private int lootingModifier = LootContext.DEFAULT_LOOT_MODIFIER;
- private Entity lootedEntity;
- private HumanEntity killer;
+ private java.util.Random random = java.util.concurrent.ThreadLocalRandom.current(); // Paper
+ private final java.util.Map<io.papermc.paper.loot.LootContextKey<?>, Object> contextMap = new java.util.IdentityHashMap<>(); // Paper
+ private boolean isLegacy = false; // Paper
/**
* Creates a new LootContext.Builder instance to facilitate easy
* creation of {@link LootContext}s.
*
* @param location the location the LootContext should use
+ * @deprecated not all loot contexts have locations
*/
+ @Deprecated // Paper
public Builder(@NotNull Location location) {
- this.location = location;
+ // Paper start
+ com.google.common.base.Preconditions.checkArgument(location.getWorld() != null, "location missing world");
+ this.world = location.getWorld();
+ this.contextMap.put(io.papermc.paper.loot.LootContextKey.ORIGIN, io.papermc.paper.math.Position.fine(location));
+ this.isLegacy = true;
+ // Paper end
+ }
+
+ // Paper start
+ public Builder(@NotNull org.bukkit.World world) {
+ this.world = world;
+ }
+
+ /**
+ * Sets the random instance to use for this context.
+ * Defaults to {@link java.util.concurrent.ThreadLocalRandom#current()}.
+ *
+ * @param random the random to use
+ * @return the builder
+ */
+ @org.jetbrains.annotations.Contract(value = "_ -> this", mutates = "this")
+ public @NotNull Builder withRandom(@NotNull java.util.Random random) {
+ this.random = random;
+ return this;
}
+ /**
+ * Sets or clears context values.
+ *
+ * @param contextKey the key to set or clear for
+ * @param context the value to set, or null to clear
+ * @param <T> the value type
+ * @return the builder
+ */
+ @org.jetbrains.annotations.Contract(value = "_, _ -> this", mutates = "this")
+ public <T> @NotNull Builder with(@NotNull io.papermc.paper.loot.LootContextKey<T> contextKey, @Nullable T context) {
+ if (context == null) {
+ this.contextMap.remove(contextKey);
+ } else {
+ this.contextMap.put(contextKey, context);
+ }
+ return this;
+ }
+ // Paper end
+
/**
* Set how much luck to have when generating loot.
*
* @param luck the luck level
* @return the Builder
*/
+ @org.jetbrains.annotations.Contract(value = "_ -> this", mutates = "this") // Paper
@NotNull
public Builder luck(float luck) {
this.luck = luck;
@@ -138,6 +282,7 @@ public final class LootContext {
@NotNull
@Deprecated
public Builder lootingModifier(int modifier) {
+ this.isLegacy = true; // Paper
this.lootingModifier = modifier;
return this;
}
@@ -147,11 +292,14 @@ public final class LootContext {
*
* @param lootedEntity the looted entity
* @return the Builder
+ * @deprecated use {@link #with(io.papermc.paper.loot.LootContextKey, Object)}
*/
@NotNull
+ @org.jetbrains.annotations.Contract(value = "_ -> this", mutates = "this") // Paper
+ @Deprecated // Paper
public Builder lootedEntity(@Nullable Entity lootedEntity) {
- this.lootedEntity = lootedEntity;
- return this;
+ this.isLegacy = true; // Paper
+ return this.with(io.papermc.paper.loot.LootContextKey.THIS_ENTITY, lootedEntity); // Paper
}
/**
@@ -161,11 +309,14 @@ public final class LootContext {
*
* @param killer the killer entity
* @return the Builder
+ * @deprecated use {@link #with(io.papermc.paper.loot.LootContextKey, Object)}
*/
@NotNull
+ @org.jetbrains.annotations.Contract(value = "_ -> this", mutates = "this") // Paper
+ @Deprecated // Paper
public Builder killer(@Nullable HumanEntity killer) {
- this.killer = killer;
- return this;
+ this.isLegacy = true; // Paper
+ return this.with(io.papermc.paper.loot.LootContextKey.ATTACKING_ENTITY, killer); // Paper
}
/**
@@ -175,8 +326,9 @@ public final class LootContext {
* @return a new {@link LootContext} instance
*/
@NotNull
+ @org.jetbrains.annotations.Contract("-> new") // Paper
public LootContext build() {
- return new LootContext(location, luck, lootingModifier, lootedEntity, killer);
+ return new LootContext(this.world, luck, this.random, this.contextMap, this.isLegacy, this.lootingModifier); // Paper
}
}
}

View File

@ -0,0 +1,248 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Tue, 22 Mar 2022 22:17:13 -0700
Subject: [PATCH] Improve LootContext API
== AT ==
public net.minecraft.world.level.storage.loot.LootContext params
diff --git a/src/main/java/io/papermc/paper/loot/PaperLootContextKey.java b/src/main/java/io/papermc/paper/loot/PaperLootContextKey.java
new file mode 100644
index 0000000000000000000000000000000000000000..9f0729c56608696d10e5b3cba5b01b85a1fb0b6a
--- /dev/null
+++ b/src/main/java/io/papermc/paper/loot/PaperLootContextKey.java
@@ -0,0 +1,115 @@
+package io.papermc.paper.loot;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import io.papermc.paper.util.MCUtil;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.storage.loot.LootParams;
+import net.minecraft.world.level.storage.loot.parameters.LootContextParam;
+import net.minecraft.world.level.storage.loot.parameters.LootContextParamSet;
+import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
+import org.bukkit.craftbukkit.block.CraftBlockEntityState;
+import org.bukkit.craftbukkit.block.CraftBlockStates;
+import org.bukkit.craftbukkit.block.data.CraftBlockData;
+import org.bukkit.craftbukkit.damage.CraftDamageSource;
+import org.bukkit.craftbukkit.entity.CraftEntity;
+import org.bukkit.craftbukkit.inventory.CraftItemStack;
+import org.bukkit.entity.Entity;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
+public final class PaperLootContextKey {
+
+ public static final BiMap<LootContextParam<?>, LootContextKey<?>> KEY_BI_MAP = HashBiMap.create();
+ private static final Set<Converter<?, ?>> CONVERTERS = new HashSet<>();
+ private static final Map<LootContextParam<?>, Converter<?, ?>> NMS_KEY_MAP = new IdentityHashMap<>();
+ private static final Map<LootContextKey<?>, Converter<?, ?>> API_KEY_MAP = new IdentityHashMap<>();
+
+ static {
+ CONVERTERS.add(entity(LootContextParams.THIS_ENTITY, LootContextKey.THIS_ENTITY));
+ CONVERTERS.add(entity(LootContextParams.LAST_DAMAGE_PLAYER, LootContextKey.LAST_DAMAGE_PLAYER));
+ CONVERTERS.add(new LambdaConverter<>(LootContextParams.DAMAGE_SOURCE, LootContextKey.DAMAGE_SOURCE, ds -> ((CraftDamageSource) ds).getHandle(), CraftDamageSource::new));
+ CONVERTERS.add(entity(LootContextParams.ATTACKING_ENTITY, LootContextKey.ATTACKING_ENTITY));
+ CONVERTERS.add(entity(LootContextParams.DIRECT_ATTACKING_ENTITY, LootContextKey.DIRECT_ATTACKING_ENTITY));
+ CONVERTERS.add(new LambdaConverter<>(LootContextParams.ORIGIN, LootContextKey.ORIGIN, MCUtil::toVec3, MCUtil::toPosition));
+ CONVERTERS.add(new LambdaConverter<>(LootContextParams.BLOCK_STATE, LootContextKey.BLOCK_DATA, bd -> ((CraftBlockData) bd).getState(), BlockState::createCraftBlockData));
+ CONVERTERS.add(new LambdaConverter<>(LootContextParams.BLOCK_ENTITY, LootContextKey.TILE_STATE, ts -> ((CraftBlockEntityState<?>) ts).getTileEntity(), CraftBlockStates::getTileState));
+ CONVERTERS.add(new LambdaConverter<>(LootContextParams.TOOL, LootContextKey.TOOL, CraftItemStack::asNMSCopy, net.minecraft.world.item.ItemStack::asBukkitCopy));
+ CONVERTERS.add(identity(LootContextParams.EXPLOSION_RADIUS, LootContextKey.EXPLOSION_RADIUS));
+ CONVERTERS.add(identity(LootContextParams.ENCHANTMENT_LEVEL, LootContextKey.ENCHANTMENT_LEVEL));
+ CONVERTERS.add(identity(LootContextParams.ENCHANTMENT_ACTIVE, LootContextKey.ENCHANTMENT_ACTIVE));
+ for (final Converter<?, ?> converter : CONVERTERS) {
+ KEY_BI_MAP.put(converter.nmsKey, converter.apiKey);
+ NMS_KEY_MAP.put(converter.nmsKey, converter);
+ API_KEY_MAP.put(converter.apiKey, converter);
+ }
+ }
+
+ private PaperLootContextKey() {
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <API, MINECRAFT> void applyToNmsBuilder(final LootContextParamSet paramSet, final LootParams.Builder builder, final LootContextKey<API> apiKey, final Object object) {
+ final LootContextParam<MINECRAFT> nmsParam = (LootContextParam<MINECRAFT>) KEY_BI_MAP.inverse().get(apiKey);
+ if (paramSet.getAllowed().contains(nmsParam) || paramSet.getRequired().contains(nmsParam)) {
+ builder.withOptionalParameter(nmsParam, ((Converter<MINECRAFT, API>) API_KEY_MAP.get(apiKey)).toMinecraft((API) object));
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <API, MINECRAFT> void applyToApiBuilder(final org.bukkit.loot.LootContext.Builder builder, final LootContextParam<MINECRAFT> nmsKey, final Object object) {
+ builder.with(((LootContextKey<API>) KEY_BI_MAP.get(nmsKey)), ((Converter<MINECRAFT, API>) NMS_KEY_MAP.get(nmsKey)).toApi((MINECRAFT) object));
+ }
+
+ abstract static class Converter<MINECRAFT, API> {
+
+ final LootContextParam<MINECRAFT> nmsKey;
+ final LootContextKey<API> apiKey;
+
+ private Converter(final LootContextParam<MINECRAFT> nmsKey, final LootContextKey<API> apiKey) {
+ this.nmsKey = nmsKey;
+ this.apiKey = apiKey;
+ }
+
+ protected abstract MINECRAFT toMinecraft(API api);
+
+ protected abstract API toApi(MINECRAFT minecraft);
+ }
+
+ static class LambdaConverter<MINECRAFT, API> extends Converter<MINECRAFT, API> {
+
+ private final Function<API, MINECRAFT> toMinecraft;
+ private final Function<MINECRAFT, API> toApi;
+
+ private LambdaConverter(final LootContextParam<MINECRAFT> nmsKey, final LootContextKey<API> apiKey, final Function<API, MINECRAFT> toMinecraft, final Function<MINECRAFT, API> toApi) {
+ super(nmsKey, apiKey);
+ this.toMinecraft = toMinecraft;
+ this.toApi = toApi;
+ }
+
+ @Override
+ protected MINECRAFT toMinecraft(final API api) {
+ return this.toMinecraft.apply(api);
+ }
+
+ @Override
+ protected API toApi(final MINECRAFT minecraft) {
+ return this.toApi.apply(minecraft);
+ }
+ }
+
+ private static <T> LambdaConverter<T, T> identity(final LootContextParam<T> nmsKey, final LootContextKey<T> apiKey) {
+ return new LambdaConverter<>(nmsKey, apiKey, Function.identity(), Function.identity());
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <MINECRAFT extends net.minecraft.world.entity.Entity, API extends Entity> LambdaConverter<MINECRAFT, API> entity(final LootContextParam<MINECRAFT> nmsKey, final LootContextKey<API> apiKey) {
+ return new LambdaConverter<>(nmsKey, apiKey, e -> (MINECRAFT) ((CraftEntity) e).getHandle(), e -> (API) e.getBukkitEntity());
+ }
+}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java b/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java
index f028daa4f23a1f1868c9922991259739cadc5da2..3d37937186eb8c2bd950816ccecd52912d3956e4 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java
@@ -101,6 +101,22 @@ public class CraftLootTable implements org.bukkit.loot.LootTable {
private LootParams convertContext(LootContext context, Random random) {
Preconditions.checkArgument(context != null, "LootContext cannot be null");
+ // Paper start
+ if (!context.isLegacy()) {
+ final LootParams.Builder paramsBuilder = new LootParams.Builder(((CraftWorld) context.getWorld()).getHandle()).withLuck(context.getLuck());
+ context.getContextMap().forEach((lootContextKey, o) -> io.papermc.paper.loot.PaperLootContextKey.applyToNmsBuilder(this.handle.getParamSet(), paramsBuilder, lootContextKey, o));
+
+ return paramsBuilder.create(this.handle.getParamSet());
+ // final net.minecraft.world.level.storage.loot.LootContext.Builder contextBuilder = new net.minecraft.world.level.storage.loot.LootContext.Builder(paramsBuilder.create(this.handle.getParamSet()));
+ // // .withRandom(new RandomSourceWrapper(random != null ? random : context.getRandom()))
+ // return contextBuilder.create(java.util.Optional.empty());
+ } else {
+ return this.convertLegacyContext(context, random);
+ }
+ }
+ @Deprecated
+ private LootParams convertLegacyContext(final LootContext context, final Random random) {
+ // Paper end
Location loc = context.getLocation();
Preconditions.checkArgument(loc.getWorld() != null, "LootContext.getLocation#getWorld cannot be null");
ServerLevel handle = ((CraftWorld) loc.getWorld()).getHandle();
@@ -151,6 +167,20 @@ public class CraftLootTable implements org.bukkit.loot.LootTable {
}
public static LootContext convertContext(net.minecraft.world.level.storage.loot.LootContext info) {
+ // Paper start
+ final LootContext.Builder builder = new LootContext.Builder(info.getLevel().getWorld())
+ .withRandom(new org.bukkit.craftbukkit.util.RandomSourceWrapper.RandomWrapper(info.getRandom()))
+ .luck(info.getLuck());
+ for (final LootContextParam<?> nmsParam : io.papermc.paper.loot.PaperLootContextKey.KEY_BI_MAP.keySet()) {
+ if (info.hasParam(nmsParam)) {
+ io.papermc.paper.loot.PaperLootContextKey.applyToApiBuilder(builder, nmsParam, info.getParam(nmsParam));
+ }
+ }
+ return builder.build();
+ }
+ @Deprecated @io.papermc.paper.annotation.DoNotUse
+ public static LootContext convertLegacyContext(net.minecraft.world.level.storage.loot.LootContext info) {
+ // Paper end
Vec3 position = info.getParamOrNull(LootContextParams.ORIGIN);
if (position == null) {
position = info.getParamOrNull(LootContextParams.THIS_ENTITY).position(); // Every vanilla context has origin or this_entity, see LootContextParameterSets
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java
index b7ff7af2513204b151340538d50a65c850bdb75f..63f1f55bea16aece9d50e0eaed4deca050f05279 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java
@@ -292,6 +292,13 @@ public final class CraftBlockStates {
return CraftBlockStates.getBlockState(null, blockPosition, blockData, tileEntity);
}
+ // Paper start
+ public static org.bukkit.block.TileState getTileState(final BlockEntity blockEntity) {
+ Preconditions.checkArgument(blockEntity.getLevel() != null, "blockEntity has no level");
+ return (org.bukkit.block.TileState) getBlockState(blockEntity.getLevel().getWorld(), blockEntity.getBlockPos(), blockEntity.getBlockState(), blockEntity);
+ }
+ // Paper end
+
// See BlockStateFactory#createBlockState(World, BlockPosition, IBlockData, TileEntity)
public static CraftBlockState getBlockState(World world, BlockPos blockPosition, net.minecraft.world.level.block.state.BlockState blockData, BlockEntity tileEntity) {
Material material = CraftBlockType.minecraftToBukkit(blockData.getBlock());
diff --git a/src/test/java/io/papermc/paper/loot/LootContextKeyTest.java b/src/test/java/io/papermc/paper/loot/LootContextKeyTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..4aa515b62245d0c20dcc5352aff1984036922932
--- /dev/null
+++ b/src/test/java/io/papermc/paper/loot/LootContextKeyTest.java
@@ -0,0 +1,47 @@
+package io.papermc.paper.loot;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import net.minecraft.world.level.storage.loot.parameters.LootContextParam;
+import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
+import org.bukkit.support.AbstractTestingBase;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+class LootContextKeyTest {
+
+ static Map<String, LootContextParam<?>> vanillaParams = new HashMap<>();
+
+ @BeforeAll
+ static void collectVanillaContextParams() throws ReflectiveOperationException {
+ Class.forName(LootContextKey.class.getName()); // force-load class
+ for (final Field field : LootContextParams.class.getDeclaredFields()) {
+ if (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers()) && field.getType().equals(LootContextParam.class)) {
+ vanillaParams.put(field.getName(), (LootContextParam<?>) field.get(null));
+ }
+ }
+ }
+
+ @Test
+ void testMinecraftToApi() {
+ vanillaParams.forEach((fieldName, lootContextParam) -> {
+ final List<LootContextKey<?>> matching = LootContextKeyImpl.KEYS.stream().filter(k -> k.key().asString().equals(lootContextParam.getName().toString())).toList();
+ assertEquals(1, matching.size(), "Did not find 1 matching context key for " + lootContextParam.getName());
+ });
+ }
+
+ @Test
+ void testApiToMinecraft() {
+ assertNotEquals(0, LootContextKeyImpl.KEYS.size());
+ LootContextKeyImpl.KEYS.forEach(lootContextKey -> {
+ final List<LootContextParam<?>> matching = vanillaParams.values().stream().filter(p -> p.getName().toString().equals(lootContextKey.key().asString())).toList();
+ assertEquals(1, matching.size(), "Did not find 1 matching loot param for " + lootContextKey.key());
+ });
+ }
+}