aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/0125-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch
blob: 54243150fc942d548bfc32cea0dcfb328703c0a8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
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 0de7b8b8d2670c6ec50eb56348ca28a315b961e5..73b6aa34ad2579d79f388c5660cdfbef41a769f2 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
@@ -433,7 +433,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 7f3ac3e8631e30c968ef664f994ad208d05eb4a3..b9160ebca0d11dbbf96da5f0f5810d302cfcea9a 100644
--- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java
+++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java
@@ -44,9 +44,63 @@ public class ExperienceOrb extends Entity {
     public int value;
     public 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);
@@ -171,12 +225,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
             }
         }
 
@@ -249,6 +311,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
@@ -257,6 +320,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 6ea3488a578a7464bd34df265742a777575c9029..237025be183b3cc93920430075edcc0bb2899b68 100644
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
@@ -1837,7 +1837,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
     protected void dropExperience(ServerLevel world, @Nullable Entity attacker) {
         // CraftBukkit start - Update getExpReward() above if the removed if() changes!
         if (!(this instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon)) { // CraftBukkit - SPIGOT-2420: Special case ender dragon will drop the xp over time
-            ExperienceOrb.award(world, this.position(), this.expToDrop);
+            ExperienceOrb.award(world, 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 e359bb8e6366b0c695fe3bb14967556a875524f4..775bfac26aaa6db998c2647ec81247b67d0bf784 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Animal.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Animal.java
@@ -277,12 +277,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();
@@ -291,7 +293,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 258fad440d4d2402a273c761da57130a57202b3c..faffc3a9ed8bc306277cad37bc43af2ef7303493 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Fox.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java
@@ -882,7 +882,7 @@ public class Fox extends Animal implements VariantHolder<Fox.Variant> {
                 if (worldserver.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 81982515b7757febb964627e9c5046d52bf5edf5..a2c5042e99a8f4cd45d502320cf1c06e8af0bd0e 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java
@@ -462,7 +462,7 @@ public class Turtle extends Animal {
             RandomSource randomsource = this.animal.getRandom();
 
             if (getServerLevel((Level) 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 95d69e3ca1a9095dfb340e9be0ec322ab6c5eb5e..0012ab1f56180081d210c8836e2a59d543b950b8 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
@@ -659,7 +659,7 @@ public class EnderDragon extends Mob implements Enemy {
 
         if (world instanceof ServerLevel worldserver) {
             if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0 && true) {  // CraftBukkit - SPIGOT-2420: Already checked for the game rule when calculating the xp
-                ExperienceOrb.award(worldserver, this.position(), Mth.floor((float) short0 * 0.08F));
+                ExperienceOrb.award(worldserver, 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()) {
@@ -691,7 +691,7 @@ public class EnderDragon extends Mob implements Enemy {
             if (world instanceof ServerLevel) {
                 ServerLevel worldserver = (ServerLevel) world; // CraftBukkit - decompile error
                 if (true) { // CraftBukkit - SPIGOT-2420: Already checked for the game rule when calculating the xp
-                    ExperienceOrb.award(worldserver, this.position(), Mth.floor((float) short0 * 0.2F));
+                    ExperienceOrb.award(worldserver, 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 624f06d630b55cdcaa97cb66736b69c7ad45dd83..83bb48891d03534468d61cf7683438b3efb131cf 100644
--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java
+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java
@@ -630,7 +630,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 380a876b3cbd660b34dd504cd20f6031b35a613a..8034588a9a87b907c35e28e220280d463f34554e 100644
--- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java
+++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java
@@ -208,7 +208,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 ed378bc8135c329cb7423da06eb26fff69ee4954..9d24d4c3802c525546dae92530c9c5b3cf77924e 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java
@@ -530,7 +530,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 89292bd47e80f7c8bd6a382a44b912a43037b58e..268e46777bdea5b539b0c6833eee08ea8a6c61c2 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java
@@ -55,7 +55,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 56d8ed71861b0a47692fde4c5acc97aaba8166da..13bc52bc856031c930370828b0381e43920aabd2 100644
--- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java
+++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java
@@ -98,7 +98,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 3cefda12d4c2ca2c4e9ef97eff961a55af164d6b..43c2b411115d3a8a0e47d3e2277789b2667897af 100644
--- a/src/main/java/net/minecraft/world/level/block/Block.java
+++ b/src/main/java/net/minecraft/world/level/block/Block.java
@@ -363,8 +363,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 bddbb351ef676a86edb5da74a2a656c7da2ecb5d..62c49afd4da165d0cb4156f106e6e5480d267d4e 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
@@ -517,7 +517,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 3dc11c82e4ccdb57a5e7510bddc5247869075d3e..1c3aacda9242444c0c7bd92cb391e6fff963bb79 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java
@@ -426,7 +426,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(), new net.minecraft.world.item.ItemStack(Items.EGG))));
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;