loot) {
+ this.loot.clear();
+ if (loot != null) {
+ this.loot.addAll(loot);
+ }
+ }
+
+ /**
+ * Get a mutable list of all loot to be generated.
+ *
+ * Any items added or removed from the returned list will be reflected in
+ * the loot generation. {@code null} items will be treated as air.
+ *
+ * @return the loot to generate
+ */
+ public List getLoot() {
+ return this.loot;
+ }
+
+ @Override
+ public void setCancelled(boolean cancel) {
+ this.cancelled = cancel;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return this.cancelled;
+ }
+
+ @Override
+ public HandlerList getHandlers() {
+ return HANDLER_LIST;
+ }
+
+ public static HandlerList getHandlerList() {
+ return HANDLER_LIST;
+ }
+}
diff --git a/paper-api/src/main/java/org/bukkit/event/world/LootGenerateEvent.java b/paper-api/src/main/java/org/bukkit/event/world/LootGenerateEvent.java
index 271e1f3f9a2c..fc62c7250c9a 100644
--- a/paper-api/src/main/java/org/bukkit/event/world/LootGenerateEvent.java
+++ b/paper-api/src/main/java/org/bukkit/event/world/LootGenerateEvent.java
@@ -2,6 +2,7 @@
import java.util.Collection;
import java.util.List;
+import io.papermc.paper.event.entity.EntityLootGenerateEvent;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.event.Cancellable;
@@ -19,8 +20,8 @@
* Called when a {@link LootTable} is generated in the world for an
* {@link InventoryHolder}.
*
- * This event is NOT currently called when an entity's loot table has been
- * generated (use {@link EntityDeathEvent#getDrops()}), but WILL be called by
+ * This event is NOT called when an entity's loot table has been
+ * generated (use {@link EntityLootGenerateEvent} or {@link EntityDeathEvent#getDrops()}), but WILL be called by
* plugins invoking
* {@link LootTable#fillInventory(org.bukkit.inventory.Inventory, java.util.Random, LootContext)}.
*/
diff --git a/paper-server/patches/features/0005-Entity-Activation-Range-2.0.patch b/paper-server/patches/features/0005-Entity-Activation-Range-2.0.patch
index 565f83bbc7d2..5e32d4288048 100644
--- a/paper-server/patches/features/0005-Entity-Activation-Range-2.0.patch
+++ b/paper-server/patches/features/0005-Entity-Activation-Range-2.0.patch
@@ -354,7 +354,7 @@ index 0000000000000000000000000000000000000000..ce6b57eeeeb1bd652f4bb53c19dcfbc0
+ }
+}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 89da20b0682708ba8bfc0d10381eb9784d80c395..b5d63d7b848c11ed322d512adeee102886cbb6fd 100644
+index 0fabe7fe4d302695505bed2cef4ce580358b8b82..9fc39230f52f4cf6b44edd34aa9542b9d500538e 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -864,6 +864,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -462,7 +462,7 @@ index 0df8332933203a904bd9ef9efb3c9bce21e65441..1a502cbd8acea9420fa6dd8d716018b5
public void tick() {
super.tick();
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
-index 593265d78564b60bacbb4899f18a6e74bf56601d..84c664711658eb83b5ff9d4c8470fd8ec54a5473 100644
+index e5d56fdb5cfd4aa291dbdb879e478134a85c9beb..196a15d3761eab416cb519fd52c86cc100b8080a 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -382,6 +382,15 @@ public abstract class Entity
@@ -521,10 +521,10 @@ index 593265d78564b60bacbb4899f18a6e74bf56601d..84c664711658eb83b5ff9d4c8470fd8e
delta = this.maybeBackOffFromEdge(delta, moverType);
Vec3 movement = this.collide(delta);
diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
-index f62e535b62d249dd3be19d7c1b02fae8dad31c10..6eb95b979ffe37d78fd68eb57ebe71891beb0d28 100644
+index e90339ef63c535c7a376747fcedcd49275612ae3..fdd9ed362c68932a5c3dd7856108243004636d05 100644
--- a/net/minecraft/world/entity/LivingEntity.java
+++ b/net/minecraft/world/entity/LivingEntity.java
-@@ -3380,6 +3380,14 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
+@@ -3398,6 +3398,14 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
protected void playAttackSound() {
}
@@ -818,7 +818,7 @@ index f46cca0467bb4da5511bc953ce5b43fa606bf978..445c5cafad71028171c1f26193966177
+
}
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index cc492445307ddc08446484d272866f01646cf7b3..a5d46832d4f8a13b0bf67b523c21d5c3b44209a9 100644
+index 46ca14a186f23a05bd0e6437adcb67dc09d94d8b..00bfadc908cfa9f1600c4bcc3cd21cca579cdc4f 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -155,6 +155,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
diff --git a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
index a69cad61f833..936482d2ce64 100644
--- a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch
@@ -966,6 +966,41 @@
}
protected void dropCustomDeathLoot(final ServerLevel level, final DamageSource source, final boolean killedByPlayer) {
+@@ -1546,8 +_,18 @@
+ builder = builder.withParameter(LootContextParams.LAST_DAMAGE_PLAYER, killerPlayer).withLuck(killerPlayer.getLuck());
+ }
+
++ // Paper start - EntityLootGenerateEvent
++ List drops = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>();
+ LootParams params = builder.create(LootContextParamSets.ENTITY);
+- table.getRandomItems(params, this.getLootTableSeed(), itemStackConsumer);
++ table.getRandomItems(params, this.getLootTableSeed(), drops::add);
++
++ List bukkitLoot = drops.stream().map(CraftItemStack::asCraftMirror).collect(java.util.stream.Collectors.toCollection(java.util.ArrayList::new));
++ io.papermc.paper.event.entity.EntityLootGenerateEvent event = new io.papermc.paper.event.entity.EntityLootGenerateEvent(this.getBukkitEntity(), table.craftLootTable, org.bukkit.craftbukkit.CraftLootTable.convertParams(params), bukkitLoot);
++ if (!event.callEvent()) {
++ return;
++ }
++ event.getLoot().stream().map(org.bukkit.craftbukkit.inventory.CraftItemStack::asNMSCopy).forEach(itemStackConsumer);
++ // Paper end
+ }
+
+ public boolean dropFromEntityInteractLootTable(
+@@ -1602,6 +_,14 @@
+ LootTable lootTable = level.getServer().reloadableRegistries().getLootTable(key);
+ LootParams params = paramsBuilder.apply(new LootParams.Builder(level));
+ List drops = lootTable.getRandomItems(params);
++ // Paper start - EntityLootGenerateEvent
++ List bukkitLoot = drops.stream().map(CraftItemStack::asCraftMirror).collect(java.util.stream.Collectors.toCollection(java.util.ArrayList::new));
++ io.papermc.paper.event.entity.EntityLootGenerateEvent event = new io.papermc.paper.event.entity.EntityLootGenerateEvent(this.getBukkitEntity(), lootTable.craftLootTable, org.bukkit.craftbukkit.CraftLootTable.convertParams(params), bukkitLoot);
++ if (!event.callEvent()) {
++ return false;
++ }
++ drops = event.getLoot().stream().map(org.bukkit.craftbukkit.inventory.CraftItemStack::asNMSCopy).collect(it.unimi.dsi.fastutil.objects.ObjectArrayList.toList());
++ // Paper end
+ if (!drops.isEmpty()) {
+ drops.forEach(stack -> consumer.accept(level, stack));
+ return true;
@@ -1611,9 +_,14 @@
}
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 f1a616432ea7..820a40f2ba74 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java
@@ -148,6 +148,29 @@ private void setMaybe(LootParams.Builder builder, ContextKey param, T val
}
}
+ public static LootContext convertParams(net.minecraft.world.level.storage.loot.LootParams info) {
+ Vec3 position = info.contextMap().getOptional(LootContextParams.ORIGIN);
+ if (position == null) {
+ position = info.contextMap().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.contextMap().has(LootContextParams.ATTACKING_ENTITY)) {
+ CraftEntity killer = info.contextMap().getOptional(LootContextParams.ATTACKING_ENTITY).getBukkitEntity();
+ if (killer instanceof CraftHumanEntity) {
+ contextBuilder.killer((CraftHumanEntity) killer);
+ }
+ }
+
+ if (info.contextMap().has(LootContextParams.THIS_ENTITY)) {
+ contextBuilder.lootedEntity(info.contextMap().getOptional(LootContextParams.THIS_ENTITY).getBukkitEntity());
+ }
+
+ contextBuilder.luck(info.getLuck());
+ return contextBuilder.build();
+ }
+
public static LootContext convertContext(net.minecraft.world.level.storage.loot.LootContext info) {
Vec3 position = info.getOptionalParameter(LootContextParams.ORIGIN);
if (position == null) {