--- a/net/minecraft/world/level/block/NetherPortalBlock.java
+++ b/net/minecraft/world/level/block/NetherPortalBlock.java
@@ -70,7 +_,7 @@
 
     @Override
     protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
-        if (level.dimensionType().natural()
+        if (level.spigotConfig.enableZombiePigmenPortalSpawns && level.dimensionType().natural() // Spigot
             && level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)
             && random.nextInt(2000) < level.getDifficulty().getId()) {
             while (level.getBlockState(pos).is(this)) {
@@ -78,9 +_,13 @@
             }
 
             if (level.getBlockState(pos).isValidSpawn(level, pos, EntityType.ZOMBIFIED_PIGLIN)) {
-                Entity entity = EntityType.ZOMBIFIED_PIGLIN.spawn(level, pos.above(), EntitySpawnReason.STRUCTURE);
+                Entity entity = EntityType.ZOMBIFIED_PIGLIN.spawn(level, pos.above(), EntitySpawnReason.STRUCTURE, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NETHER_PORTAL); // CraftBukkit - set spawn reason to NETHER_PORTAL
                 if (entity != null) {
                     entity.setPortalCooldown();
+                    // Paper start - Add option to nerf pigmen from nether portals
+                    entity.fromNetherPortal = true;
+                    if (level.paperConfig().entities.behavior.nerfPigmenFromNetherPortals) ((net.minecraft.world.entity.Mob) entity).aware = false;
+                    // Paper end - Add option to nerf pigmen from nether portals
                     Entity vehicle = entity.getVehicle();
                     if (vehicle != null) {
                         vehicle.setPortalCooldown();
@@ -111,7 +_,13 @@
 
     @Override
     protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
         if (entity.canUsePortal(false)) {
+            // CraftBukkit start - Entity in portal
+            org.bukkit.event.entity.EntityPortalEnterEvent event = new org.bukkit.event.entity.EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(level.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.NETHER); // Paper - add portal type
+            level.getCraftServer().getPluginManager().callEvent(event);
+            if (event.isCancelled()) return; // Paper - make cancellable
+            // CraftBukkit end
             entity.setAsInsidePortal(this, pos);
         }
     }
@@ -134,22 +_,46 @@
     @Nullable
     @Override
     public TeleportTransition getPortalDestination(ServerLevel level, Entity entity, BlockPos pos) {
-        ResourceKey<Level> resourceKey = level.dimension() == Level.NETHER ? Level.OVERWORLD : Level.NETHER;
+        // CraftBukkit start
+        ResourceKey<Level> resourceKey = level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER ? Level.OVERWORLD : Level.NETHER;
         ServerLevel level1 = level.getServer().getLevel(resourceKey);
+        // Paper start - Add EntityPortalReadyEvent
+        io.papermc.paper.event.entity.EntityPortalReadyEvent portalReadyEvent = new io.papermc.paper.event.entity.EntityPortalReadyEvent(entity.getBukkitEntity(), level1 == null ? null : level1.getWorld(), org.bukkit.PortalType.NETHER);
+        if (!portalReadyEvent.callEvent()) {
+            entity.portalProcess = null;
+            return null;
+        }
+        level1 = portalReadyEvent.getTargetWorld() == null ? null : ((org.bukkit.craftbukkit.CraftWorld) portalReadyEvent.getTargetWorld()).getHandle();
+        // Paper end - Add EntityPortalReadyEvent
         if (level1 == null) {
             return null;
         } else {
-            boolean flag = level1.dimension() == Level.NETHER;
+            boolean flag = level1.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER; // CraftBukkit
             WorldBorder worldBorder = level1.getWorldBorder();
             double teleportationScale = DimensionType.getTeleportationScale(level.dimensionType(), level1.dimensionType());
             BlockPos blockPos = worldBorder.clampToBounds(entity.getX() * teleportationScale, entity.getY(), entity.getZ() * teleportationScale);
-            return this.getExitPortal(level1, entity, pos, blockPos, flag, worldBorder);
+            // Paper start - Configurable portal search radius
+            int portalSearchRadius = level1.paperConfig().environment.portalSearchRadius;
+            if (entity.level().paperConfig().environment.portalSearchVanillaDimensionScaling && flag) { // flag = is going to nether
+                portalSearchRadius = (int) (portalSearchRadius / level1.dimensionType().coordinateScale());
+            }
+            // Paper end - Configurable portal search radius
+            // CraftBukkit start
+            org.bukkit.craftbukkit.event.CraftPortalEvent event = entity.callPortalEvent(entity, org.bukkit.craftbukkit.util.CraftLocation.toBukkit(blockPos, level1.getWorld()), org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.NETHER_PORTAL, portalSearchRadius, level1.paperConfig().environment.portalCreateRadius); // Paper - use custom portal search radius
+            if (event == null) {
+                return null;
+            }
+            level1 = ((org.bukkit.craftbukkit.CraftWorld) event.getTo().getWorld()).getHandle();
+            worldBorder = level1.getWorldBorder();
+            blockPos = worldBorder.clampToBounds(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ());
+            return this.getExitPortal(level1, entity, pos, blockPos, flag, worldBorder, event.getSearchRadius(), event.getCanCreatePortal(), event.getCreationRadius());
+            // CraftBukkit end
         }
     }
 
     @Nullable
-    private TeleportTransition getExitPortal(ServerLevel level, Entity entity, BlockPos pos, BlockPos exitPos, boolean isNether, WorldBorder worldBorder) {
-        Optional<BlockPos> optional = level.getPortalForcer().findClosestPortalPosition(exitPos, isNether, worldBorder);
+    private TeleportTransition getExitPortal(ServerLevel level, Entity entity, BlockPos pos, BlockPos exitPos, boolean isNether, WorldBorder worldBorder, int searchRadius, boolean canCreatePortal, int createRadius) { // CraftBukkit
+        Optional<BlockPos> optional = level.getPortalForcer().findClosestPortalPosition(exitPos, worldBorder, searchRadius); // CraftBukkit
         BlockUtil.FoundRectangle largestRectangleAround;
         TeleportTransition.PostTeleportTransition postTeleportTransition;
         if (optional.isPresent()) {
@@ -164,17 +_,22 @@
                 blockPos1 -> level.getBlockState(blockPos1) == blockState
             );
             postTeleportTransition = TeleportTransition.PLAY_PORTAL_SOUND.then(entity1 -> entity1.placePortalTicket(blockPos));
-        } else {
+        } else if (canCreatePortal) { // CraftBukkit
             Direction.Axis axis = entity.level().getBlockState(pos).getOptionalValue(AXIS).orElse(Direction.Axis.X);
-            Optional<BlockUtil.FoundRectangle> optional1 = level.getPortalForcer().createPortal(exitPos, axis);
+            Optional<BlockUtil.FoundRectangle> optional1 = level.getPortalForcer().createPortal(exitPos, axis, entity, createRadius); // CraftBukkit
             if (optional1.isEmpty()) {
-                LOGGER.error("Unable to create a portal, likely target out of worldborder");
+                // LOGGER.error("Unable to create a portal, likely target out of worldborder"); // CraftBukkit
                 return null;
             }
 
             largestRectangleAround = optional1.get();
             postTeleportTransition = TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET);
         }
+        // CraftBukkit start
+        else {
+            return null;
+        }
+        // CraftBukkit end
 
         return getDimensionTransitionFromExit(entity, pos, largestRectangleAround, level, postTeleportTransition);
     }
@@ -220,7 +_,7 @@
         boolean flag = axis1 == Direction.Axis.X;
         Vec3 vec3 = new Vec3(blockPos.getX() + (flag ? d2 : d4), blockPos.getY() + d3, blockPos.getZ() + (flag ? d4 : d2));
         Vec3 vec31 = PortalShape.findCollisionFreePosition(vec3, level, entity, dimensions);
-        return new TeleportTransition(level, vec31, Vec3.ZERO, i, 0.0F, Relative.union(Relative.DELTA, Relative.ROTATION), postTeleportTransition);
+        return new TeleportTransition(level, vec31, Vec3.ZERO, i, 0.0F, Relative.union(Relative.DELTA, Relative.ROTATION), postTeleportTransition, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.NETHER_PORTAL); // CraftBukkit
     }
 
     @Override
