diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch new file mode 100644 index 0000000000..d69e9ebf10 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch @@ -0,0 +1,79 @@ +--- a/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java ++++ b/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java +@@ -25,6 +_,7 @@ + ); + private static final EntityDataAccessor DATA_SHOW_BOTTOM = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.BOOLEAN); + public int time; ++ public boolean generatedByDragonFight = false; // Paper - Fix invulnerable end crystals + + public EndCrystal(EntityType entityType, Level level) { + super(entityType, level); +@@ -56,9 +_,23 @@ + if (this.level() instanceof ServerLevel) { + BlockPos blockPos = this.blockPosition(); + if (((ServerLevel)this.level()).getDragonFight() != null && this.level().getBlockState(blockPos).isAir()) { ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(this.level(), blockPos, this).isCancelled()) { // Paper + this.level().setBlockAndUpdate(blockPos, BaseFireBlock.getState(this.level(), blockPos)); +- } +- } ++ } // Paper ++ } ++ } ++ ++ // Paper start - Fix invulnerable end crystals ++ if (this.level().paperConfig().unsupportedSettings.fixInvulnerableEndCrystalExploit && this.generatedByDragonFight && this.isInvulnerable()) { ++ if (!java.util.Objects.equals(((ServerLevel) this.level()).uuid, this.getOriginWorld()) ++ || ((ServerLevel) this.level()).getDragonFight() == null ++ || ((ServerLevel) this.level()).getDragonFight().respawnStage == null ++ || ((ServerLevel) this.level()).getDragonFight().respawnStage.ordinal() > net.minecraft.world.level.dimension.end.DragonRespawnAnimation.SUMMONING_DRAGON.ordinal()) { ++ this.setInvulnerable(false); ++ this.setBeamTarget(null); ++ } ++ } ++ // Paper end - Fix invulnerable end crystals + } + + @Override +@@ -68,6 +_,7 @@ + } + + compound.putBoolean("ShowBottom", this.showsBottom()); ++ if (this.generatedByDragonFight) compound.putBoolean("Paper.GeneratedByDragonFight", this.generatedByDragonFight); // Paper - Fix invulnerable end crystals + } + + @Override +@@ -76,6 +_,7 @@ + if (compound.contains("ShowBottom", 1)) { + this.setShowBottom(compound.getBoolean("ShowBottom")); + } ++ if (compound.contains("Paper.GeneratedByDragonFight", 1)) this.generatedByDragonFight = compound.getBoolean("Paper.GeneratedByDragonFight"); // Paper - Fix invulnerable end crystals + } + + @Override +@@ -96,10 +_,24 @@ + return false; + } else { + if (!this.isRemoved()) { +- this.remove(Entity.RemovalReason.KILLED); ++ // Paper start - All non-living entities need this ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, damageSource, amount, false)) { ++ return false; ++ } ++ // Paper end + if (!damageSource.is(DamageTypeTags.IS_EXPLOSION)) { + DamageSource damageSource1 = damageSource.getEntity() != null ? this.damageSources().explosion(this, damageSource.getEntity()) : null; +- level.explode(this, damageSource1, null, this.getX(), this.getY(), this.getZ(), 6.0F, false, Level.ExplosionInteraction.BLOCK); ++ // Paper start ++ org.bukkit.event.entity.ExplosionPrimeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callExplosionPrimeEvent(this, 6.0F, false); ++ if (event.isCancelled()) { ++ return false; ++ } ++ ++ this.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.EXPLODE); // Paper - add Bukkit remove cause ++ level.explode(this, damageSource1, null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.BLOCK); ++ } else { ++ this.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DEATH); // Paper - add Bukkit remove cause ++ // Paper end + } + + this.onDestroyedBy(level, damageSource); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch new file mode 100644 index 0000000000..95f6a4e90e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch @@ -0,0 +1,283 @@ +--- a/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java ++++ b/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +@@ -86,6 +_,10 @@ + private final Node[] nodes = new Node[24]; + private final int[] nodeAdjacency = new int[24]; + private final BinaryHeap openSet = new BinaryHeap(); ++ // Paper start ++ private final net.minecraft.world.level.Explosion explosionSource; // Paper - reusable source for CraftTNTPrimed.getSource() ++ @Nullable private BlockPos podium; ++ // Paper end + + public EnderDragon(EntityType entityType, Level level) { + super(EntityType.ENDER_DRAGON, level); +@@ -101,6 +_,7 @@ + this.setHealth(this.getMaxHealth()); + this.noPhysics = true; + this.phaseManager = new EnderDragonPhaseManager(this); ++ this.explosionSource = new net.minecraft.world.level.ServerExplosion(level.getMinecraftWorld(), this, null, null, new Vec3(Double.NaN, Double.NaN, Double.NaN), Float.NaN, true, net.minecraft.world.level.Explosion.BlockInteraction.DESTROY); // Paper + } + + public void setDragonFight(EndDragonFight dragonFight) { +@@ -119,6 +_,19 @@ + return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0); + } + ++ // Paper start - Allow changing the EnderDragon podium ++ public BlockPos getPodium() { ++ if (this.podium == null) { ++ return EndPodiumFeature.getLocation(this.getFightOrigin()); ++ } ++ return this.podium; ++ } ++ ++ public void setPodium(@Nullable BlockPos blockPos) { ++ this.podium = blockPos; ++ } ++ // Paper end ++ + @Override + public boolean isFlapping() { + float cos = Mth.cos(this.flapTime * (float) (Math.PI * 2)); +@@ -210,7 +_,7 @@ + } + + Vec3 flyTargetLocation = currentPhase.getFlyTargetLocation(); +- if (flyTargetLocation != null) { ++ if (flyTargetLocation != null && currentPhase.getPhase() != EnderDragonPhase.HOVERING) { // Paper - Don't move when hovering + double d = flyTargetLocation.x - this.getX(); + double d1 = flyTargetLocation.y - this.getY(); + double d2 = flyTargetLocation.z - this.getZ(); +@@ -369,7 +_,14 @@ + if (this.nearestCrystal.isRemoved()) { + this.nearestCrystal = null; + } else if (this.tickCount % 10 == 0 && this.getHealth() < this.getMaxHealth()) { +- this.setHealth(this.getHealth() + 1.0F); ++ // Paper start ++ org.bukkit.event.entity.EntityRegainHealthEvent event = new org.bukkit.event.entity.EntityRegainHealthEvent(this.getBukkitEntity(), 1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.ENDER_CRYSTAL); ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ this.setHealth((float) (this.getHealth() + event.getAmount())); ++ } ++ // Paper end + } + } + +@@ -400,7 +_,7 @@ + double d2 = entity.getX() - d; + double d3 = entity.getZ() - d1; + double max = Math.max(d2 * d2 + d3 * d3, 0.1); +- entity.push(d2 / max * 4.0, 0.2F, d3 / max * 4.0); ++ entity.push(d2 / max * 4.0, 0.2F, d3 / max * 4.0, this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + if (!this.phaseManager.getCurrentPhase().isSitting() && livingEntity.getLastHurtByMobTimestamp() < entity.tickCount - 2) { + DamageSource damageSource = this.damageSources().mobAttack(this); + entity.hurtServer(level, damageSource, 5.0F); +@@ -433,6 +_,7 @@ + int floor5 = Mth.floor(box.maxZ); + boolean flag = false; + boolean flag1 = false; ++ List destroyedBlocks = new java.util.ArrayList<>(); // Paper - Create a list to hold all the destroyed blocks + + for (int i = floor; i <= floor3; i++) { + for (int i1 = floor1; i1 <= floor4; i1++) { +@@ -441,7 +_,11 @@ + BlockState blockState = level.getBlockState(blockPos); + if (!blockState.isAir() && !blockState.is(BlockTags.DRAGON_TRANSPARENT)) { + if (level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !blockState.is(BlockTags.DRAGON_IMMUNE)) { +- flag1 = level.removeBlock(blockPos, false) || flag1; ++ // Paper start - Add blocks to list rather than destroying them ++ //flag1 = level.removeBlock(blockPos, false) || flag1; ++ flag1 = true; ++ destroyedBlocks.add(org.bukkit.craftbukkit.block.CraftBlock.at(level, blockPos)); ++ // Paper end + } else { + flag = true; + } +@@ -450,6 +_,54 @@ + } + } + ++ // Paper start - Set off an EntityExplodeEvent for the dragon exploding all these blocks ++ // SPIGOT-4882: don't fire event if nothing hit ++ if (!flag1) { ++ return flag; ++ } ++ ++ org.bukkit.event.entity.EntityExplodeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityExplodeEvent(this, destroyedBlocks, 0F, this.explosionSource.getBlockInteraction()); ++ if (event.isCancelled()) { ++ // This flag literally means 'Dragon hit something hard' (Obsidian, White Stone or Bedrock) and will cause the dragon to slow down. ++ // We should consider adding an event extension for it, or perhaps returning true if the event is cancelled. ++ return flag; ++ } else if (event.getYield() == 0F) { ++ // Yield zero ==> no drops ++ for (org.bukkit.block.Block block : event.blockList()) { ++ this.level().removeBlock(new BlockPos(block.getX(), block.getY(), block.getZ()), false); ++ } ++ } else { ++ for (org.bukkit.block.Block block : event.blockList()) { ++ org.bukkit.Material blockId = block.getType(); ++ if (blockId.isAir()) { ++ continue; ++ } ++ ++ org.bukkit.craftbukkit.block.CraftBlock craftBlock = ((org.bukkit.craftbukkit.block.CraftBlock) block); ++ BlockPos blockposition = craftBlock.getPosition(); ++ ++ net.minecraft.world.level.block.Block nmsBlock = craftBlock.getNMS().getBlock(); ++ if (nmsBlock.dropFromExplosion(this.explosionSource)) { ++ net.minecraft.world.level.block.entity.BlockEntity tileentity = craftBlock.getNMS().hasBlockEntity() ? this.level().getBlockEntity(blockposition) : null; ++ net.minecraft.world.level.storage.loot.LootParams.Builder loottableinfo_builder = (new net.minecraft.world.level.storage.loot.LootParams.Builder((ServerLevel) this.level())).withParameter(net.minecraft.world.level.storage.loot.parameters.LootContextParams.ORIGIN, Vec3.atCenterOf(blockposition)).withParameter(net.minecraft.world.level.storage.loot.parameters.LootContextParams.TOOL, net.minecraft.world.item.ItemStack.EMPTY).withParameter(net.minecraft.world.level.storage.loot.parameters.LootContextParams.EXPLOSION_RADIUS, 1.0F / event.getYield()).withOptionalParameter(net.minecraft.world.level.storage.loot.parameters.LootContextParams.BLOCK_ENTITY, tileentity); ++ ++ craftBlock.getNMS().getDrops(loottableinfo_builder).forEach((itemstack) -> { ++ net.minecraft.world.level.block.Block.popResource(this.level(), blockposition, itemstack); ++ }); ++ craftBlock.getNMS().spawnAfterBreak((ServerLevel) this.level(), blockposition, net.minecraft.world.item.ItemStack.EMPTY, false); ++ } ++ // Paper start - TNTPrimeEvent ++ org.bukkit.block.Block tntBlock = org.bukkit.craftbukkit.block.CraftBlock.at(this.level(), blockposition); ++ if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.EXPLOSION, explosionSource.getIndirectSourceEntity().getBukkitEntity()).callEvent()) ++ continue; ++ // Paper end - TNTPrimeEvent ++ nmsBlock.wasExploded((ServerLevel) this.level(), blockposition, this.explosionSource); ++ ++ this.level().removeBlock(blockposition, false); ++ } ++ } ++ // Paper end ++ + if (flag1) { + BlockPos blockPos1 = new BlockPos( + floor + this.random.nextInt(floor3 - floor + 1), +@@ -507,7 +_,15 @@ + + @Override + public void kill(ServerLevel level) { +- this.remove(Entity.RemovalReason.KILLED); ++ // Paper start - Fire entity death event ++ this.silentDeath = true; ++ org.bukkit.event.entity.EntityDeathEvent deathEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, this.damageSources().genericKill()); ++ if (deathEvent.isCancelled()) { ++ this.silentDeath = false; // Reset to default if event was cancelled ++ return; ++ } ++ this.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DEATH); // Paper - add Bukkit remove cause ++ // Paper end - Fire entity death event + this.gameEvent(GameEvent.ENTITY_DIE); + if (this.dragonFight != null) { + this.dragonFight.updateDragon(this); +@@ -529,18 +_,41 @@ + this.level().addParticle(ParticleTypes.EXPLOSION_EMITTER, this.getX() + f, this.getY() + 2.0 + f1, this.getZ() + f2, 0.0, 0.0, 0.0); + } + ++ // Paper start - SPIGOT-2420: Moved up to #getExpReward method ++ /* + int i = 500; + if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) { + i = 12000; + } ++ */ ++ int i = this.expToDrop; ++ // Paper end + + if (this.level() instanceof ServerLevel serverLevel) { +- if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0 && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { +- ExperienceOrb.award(serverLevel, this.position(), Mth.floor(i * 0.08F)); ++ if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0) { // Paper - SPIGOT-2420: Already checked for the game rule when calculating the xp ++ ExperienceOrb.award(serverLevel, this.position(), Mth.floor(i * 0.08F), org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this); // Paper + } + + if (this.dragonDeathTime == 1 && !this.isSilent()) { +- serverLevel.globalLevelEvent(1028, this.blockPosition(), 0); ++ // Paper start - Use relative location for far away sounds ++ // serverLevel.globalLevelEvent(1028, this.blockPosition(), 0); ++ int viewDistance = serverLevel.getCraftServer().getViewDistance() * 16; ++ for (net.minecraft.server.level.ServerPlayer player : serverLevel.getPlayersForGlobalSoundGamerule()) { // Paper - respect global sound events gamerule ++ double deltaX = this.getX() - player.getX(); ++ double deltaZ = this.getZ() - player.getZ(); ++ double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; ++ final double soundRadiusSquared = serverLevel.getGlobalSoundRangeSquared(config -> config.dragonDeathSoundRadius); // Paper - respect global sound events gamerule ++ if (!serverLevel.getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS) && distanceSquared > soundRadiusSquared) continue; // Paper - respect global sound events gamerule ++ if (distanceSquared > viewDistance * viewDistance) { ++ double deltaLength = Math.sqrt(distanceSquared); ++ double relativeX = player.getX() + (deltaX / deltaLength) * viewDistance; ++ double relativeZ = player.getZ() + (deltaZ / deltaLength) * viewDistance; ++ player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(1028, new BlockPos((int) relativeX, (int) this.getY(), (int) relativeZ), 0, true)); ++ } else { ++ player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(1028, new BlockPos((int) this.getX(), (int) this.getY(), (int) this.getZ()), 0, true)); ++ } ++ } ++ // Paper end + } + } + +@@ -553,15 +_,15 @@ + } + + if (this.dragonDeathTime == 200 && this.level() instanceof ServerLevel serverLevel1) { +- if (serverLevel1.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { +- ExperienceOrb.award(serverLevel1, this.position(), Mth.floor(i * 0.2F)); ++ if (true) { // Paper - SPIGOT-2420: Already checked for the game rule when calculating the xp ++ ExperienceOrb.award(serverLevel1, this.position(), Mth.floor(i * 0.2F), org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this); // Paper + } + + if (this.dragonFight != null) { + this.dragonFight.setDragonKilled(this); + } + +- this.remove(Entity.RemovalReason.KILLED); ++ this.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DEATH); // Paper - add Bukkit remove cause + this.gameEvent(GameEvent.ENTITY_DIE); + } + } +@@ -743,6 +_,7 @@ + super.addAdditionalSaveData(compound); + compound.putInt("DragonPhase", this.phaseManager.getCurrentPhase().getPhase().getId()); + compound.putInt("DragonDeathTime", this.dragonDeathTime); ++ compound.putInt("Bukkit.expToDrop", this.expToDrop); // Paper - SPIGOT-2420: The ender dragon drops xp over time which can also happen between server starts + } + + @Override +@@ -755,6 +_,12 @@ + if (compound.contains("DragonDeathTime")) { + this.dragonDeathTime = compound.getInt("DragonDeathTime"); + } ++ ++ // Paper start - SPIGOT-2420: The ender dragon drops xp over time which can also happen between server starts ++ if (compound.contains("Bukkit.expToDrop")) { ++ this.expToDrop = compound.getInt("Bukkit.expToDrop"); ++ } ++ // Paper end + } + + @Override +@@ -795,7 +_,7 @@ + EnderDragonPhase phase = currentPhase.getPhase(); + Vec3 viewVector; + if (phase == EnderDragonPhase.LANDING || phase == EnderDragonPhase.TAKEOFF) { +- BlockPos heightmapPos = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.fightOrigin)); ++ BlockPos heightmapPos = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.getPodium()); // Paper - Allow changing the EnderDragon podium + float max = Math.max((float)Math.sqrt(heightmapPos.distToCenterSqr(this.position())) / 4.0F, 1.0F); + float f = 6.0F / max; + float xRot = this.getXRot(); +@@ -883,4 +_,20 @@ + protected float sanitizeScale(float scale) { + return 1.0F; + } ++ ++ // Paper start - SPIGOT-2420: Special case, the ender dragon drops 12000 xp for the first kill and 500 xp for every other kill and this over time. ++ @Override ++ public int getExpReward(ServerLevel worldserver, Entity entity) { ++ // CraftBukkit - Moved from #tickDeath method ++ boolean flag = worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT); ++ short short0 = 500; ++ ++ if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) { ++ short0 = 12000; ++ } ++ ++ return flag ? short0 : 0; ++ } ++ // Paper end ++ + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java.patch new file mode 100644 index 0000000000..3c43f0ec62 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java ++++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java +@@ -34,7 +_,7 @@ + public void doServerTick(ServerLevel level) { + this.time++; + if (this.targetLocation == null) { +- BlockPos heightmapPos = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); ++ BlockPos heightmapPos = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium + this.targetLocation = Vec3.atBottomCenterOf(heightmapPos); + } + diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java.patch similarity index 55% rename from paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java.patch rename to paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java.patch index f07b2b70e3..9652e080af 100644 --- a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java.patch @@ -1,11 +1,11 @@ --- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java +++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java -@@ -53,7 +53,7 @@ +@@ -53,7 +_,7 @@ - private void findNewTarget(ServerLevel world) { + private void findNewTarget(ServerLevel level) { if (this.currentPath != null && this.currentPath.isDone()) { -- BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); -+ BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium +- BlockPos heightmapPos = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); ++ BlockPos heightmapPos = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium int i = this.dragon.getDragonFight() == null ? 0 : this.dragon.getDragonFight().getCrystalsAlive(); if (this.dragon.getRandom().nextInt(i + 3) == 0) { this.dragon.getPhaseManager().setPhase(EnderDragonPhase.LANDING_APPROACH); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java.patch new file mode 100644 index 0000000000..bfc7cb63f7 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java ++++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java +@@ -52,7 +_,7 @@ + private void findNewTarget(ServerLevel level) { + if (this.currentPath == null || this.currentPath.isDone()) { + int i = this.dragon.findClosestNode(); +- BlockPos heightmapPos = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); ++ BlockPos heightmapPos = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium + Player nearestPlayer = level.getNearestPlayer(NEAR_EGG_TARGETING, this.dragon, heightmapPos.getX(), heightmapPos.getY(), heightmapPos.getZ()); + int i1; + if (nearestPlayer != null) { diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java.patch similarity index 70% rename from paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java.patch rename to paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java.patch index 0d9035a506..dcadba5126 100644 --- a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java.patch @@ -1,11 +1,11 @@ --- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java +++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java -@@ -42,7 +42,7 @@ - public void doServerTick(ServerLevel world) { +@@ -50,7 +_,7 @@ + public void doServerTick(ServerLevel level) { if (this.targetLocation == null) { this.targetLocation = Vec3.atBottomCenterOf( -- world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())) -+ world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()) // Paper - Allow changing the EnderDragon podium +- level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())) ++ level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()) // Paper - Allow changing the EnderDragon podium ); } diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java.patch similarity index 57% rename from paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java.patch rename to paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java.patch index 9477328964..f6723920d3 100644 --- a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java.patch @@ -1,35 +1,25 @@ --- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java +++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java -@@ -10,6 +10,9 @@ - import net.minecraft.world.entity.AreaEffectCloud; - import net.minecraft.world.entity.boss.enderdragon.EnderDragon; - import net.minecraft.world.phys.Vec3; -+// CraftBukkit start -+import org.bukkit.event.entity.EntityRemoveEvent; -+// CraftBukkit end - - public class DragonSittingFlamingPhase extends AbstractDragonSittingPhase { - -@@ -86,7 +89,13 @@ +@@ -82,7 +_,13 @@ this.flame.setDuration(200); this.flame.setParticle(ParticleTypes.DRAGON_BREATH); this.flame.addEffect(new MobEffectInstance(MobEffects.HARM)); + if (new com.destroystokyo.paper.event.entity.EnderDragonFlameEvent((org.bukkit.entity.EnderDragon) this.dragon.getBukkitEntity(), (org.bukkit.entity.AreaEffectCloud) this.flame.getBukkitEntity()).callEvent()) { // Paper - EnderDragon Events - world.addFreshEntity(this.flame); -+ // Paper start - EnderDragon Events + level.addFreshEntity(this.flame); ++ // Paper start + } else { + this.end(); + } -+ // Paper end - EnderDragon Events ++ // Paper end } - } -@@ -100,7 +109,7 @@ + +@@ -95,7 +_,7 @@ @Override public void end() { if (this.flame != null) { - this.flame.discard(); -+ this.flame.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause ++ this.flame.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // Paper- add Bukkit remove cause this.flame = null; } - + } diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java.patch similarity index 81% rename from paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java.patch rename to paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java.patch index 7a5a567fd1..5556c6a0ae 100644 --- a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java.patch @@ -1,13 +1,13 @@ --- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java +++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonStrafePlayerPhase.java -@@ -79,8 +79,11 @@ +@@ -77,8 +_,11 @@ } - DragonFireball dragonFireball = new DragonFireball(world, this.dragon, vec34.normalize()); + DragonFireball dragonFireball = new DragonFireball(level, this.dragon, vec32.normalize()); + dragonFireball.preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported - dragonFireball.moveTo(o, p, q, 0.0F, 0.0F); + dragonFireball.moveTo(d2, d3, d4, 0.0F, 0.0F); + if (new com.destroystokyo.paper.event.entity.EnderDragonShootFireballEvent((org.bukkit.entity.EnderDragon) dragon.getBukkitEntity(), (org.bukkit.entity.DragonFireball) dragonFireball.getBukkitEntity()).callEvent()) // Paper - EnderDragon Events - world.addFreshEntity(dragonFireball); + level.addFreshEntity(dragonFireball); + else dragonFireball.discard(null); // Paper - EnderDragon Events this.fireballCharge = 0; if (this.currentPath != null) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java.patch new file mode 100644 index 0000000000..d81a699daa --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java ++++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java +@@ -24,7 +_,7 @@ + @Override + public void doServerTick(ServerLevel level) { + if (!this.firstTick && this.currentPath != null) { +- BlockPos heightmapPos = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); ++ BlockPos heightmapPos = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium + if (!heightmapPos.closerToCenterThan(this.dragon.position(), 10.0)) { + this.dragon.getPhaseManager().setPhase(EnderDragonPhase.HOLDING_PATTERN); + } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch new file mode 100644 index 0000000000..8f1877cd14 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch @@ -0,0 +1,22 @@ +--- a/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java ++++ b/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java +@@ -23,6 +_,19 @@ + this.currentPhase.end(); + } + ++ // Paper start - Call EnderDragonChangePhaseEvent ++ org.bukkit.event.entity.EnderDragonChangePhaseEvent event = new org.bukkit.event.entity.EnderDragonChangePhaseEvent( ++ (org.bukkit.craftbukkit.entity.CraftEnderDragon) this.dragon.getBukkitEntity(), ++ (this.currentPhase == null) ? null : org.bukkit.craftbukkit.entity.CraftEnderDragon.getBukkitPhase(this.currentPhase.getPhase()), ++ org.bukkit.craftbukkit.entity.CraftEnderDragon.getBukkitPhase(phase) ++ ); ++ this.dragon.level().getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return; ++ } ++ phase = org.bukkit.craftbukkit.entity.CraftEnderDragon.getMinecraftPhase(event.getNewPhase()); ++ // Paper end ++ + this.currentPhase = this.getPhase((EnderDragonPhase)phase); + if (!this.dragon.level().isClientSide) { + this.dragon.getEntityData().set(EnderDragon.DATA_PHASE, phase.getId()); diff --git a/paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch b/paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch new file mode 100644 index 0000000000..c28a5c1792 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/level/dimension/end/EndDragonFight.java ++++ b/net/minecraft/world/level/dimension/end/EndDragonFight.java +@@ -92,7 +_,7 @@ + @Nullable + private BlockPos portalLocation; + @Nullable +- private DragonRespawnAnimation respawnStage; ++ public DragonRespawnAnimation respawnStage;// Paper-At: public net.minecraft.world.level.dimension.end.EndDragonFight respawnStage + private int respawnTime; + @Nullable + private List respawnCrystals; diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch deleted file mode 100644 index 9f6803d4ee..0000000000 --- a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch +++ /dev/null @@ -1,91 +0,0 @@ ---- a/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java -+++ b/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java -@@ -19,12 +19,18 @@ - import net.minecraft.world.level.Level; - import net.minecraft.world.level.block.BaseFireBlock; - import net.minecraft.world.level.dimension.end.EndDragonFight; -+// CraftBukkit start -+import org.bukkit.craftbukkit.event.CraftEventFactory; -+import org.bukkit.event.entity.EntityRemoveEvent; -+import org.bukkit.event.entity.ExplosionPrimeEvent; -+// CraftBukkit end - - public class EndCrystal extends Entity { - - private static final EntityDataAccessor> DATA_BEAM_TARGET = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.OPTIONAL_BLOCK_POS); - private static final EntityDataAccessor DATA_SHOW_BOTTOM = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.BOOLEAN); - public int time; -+ public boolean generatedByDragonFight = false; // Paper - Fix invulnerable end crystals - - public EndCrystal(EntityType type, Level world) { - super(type, world); -@@ -57,8 +63,23 @@ - BlockPos blockposition = this.blockPosition(); - - if (((ServerLevel) this.level()).getDragonFight() != null && this.level().getBlockState(blockposition).isAir()) { -- this.level().setBlockAndUpdate(blockposition, BaseFireBlock.getState(this.level(), blockposition)); -+ // CraftBukkit start -+ if (!CraftEventFactory.callBlockIgniteEvent(this.level(), blockposition, this).isCancelled()) { -+ this.level().setBlockAndUpdate(blockposition, BaseFireBlock.getState(this.level(), blockposition)); -+ } -+ // CraftBukkit end - } -+ // Paper start - Fix invulnerable end crystals -+ if (this.level().paperConfig().unsupportedSettings.fixInvulnerableEndCrystalExploit && this.generatedByDragonFight && this.isInvulnerable()) { -+ if (!java.util.Objects.equals(((ServerLevel) this.level()).uuid, this.getOriginWorld()) -+ || ((ServerLevel) this.level()).getDragonFight() == null -+ || ((ServerLevel) this.level()).getDragonFight().respawnStage == null -+ || ((ServerLevel) this.level()).getDragonFight().respawnStage.ordinal() > net.minecraft.world.level.dimension.end.DragonRespawnAnimation.SUMMONING_DRAGON.ordinal()) { -+ this.setInvulnerable(false); -+ this.setBeamTarget(null); -+ } -+ } -+ // Paper end - Fix invulnerable end crystals - } - - } -@@ -70,6 +91,7 @@ - } - - nbt.putBoolean("ShowBottom", this.showsBottom()); -+ if (this.generatedByDragonFight) nbt.putBoolean("Paper.GeneratedByDragonFight", this.generatedByDragonFight); // Paper - Fix invulnerable end crystals - } - - @Override -@@ -78,6 +100,7 @@ - if (nbt.contains("ShowBottom", 1)) { - this.setShowBottom(nbt.getBoolean("ShowBottom")); - } -+ if (nbt.contains("Paper.GeneratedByDragonFight", 1)) this.generatedByDragonFight = nbt.getBoolean("Paper.GeneratedByDragonFight"); // Paper - Fix invulnerable end crystals - - } - -@@ -99,12 +122,26 @@ - return false; - } else { - if (!this.isRemoved()) { -- this.remove(Entity.RemovalReason.KILLED); -+ // CraftBukkit start - All non-living entities need this -+ if (CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount, false)) { -+ return false; -+ } -+ // CraftBukkit end - if (!source.is(DamageTypeTags.IS_EXPLOSION)) { - DamageSource damagesource1 = source.getEntity() != null ? this.damageSources().explosion(this, source.getEntity()) : null; - -- world.explode(this, damagesource1, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), 6.0F, false, Level.ExplosionInteraction.BLOCK); -+ // CraftBukkit start -+ ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, 6.0F, false); -+ if (event.isCancelled()) { -+ return false; -+ } -+ -+ this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause -+ world.explode(this, damagesource1, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.BLOCK); -+ } else { -+ this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause - } -+ // CraftBukkit end - - this.onDestroyedBy(world, source); - } diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch deleted file mode 100644 index 01ecc5bc3d..0000000000 --- a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch +++ /dev/null @@ -1,331 +0,0 @@ ---- a/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -+++ b/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -@@ -37,20 +37,35 @@ - import net.minecraft.world.entity.boss.enderdragon.phases.EnderDragonPhaseManager; - import net.minecraft.world.entity.monster.Enemy; - import net.minecraft.world.entity.player.Player; --import net.minecraft.world.item.enchantment.EnchantmentHelper; - import net.minecraft.world.level.GameRules; - import net.minecraft.world.level.Level; --import net.minecraft.world.level.block.state.BlockState; --import net.minecraft.world.level.dimension.end.EndDragonFight; - import net.minecraft.world.level.gameevent.GameEvent; - import net.minecraft.world.level.levelgen.Heightmap; - import net.minecraft.world.level.levelgen.feature.EndPodiumFeature; - import net.minecraft.world.level.pathfinder.BinaryHeap; - import net.minecraft.world.level.pathfinder.Node; - import net.minecraft.world.level.pathfinder.Path; -+import org.slf4j.Logger; -+ -+// CraftBukkit start -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.item.enchantment.EnchantmentHelper; -+import net.minecraft.world.level.Explosion; -+import net.minecraft.world.level.ServerExplosion; -+import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.block.entity.BlockEntity; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.dimension.end.EndDragonFight; -+import net.minecraft.world.level.storage.loot.LootParams; -+import net.minecraft.world.level.storage.loot.parameters.LootContextParams; - import net.minecraft.world.phys.AABB; - import net.minecraft.world.phys.Vec3; --import org.slf4j.Logger; -+import org.bukkit.craftbukkit.block.CraftBlock; -+import org.bukkit.event.entity.EntityExplodeEvent; -+import org.bukkit.event.entity.EntityRegainHealthEvent; -+import org.bukkit.event.entity.EntityRemoveEvent; -+import org.bukkit.craftbukkit.event.CraftEventFactory; -+// CraftBukkit end - - public class EnderDragon extends Mob implements Enemy { - -@@ -88,6 +103,11 @@ - private final Node[] nodes; - private final int[] nodeAdjacency; - private final BinaryHeap openSet; -+ private final Explosion explosionSource; // CraftBukkit - reusable source for CraftTNTPrimed.getSource() -+ // Paper start - Allow changing the EnderDragon podium -+ @Nullable -+ private BlockPos podium; -+ // Paper end - Allow changing the EnderDragon podium - - public EnderDragon(EntityType entitytypes, Level world) { - super(EntityType.ENDER_DRAGON, world); -@@ -108,6 +128,7 @@ - this.setHealth(this.getMaxHealth()); - this.noPhysics = true; - this.phaseManager = new EnderDragonPhaseManager(this); -+ this.explosionSource = new ServerExplosion(world.getMinecraftWorld(), this, null, null, new Vec3(Double.NaN, Double.NaN, Double.NaN), Float.NaN, true, Explosion.BlockInteraction.DESTROY); // CraftBukkit - } - - public void setDragonFight(EndDragonFight fight) { -@@ -124,7 +145,20 @@ - - public static AttributeSupplier.Builder createAttributes() { - return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0D); -+ } -+ -+ // Paper start - Allow changing the EnderDragon podium -+ public BlockPos getPodium() { -+ if (this.podium == null) { -+ return EndPodiumFeature.getLocation(this.getFightOrigin()); -+ } -+ return this.podium; -+ } -+ -+ public void setPodium(@Nullable BlockPos blockPos) { -+ this.podium = blockPos; - } -+ // Paper end - Allow changing the EnderDragon podium - - @Override - public boolean isFlapping() { -@@ -218,7 +252,7 @@ - - Vec3 vec3d1 = idragoncontroller.getFlyTargetLocation(); - -- if (vec3d1 != null) { -+ if (vec3d1 != null && idragoncontroller.getPhase() != EnderDragonPhase.HOVERING) { // CraftBukkit - Don't move when hovering - double d0 = vec3d1.x - this.getX(); - double d1 = vec3d1.y - this.getY(); - double d2 = vec3d1.z - this.getZ(); -@@ -379,7 +413,14 @@ - if (this.nearestCrystal.isRemoved()) { - this.nearestCrystal = null; - } else if (this.tickCount % 10 == 0 && this.getHealth() < this.getMaxHealth()) { -- this.setHealth(this.getHealth() + 1.0F); -+ // CraftBukkit start -+ EntityRegainHealthEvent event = new EntityRegainHealthEvent(this.getBukkitEntity(), 1.0F, EntityRegainHealthEvent.RegainReason.ENDER_CRYSTAL); -+ this.level().getCraftServer().getPluginManager().callEvent(event); -+ -+ if (!event.isCancelled()) { -+ this.setHealth((float) (this.getHealth() + event.getAmount())); -+ } -+ // CraftBukkit end - } - } - -@@ -417,7 +458,7 @@ - double d3 = entity.getZ() - d1; - double d4 = Math.max(d2 * d2 + d3 * d3, 0.1D); - -- entity.push(d2 / d4 * 4.0D, 0.20000000298023224D, d3 / d4 * 4.0D); -+ entity.push(d2 / d4 * 4.0D, 0.20000000298023224D, d3 / d4 * 4.0D, this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - if (!this.phaseManager.getCurrentPhase().isSitting() && entityliving.getLastHurtByMobTimestamp() < entity.tickCount - 2) { - DamageSource damagesource = this.damageSources().mobAttack(this); - -@@ -458,6 +499,9 @@ - int j1 = Mth.floor(box.maxZ); - boolean flag = false; - boolean flag1 = false; -+ // CraftBukkit start - Create a list to hold all the destroyed blocks -+ List destroyedBlocks = new java.util.ArrayList(); -+ // CraftBukkit end - - for (int k1 = i; k1 <= l; ++k1) { - for (int l1 = j; l1 <= i1; ++l1) { -@@ -467,14 +511,66 @@ - - if (!iblockdata.isAir() && !iblockdata.is(BlockTags.DRAGON_TRANSPARENT)) { - if (world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !iblockdata.is(BlockTags.DRAGON_IMMUNE)) { -- flag1 = world.removeBlock(blockposition, false) || flag1; -+ // CraftBukkit start - Add blocks to list rather than destroying them -+ // flag1 = worldserver.removeBlock(blockposition, false) || flag1; -+ flag1 = true; -+ destroyedBlocks.add(CraftBlock.at(world, blockposition)); -+ // CraftBukkit end - } else { - flag = true; - } - } -+ } -+ } -+ } -+ -+ // CraftBukkit start - Set off an EntityExplodeEvent for the dragon exploding all these blocks -+ // SPIGOT-4882: don't fire event if nothing hit -+ if (!flag1) { -+ return flag; -+ } -+ -+ EntityExplodeEvent event = CraftEventFactory.callEntityExplodeEvent(this, destroyedBlocks, 0F, this.explosionSource.getBlockInteraction()); -+ if (event.isCancelled()) { -+ // This flag literally means 'Dragon hit something hard' (Obsidian, White Stone or Bedrock) and will cause the dragon to slow down. -+ // We should consider adding an event extension for it, or perhaps returning true if the event is cancelled. -+ return flag; -+ } else if (event.getYield() == 0F) { -+ // Yield zero ==> no drops -+ for (org.bukkit.block.Block block : event.blockList()) { -+ this.level().removeBlock(new BlockPos(block.getX(), block.getY(), block.getZ()), false); -+ } -+ } else { -+ for (org.bukkit.block.Block block : event.blockList()) { -+ org.bukkit.Material blockId = block.getType(); -+ if (blockId.isAir()) { -+ continue; -+ } -+ -+ CraftBlock craftBlock = ((CraftBlock) block); -+ BlockPos blockposition = craftBlock.getPosition(); -+ -+ Block nmsBlock = craftBlock.getNMS().getBlock(); -+ if (nmsBlock.dropFromExplosion(this.explosionSource)) { -+ BlockEntity tileentity = craftBlock.getNMS().hasBlockEntity() ? this.level().getBlockEntity(blockposition) : null; -+ LootParams.Builder loottableinfo_builder = (new LootParams.Builder((ServerLevel) this.level())).withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(blockposition)).withParameter(LootContextParams.TOOL, ItemStack.EMPTY).withParameter(LootContextParams.EXPLOSION_RADIUS, 1.0F / event.getYield()).withOptionalParameter(LootContextParams.BLOCK_ENTITY, tileentity); -+ -+ craftBlock.getNMS().getDrops(loottableinfo_builder).forEach((itemstack) -> { -+ Block.popResource(this.level(), blockposition, itemstack); -+ }); -+ craftBlock.getNMS().spawnAfterBreak((ServerLevel) this.level(), blockposition, ItemStack.EMPTY, false); - } -+ // Paper start - TNTPrimeEvent -+ org.bukkit.block.Block tntBlock = CraftBlock.at(this.level(), blockposition); -+ if (!new com.destroystokyo.paper.event.block.TNTPrimeEvent(tntBlock, com.destroystokyo.paper.event.block.TNTPrimeEvent.PrimeReason.EXPLOSION, explosionSource.getIndirectSourceEntity().getBukkitEntity()).callEvent()) -+ continue; -+ // Paper end - TNTPrimeEvent -+ nmsBlock.wasExploded((ServerLevel) this.level(), blockposition, this.explosionSource); -+ -+ this.level().removeBlock(blockposition, false); - } - } -+ // CraftBukkit end - - if (flag1) { - BlockPos blockposition1 = new BlockPos(i + this.random.nextInt(l - i + 1), j + this.random.nextInt(i1 - j + 1), k + this.random.nextInt(j1 - k + 1)); -@@ -531,7 +627,15 @@ - - @Override - public void kill(ServerLevel world) { -- this.remove(Entity.RemovalReason.KILLED); -+ // Paper start - Fire entity death event -+ this.silentDeath = true; -+ org.bukkit.event.entity.EntityDeathEvent deathEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, this.damageSources().genericKill()); -+ if (deathEvent.isCancelled()) { -+ this.silentDeath = false; // Reset to default if event was cancelled -+ return; -+ } -+ // Paper end - Fire entity death event -+ this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause - this.gameEvent(GameEvent.ENTITY_DIE); - if (this.dragonFight != null) { - this.dragonFight.updateDragon(this); -@@ -540,7 +644,22 @@ - - } - -+ // CraftBukkit start - SPIGOT-2420: Special case, the ender dragon drops 12000 xp for the first kill and 500 xp for every other kill and this over time. - @Override -+ public int getExpReward(ServerLevel worldserver, Entity entity) { -+ // CraftBukkit - Moved from #tickDeath method -+ boolean flag = worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT); -+ short short0 = 500; -+ -+ if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) { -+ short0 = 12000; -+ } -+ -+ return flag ? short0 : 0; -+ } -+ // CraftBukkit end -+ -+ @Override - protected void tickDeath() { - if (this.dragonFight != null) { - this.dragonFight.updateDragon(this); -@@ -555,21 +674,44 @@ - this.level().addParticle(ParticleTypes.EXPLOSION_EMITTER, this.getX() + (double) f, this.getY() + 2.0D + (double) f1, this.getZ() + (double) f2, 0.0D, 0.0D, 0.0D); - } - -+ // CraftBukkit start - SPIGOT-2420: Moved up to #getExpReward method -+ /* - short short0 = 500; - - if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) { - short0 = 12000; - } -+ */ -+ int short0 = this.expToDrop; -+ // CraftBukkit end - - Level world = this.level(); - - if (world instanceof ServerLevel worldserver) { -- if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0 && worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { -- ExperienceOrb.award(worldserver, this.position(), Mth.floor((float) short0 * 0.08F)); -+ 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), org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this); // Paper - } - - if (this.dragonDeathTime == 1 && !this.isSilent()) { -- worldserver.globalLevelEvent(1028, this.blockPosition(), 0); -+ // CraftBukkit start - Use relative location for far away sounds -+ // worldserver.globalLevelEvent(1028, this.blockPosition(), 0); -+ int viewDistance = worldserver.getCraftServer().getViewDistance() * 16; -+ for (net.minecraft.server.level.ServerPlayer player : worldserver.getPlayersForGlobalSoundGamerule()) { // Paper - respect global sound events gamerule -+ double deltaX = this.getX() - player.getX(); -+ double deltaZ = this.getZ() - player.getZ(); -+ double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; -+ final double soundRadiusSquared = worldserver.getGlobalSoundRangeSquared(config -> config.dragonDeathSoundRadius); // Paper - respect global sound events gamerule -+ if ( !worldserver.getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS) && distanceSquared > soundRadiusSquared ) continue; // Spigot // Paper - respect global sound events gamerule -+ if (distanceSquared > viewDistance * viewDistance) { -+ double deltaLength = Math.sqrt(distanceSquared); -+ double relativeX = player.getX() + (deltaX / deltaLength) * viewDistance; -+ double relativeZ = player.getZ() + (deltaZ / deltaLength) * viewDistance; -+ player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(1028, new BlockPos((int) relativeX, (int) this.getY(), (int) relativeZ), 0, true)); -+ } else { -+ player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(1028, new BlockPos((int) this.getX(), (int) this.getY(), (int) this.getZ()), 0, true)); -+ } -+ } -+ // CraftBukkit end - } - } - -@@ -592,15 +734,15 @@ - if (world1 instanceof ServerLevel) { - ServerLevel worldserver1 = (ServerLevel) world1; - -- if (worldserver1.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { -- ExperienceOrb.award(worldserver1, this.position(), Mth.floor((float) short0 * 0.2F)); -+ if (true) { // CraftBukkit - SPIGOT-2420: Already checked for the game rule when calculating the xp -+ ExperienceOrb.award(worldserver1, this.position(), Mth.floor((float) short0 * 0.2F), org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this); // Paper - } - - if (this.dragonFight != null) { - this.dragonFight.setDragonKilled(this); - } - -- this.remove(Entity.RemovalReason.KILLED); -+ this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause - this.gameEvent(GameEvent.ENTITY_DIE); - } - } -@@ -814,6 +956,7 @@ - super.addAdditionalSaveData(nbt); - nbt.putInt("DragonPhase", this.phaseManager.getCurrentPhase().getPhase().getId()); - nbt.putInt("DragonDeathTime", this.dragonDeathTime); -+ nbt.putInt("Bukkit.expToDrop", this.expToDrop); // CraftBukkit - SPIGOT-2420: The ender dragon drops xp over time which can also happen between server starts - } - - @Override -@@ -827,6 +970,11 @@ - this.dragonDeathTime = nbt.getInt("DragonDeathTime"); - } - -+ // CraftBukkit start - SPIGOT-2420: The ender dragon drops xp over time which can also happen between server starts -+ if (nbt.contains("Bukkit.expToDrop")) { -+ this.expToDrop = nbt.getInt("Bukkit.expToDrop"); -+ } -+ // CraftBukkit end - } - - @Override -@@ -879,7 +1027,7 @@ - vec3d = this.getViewVector(tickDelta); - } - } else { -- BlockPos blockposition = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.fightOrigin)); -+ BlockPos blockposition = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.getPodium()); // Paper - Allow changing the EnderDragon podium - - f1 = Math.max((float) Math.sqrt(blockposition.distToCenterSqr(this.position())) / 4.0F, 1.0F); - float f3 = 6.0F / f1; diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java.patch deleted file mode 100644 index 2dcf3ac15b..0000000000 --- a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java -+++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java -@@ -42,7 +42,7 @@ - public void doServerTick(ServerLevel world) { - this.time++; - if (this.targetLocation == null) { -- BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); -+ BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium - this.targetLocation = Vec3.atBottomCenterOf(blockPos); - } - diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java.patch deleted file mode 100644 index ce24273215..0000000000 --- a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java -+++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java -@@ -52,7 +52,7 @@ - private void findNewTarget(ServerLevel world) { - if (this.currentPath == null || this.currentPath.isDone()) { - int i = this.dragon.findClosestNode(); -- BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); -+ BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium - Player player = world.getNearestPlayer(NEAR_EGG_TARGETING, this.dragon, (double)blockPos.getX(), (double)blockPos.getY(), (double)blockPos.getZ()); - int j; - if (player != null) { diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java.patch deleted file mode 100644 index 54fe962df5..0000000000 --- a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java -+++ b/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java -@@ -24,7 +24,7 @@ - @Override - public void doServerTick(ServerLevel world) { - if (!this.firstTick && this.currentPath != null) { -- BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); -+ BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium - if (!blockPos.closerToCenterThan(this.dragon.position(), 10.0)) { - this.dragon.getPhaseManager().setPhase(EnderDragonPhase.HOLDING_PATTERN); - } diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch deleted file mode 100644 index d39507da58..0000000000 --- a/paper-server/patches/unapplied/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch +++ /dev/null @@ -1,42 +0,0 @@ ---- a/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java -+++ b/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java -@@ -5,6 +5,11 @@ - import net.minecraft.world.entity.boss.enderdragon.EnderDragon; - import org.slf4j.Logger; - -+// CraftBukkit start -+import org.bukkit.craftbukkit.entity.CraftEnderDragon; -+import org.bukkit.event.entity.EnderDragonChangePhaseEvent; -+// CraftBukkit end -+ - public class EnderDragonPhaseManager { - - private static final Logger LOGGER = LogUtils.getLogger(); -@@ -24,6 +29,19 @@ - this.currentPhase.end(); - } - -+ // CraftBukkit start - Call EnderDragonChangePhaseEvent -+ EnderDragonChangePhaseEvent event = new EnderDragonChangePhaseEvent( -+ (CraftEnderDragon) this.dragon.getBukkitEntity(), -+ (this.currentPhase == null) ? null : CraftEnderDragon.getBukkitPhase(this.currentPhase.getPhase()), -+ CraftEnderDragon.getBukkitPhase(type) -+ ); -+ this.dragon.level().getCraftServer().getPluginManager().callEvent(event); -+ if (event.isCancelled()) { -+ return; -+ } -+ type = CraftEnderDragon.getMinecraftPhase(event.getNewPhase()); -+ // CraftBukkit end -+ - this.currentPhase = this.getPhase(type); - if (!this.dragon.level().isClientSide) { - this.dragon.getEntityData().set(EnderDragon.DATA_PHASE, type.getId()); -@@ -45,6 +63,6 @@ - this.phases[i] = type.createInstance(this.dragon); - } - -- return this.phases[i]; -+ return (T) this.phases[i]; // CraftBukkit - decompile error - } - }