--- a/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java
+++ b/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java
@@ -214,7 +_,14 @@
                 nextSpawnData.getEquipment().ifPresent(mob::equip);
             }
 
-            if (!level.tryAddFreshEntityWithPassengers(entity)) {
+            // Paper start - TrialSpawnerSpawnEvent + SpawnReason
+            entity.spawnedViaMobSpawner = true; // Mark entity as spawned via spawner
+            entity.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.TRIAL_SPAWNER; // Paper - Entity#getEntitySpawnReason
+            if (org.bukkit.craftbukkit.event.CraftEventFactory.callTrialSpawnerSpawnEvent(entity, pos).isCancelled()) {
+                return Optional.empty();
+            }
+            if (!level.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.TRIAL_SPAWNER)) {
+                // Paper end - TrialSpawnerSpawnEvent + SpawnReason
                 return Optional.empty();
             }
 
@@ -233,6 +_,19 @@
         LootParams lootParams = new LootParams.Builder(level).create(LootContextParamSets.EMPTY);
         ObjectArrayList<ItemStack> randomItems = lootTable1.getRandomItems(lootParams);
         if (!randomItems.isEmpty()) {
+            // CraftBukkit start
+            org.bukkit.event.block.BlockDispenseLootEvent spawnerDispenseLootEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockDispenseLootEvent(
+                level,
+                pos,
+                null,
+                randomItems
+            );
+            if (spawnerDispenseLootEvent.isCancelled()) {
+                return;
+            }
+
+            randomItems = new ObjectArrayList<>(spawnerDispenseLootEvent.getDispensedLoot().stream().map(org.bukkit.craftbukkit.inventory.CraftItemStack::asNMSCopy).toList());
+            // CraftBukkit end
             for (ItemStack itemStack : randomItems) {
                 DefaultDispenseItemBehavior.spawnItem(level, itemStack, 2, Direction.UP, Vec3.atBottomCenterOf(pos).relative(Direction.UP, 1.2));
             }
@@ -402,6 +_,35 @@
                 this.requiredPlayerRange
             );
         }
+
+        // Paper start - trial spawner API - withers
+        public TrialSpawner.FullConfig overrideTargetCooldownLength(final int targetCooldownLength) {
+            return new TrialSpawner.FullConfig(
+                this.normal,
+                this.ominous,
+                targetCooldownLength,
+                this.requiredPlayerRange
+            );
+        }
+
+        public TrialSpawner.FullConfig overrideRequiredPlayerRange(final int requiredPlayerRange) {
+            return new TrialSpawner.FullConfig(
+                this.normal,
+                this.ominous,
+                this.targetCooldownLength,
+                requiredPlayerRange
+            );
+        }
+
+        public TrialSpawner.FullConfig overrideConfigs(final Holder<TrialSpawnerConfig> normal, final Holder<TrialSpawnerConfig> ominous) {
+            return new TrialSpawner.FullConfig(
+                normal,
+                ominous,
+                this.targetCooldownLength,
+                this.requiredPlayerRange
+            );
+        }
+        // Paper end - trial spawner API - withers
     }
 
     public interface StateAccessor {
