From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Aikar 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 2f21c7920148b812c48da95168fbc63b86b86c9c..fc42bc68ae31f4fad1c382ef3d3c482c1f335f52 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java @@ -427,7 +427,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 f9ccde41eb20c1d53c438ddb40ca7cc5d2b89836..e952aae85a80a020087c3697624c8c13eab3f914 100644 --- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java @@ -39,13 +39,65 @@ public class ExperienceOrb extends Entity { public int value; private int count; private Player followingPlayer; + // Paper start + public java.util.UUID sourceEntityId; + public java.util.UUID triggerEntityId; + public org.bukkit.entity.ExperienceOrb.SpawnReason spawnReason = org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN; + + private void loadPaperNBT(CompoundTag nbttagcompound) { + if (!nbttagcompound.contains("Paper.ExpData", 10)) { // 10 = compound + return; + } + CompoundTag comp = nbttagcompound.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 nbttagcompound) { + 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()); + } + nbttagcompound.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 d0, double d1, double d2, int i, org.bukkit.entity.ExperienceOrb.SpawnReason reason, Entity triggerId) { + this(world, d0, d1, d2, i, reason, triggerId, null); + } + + public ExperienceOrb(Level world, double d0, double d1, double d2, int i, org.bukkit.entity.ExperienceOrb.SpawnReason reason, Entity triggerId, Entity sourceId) { this(EntityType.EXPERIENCE_ORB, world); - this.setPos(x, y, z); + 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(d0, d1, d2); 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); - this.value = amount; + this.value = i; } public ExperienceOrb(EntityType type, Level world) { @@ -155,12 +207,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 } } @@ -230,6 +290,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 @@ -238,6 +299,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 46701f9c30c5b85b49311ad15da7e1fe2993c789..08087527293c5137af078fa17b6c0f219eab15d0 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -1716,7 +1716,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 3cdd7180a41b87caa942d2b3436ba90726686ecb..6216513805add7c8f52e1ed6c77e2d26786b3ab5 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Animal.java +++ b/src/main/java/net/minecraft/world/entity/animal/Animal.java @@ -262,7 +262,7 @@ public abstract class Animal extends AgeableMob { if (world.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // CraftBukkit start - use event experience if (experience > 0) { - world.addFreshEntity(new ExperienceOrb(world, this.getX(), this.getY(), this.getZ(), experience)); + world.addFreshEntity(new ExperienceOrb(world, 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 18344ba254756bcf81061f46bc44c0cc37358433..13061aed29649acfc52d13207aaebcd8ba339ebe 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Fox.java +++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java @@ -898,7 +898,7 @@ public class Fox extends Animal implements VariantHolder { 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 a42f419524ddb5263467b59075850ff24920d21d..fb5af59559bc18ce2850d95ef73e5545dcab5ebc 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/animal/frog/Frog.java b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java index c12b8831deb54deb191908ba8a769d4c78825d5b..1b3d462e1e229970c53f763b5cdce7d21f1b1703 100644 --- a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java +++ b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java @@ -264,7 +264,7 @@ public class Frog extends Animal implements VariantHolder { this.getBrain().setMemory(MemoryModuleType.IS_PREGNANT, Unit.INSTANCE); world.broadcastEntityEvent(this, (byte)18); if (world.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { - world.addFreshEntity(new ExperienceOrb(world, this.getX(), this.getY(), this.getZ(), this.getRandom().nextInt(7) + 1)); + world.addFreshEntity(new ExperienceOrb(world, this.getX(), this.getY(), this.getZ(), this.getRandom().nextInt(7) + 1, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, serverPlayer)); // 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 039a5246a2e9a5e84f3b712f1d138053605b33ec..18a9050158f71245d5d88f9c0833fd5d4e5fdca6 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 @@ -649,7 +649,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()) { @@ -677,7 +677,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 cad8854cc7523d60c06ca1f03bfd4fbff984087c..806c49c127578a8f0a0bde11a4ad213e18d629af 100644 --- a/src/main/java/net/minecraft/world/entity/npc/Villager.java +++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java @@ -636,7 +636,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 6a56b3c12a733662b23c984975f8dfdb43cf6f9b..69ab58f2d8d9287a64f330a02e7cd3be3fe02402 100644 --- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java +++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java @@ -197,7 +197,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 9a753ffd35403358df07408ffe85197ee3318f39..082fa4318dc03defbdb76e99250b7d71aa6710f5 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java @@ -522,7 +522,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 db6b1a9804a6d75dce22b780044beb04ca69cc94..dcbbff3a8dfcac869f07025e0e8e3d9c47956093 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java +++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java @@ -51,7 +51,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(); } diff --git a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java index 2ad1bffec6242af06b33cc777f27034ed6d6c437..5bcd3857fd30ec43e0191a862fc9c7712149e3dd 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 6603be2c0906c8d78e7de5c3dbed56f9552ff9c1..f475926cfa6fc98f9f8f4fe8be6fd200cce39c7e 100644 --- a/src/main/java/net/minecraft/world/level/block/Block.java +++ b/src/main/java/net/minecraft/world/level/block/Block.java @@ -374,8 +374,13 @@ public class Block extends BlockBehaviour implements ItemLike { } public void popExperience(ServerLevel world, BlockPos pos, int size) { + // Paper start - add player parameter + popExperience(world, pos, size, null); + } + public void popExperience(ServerLevel world, BlockPos pos, int size, net.minecraft.server.level.ServerPlayer player) { + // Paper end - add player 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, player); // 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 9b54fd08fae889ac9db09ba67deb5eaae0f3d010..4ed87cc5a46d9e10c755631a16f0e0cb388fb7f2 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 @@ -637,7 +637,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/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java index 0b4f7953d88971fd86f85d29beefbb84cd2121b1..7c5d5355237bc00f1c8c8433351ca7a9938f2e8b 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java @@ -946,7 +946,7 @@ public abstract class CraftRegionAccessor implements RegionAccessor { } else if (TNTPrimed.class.isAssignableFrom(clazz)) { entity = new PrimedTnt(world, x, y, z, null); } else if (ExperienceOrb.class.isAssignableFrom(clazz)) { - entity = new net.minecraft.world.entity.ExperienceOrb(world, x, y, z, 0); + entity = new net.minecraft.world.entity.ExperienceOrb(world, x, y, z, 0, org.bukkit.entity.ExperienceOrb.SpawnReason.CUSTOM, null, null); // Paper } else if (LightningStrike.class.isAssignableFrom(clazz)) { entity = net.minecraft.world.entity.EntityType.LIGHTNING_BOLT.create(world); entity.moveTo(location.getX(), location.getY(), location.getZ()); diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java index 40713228b149b4532fcee3a54bbe63e161318258..84899284703baeb04bfc79251941265d52ac07e8 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java @@ -19,6 +19,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) entity;