--- a/net/minecraft/world/level/block/CactusBlock.java
+++ b/net/minecraft/world/level/block/CactusBlock.java
@@ -1,6 +1,7 @@
 package net.minecraft.world.level.block;
 
 import com.mojang.serialization.MapCodec;
+import java.util.Iterator;
 import net.minecraft.core.BlockPos;
 import net.minecraft.core.Direction;
 import net.minecraft.server.level.ServerLevel;
@@ -20,23 +21,25 @@
 import net.minecraft.world.level.pathfinder.PathComputationType;
 import net.minecraft.world.phys.shapes.CollisionContext;
 import net.minecraft.world.phys.shapes.VoxelShape;
+import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
 
 public class CactusBlock extends Block {
+
     public static final MapCodec<CactusBlock> CODEC = simpleCodec(CactusBlock::new);
     public static final IntegerProperty AGE = BlockStateProperties.AGE_15;
     public static final int MAX_AGE = 15;
     protected static final int AABB_OFFSET = 1;
-    protected static final VoxelShape COLLISION_SHAPE = Block.box(1.0, 0.0, 1.0, 15.0, 15.0, 15.0);
-    protected static final VoxelShape OUTLINE_SHAPE = Block.box(1.0, 0.0, 1.0, 15.0, 16.0, 15.0);
+    protected static final VoxelShape COLLISION_SHAPE = Block.box(1.0D, 0.0D, 1.0D, 15.0D, 15.0D, 15.0D);
+    protected static final VoxelShape OUTLINE_SHAPE = Block.box(1.0D, 0.0D, 1.0D, 15.0D, 16.0D, 15.0D);
 
     @Override
     public MapCodec<CactusBlock> codec() {
-        return CODEC;
+        return CactusBlock.CODEC;
     }
 
     protected CactusBlock(BlockBehaviour.Properties settings) {
         super(settings);
-        this.registerDefaultState(this.stateDefinition.any().setValue(AGE, Integer.valueOf(0)));
+        this.registerDefaultState((BlockState) ((BlockState) this.stateDefinition.any()).setValue(CactusBlock.AGE, 0));
     }
 
     @Override
@@ -44,46 +47,52 @@
         if (!state.canSurvive(world, pos)) {
             world.destroyBlock(pos, true);
         }
+
     }
 
     @Override
     protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-        BlockPos blockPos = pos.above();
-        if (world.isEmptyBlock(blockPos)) {
-            int i = 1;
+        BlockPos blockposition1 = pos.above();
 
-            while (world.getBlockState(pos.below(i)).is(this)) {
-                i++;
+        if (world.isEmptyBlock(blockposition1)) {
+            int i;
+
+            for (i = 1; world.getBlockState(pos.below(i)).is((Block) this); ++i) {
+                ;
             }
 
-            if (i < 3) {
-                int j = state.getValue(AGE);
-                if (j == 15) {
-                    world.setBlockAndUpdate(blockPos, this.defaultBlockState());
-                    BlockState blockState = state.setValue(AGE, Integer.valueOf(0));
-                    world.setBlock(pos, blockState, 4);
-                    world.neighborChanged(blockState, blockPos, this, pos, false);
-                } else {
-                    world.setBlock(pos, state.setValue(AGE, Integer.valueOf(j + 1)), 4);
+            if (i < world.paperConfig().maxGrowthHeight.cactus) { // Paper - Configurable cactus/bamboo/reed growth height
+                int j = (Integer) state.getValue(CactusBlock.AGE);
+
+                int modifier = world.spigotConfig.cactusModifier; // Spigot - SPIGOT-7159: Better modifier resolution
+                if (j >= 15 || (modifier != 100 && random.nextFloat() < (modifier / (100.0f * 16)))) { // Spigot - SPIGOT-7159: Better modifier resolution
+                    CraftEventFactory.handleBlockGrowEvent(world, blockposition1, this.defaultBlockState()); // CraftBukkit
+                    BlockState iblockdata1 = (BlockState) state.setValue(CactusBlock.AGE, 0);
+
+                    world.setBlock(pos, iblockdata1, 4);
+                    world.neighborChanged(iblockdata1, blockposition1, this, pos, false);
+                } else if (modifier == 100 || random.nextFloat() < (modifier / (100.0f * 16))) { // Spigot - SPIGOT-7159: Better modifier resolution
+                    world.setBlock(pos, (BlockState) state.setValue(CactusBlock.AGE, j + 1), 4);
                 }
+
             }
         }
     }
 
     @Override
     protected VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) {
-        return COLLISION_SHAPE;
+        return CactusBlock.COLLISION_SHAPE;
     }
 
     @Override
     protected VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) {
-        return OUTLINE_SHAPE;
+        return CactusBlock.OUTLINE_SHAPE;
     }
 
     @Override
     protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) {
         if (!state.canSurvive(world, pos)) {
-            world.scheduleTick(pos, this, 1);
+            world.scheduleTick(pos, (Block) this, 1);
         }
 
         return super.updateShape(state, direction, neighborState, world, pos, neighborPos);
@@ -91,25 +100,34 @@
 
     @Override
     protected boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) {
-        for (Direction direction : Direction.Plane.HORIZONTAL) {
-            BlockState blockState = world.getBlockState(pos.relative(direction));
-            if (blockState.isSolid() || world.getFluidState(pos.relative(direction)).is(FluidTags.LAVA)) {
-                return false;
+        Iterator iterator = Direction.Plane.HORIZONTAL.iterator();
+
+        Direction enumdirection;
+        BlockState iblockdata1;
+
+        do {
+            if (!iterator.hasNext()) {
+                BlockState iblockdata2 = world.getBlockState(pos.below());
+
+                return (iblockdata2.is(Blocks.CACTUS) || iblockdata2.is(BlockTags.SAND)) && !world.getBlockState(pos.above()).liquid();
             }
-        }
 
-        BlockState blockState2 = world.getBlockState(pos.below());
-        return (blockState2.is(Blocks.CACTUS) || blockState2.is(BlockTags.SAND)) && !world.getBlockState(pos.above()).liquid();
+            enumdirection = (Direction) iterator.next();
+            iblockdata1 = world.getBlockState(pos.relative(enumdirection));
+        } while (!iblockdata1.isSolid() && !world.getFluidState(pos.relative(enumdirection)).is(FluidTags.LAVA));
+
+        return false;
     }
 
     @Override
     protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-        entity.hurt(world.damageSources().cactus(), 1.0F);
+        if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+        entity.hurt(world.damageSources().cactus().directBlock(world, pos), 1.0F); // CraftBukkit
     }
 
     @Override
     protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
-        builder.add(AGE);
+        builder.add(CactusBlock.AGE);
     }
 
     @Override
