386 lines
20 KiB
Diff
386 lines
20 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Phoenix616 <mail@moep.tv>
|
|
Date: Tue, 21 Aug 2018 01:39:35 +0100
|
|
Subject: [PATCH] Improve death events
|
|
|
|
This adds the ability to cancel the death events and to modify the sound
|
|
an entity makes when dying. (In cases were no sound should it will be
|
|
called with shouldPlaySound set to false allowing unsilencing of silent
|
|
entities)
|
|
|
|
It makes handling of entity deaths a lot nicer as you no longer need
|
|
to listen on the damage event and calculate if the entity dies yourself
|
|
to cancel the death which has the benefit of also receiving the dropped
|
|
items and experience which is otherwise only properly possible by using
|
|
internal code.
|
|
|
|
TODO 1.17: this needs to be checked (actually get off your lazy ass and cancel the events) for the following entities,
|
|
maybe more (please check patch overrides for drops for more):
|
|
- players, armor stands, foxes, chested donkeys/llamas
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
index fd1937f49312204d38510996a5be43b731f38bde..a2e2b6ea166bf64fe5b49672a6c6f86af3776638 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
@@ -222,6 +222,10 @@ public class ServerPlayer extends Player {
|
|
public int latency;
|
|
public boolean wonGame;
|
|
private int containerUpdateDelay; // Paper
|
|
+ // Paper start - cancellable death event
|
|
+ public boolean queueHealthUpdatePacket = false;
|
|
+ public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket;
|
|
+ // Paper end
|
|
|
|
// CraftBukkit start
|
|
public String displayName;
|
|
@@ -786,6 +790,15 @@ public class ServerPlayer extends Player {
|
|
String deathmessage = defaultMessage.getString();
|
|
this.keepLevel = keepInventory; // SPIGOT-2222: pre-set keepLevel
|
|
org.bukkit.event.entity.PlayerDeathEvent event = CraftEventFactory.callPlayerDeathEvent(this, loot, PaperAdventure.asAdventure(defaultMessage), defaultMessage.getString(), keepInventory); // Paper - Adventure
|
|
+ // Paper start - cancellable death event
|
|
+ if (event.isCancelled()) {
|
|
+ // make compatible with plugins that might have already set the health in an event listener
|
|
+ if (this.getHealth() <= 0) {
|
|
+ this.setHealth((float) event.getReviveHealth());
|
|
+ }
|
|
+ return;
|
|
+ }
|
|
+ // Paper end
|
|
|
|
// SPIGOT-943 - only call if they have an inventory open
|
|
if (this.containerMenu != this.inventoryMenu) {
|
|
@@ -933,8 +946,17 @@ public class ServerPlayer extends Player {
|
|
}
|
|
}
|
|
}
|
|
-
|
|
- return super.hurt(source, amount);
|
|
+ // Paper start - cancellable death events
|
|
+ //return super.hurt(source, amount);
|
|
+ this.queueHealthUpdatePacket = true;
|
|
+ boolean damaged = super.hurt(source, amount);
|
|
+ this.queueHealthUpdatePacket = false;
|
|
+ if (this.queuedHealthUpdatePacket != null) {
|
|
+ this.connection.send(this.queuedHealthUpdatePacket);
|
|
+ this.queuedHealthUpdatePacket = null;
|
|
+ }
|
|
+ return damaged;
|
|
+ // Paper end
|
|
}
|
|
}
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
|
|
index cf4640f1e38f3e50b3a85692e032a78b4102d31e..28ec62fd75abc04d4dc22204bfd999d23941f6d2 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
|
|
@@ -258,6 +258,7 @@ public abstract class LivingEntity extends Entity {
|
|
public Set<UUID> collidableExemptions = new HashSet<>();
|
|
public boolean bukkitPickUpLoot;
|
|
public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper
|
|
+ public boolean silentDeath = false; // Paper - mark entity as dying silently for cancellable death event
|
|
|
|
@Override
|
|
public float getBukkitYaw() {
|
|
@@ -1441,13 +1442,12 @@ public abstract class LivingEntity extends Entity {
|
|
if (knockbackCancelled) this.level.broadcastEntityEvent(this, (byte) 2); // Paper - Disable explosion knockback
|
|
if (this.isDeadOrDying()) {
|
|
if (!this.checkTotemDeathProtection(source)) {
|
|
- SoundEvent soundeffect = this.getDeathSound();
|
|
-
|
|
- if (flag1 && soundeffect != null) {
|
|
- this.playSound(soundeffect, this.getSoundVolume(), this.getVoicePitch());
|
|
- }
|
|
+ // Paper start - moved into CraftEventFactory event caller for cancellable death event
|
|
+ this.silentDeath = !flag1; // mark entity as dying silently
|
|
+ // Paper end
|
|
|
|
this.die(source);
|
|
+ this.silentDeath = false; // Paper - cancellable death event - reset to default
|
|
}
|
|
} else if (flag1) {
|
|
this.playHurtSound(source);
|
|
@@ -1596,7 +1596,7 @@ public abstract class LivingEntity extends Entity {
|
|
if (!this.isRemoved() && !this.dead) {
|
|
Entity entity = source.getEntity();
|
|
LivingEntity entityliving = this.getKillCredit();
|
|
-
|
|
+ /* // Paper - move down to make death event cancellable - this is the awardKillScore below
|
|
if (this.deathScore >= 0 && entityliving != null) {
|
|
entityliving.awardKillScore(this, this.deathScore, source);
|
|
}
|
|
@@ -1608,20 +1608,52 @@ public abstract class LivingEntity extends Entity {
|
|
if (!this.level.isClientSide && this.hasCustomName()) {
|
|
if (org.spigotmc.SpigotConfig.logNamedDeaths) LivingEntity.LOGGER.info("Named entity {} died: {}", this, this.getCombatTracker().getDeathMessage().getString()); // Spigot
|
|
}
|
|
+ */ // Paper - move down to make death event cancellable - this is the awardKillScore below
|
|
|
|
this.dead = true;
|
|
- this.getCombatTracker().recheckStatus();
|
|
+ // Paper - moved into if below
|
|
if (this.level instanceof ServerLevel) {
|
|
if (entity != null) {
|
|
- entity.killed((ServerLevel) this.level, this);
|
|
+ // Paper - move below into if for onKill
|
|
}
|
|
|
|
- this.dropAllDeathLoot(source);
|
|
+ // Paper start
|
|
+ org.bukkit.event.entity.EntityDeathEvent deathEvent = this.dropAllDeathLoot(source);
|
|
+ if (deathEvent == null || !deathEvent.isCancelled()) {
|
|
+ if (this.deathScore >= 0 && entityliving != null) {
|
|
+ entityliving.awardKillScore(this, this.deathScore, source);
|
|
+ }
|
|
+ // Paper start - clear equipment if event is not cancelled
|
|
+ if (this instanceof Mob mob) {
|
|
+ java.util.Collections.fill(mob.handItems, ItemStack.EMPTY);
|
|
+ java.util.Collections.fill(mob.armorItems, ItemStack.EMPTY);
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
+ if (this.isSleeping()) {
|
|
+ this.stopSleeping();
|
|
+ }
|
|
+
|
|
+ if (!this.level.isClientSide && this.hasCustomName()) {
|
|
+ if (org.spigotmc.SpigotConfig.logNamedDeaths) LivingEntity.LOGGER.info("Named entity {} died: {}", this, this.getCombatTracker().getDeathMessage().getString()); // Spigot
|
|
+ }
|
|
+
|
|
+ this.getCombatTracker().recheckStatus();
|
|
+ if (entity != null) {
|
|
+ entity.killed((ServerLevel) this.level, this);
|
|
+ }
|
|
+ } else {
|
|
+ this.dead = false;
|
|
+ this.setHealth((float) deathEvent.getReviveHealth());
|
|
+ }
|
|
+ // Paper end
|
|
this.createWitherRose(entityliving);
|
|
}
|
|
|
|
+ if (this.dead) { // Paper
|
|
this.level.broadcastEntityEvent(this, (byte) 3);
|
|
this.setPose(Pose.DYING);
|
|
+ } // Paper
|
|
}
|
|
}
|
|
|
|
@@ -1629,7 +1661,7 @@ public abstract class LivingEntity extends Entity {
|
|
if (!this.level.isClientSide) {
|
|
boolean flag = false;
|
|
|
|
- if (adversary instanceof WitherBoss) {
|
|
+ if (this.dead && adversary instanceof WitherBoss) { // Paper
|
|
if (this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
|
|
BlockPos blockposition = this.blockPosition();
|
|
BlockState iblockdata = Blocks.WITHER_ROSE.defaultBlockState();
|
|
@@ -1658,7 +1690,8 @@ public abstract class LivingEntity extends Entity {
|
|
}
|
|
}
|
|
|
|
- protected void dropAllDeathLoot(DamageSource source) {
|
|
+ protected boolean clearEquipmentSlots = true; // Paper
|
|
+ protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(DamageSource source) { // Paper
|
|
Entity entity = source.getEntity();
|
|
int i;
|
|
|
|
@@ -1673,18 +1706,23 @@ public abstract class LivingEntity extends Entity {
|
|
this.dropEquipment(); // CraftBukkit - from below
|
|
if (this.shouldDropLoot() && this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
|
|
this.dropFromLootTable(source, flag);
|
|
+ this.clearEquipmentSlots = false; // Paper
|
|
this.dropCustomDeathLoot(source, i, flag);
|
|
+ this.clearEquipmentSlots = true; // Paper
|
|
}
|
|
// CraftBukkit start - Call death event
|
|
- CraftEventFactory.callEntityDeathEvent(this, this.drops);
|
|
+ org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, this.drops); // Paper
|
|
+ this.postDeathDropItems(deathEvent); // Paper
|
|
this.drops = new ArrayList<>();
|
|
// CraftBukkit end
|
|
|
|
// this.dropInventory();// CraftBukkit - moved up
|
|
this.dropExperience();
|
|
+ return deathEvent; // Paper
|
|
}
|
|
|
|
protected void dropEquipment() {}
|
|
+ protected void postDeathDropItems(org.bukkit.event.entity.EntityDeathEvent event) {} // Paper - method for post death logic that cannot be ran before the event is potentially cancelled
|
|
|
|
// CraftBukkit start
|
|
public int getExpReward() {
|
|
diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java
|
|
index 85af49ff909635973ff20ec3e528a4a0a7c19fc4..7db99b4ad89fd5b38a4767d166caedda86a6188a 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/Mob.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/Mob.java
|
|
@@ -1002,7 +1002,9 @@ public abstract class Mob extends LivingEntity {
|
|
}
|
|
|
|
this.spawnAtLocation(itemstack);
|
|
+ if (this.clearEquipmentSlots) { // Paper
|
|
this.setItemSlot(enumitemslot, ItemStack.EMPTY);
|
|
+ } // Paper
|
|
}
|
|
}
|
|
|
|
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 2caf3aa6ca876d59deb98ee917e8228df140b414..63f0ed4c80b7afa091c4a835eefd6d709428f984 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/animal/Fox.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java
|
|
@@ -696,15 +696,25 @@ public class Fox extends Animal {
|
|
}
|
|
|
|
@Override
|
|
- protected void dropAllDeathLoot(DamageSource source) {
|
|
- ItemStack itemstack = this.getItemBySlot(EquipmentSlot.MAINHAND);
|
|
+ // Paper start - Cancellable death event
|
|
+ protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(DamageSource source) {
|
|
+ ItemStack itemstack = this.getItemBySlot(EquipmentSlot.MAINHAND).copy(); // Paper - modified by supercall
|
|
+
|
|
+ org.bukkit.event.entity.EntityDeathEvent deathEvent = super.dropAllDeathLoot(source);
|
|
+
|
|
+ // Below is code to drop
|
|
+
|
|
+ if (deathEvent == null || deathEvent.isCancelled()) {
|
|
+ return deathEvent;
|
|
+ }
|
|
+ // Paper end
|
|
|
|
if (!itemstack.isEmpty()) {
|
|
this.spawnAtLocation(itemstack);
|
|
this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY);
|
|
}
|
|
|
|
- super.dropAllDeathLoot(source);
|
|
+ return deathEvent; // Paper
|
|
}
|
|
|
|
public static boolean isPathClear(Fox fox, LivingEntity chasedEntity) {
|
|
diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java
|
|
index 224eca7d20cf4b890a6bc1b314d566e02e716762..7281eb294ddd178ba742088d3c61bf3d529ff0c4 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java
|
|
@@ -68,11 +68,19 @@ public abstract class AbstractChestedHorse extends AbstractHorse {
|
|
this.spawnAtLocation(Blocks.CHEST);
|
|
}
|
|
|
|
- this.setChest(false);
|
|
+ //this.setCarryingChest(false); // Paper - moved to post death logic
|
|
}
|
|
|
|
}
|
|
|
|
+ // Paper start
|
|
+ protected void postDeathDropItems(org.bukkit.event.entity.EntityDeathEvent event) {
|
|
+ if (this.hasChest() && (event == null || !event.isCancelled())) {
|
|
+ this.setChest(false);
|
|
+ }
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
@Override
|
|
public void addAdditionalSaveData(CompoundTag nbt) {
|
|
super.addAdditionalSaveData(nbt);
|
|
diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
|
|
index 91cf7728aee475cb36f2c02bbfb7e3d2e0d00576..a3a900d10440ed5ebe24370a77ccb6cad911cfc9 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
|
|
@@ -754,7 +754,8 @@ public class ArmorStand extends LivingEntity {
|
|
|
|
@Override
|
|
public void kill() {
|
|
- org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, drops); // CraftBukkit - call event
|
|
+ org.bukkit.event.entity.EntityDeathEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, drops); // CraftBukkit - call event // Paper - make cancellable
|
|
+ if (event.isCancelled()) return; // Paper - make cancellable
|
|
this.remove(Entity.RemovalReason.KILLED);
|
|
}
|
|
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
|
index 602164382586cc73704a776efebcd82ffba22e83..e2c4f56d7bcf4c0c0be38a60d47b814fc615732b 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
|
@@ -1920,7 +1920,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
|
|
}
|
|
|
|
public void sendHealthUpdate() {
|
|
- this.getHandle().connection.send(new ClientboundSetHealthPacket(this.getScaledHealth(), this.getHandle().getFoodData().getFoodLevel(), this.getHandle().getFoodData().getSaturationLevel()));
|
|
+ // Paper start - cancellable death event
|
|
+ ClientboundSetHealthPacket packet = new ClientboundSetHealthPacket(this.getScaledHealth(), this.getHandle().getFoodData().getFoodLevel(), this.getHandle().getFoodData().getSaturationLevel());
|
|
+ if (this.getHandle().queueHealthUpdatePacket) {
|
|
+ this.getHandle().queuedHealthUpdatePacket = packet;
|
|
+ } else {
|
|
+ this.getHandle().connection.send(packet);
|
|
+ }
|
|
+ // Paper end
|
|
}
|
|
|
|
public void injectScaledMaxHealth(Collection<AttributeInstance> collection, boolean force) {
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
|
|
index 47b125ba8924d25e97e34611bed3be819d9e1814..b4032ce470346915251d85d1aa7375a116efe771 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
|
|
@@ -807,9 +807,16 @@ public class CraftEventFactory {
|
|
public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List<org.bukkit.inventory.ItemStack> drops) {
|
|
CraftLivingEntity entity = (CraftLivingEntity) victim.getBukkitEntity();
|
|
EntityDeathEvent event = new EntityDeathEvent(entity, drops, victim.getExpReward());
|
|
+ populateFields(victim, event); // Paper - make cancellable
|
|
CraftWorld world = (CraftWorld) entity.getWorld();
|
|
Bukkit.getServer().getPluginManager().callEvent(event);
|
|
|
|
+ // Paper start - make cancellable
|
|
+ if (event.isCancelled()) {
|
|
+ return event;
|
|
+ }
|
|
+ playDeathSound(victim, event);
|
|
+ // Paper end
|
|
victim.expToDrop = event.getDroppedExp();
|
|
|
|
for (org.bukkit.inventory.ItemStack stack : event.getDrops()) {
|
|
@@ -826,8 +833,15 @@ public class CraftEventFactory {
|
|
PlayerDeathEvent event = new PlayerDeathEvent(entity, drops, victim.getExpReward(), 0, deathMessage, stringDeathMessage); // Paper - Adventure
|
|
event.setKeepInventory(keepInventory);
|
|
event.setKeepLevel(victim.keepLevel); // SPIGOT-2222: pre-set keepLevel
|
|
+ populateFields(victim, event); // Paper - make cancellable
|
|
org.bukkit.World world = entity.getWorld();
|
|
Bukkit.getServer().getPluginManager().callEvent(event);
|
|
+ // Paper start - make cancellable
|
|
+ if (event.isCancelled()) {
|
|
+ return event;
|
|
+ }
|
|
+ playDeathSound(victim, event);
|
|
+ // Paper end
|
|
|
|
victim.keepLevel = event.getKeepLevel();
|
|
victim.newLevel = event.getNewLevel();
|
|
@@ -844,6 +858,31 @@ public class CraftEventFactory {
|
|
return event;
|
|
}
|
|
|
|
+ // Paper start - helper methods for making death event cancellable
|
|
+ // Add information to death event
|
|
+ private static void populateFields(net.minecraft.world.entity.LivingEntity victim, EntityDeathEvent event) {
|
|
+ event.setReviveHealth(event.getEntity().getAttribute(org.bukkit.attribute.Attribute.GENERIC_MAX_HEALTH).getValue());
|
|
+ event.setShouldPlayDeathSound(!victim.silentDeath && !victim.isSilent());
|
|
+ net.minecraft.sounds.SoundEvent soundEffect = victim.getDeathSound();
|
|
+ event.setDeathSound(soundEffect != null ? org.bukkit.craftbukkit.CraftSound.getBukkit(soundEffect) : null);
|
|
+ event.setDeathSoundCategory(org.bukkit.SoundCategory.valueOf(victim.getSoundSource().name()));
|
|
+ event.setDeathSoundVolume(victim.getSoundVolume());
|
|
+ event.setDeathSoundPitch(victim.getVoicePitch());
|
|
+ }
|
|
+
|
|
+ // Play death sound manually
|
|
+ private static void playDeathSound(net.minecraft.world.entity.LivingEntity victim, EntityDeathEvent event) {
|
|
+ if (event.shouldPlayDeathSound() && event.getDeathSound() != null && event.getDeathSoundCategory() != null) {
|
|
+ net.minecraft.world.entity.player.Player source = victim instanceof net.minecraft.world.entity.player.Player ? (net.minecraft.world.entity.player.Player) victim : null;
|
|
+ double x = event.getEntity().getLocation().getX();
|
|
+ double y = event.getEntity().getLocation().getY();
|
|
+ double z = event.getEntity().getLocation().getZ();
|
|
+ net.minecraft.sounds.SoundEvent soundEffect = org.bukkit.craftbukkit.CraftSound.getSoundEffect(event.getDeathSound());
|
|
+ net.minecraft.sounds.SoundSource soundCategory = net.minecraft.sounds.SoundSource.valueOf(event.getDeathSoundCategory().name());
|
|
+ victim.level.playSound(source, x, y, z, soundEffect, soundCategory, event.getDeathSoundVolume(), event.getDeathSoundPitch());
|
|
+ }
|
|
+ }
|
|
+ // Paper end
|
|
/**
|
|
* Server methods
|
|
*/
|