--- a/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java
+++ b/net/minecraft/world/level/block/entity/trialspawner/TrialSpawner.java
@@ -52,16 +_,16 @@
     private static final int MAX_MOB_TRACKING_DISTANCE = 47;
     private static final int MAX_MOB_TRACKING_DISTANCE_SQR = Mth.square(47);
     private static final float SPAWNING_AMBIENT_SOUND_CHANCE = 0.02F;
-    private Holder<TrialSpawnerConfig> normalConfig;
-    private Holder<TrialSpawnerConfig> ominousConfig;
+    public Holder<TrialSpawnerConfig> normalConfig;
+    public Holder<TrialSpawnerConfig> ominousConfig;
     private final TrialSpawnerData data;
-    private final int requiredPlayerRange;
-    private final int targetCooldownLength;
-    private final TrialSpawner.StateAccessor stateAccessor;
+    public int requiredPlayerRange;
+    public int targetCooldownLength;
+    public final TrialSpawner.StateAccessor stateAccessor;
     private PlayerDetector playerDetector;
     private final PlayerDetector.EntitySelector entitySelector;
     private boolean overridePeacefulAndMobSpawnRule;
-    private boolean isOminous;
+    public boolean isOminous;
 
     public MapCodec<TrialSpawner> codec() {
         return RecordCodecBuilder.mapCodec(
@@ -238,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();
                         } else {
                             TrialSpawner.FlameParticle flameParticle = this.isOminous ? TrialSpawner.FlameParticle.OMINOUS : TrialSpawner.FlameParticle.NORMAL;
@@ -258,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));
             }
