--- a/net/minecraft/world/entity/npc/Villager.java
+++ b/net/minecraft/world/entity/npc/Villager.java
@@ -286,7 +_,7 @@
                     this.increaseProfessionLevelOnUpdate = false;
                 }
 
-                this.addEffect(new MobEffectInstance(MobEffects.REGENERATION, 200, 0));
+                this.addEffect(new MobEffectInstance(MobEffects.REGENERATION, 200, 0), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.VILLAGER_TRADE); // CraftBukkit
             }
         }
 
@@ -395,7 +_,12 @@
         this.updateDemand();
 
         for (MerchantOffer merchantOffer : this.getOffers()) {
-            merchantOffer.resetUses();
+            // CraftBukkit start
+            org.bukkit.event.entity.VillagerReplenishTradeEvent event = new org.bukkit.event.entity.VillagerReplenishTradeEvent((org.bukkit.entity.Villager) this.getBukkitEntity(), merchantOffer.asBukkit());
+            if (event.callEvent()) {
+                merchantOffer.resetUses();
+            }
+            // CraftBukkit end
         }
 
         this.resendOffersToTradingPlayer();
@@ -456,7 +_,12 @@
         int i = 2 - this.numberOfRestocksToday;
         if (i > 0) {
             for (MerchantOffer merchantOffer : this.getOffers()) {
-                merchantOffer.resetUses();
+                // CraftBukkit start
+                org.bukkit.event.entity.VillagerReplenishTradeEvent event = new org.bukkit.event.entity.VillagerReplenishTradeEvent((org.bukkit.entity.Villager) this.getBukkitEntity(), merchantOffer.asBukkit());
+                if (event.callEvent()) {
+                    merchantOffer.resetUses();
+                }
+                // CraftBukkit end
             }
         }
 
@@ -477,6 +_,7 @@
         int playerReputation = this.getPlayerReputation(player);
         if (playerReputation != 0) {
             for (MerchantOffer merchantOffer : this.getOffers()) {
+                if (merchantOffer.ignoreDiscounts) continue; // Paper - Add ignore discounts API
                 merchantOffer.addToSpecialPriceDiff(-Mth.floor(playerReputation * merchantOffer.getPriceMultiplier()));
             }
         }
@@ -486,6 +_,7 @@
             int amplifier = effect.getAmplifier();
 
             for (MerchantOffer merchantOffer1 : this.getOffers()) {
+                if (merchantOffer1.ignoreDiscounts) continue; // Paper - Add ignore discounts API
                 double d = 0.3 + 0.0625 * amplifier;
                 int i = (int)Math.floor(d * merchantOffer1.getBaseCostA().getCount());
                 merchantOffer1.addToSpecialPriceDiff(-Math.max(i, 1));
@@ -594,7 +_,7 @@
         }
 
         if (offer.shouldRewardExp()) {
-            this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5, this.getZ(), i));
+            this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5, this.getZ(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.getTradingPlayer(), this)); // Paper
         }
     }
 
@@ -612,7 +_,7 @@
 
     @Override
     public void die(DamageSource cause) {
-        LOGGER.info("Villager {} died, message: '{}'", this, cause.getLocalizedDeathMessage(this).getString());
+        if (org.spigotmc.SpigotConfig.logVillagerDeaths) LOGGER.info("Villager {} died, message: '{}'", this, cause.getLocalizedDeathMessage(this).getString()); // Spigot
         Entity entity = cause.getEntity();
         if (entity != null) {
             this.tellWitnessesThatIWasMurdered(entity);
@@ -780,12 +_,19 @@
     @Override
     public void thunderHit(ServerLevel level, LightningBolt lightning) {
         if (level.getDifficulty() != Difficulty.PEACEFUL) {
-            LOGGER.info("Villager {} was struck by lightning {}.", this, lightning);
+            // Paper - Add EntityZapEvent; move log down, event can cancel
             Witch witch = this.convertTo(EntityType.WITCH, ConversionParams.single(this, false, false), witch1 -> {
+                // Paper start - Add EntityZapEvent
+                if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityZapEvent(this, lightning, witch1).isCancelled()) {
+                    return false;
+                }
+                if (org.spigotmc.SpigotConfig.logVillagerDeaths) LOGGER.info("Villager {} was struck by lightning {}.", this, lightning); // Move down
+                // Paper end - Add EntityZapEvent
                 witch1.finalizeSpawn(level, level.getCurrentDifficultyAt(witch1.blockPosition()), EntitySpawnReason.CONVERSION, null);
                 witch1.setPersistenceRequired();
                 this.releaseAllPois();
-            });
+                return true; // Paper start - Add EntityZapEvent
+            }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.LIGHTNING, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit
             if (witch == null) {
                 super.thunderHit(level, lightning);
             }
@@ -825,6 +_,12 @@
 
     @Override
     protected void updateTrades() {
+        // Paper start - More vanilla friendly methods to update trades
+        this.updateTrades(TRADES_PER_LEVEL);
+    }
+
+    public boolean updateTrades(int amount) {
+        // Paper end - More vanilla friendly methods to update trades
         VillagerData villagerData = this.getVillagerData();
         ResourceKey<VillagerProfession> resourceKey = villagerData.profession().unwrapKey().orElse(null);
         if (resourceKey != null) {
@@ -840,10 +_,12 @@
                 VillagerTrades.ItemListing[] itemListings = map1.get(villagerData.level());
                 if (itemListings != null) {
                     MerchantOffers offers = this.getOffers();
-                    this.addOffersFromItemListings(offers, itemListings, 2);
+                    this.addOffersFromItemListings(offers, itemListings, amount); // Paper - More vanilla friendly methods to update trades
+                    return true; // Paper - More vanilla friendly methods to update trades
                 }
             }
         }
+        return false; // Paper - More vanilla friendly methods to update trades
     }
 
     public void gossip(ServerLevel serverLevel, Villager target, long gameTime) {
@@ -872,7 +_,7 @@
             List<Villager> entitiesOfClass = serverLevel.getEntitiesOfClass(Villager.class, aabb);
             List<Villager> list = entitiesOfClass.stream().filter(villager -> villager.wantsToSpawnGolem(gameTime)).limit(5L).toList();
             if (list.size() >= minVillagerAmount) {
-                if (!SpawnUtil.trySpawnMob(
+                if (SpawnUtil.trySpawnMob( // Paper - Set Golem Last Seen to stop it from spawning another one - switch to isPresent
                         EntityType.IRON_GOLEM,
                         EntitySpawnReason.MOB_SUMMONED,
                         serverLevel,
@@ -881,9 +_,11 @@
                         8,
                         6,
                         SpawnUtil.Strategy.LEGACY_IRON_GOLEM,
-                        false
+                        false,
+                        org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_DEFENSE, // CraftBukkit,
+                        () -> {GolemSensor.golemDetected(this);} // Paper - Set Golem Last Seen to stop it from spawning another one
                     )
-                    .isEmpty()) {
+                    .isPresent()) { // Paper - Set Golem Last Seen to stop it from spawning another one - switch to isPresent
                     entitiesOfClass.forEach(GolemSensor::golemDetected);
                 }
             }
