From 01a13871deefa50e186a10b63f71c5e0459e7d30 Mon Sep 17 00:00:00 2001 From: Spottedleaf <6100722+Spottedleaf@users.noreply.github.com> Date: Mon, 26 Sep 2022 01:02:51 -0700 Subject: Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again. --- patches/server/0256-Improve-death-events.patch | 400 +++++++++++++++++++++++++ 1 file changed, 400 insertions(+) create mode 100644 patches/server/0256-Improve-death-events.patch (limited to 'patches/server/0256-Improve-death-events.patch') diff --git a/patches/server/0256-Improve-death-events.patch b/patches/server/0256-Improve-death-events.patch new file mode 100644 index 0000000000..28accbc187 --- /dev/null +++ b/patches/server/0256-Improve-death-events.patch @@ -0,0 +1,400 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +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 1a3f9d09df6b6d8c6a84f7f62e142f341d74c6c1..645012ea3590a46773e2bb14e9cafd9e3c695fd4 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -230,6 +230,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; +@@ -801,6 +805,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) { +@@ -946,8 +959,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 a6010e8ddf640a7045f95be04dfce26918d9cd3e..15af85ec11e40337338696c51083aef91591681b 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -262,6 +262,7 @@ public abstract class LivingEntity extends Entity { + public Set 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() { +@@ -1465,13 +1466,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); +@@ -1620,7 +1620,7 @@ public abstract class LivingEntity extends Entity { + if (!this.isRemoved() && !this.dead) { + Entity entity = damageSource.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, damageSource); + } +@@ -1632,20 +1632,53 @@ 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.wasKilled((ServerLevel) this.level, this)) { ++ // Paper - move below into if for onKill ++ ++ // Paper start ++ org.bukkit.event.entity.EntityDeathEvent deathEvent = this.dropAllDeathLoot(damageSource); ++ if (deathEvent == null || !deathEvent.isCancelled()) { ++ if (this.deathScore >= 0 && entityliving != null) { ++ entityliving.awardKillScore(this, this.deathScore, damageSource); ++ } ++ // Paper start - clear equipment if event is not cancelled ++ if (this instanceof Mob) { ++ for (EquipmentSlot slot : this.clearedEquipmentSlots) { ++ this.setItemSlot(slot, ItemStack.EMPTY); ++ } ++ this.clearedEquipmentSlots.clear(); ++ } ++ // 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.wasKilled((ServerLevel) this.level, this); ++ } + this.gameEvent(GameEvent.ENTITY_DIE); +- this.dropAllDeathLoot(damageSource); +- this.createWitherRose(entityliving); ++ } else { ++ this.dead = false; ++ this.setHealth((float) deathEvent.getReviveHealth()); + } +- +- this.level.broadcastEntityEvent(this, (byte) 3); ++ // Paper end ++ this.createWitherRose(entityliving); + } + ++ if (this.dead) { // Paper ++ this.level.broadcastEntityEvent(this, (byte) 3); + this.setPose(Pose.DYING); ++ } // Paper + } + } + +@@ -1653,7 +1686,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(); +@@ -1682,7 +1715,11 @@ public abstract class LivingEntity extends Entity { + } + } + +- protected void dropAllDeathLoot(DamageSource source) { ++ // Paper start ++ protected boolean clearEquipmentSlots = true; ++ protected Set clearedEquipmentSlots = new java.util.HashSet<>(); ++ protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(DamageSource source) { ++ // Paper end + Entity entity = source.getEntity(); + int i; + +@@ -1697,18 +1734,27 @@ 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); ++ // Paper start ++ final boolean prev = this.clearEquipmentSlots; ++ this.clearEquipmentSlots = false; ++ this.clearedEquipmentSlots.clear(); ++ // Paper end + this.dropCustomDeathLoot(source, i, flag); ++ this.clearEquipmentSlots = prev; // 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 ea836c55bad8b897e0fe0cad6d297b9b52209d69..37102e8cdaeb558e80889ff553656f14eaaeb650 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -1018,7 +1018,13 @@ public abstract class Mob extends LivingEntity { + } + + this.spawnAtLocation(itemstack); ++ if (this.clearEquipmentSlots) { // Paper + this.setItemSlot(enumitemslot, ItemStack.EMPTY); ++ // Paper start ++ } else { ++ this.clearedEquipmentSlots.add(enumitemslot); ++ } ++ // Paper 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 fb3b42611d8386b110ea079094d5d50fefceac1a..8f294f10aca2df007830b12da0506f7614206a89 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Fox.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java +@@ -700,15 +700,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 c47884bab387647d99ed842b46f1c078ef9b6995..a3f3e06679cef10f50346e9cc6672ec91c6f04a6 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 +@@ -69,11 +69,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 f94a74728bd7c02a7f8245c92e7916f0b669ee0d..cd54fa8f7bbcb6036e90f4ef7cdc01d7af835a13 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -752,7 +752,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); + this.gameEvent(GameEvent.ENTITY_DIE); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 04b329bf2a71f600debfbac193c0d1f49afff692..8676067be5cbb149a6a50a9582a22b69ebcfb61b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2153,7 +2153,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 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 518e44f783f5062ce53e9907501bd3a54c8014bb..64cd5b2bc4032bfb0c917cc33884062ddba2738f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -816,9 +816,16 @@ public class CraftEventFactory { + public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List 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()) { +@@ -835,8 +842,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(); +@@ -853,6 +867,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 + */ -- cgit v1.2.3