aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/0127-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch
diff options
context:
space:
mode:
Diffstat (limited to 'patches/server/0127-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch')
-rw-r--r--patches/server/0127-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch355
1 files changed, 355 insertions, 0 deletions
diff --git a/patches/server/0127-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch b/patches/server/0127-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch
new file mode 100644
index 0000000000..d4497b804f
--- /dev/null
+++ b/patches/server/0127-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch
@@ -0,0 +1,355 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Aikar <[email protected]>
+Date: Tue, 19 Dec 2017 16:31:46 -0500
+Subject: [PATCH] ExperienceOrbs API for Reason/Source/Triggering player
+
+Adds lots of information about why this orb exists.
+
+Replaces isFromBottle() with logic that persists entity reloads too.
+
+diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
+index 344d22ae4067bccd6d42b444f6b057c9d1ea6e82..f4bf927477af23365a2d4618a1634195bd2054c8 100644
+--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
+@@ -430,7 +430,7 @@ public class ServerPlayerGameMode {
+
+ // Drop event experience
+ if (flag && event != null) {
+- iblockdata.getBlock().popExperience(this.level, pos, event.getExpToDrop());
++ iblockdata.getBlock().popExperience(this.level, pos, event.getExpToDrop(), this.player); // Paper
+ }
+
+ return true;
+diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java
+index 1f29ed95ef3d1904a014715028d9d591fe39231f..1a829f79e6f9e03ead745e13ece4d1b57195dbb4 100644
+--- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java
++++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java
+@@ -41,9 +41,63 @@ public class ExperienceOrb extends Entity {
+ public int value;
+ private int count;
+ private Player followingPlayer;
++ // Paper start
++ @javax.annotation.Nullable
++ public java.util.UUID sourceEntityId;
++ @javax.annotation.Nullable
++ public java.util.UUID triggerEntityId;
++ public org.bukkit.entity.ExperienceOrb.SpawnReason spawnReason = org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN;
++
++ private void loadPaperNBT(CompoundTag tag) {
++ if (!tag.contains("Paper.ExpData", net.minecraft.nbt.Tag.TAG_COMPOUND)) {
++ return;
++ }
++ CompoundTag comp = tag.getCompound("Paper.ExpData");
++ if (comp.hasUUID("source")) {
++ this.sourceEntityId = comp.getUUID("source");
++ }
++ if (comp.hasUUID("trigger")) {
++ this.triggerEntityId = comp.getUUID("trigger");
++ }
++ if (comp.contains("reason")) {
++ String reason = comp.getString("reason");
++ try {
++ this.spawnReason = org.bukkit.entity.ExperienceOrb.SpawnReason.valueOf(reason);
++ } catch (Exception e) {
++ this.level().getCraftServer().getLogger().warning("Invalid spawnReason set for experience orb: " + e.getMessage() + " - " + reason);
++ }
++ }
++ }
++ private void savePaperNBT(CompoundTag tag) {
++ CompoundTag comp = new CompoundTag();
++ if (this.sourceEntityId != null) {
++ comp.putUUID("source", this.sourceEntityId);
++ }
++ if (this.triggerEntityId != null) {
++ comp.putUUID("trigger", triggerEntityId);
++ }
++ if (this.spawnReason != null && this.spawnReason != org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN) {
++ comp.putString("reason", this.spawnReason.name());
++ }
++ tag.put("Paper.ExpData", comp);
++ }
+
++ @io.papermc.paper.annotation.DoNotUse
++ @Deprecated
+ public ExperienceOrb(Level world, double x, double y, double z, int amount) {
++ this(world, x, y, z, amount, null, null);
++ }
++
++ public ExperienceOrb(Level world, double x, double y, double z, int amount, @javax.annotation.Nullable org.bukkit.entity.ExperienceOrb.SpawnReason reason, @javax.annotation.Nullable Entity triggerId) {
++ this(world, x, y, z, amount, reason, triggerId, null);
++ }
++
++ public ExperienceOrb(Level world, double x, double y, double z, int amount, @javax.annotation.Nullable org.bukkit.entity.ExperienceOrb.SpawnReason reason, @javax.annotation.Nullable Entity triggerId, @javax.annotation.Nullable Entity sourceId) {
+ this(EntityType.EXPERIENCE_ORB, world);
++ this.sourceEntityId = sourceId != null ? sourceId.getUUID() : null;
++ this.triggerEntityId = triggerId != null ? triggerId.getUUID() : null;
++ this.spawnReason = reason != null ? reason : org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN;
++ // Paper end
+ this.setPos(x, y, z);
+ this.setYRot((float) (this.random.nextDouble() * 360.0D));
+ this.setDeltaMovement((this.random.nextDouble() * 0.20000000298023224D - 0.10000000149011612D) * 2.0D, this.random.nextDouble() * 0.2D * 2.0D, (this.random.nextDouble() * 0.20000000298023224D - 0.10000000149011612D) * 2.0D);
+@@ -167,12 +221,20 @@ public class ExperienceOrb extends Entity {
+ }
+
+ public static void award(ServerLevel world, Vec3 pos, int amount) {
++ // Paper start - add reasons for orbs
++ award(world, pos, amount, null, null, null);
++ }
++ public static void award(ServerLevel world, Vec3 pos, int amount, org.bukkit.entity.ExperienceOrb.SpawnReason reason, Entity triggerId) {
++ award(world, pos, amount, reason, triggerId, null);
++ }
++ public static void award(ServerLevel world, Vec3 pos, int amount, org.bukkit.entity.ExperienceOrb.SpawnReason reason, Entity triggerId, Entity sourceId) {
++ // Paper end - add reasons for orbs
+ while (amount > 0) {
+ int j = ExperienceOrb.getExperienceValue(amount);
+
+ amount -= j;
+ if (!ExperienceOrb.tryMergeToExisting(world, pos, j)) {
+- world.addFreshEntity(new ExperienceOrb(world, pos.x(), pos.y(), pos.z(), j));
++ world.addFreshEntity(new ExperienceOrb(world, pos.x(), pos.y(), pos.z(), j, reason, triggerId, sourceId)); // Paper - add reason
+ }
+ }
+
+@@ -242,6 +304,7 @@ public class ExperienceOrb extends Entity {
+ nbt.putShort("Age", (short) this.age);
+ nbt.putShort("Value", (short) this.value);
+ nbt.putInt("Count", this.count);
++ this.savePaperNBT(nbt); // Paper
+ }
+
+ @Override
+@@ -250,6 +313,7 @@ public class ExperienceOrb extends Entity {
+ this.age = nbt.getShort("Age");
+ this.value = nbt.getShort("Value");
+ this.count = Math.max(nbt.getInt("Count"), 1);
++ this.loadPaperNBT(nbt); // Paper
+ }
+
+ @Override
+diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+index 19dbfbef6d008ba858ab37fb6d5139b0846ef95f..1379f0ba7cf99e7829aaaca62ac4c56c359f6bf0 100644
+--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+@@ -1819,7 +1819,8 @@ public abstract class LivingEntity extends Entity implements Attackable {
+ protected void dropExperience() {
+ // CraftBukkit start - Update getExpReward() above if the removed if() changes!
+ if (true && !(this instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon)) { // CraftBukkit - SPIGOT-2420: Special case ender dragon will drop the xp over time
+- ExperienceOrb.award((ServerLevel) this.level(), this.position(), this.expToDrop);
++ LivingEntity attacker = this.lastHurtByPlayer != null ? this.lastHurtByPlayer : this.lastHurtByMob; // Paper
++ ExperienceOrb.award((ServerLevel) this.level(), this.position(), this.expToDrop, this instanceof ServerPlayer ? org.bukkit.entity.ExperienceOrb.SpawnReason.PLAYER_DEATH : org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, attacker, this); // Paper
+ this.expToDrop = 0;
+ }
+ // CraftBukkit end
+diff --git a/src/main/java/net/minecraft/world/entity/animal/Animal.java b/src/main/java/net/minecraft/world/entity/animal/Animal.java
+index ec112d626a189aafb148455d097919e88af7ef28..3bd13c9b8c7573726ae7defdc3c0f32876d1cf1f 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/Animal.java
++++ b/src/main/java/net/minecraft/world/entity/animal/Animal.java
+@@ -250,12 +250,14 @@ public abstract class Animal extends AgeableMob {
+
+ public void finalizeSpawnChildFromBreeding(ServerLevel worldserver, Animal entityanimal, @Nullable AgeableMob entityageable, int experience) {
+ // CraftBukkit end
+- Optional.ofNullable(this.getLoveCause()).or(() -> {
+- return Optional.ofNullable(entityanimal.getLoveCause());
+- }).ifPresent((entityplayer) -> {
++ // Paper start
++ ServerPlayer entityplayer = this.getLoveCause();
++ if (entityplayer == null) entityplayer = entityanimal.getLoveCause();
++ if (entityplayer != null) {
++ // Paper end
+ entityplayer.awardStat(Stats.ANIMALS_BRED);
+ CriteriaTriggers.BRED_ANIMALS.trigger(entityplayer, this, entityanimal, entityageable);
+- });
++ } // Paper
+ this.setAge(6000);
+ entityanimal.setAge(6000);
+ this.resetLove();
+@@ -264,7 +266,7 @@ public abstract class Animal extends AgeableMob {
+ if (worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
+ // CraftBukkit start - use event experience
+ if (experience > 0) {
+- worldserver.addFreshEntity(new ExperienceOrb(worldserver, this.getX(), this.getY(), this.getZ(), experience));
++ worldserver.addFreshEntity(new ExperienceOrb(worldserver, this.getX(), this.getY(), this.getZ(), experience, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer, entityageable)); // Paper
+ }
+ // CraftBukkit end
+ }
+diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java
+index 40ea9af580f841c3aba61db7c34efb88d542a547..d2dfccd1e31b2f050c9f480220cf17df71c687c3 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/Fox.java
++++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java
+@@ -897,7 +897,7 @@ public class Fox extends Animal implements VariantHolder<Fox.Type> {
+ if (this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
+ // CraftBukkit start - use event experience
+ if (experience > 0) {
+- this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), experience));
++ this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), experience, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer, entityfox)); // Paper
+ }
+ // CraftBukkit end
+ }
+diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java
+index e401efe0c15bbc6236259645447791142ea2fbda..cf565f3a4baf5898000d23eeedcf314b0c10db5a 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java
++++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java
+@@ -455,7 +455,7 @@ public class Turtle extends Animal {
+ RandomSource randomsource = this.animal.getRandom();
+
+ if (this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
+- this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), randomsource.nextInt(7) + 1));
++ this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), randomsource.nextInt(7) + 1, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer)); // Paper;
+ }
+
+ }
+diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
+index 993ccde9e6ee769ce51cdfa4ff06cfe1bd27bbb0..963fdb4132001aa781eda45b75cb4df97d782ddc 100644
+--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
+@@ -672,7 +672,7 @@ public class EnderDragon extends Mob implements Enemy {
+
+ if (this.level() instanceof ServerLevel) {
+ if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0 && true) { // CraftBukkit - SPIGOT-2420: Already checked for the game rule when calculating the xp
+- ExperienceOrb.award((ServerLevel) this.level(), this.position(), Mth.floor((float) short0 * 0.08F));
++ ExperienceOrb.award((ServerLevel) this.level(), this.position(), Mth.floor((float) short0 * 0.08F), org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this); // Paper
+ }
+
+ if (this.dragonDeathTime == 1 && !this.isSilent()) {
+@@ -701,7 +701,7 @@ public class EnderDragon extends Mob implements Enemy {
+ this.move(MoverType.SELF, new Vec3(0.0D, 0.10000000149011612D, 0.0D));
+ if (this.dragonDeathTime == 200 && this.level() instanceof ServerLevel) {
+ if (true) { // CraftBukkit - SPIGOT-2420: Already checked for the game rule when calculating the xp
+- ExperienceOrb.award((ServerLevel) this.level(), this.position(), Mth.floor((float) short0 * 0.2F));
++ ExperienceOrb.award((ServerLevel) this.level(), this.position(), Mth.floor((float) short0 * 0.2F), org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this); // Paper
+ }
+
+ if (this.dragonFight != null) {
+diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java
+index 0da329781f0afd4ec4a731889b892afe7e399d21..d1c3f5d076a3d560d02553d6a33e5890d0c2bfc7 100644
+--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java
++++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java
+@@ -632,7 +632,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
+ }
+
+ if (offer.shouldRewardExp()) {
+- this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i));
++ this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.getTradingPlayer(), this)); // Paper
+ }
+
+ }
+diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java
+index c493c324a2606a40a3a04a55cb77790614d2707b..057eca881d279c610e933ebff5fb654625b036be 100644
+--- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java
++++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java
+@@ -207,7 +207,7 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill
+ if (offer.shouldRewardExp()) {
+ int i = 3 + this.random.nextInt(4);
+
+- this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i));
++ this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.getTradingPlayer(), this)); // Paper
+ }
+
+ }
+diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java
+index 6b3cf71d7ced6405308b41ac316040bcc03f654d..453da998a98eabe5a55a968277b71bd9998f3221 100644
+--- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java
++++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java
+@@ -524,7 +524,7 @@ public class FishingHook extends Projectile {
+ this.level().addFreshEntity(entityitem);
+ // CraftBukkit start - this.random.nextInt(6) + 1 -> playerFishEvent.getExpToDrop()
+ if (playerFishEvent.getExpToDrop() > 0) {
+- entityhuman.level().addFreshEntity(new ExperienceOrb(entityhuman.level(), entityhuman.getX(), entityhuman.getY() + 0.5D, entityhuman.getZ() + 0.5D, playerFishEvent.getExpToDrop()));
++ entityhuman.level().addFreshEntity(new ExperienceOrb(entityhuman.level(), entityhuman.getX(), entityhuman.getY() + 0.5D, entityhuman.getZ() + 0.5D, playerFishEvent.getExpToDrop(), org.bukkit.entity.ExperienceOrb.SpawnReason.FISHING, this.getPlayerOwner(), this)); // Paper
+ }
+ // CraftBukkit end
+ if (itemstack1.is(ItemTags.FISHES)) {
+diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java
+index 9963db38420b91ae817a18ff084311cb45c0edee..70ceef96c6305324aef3b006f6603817ef187e9f 100644
+--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java
++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java
+@@ -54,7 +54,7 @@ public class ThrownExperienceBottle extends ThrowableItemProjectile {
+ }
+ // CraftBukkit end
+
+- ExperienceOrb.award((ServerLevel) this.level(), this.position(), i);
++ ExperienceOrb.award((ServerLevel) this.level(), this.position(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.EXP_BOTTLE, this.getOwner(), this); // Paper
+ this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
+ }
+
+diff --git a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java
+index 4e3c70f7cff0235dd58414e258567ef07661f53f..d41197db6bbf9e17b67bb1faa5d370eea0212bd3 100644
+--- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java
++++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java
+@@ -97,7 +97,7 @@ public class GrindstoneMenu extends AbstractContainerMenu {
+ public void onTake(net.minecraft.world.entity.player.Player player, ItemStack stack) {
+ context.execute((world, blockposition) -> {
+ if (world instanceof ServerLevel) {
+- ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), this.getExperienceAmount(world));
++ ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), this.getExperienceAmount(world), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player); // Paper
+ }
+
+ world.levelEvent(1042, blockposition, 0);
+diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java
+index 946001c57c326f2d2f0677bca954e8558f1c851b..ed336112581ac77196ed3e60dc8aaf50312c078b 100644
+--- a/src/main/java/net/minecraft/world/level/block/Block.java
++++ b/src/main/java/net/minecraft/world/level/block/Block.java
+@@ -361,8 +361,13 @@ public class Block extends BlockBehaviour implements ItemLike {
+ }
+
+ public void popExperience(ServerLevel world, BlockPos pos, int size) {
++ // Paper start - add entity parameter
++ popExperience(world, pos, size, null);
++ }
++ public void popExperience(ServerLevel world, BlockPos pos, int size, net.minecraft.world.entity.Entity entity) {
++ // Paper end - add entity parameter
+ if (world.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) {
+- ExperienceOrb.award(world, Vec3.atCenterOf(pos), size);
++ ExperienceOrb.award(world, Vec3.atCenterOf(pos), size, org.bukkit.entity.ExperienceOrb.SpawnReason.BLOCK_BREAK, entity); // Paper
+ }
+
+ }
+diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
+index 8bda1a42907132b1f3289f860530ded0a4e61f93..70e068c3d0f72f31df749add2c57450f793524ae 100644
+--- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
++++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
+@@ -630,7 +630,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit
+ j = event.getExpToDrop();
+ // CraftBukkit end
+
+- ExperienceOrb.award(worldserver, vec3d, j);
++ ExperienceOrb.award(worldserver, vec3d, j, org.bukkit.entity.ExperienceOrb.SpawnReason.FURNACE, entityhuman); // Paper
+ }
+
+ @Override
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java
+index 52d4eaf7c92b9b4252e0cae25128b5ae7b0092cc..3bbd314d94de3736e039b4b2d836afa8cc892511 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java
+@@ -371,7 +371,7 @@ public final class CraftEntityTypes {
+ return item;
+ }));
+ register(new EntityTypeData<>(EntityType.EXPERIENCE_ORB, ExperienceOrb.class, CraftExperienceOrb::new,
+- spawnData -> new net.minecraft.world.entity.ExperienceOrb(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), 0)
++ spawnData -> new net.minecraft.world.entity.ExperienceOrb(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), 0, org.bukkit.entity.ExperienceOrb.SpawnReason.CUSTOM, null, null) // Paper
+ ));
+ register(new EntityTypeData<>(EntityType.AREA_EFFECT_CLOUD, AreaEffectCloud.class, CraftAreaEffectCloud::new, spawnData -> new net.minecraft.world.entity.AreaEffectCloud(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z())));
+ register(new EntityTypeData<>(EntityType.EGG, Egg.class, CraftEgg::new, spawnData -> new ThrownEgg(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z())));
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java
+index 9231511af4cba747594000364f0b8fceeeab4819..5a7d314ec0562e472f5dc45924a7b24841cff126 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java
+@@ -18,6 +18,18 @@ public class CraftExperienceOrb extends CraftEntity implements ExperienceOrb {
+ this.getHandle().value = value;
+ }
+
++ // Paper start
++ public java.util.UUID getTriggerEntityId() {
++ return getHandle().triggerEntityId;
++ }
++ public java.util.UUID getSourceEntityId() {
++ return getHandle().sourceEntityId;
++ }
++ public SpawnReason getSpawnReason() {
++ return getHandle().spawnReason;
++ }
++ // Paper end
++
+ @Override
+ public net.minecraft.world.entity.ExperienceOrb getHandle() {
+ return (net.minecraft.world.entity.ExperienceOrb) this.entity;