Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package io.papermc.paper.event.entity;

import org.bukkit.entity.Entity;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.loot.LootContext;
import org.bukkit.loot.LootTable;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import java.util.Collection;
import java.util.List;

/**
* Called when a {@link LootTable} is generated for an {@link Entity}.
* <br>
* For example: When an entity dies, an armadillo sheds, etc.
*/
@NullMarked
public class EntityLootGenerateEvent extends EntityEvent implements Cancellable {

private static final HandlerList HANDLER_LIST = new HandlerList();

private final LootTable lootTable;
private final LootContext lootContext;
private final List<ItemStack> loot;

private boolean cancelled;

@ApiStatus.Internal
public EntityLootGenerateEvent(Entity entity, LootTable lootTable, LootContext lootContext, List<ItemStack> loot) {
super(entity);
this.lootTable = lootTable;
this.lootContext = lootContext;
this.loot = loot;
}

/**
* Get the loot table used to generate loot.
*
* @return the loot table
*/
public LootTable getLootTable() {
return this.lootTable;
}

/**
* Get the loot context used to provide context to the loot table's loot
* generation.
*
* @return the loot context
*/
public LootContext getLootContext() {
return this.lootContext;
}

/**
* Set the loot to be generated. {@code null} items will be treated as air.
* <br>
* Note: the set collection is not the one which will be returned by
* {@link #getLoot()}.
*
* @param loot the loot to generate, {@code null} to clear all loot
*/
public void setLoot(@Nullable Collection<ItemStack> loot) {
this.loot.clear();
if (loot != null) {
this.loot.addAll(loot);
}
}

/**
* Get a mutable list of all loot to be generated.
* <p>
* 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<ItemStack> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -19,8 +20,8 @@
* Called when a {@link LootTable} is generated in the world for an
* {@link InventoryHolder}.
* <p>
* 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)}.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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() {
}

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ItemStack> 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<org.bukkit.inventory.ItemStack> 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<ItemStack> drops = lootTable.getRandomItems(params);
+ // Paper start - EntityLootGenerateEvent
+ List<org.bukkit.inventory.ItemStack> 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 @@
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,29 @@ private <T> void setMaybe(LootParams.Builder builder, ContextKey<T> 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) {
Expand Down
Loading