--- a/net/minecraft/world/entity/boss/wither/WitherBoss.java
+++ b/net/minecraft/world/entity/boss/wither/WitherBoss.java
@@ -69,6 +_,7 @@
     private final int[] nextHeadUpdate = new int[2];
     private final int[] idleHeadUpdates = new int[2];
     private int destroyBlocksTick;
+    private boolean canPortal = false; // Paper
     public final ServerBossEvent bossEvent = (ServerBossEvent)new ServerBossEvent(
             this.getDisplayName(), BossEvent.BossBarColor.PURPLE, BossEvent.BossBarOverlay.PROGRESS
         )
@@ -260,15 +_,40 @@
             int i = this.getInvulnerableTicks() - 1;
             this.bossEvent.setProgress(1.0F - i / 220.0F);
             if (i <= 0) {
-                level.explode(this, this.getX(), this.getEyeY(), this.getZ(), 7.0F, false, Level.ExplosionInteraction.MOB);
+                // CraftBukkit start
+                org.bukkit.event.entity.ExplosionPrimeEvent event = new org.bukkit.event.entity.ExplosionPrimeEvent(this.getBukkitEntity(), 7.0F, false);
+                level.getCraftServer().getPluginManager().callEvent(event);
+
+                if (!event.isCancelled()) {
+                    level.explode(this, this.getX(), this.getEyeY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB);
+                }
+                // CraftBukkit end
                 if (!this.isSilent()) {
-                    level.globalLevelEvent(1023, this.blockPosition(), 0);
+                    // CraftBukkit start - Use relative location for far away sounds
+                    // level.globalLevelEvent(1023, this.blockPosition(), 0);
+                    int viewDistance = level.getCraftServer().getViewDistance() * 16;
+                    for (ServerPlayer player : level.getPlayersForGlobalSoundGamerule()) { // Paper - respect global sound events gamerule
+                        double deltaX = this.getX() - player.getX();
+                        double deltaZ = this.getZ() - player.getZ();
+                        double distanceSquared = Mth.square(deltaX) + Mth.square(deltaZ);
+                        final double soundRadiusSquared = level.getGlobalSoundRangeSquared(config -> config.witherSpawnSoundRadius); // Paper - respect global sound events gamerule
+                        if (!level.getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS) && distanceSquared > soundRadiusSquared) continue; // Spigot // Paper - respect global sound events gamerule
+                        if (distanceSquared > Mth.square(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(net.minecraft.world.level.block.LevelEvent.SOUND_WITHER_BOSS_SPAWN, new BlockPos((int) relativeX, (int) this.getY(), (int) relativeZ), 0, true));
+                        } else {
+                            player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelEventPacket(net.minecraft.world.level.block.LevelEvent.SOUND_WITHER_BOSS_SPAWN, this.blockPosition(), 0, true));
+                        }
+                    }
+                    // CraftBukkit end
                 }
             }
 
             this.setInvulnerableTicks(i);
             if (this.tickCount % 10 == 0) {
-                this.heal(10.0F);
+                this.heal(10.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.WITHER_SPAWN); // CraftBukkit
             }
         } else {
             super.customServerAiStep(level);
@@ -305,6 +_,7 @@
                         );
                         if (!nearbyEntities.isEmpty()) {
                             LivingEntity livingEntity1 = nearbyEntities.get(this.random.nextInt(nearbyEntities.size()));
+                            if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(this, livingEntity1, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY).isCancelled()) continue; // CraftBukkit
                             this.setAlternativeTarget(ix, livingEntity1.getId());
                         }
                     }
@@ -334,6 +_,11 @@
                     )) {
                         BlockState blockState = level.getBlockState(blockPos);
                         if (canDestroy(blockState)) {
+                            // CraftBukkit start
+                            if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, blockPos, blockState.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
+                                continue;
+                            }
+                            // CraftBukkit end
                             flag = level.destroyBlock(blockPos, true, this) || flag;
                         }
                     }
@@ -345,7 +_,7 @@
             }
 
             if (this.tickCount % 20 == 0) {
-                this.heal(1.0F);
+                this.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit
             }
 
             this.bossEvent.setProgress(this.getHealth() / this.getMaxHealth());
@@ -483,16 +_,16 @@
     @Override
     protected void dropCustomDeathLoot(ServerLevel level, DamageSource damageSource, boolean recentlyHit) {
         super.dropCustomDeathLoot(level, damageSource, recentlyHit);
-        ItemEntity itemEntity = this.spawnAtLocation(level, Items.NETHER_STAR);
+        ItemEntity itemEntity = this.spawnAtLocation(level, new net.minecraft.world.item.ItemStack(Items.NETHER_STAR), 0, ItemEntity::setExtendedLifetime); // Paper - Restore vanilla drops behavior; spawnAtLocation returns null so modify the item entity with a consumer
         if (itemEntity != null) {
-            itemEntity.setExtendedLifetime();
+            itemEntity.setExtendedLifetime(); // Paper - diff on change
         }
     }
 
     @Override
     public void checkDespawn() {
         if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) {
-            this.discard();
+            this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
         } else {
             this.noActionTime = 0;
         }
@@ -547,12 +_,18 @@
 
     @Override
     public boolean canUsePortal(boolean allowPassengers) {
-        return false;
-    }
+        return this.canPortal; // Paper
+    }
+
+    // Paper start
+    public void setCanTravelThroughPortals(boolean canPortal) {
+        this.canPortal = canPortal;
+    }
+    // Paper end
 
     @Override
     public boolean canBeAffected(MobEffectInstance potioneffect) {
-        return !potioneffect.is(MobEffects.WITHER) && super.canBeAffected(potioneffect);
+        return (!potioneffect.is(MobEffects.WITHER) || !this.level().paperConfig().entities.mobEffects.immuneToWitherEffect.wither) && super.canBeAffected(potioneffect);
     }
 
     class WitherDoNothingGoal extends Goal {
