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 cd57a7f3fdedd396d3b8dc4d195eb27bb32969eb..61bc50ef899d2867d4f3c762b71012b27d74e7f6 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 f25466e132cb6b0012dc336877fdf17b88a12ddc..9d1cce3fb02bb2c3ff71055fbafc974b6b98625a 100644 --- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java @@ -39,13 +39,67 @@ 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 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, @javax.annotation.Nullable org.bukkit.entity.ExperienceOrb.SpawnReason reason, @javax.annotation.Nullable Entity triggerId) { + this(world, d0, d1, d2, i, reason, triggerId, null); + } + + public ExperienceOrb(Level world, double d0, double d1, double d2, int i, @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.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) { @@ -160,12 +214,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 } } @@ -235,6 +297,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 @@ -243,6 +306,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 c178a564156562d1dd2c0a30eec3040cb8b4c2d1..3d99e7ea6109261dc5d8de610791ec08dde180b4 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -1789,7 +1789,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 b5b93396166202cbe5a4456794fd394de932262a..be105a4460e9bf2ef4b72a307fa31291c37d5e0e 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Animal.java +++ b/src/main/java/net/minecraft/world/entity/animal/Animal.java @@ -256,12 +256,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(); @@ -270,7 +272,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 408642708961ecb3a1363f84bb9e0496bdde7827..65592c6bd1874e08037e0e5287b370e3924aea3f 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Fox.java +++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java @@ -909,7 +909,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 6dba24f47ca76a432a836178af178c2d52631eee..ed1f6e77d5fc9b6f5f52b1a10275783b514c162c 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java +++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java @@ -464,7 +464,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 4406d9622c6a5f817b7fbd1b448b8c3095c617b2..f68aef2298c3b2994c46d34b0888f988af9190dc 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 @@ -673,7 +673,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 b70ee1dff0442de32a9e20ad54b246d5eae45e58..b3127238cea62c47d710abab44f6570103ba9364 100644 --- a/src/main/java/net/minecraft/world/entity/npc/Villager.java +++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java @@ -638,7 +638,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 d4d1cad7963a5adcc8c32e1bc02104eb70514331..0321b4bb622930bfe57661b0e6b893d7635668fb 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 af6829c25fcd186adc3575d50cab6d46db9a2fac..5c77992e0c49eb217dd66657b35164225b400518 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 2191af62f4202413738542d738456ca71febdbf4..5b3059460d4b18d984483d655526d83204fb1c73 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 8d09c134058e55a23df4e23d965a7a783aed701e..45242f0ed5a0f98953df5f27fb76874d2d9e3473 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 7ed82beb538a25a5246f6288c1dddb4f5f85e997..7646b66bc5ba0288608de0d836c7307e02eebe67 100644 --- a/src/main/java/net/minecraft/world/level/block/Block.java +++ b/src/main/java/net/minecraft/world/level/block/Block.java @@ -362,8 +362,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 e72ed2ed8664cbc408cbe1e38f1f0acacc3841dd..2f628f36c32752d1bac910e180b90639c1ae2c9d 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 @@ -650,7 +650,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 8beb4670fffa2df0a9bd64ebb8810b25ce670f16..134b138abdf17fae340226ecac3868ad7a993086 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java @@ -943,7 +943,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 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;