diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/AbstractVillager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/AbstractVillager.java.patch similarity index 64% rename from paper-server/patches/unapplied/net/minecraft/world/entity/npc/AbstractVillager.java.patch rename to paper-server/patches/sources/net/minecraft/world/entity/npc/AbstractVillager.java.patch index ba7ad87dd7..458bc1fb33 100644 --- a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/AbstractVillager.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/AbstractVillager.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/world/entity/npc/AbstractVillager.java +++ b/net/minecraft/world/entity/npc/AbstractVillager.java -@@ -40,8 +40,21 @@ +@@ -37,7 +_,20 @@ import net.minecraft.world.phys.Vec3; import org.slf4j.Logger; @@ -12,7 +12,6 @@ +// CraftBukkit end + public abstract class AbstractVillager extends AgeableMob implements InventoryCarrier, Npc, Merchant { - + // CraftBukkit start + @Override + public CraftMerchant getCraftMerchant() { @@ -22,16 +21,16 @@ private static final EntityDataAccessor DATA_UNHAPPY_COUNTER = SynchedEntityData.defineId(AbstractVillager.class, EntityDataSerializers.INT); private static final Logger LOGGER = LogUtils.getLogger(); public static final int VILLAGER_SLOT_OFFSET = 300; -@@ -50,7 +63,7 @@ +@@ -46,7 +_,7 @@ private Player tradingPlayer; @Nullable protected MerchantOffers offers; - private final SimpleContainer inventory = new SimpleContainer(8); -+ private final SimpleContainer inventory = new SimpleContainer(8, (org.bukkit.craftbukkit.entity.CraftAbstractVillager) this.getBukkitEntity()); // CraftBukkit add argument ++ private final SimpleContainer inventory = new SimpleContainer(8, (org.bukkit.craftbukkit.entity.CraftAbstractVillager) this.getBukkitEntity()); // CraftBukkit - add argument - public AbstractVillager(EntityType type, Level world) { - super(type, world); -@@ -101,6 +114,13 @@ + public AbstractVillager(EntityType entityType, Level level) { + super(entityType, level); +@@ -99,6 +_,13 @@ return this.tradingPlayer != null; } @@ -45,50 +44,40 @@ @Override public MerchantOffers getOffers() { if (this.level().isClientSide) { -@@ -121,11 +141,24 @@ - @Override - public void overrideXp(int experience) {} +@@ -121,11 +_,24 @@ + public void overrideXp(int xp) { + } + // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent - @Override -- public void notifyTrade(MerchantOffer offer) { -- offer.increaseUses(); -+ public void processTrade(MerchantOffer recipe, @Nullable io.papermc.paper.event.player.PlayerPurchaseEvent event) { // The MerchantRecipe passed in here is the one set by the PlayerPurchaseEvent ++ @Override ++ public void processTrade(MerchantOffer offer, @Nullable io.papermc.paper.event.player.PlayerPurchaseEvent event) { // The MerchantRecipe passed in here is the one set by the PlayerPurchaseEvent + if (event == null || event.willIncreaseTradeUses()) { -+ recipe.increaseUses(); ++ offer.increaseUses(); + } + if (event == null || event.isRewardingExp()) { -+ this.rewardTradeXp(recipe); ++ this.rewardTradeXp(offer); + } -+ this.notifyTrade(recipe); ++ this.notifyTrade(offer); + } + // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent + -+ @Override -+ public void notifyTrade(MerchantOffer offer) { + @Override + public void notifyTrade(MerchantOffer offer) { +- offer.increaseUses(); + // offer.increaseUses(); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent this.ambientSoundTime = -this.getAmbientSoundInterval(); - this.rewardTradeXp(offer); + // this.rewardTradeXp(offer); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent if (this.tradingPlayer instanceof ServerPlayer) { - CriteriaTriggers.TRADE.trigger((ServerPlayer) this.tradingPlayer, this, offer.getResult()); + CriteriaTriggers.TRADE.trigger((ServerPlayer)this.tradingPlayer, this, offer.getResult()); } -@@ -179,7 +212,7 @@ - public void readAdditionalSaveData(CompoundTag nbt) { - super.readAdditionalSaveData(nbt); - if (nbt.contains("Offers")) { -- DataResult dataresult = MerchantOffers.CODEC.parse(this.registryAccess().createSerializationContext(NbtOps.INSTANCE), nbt.get("Offers")); -+ DataResult dataresult = MerchantOffers.CODEC.parse(this.registryAccess().createSerializationContext(NbtOps.INSTANCE), nbt.get("Offers")); // CraftBukkit - decompile error - Logger logger = AbstractVillager.LOGGER; - - Objects.requireNonNull(logger); -@@ -246,7 +279,20 @@ - MerchantOffer merchantrecipe = ((VillagerTrades.ItemListing) arraylist.remove(this.random.nextInt(arraylist.size()))).getOffer(this, this.random); - - if (merchantrecipe != null) { -- recipeList.add(merchantrecipe); +@@ -236,7 +_,20 @@ + while (i < maxNumbers && !list.isEmpty()) { + MerchantOffer offer = list.remove(this.random.nextInt(list.size())).getOffer(this, this.random); + if (offer != null) { +- givenMerchantOffers.add(offer); + // CraftBukkit start -+ VillagerAcquireTradeEvent event = new VillagerAcquireTradeEvent((org.bukkit.entity.AbstractVillager) this.getBukkitEntity(), merchantrecipe.asBukkit()); ++ VillagerAcquireTradeEvent event = new VillagerAcquireTradeEvent((org.bukkit.entity.AbstractVillager) this.getBukkitEntity(), offer.asBukkit()); + // Suppress during worldgen + if (this.valid) { + Bukkit.getPluginManager().callEvent(event); @@ -97,10 +86,10 @@ + // Paper start - Fix crash from invalid ingredient list + final CraftMerchantRecipe craftMerchantRecipe = CraftMerchantRecipe.fromBukkit(event.getRecipe()); + if (craftMerchantRecipe.getIngredients().isEmpty()) return; -+ recipeList.add(craftMerchantRecipe.toMinecraft()); ++ givenMerchantOffers.add(craftMerchantRecipe.toMinecraft()); + // Paper end - Fix crash from invalid ingredient list + } + // CraftBukkit end - ++j; + i++; } } diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/CatSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/CatSpawner.java.patch similarity index 62% rename from paper-server/patches/unapplied/net/minecraft/world/entity/npc/CatSpawner.java.patch rename to paper-server/patches/sources/net/minecraft/world/entity/npc/CatSpawner.java.patch index 6d93d621f8..1c1335c6b5 100644 --- a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/CatSpawner.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/CatSpawner.java.patch @@ -1,12 +1,12 @@ --- a/net/minecraft/world/entity/npc/CatSpawner.java +++ b/net/minecraft/world/entity/npc/CatSpawner.java -@@ -82,8 +82,8 @@ +@@ -82,8 +_,8 @@ if (cat == null) { return 0; } else { + cat.moveTo(pos, 0.0F, 0.0F); // Paper - move up - Fix MC-147659 - cat.finalizeSpawn(world, world.getCurrentDifficultyAt(pos), EntitySpawnReason.NATURAL, null); + cat.finalizeSpawn(serverLevel, serverLevel.getCurrentDifficultyAt(pos), EntitySpawnReason.NATURAL, null); - cat.moveTo(pos, 0.0F, 0.0F); - world.addFreshEntityWithPassengers(cat); + serverLevel.addFreshEntityWithPassengers(cat); return 1; } diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/InventoryCarrier.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/InventoryCarrier.java.patch similarity index 53% rename from paper-server/patches/unapplied/net/minecraft/world/entity/npc/InventoryCarrier.java.patch rename to paper-server/patches/sources/net/minecraft/world/entity/npc/InventoryCarrier.java.patch index d6c445bea3..7728954a04 100644 --- a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/InventoryCarrier.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/InventoryCarrier.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/world/entity/npc/InventoryCarrier.java +++ b/net/minecraft/world/entity/npc/InventoryCarrier.java -@@ -8,6 +8,10 @@ +@@ -8,6 +_,10 @@ import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.item.ItemStack; @@ -9,27 +9,26 @@ +// CraftBukkit end + public interface InventoryCarrier { - String TAG_INVENTORY = "Inventory"; -@@ -25,13 +29,20 @@ + +@@ -22,12 +_,19 @@ return; } + // CraftBukkit start -+ ItemStack remaining = new SimpleContainer(inventorysubcontainer).addItem(itemstack); -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(entity, item, remaining.getCount(), false).isCancelled()) { ++ ItemStack remaining = new SimpleContainer(inventory).addItem(item); ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(mob, itemEntity, remaining.getCount(), false).isCancelled()) { + return; + } + // CraftBukkit end + - entity.onItemPickup(item); - int i = itemstack.getCount(); - ItemStack itemstack1 = inventorysubcontainer.addItem(itemstack); - - entity.take(item, i - itemstack1.getCount()); - if (itemstack1.isEmpty()) { -- item.discard(); -+ item.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause + mob.onItemPickup(itemEntity); + int count = item.getCount(); + ItemStack itemStack = inventory.addItem(item); + mob.take(itemEntity, count - itemStack.getCount()); + if (itemStack.isEmpty()) { +- itemEntity.discard(); ++ itemEntity.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause } else { - itemstack.setCount(itemstack1.getCount()); + item.setCount(itemStack.getCount()); } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch new file mode 100644 index 0000000000..61d20a9b99 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch @@ -0,0 +1,188 @@ +--- a/net/minecraft/world/entity/npc/Villager.java ++++ b/net/minecraft/world/entity/npc/Villager.java +@@ -90,6 +_,14 @@ + import net.minecraft.world.phys.AABB; + import org.slf4j.Logger; + ++// CraftBukkit start ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.entity.EntityTransformEvent; ++import org.bukkit.event.entity.VillagerReplenishTradeEvent; ++// CraftBukkit end ++ + public class Villager extends AbstractVillager implements ReputationEventHandler, VillagerDataHolder { + private static final Logger LOGGER = LogUtils.getLogger(); + private static final EntityDataAccessor DATA_VILLAGER_DATA = SynchedEntityData.defineId(Villager.class, EntityDataSerializers.VILLAGER_DATA); +@@ -257,6 +_,17 @@ + return this.assignProfessionWhenSpawned; + } + ++ // Spigot Start ++ @Override ++ public void inactiveTick() { ++ // SPIGOT-3874, SPIGOT-3894, SPIGOT-3846, SPIGOT-5286 :( ++ if (this.level().spigotConfig.tickInactiveVillagers && this.isEffectiveAi()) { ++ this.customServerAiStep((ServerLevel) this.level()); ++ } ++ super.inactiveTick(); ++ } ++ // Spigot End ++ + @Override + protected void customServerAiStep(ServerLevel level) { + ProfilerFiller profilerFiller = Profiler.get(); +@@ -275,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 + } + } + +@@ -384,7 +_,13 @@ + this.updateDemand(); + + for (MerchantOffer merchantOffer : this.getOffers()) { +- merchantOffer.resetUses(); ++ // CraftBukkit start ++ VillagerReplenishTradeEvent event = new VillagerReplenishTradeEvent((org.bukkit.entity.Villager) this.getBukkitEntity(), merchantOffer.asBukkit()); ++ Bukkit.getPluginManager().callEvent(event); ++ if (!event.isCancelled()) { ++ merchantOffer.resetUses(); ++ } ++ // CraftBukkit end + } + + this.resendOffersToTradingPlayer(); +@@ -445,7 +_,13 @@ + int i = 2 - this.numberOfRestocksToday; + if (i > 0) { + for (MerchantOffer merchantOffer : this.getOffers()) { +- merchantOffer.resetUses(); ++ // CraftBukkit start ++ VillagerReplenishTradeEvent event = new VillagerReplenishTradeEvent((org.bukkit.entity.Villager) this.getBukkitEntity(), merchantOffer.asBukkit()); ++ Bukkit.getPluginManager().callEvent(event); ++ if (!event.isCancelled()) { ++ merchantOffer.resetUses(); ++ } ++ // CraftBukkit end + } + } + +@@ -466,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())); + } + } +@@ -475,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)); +@@ -559,7 +_,7 @@ + } + + @Override +- protected SoundEvent getDeathSound() { ++ public SoundEvent getDeathSound() { + return SoundEvents.VILLAGER_DEATH; + } + +@@ -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); +@@ -782,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), mob -> { ++ // Paper start - Add EntityZapEvent ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityZapEvent(this, lightning, mob).isCancelled()) { ++ return false; ++ } ++ if (org.spigotmc.SpigotConfig.logVillagerDeaths) Villager.LOGGER.info("Villager {} was struck by lightning {}.", this, lightning); // Move down ++ // Paper end - Add EntityZapEvent + mob.finalizeSpawn(level, level.getCurrentDifficultyAt(mob.blockPosition()), EntitySpawnReason.CONVERSION, null); + mob.setPersistenceRequired(); + this.releaseAllPois(); +- }); ++ return true; // Paper start - Add EntityZapEvent ++ }, EntityTransformEvent.TransformReason.LIGHTNING, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit + if (witch == null) { + super.thunderHit(level, lightning); + } +@@ -827,6 +_,12 @@ + + @Override + protected void updateTrades() { ++ // Paper start - More vanilla friendly methods to update trades ++ updateTrades(TRADES_PER_LEVEL); ++ } ++ ++ public boolean updateTrades(int amount) { ++ // Paper end - More vanilla friendly methods to update trades + VillagerData villagerData = this.getVillagerData(); + Int2ObjectMap map1; + if (this.level().enabledFeatures().contains(FeatureFlags.TRADE_REBALANCE)) { +@@ -840,9 +_,11 @@ + VillagerTrades.ItemListing[] itemListings = map1.get(villagerData.getLevel()); + 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) { +@@ -871,7 +_,7 @@ + List entitiesOfClass = serverLevel.getEntitiesOfClass(Villager.class, aabb); + List 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, +@@ -880,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); + } + } diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/VillagerTrades.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/VillagerTrades.java.patch similarity index 79% rename from paper-server/patches/unapplied/net/minecraft/world/entity/npc/VillagerTrades.java.patch rename to paper-server/patches/sources/net/minecraft/world/entity/npc/VillagerTrades.java.patch index a21a5e780e..9aed42eb71 100644 --- a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/VillagerTrades.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/VillagerTrades.java.patch @@ -1,12 +1,12 @@ --- a/net/minecraft/world/entity/npc/VillagerTrades.java +++ b/net/minecraft/world/entity/npc/VillagerTrades.java -@@ -1834,7 +1834,8 @@ +@@ -1844,7 +_,8 @@ return null; } else { - ServerLevel serverLevel = (ServerLevel)entity.level(); -- BlockPos blockPos = serverLevel.findNearestMapStructure(this.destination, entity.blockPosition(), 100, true); + ServerLevel serverLevel = (ServerLevel)trader.level(); +- BlockPos blockPos = serverLevel.findNearestMapStructure(this.destination, trader.blockPosition(), 100, true); + if (!serverLevel.paperConfig().environment.treasureMaps.enabled) return null; // Paper - Configurable cartographer treasure maps -+ BlockPos blockPos = serverLevel.findNearestMapStructure(this.destination, entity.blockPosition(), 100, !serverLevel.paperConfig().environment.treasureMaps.findAlreadyDiscoveredVillager); // Paper - Configurable cartographer treasure maps ++ BlockPos blockPos = serverLevel.findNearestMapStructure(this.destination, trader.blockPosition(), 100, !serverLevel.paperConfig().environment.treasureMaps.findAlreadyDiscoveredVillager); // Paper - Configurable cartographer treasure maps if (blockPos != null) { ItemStack itemStack = MapItem.create(serverLevel, blockPos.getX(), blockPos.getZ(), (byte)2, true, true); MapItem.renderBiomePreviewMap(serverLevel, itemStack); diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/WanderingTrader.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTrader.java.patch similarity index 52% rename from paper-server/patches/unapplied/net/minecraft/world/entity/npc/WanderingTrader.java.patch rename to paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTrader.java.patch index c3bde269c7..d5e6a224ee 100644 --- a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/WanderingTrader.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTrader.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/world/entity/npc/WanderingTrader.java +++ b/net/minecraft/world/entity/npc/WanderingTrader.java -@@ -48,25 +48,38 @@ +@@ -47,11 +_,23 @@ import net.minecraft.world.phys.Vec3; import org.apache.commons.lang3.tuple.Pair; @@ -12,9 +12,8 @@ +import org.bukkit.event.entity.EntityRemoveEvent; +import org.bukkit.event.entity.VillagerAcquireTradeEvent; +// CraftBukkit end - -+public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVillager implements Consumable.OverrideConsumeSound { + ++public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVillager implements Consumable.OverrideConsumeSound { private static final int NUMBER_OF_TRADE_OFFERS = 5; @Nullable private BlockPos wanderTarget; @@ -24,57 +23,68 @@ + public boolean canDrinkMilk = true; + // Paper end - Add more WanderingTrader API - public WanderingTrader(EntityType type, Level world) { - super(type, world); -+ //this.setDespawnDelay(48000); // CraftBukkit - set default from MobSpawnerTrader // Paper - move back to MobSpawnerTrader - Vanilla behavior is that only traders spawned by it have this value set. - } - - @Override - protected void registerGoals() { - this.goalSelector.addGoal(0, new FloatGoal(this)); - this.goalSelector.addGoal(0, new UseItemGoal<>(this, PotionContents.createItemStack(Items.POTION, Potions.INVISIBILITY), SoundEvents.WANDERING_TRADER_DISAPPEARED, (entityvillagertrader) -> { -- return this.level().isNight() && !entityvillagertrader.isInvisible(); -+ return this.canDrinkPotion && this.level().isNight() && !entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API - })); - this.goalSelector.addGoal(0, new UseItemGoal<>(this, new ItemStack(Items.MILK_BUCKET), SoundEvents.WANDERING_TRADER_REAPPEARED, (entityvillagertrader) -> { -- return this.level().isDay() && entityvillagertrader.isInvisible(); -+ return this.canDrinkMilk && this.level().isDay() && entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API - })); + public WanderingTrader(EntityType entityType, Level level) { + super(entityType, level); +@@ -67,7 +_,7 @@ + this, + PotionContents.createItemStack(Items.POTION, Potions.INVISIBILITY), + SoundEvents.WANDERING_TRADER_DISAPPEARED, +- wanderingTrader -> this.level().isNight() && !wanderingTrader.isInvisible() ++ wanderingTrader -> this.canDrinkPotion && this.level().isNight() && !wanderingTrader.isInvisible() // Paper - Add more WanderingTrader API + ) + ); + this.goalSelector +@@ -77,7 +_,7 @@ + this, + new ItemStack(Items.MILK_BUCKET), + SoundEvents.WANDERING_TRADER_REAPPEARED, +- wanderingTrader -> this.level().isDay() && wanderingTrader.isInvisible() ++ wanderingTrader -> this.canDrinkMilk && this.level().isDay() && wanderingTrader.isInvisible() // Paper - Add more WanderingTrader API + ) + ); this.goalSelector.addGoal(1, new TradeWithPlayerGoal(this)); - this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Zombie.class, 8.0F, 0.5D, 0.5D)); -@@ -137,7 +150,16 @@ - MerchantOffer merchantrecipe = villagertrades_imerchantrecipeoption.getOffer(this, this.random); - - if (merchantrecipe != null) { -- merchantrecipelist.add(merchantrecipe); +@@ -145,7 +_,16 @@ + VillagerTrades.ItemListing itemListing = itemListings1[randomInt]; + MerchantOffer offer = itemListing.getOffer(this, this.random); + if (offer != null) { +- offers.add(offer); + // CraftBukkit start -+ VillagerAcquireTradeEvent event = new VillagerAcquireTradeEvent((AbstractVillager) this.getBukkitEntity(), merchantrecipe.asBukkit()); ++ VillagerAcquireTradeEvent event = new VillagerAcquireTradeEvent((AbstractVillager) this.getBukkitEntity(), offer.asBukkit()); + // Suppress during worldgen + if (this.valid) { + Bukkit.getPluginManager().callEvent(event); + } + if (!event.isCancelled()) { -+ merchantrecipelist.add(CraftMerchantRecipe.fromBukkit(event.getRecipe()).toMinecraft()); ++ offers.add(CraftMerchantRecipe.fromBukkit(event.getRecipe()).toMinecraft()); + } + // CraftBukkit end } - } -@@ -190,7 +212,7 @@ + } +@@ -189,7 +_,7 @@ + protected void rewardTradeXp(MerchantOffer offer) { if (offer.shouldRewardExp()) { int i = 3 + this.random.nextInt(4); - -- this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i)); -+ this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.getTradingPlayer(), this)); // Paper +- 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 } - } -@@ -244,7 +266,7 @@ + +@@ -204,7 +_,7 @@ + } + + @Override +- protected SoundEvent getDeathSound() { ++ public SoundEvent getDeathSound() { + return SoundEvents.WANDERING_TRADER_DEATH; + } + +@@ -241,7 +_,7 @@ private void maybeDespawn() { if (this.despawnDelay > 0 && !this.isTrading() && --this.despawnDelay == 0) { - this.discard(); + this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause } - } + diff --git a/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch new file mode 100644 index 0000000000..8884c9e247 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch @@ -0,0 +1,96 @@ +--- a/net/minecraft/world/entity/npc/WanderingTraderSpawner.java ++++ b/net/minecraft/world/entity/npc/WanderingTraderSpawner.java +@@ -38,41 +_,50 @@ + + public WanderingTraderSpawner(ServerLevelData serverLevelData) { + this.serverLevelData = serverLevelData; +- this.tickDelay = 1200; +- this.spawnDelay = serverLevelData.getWanderingTraderSpawnDelay(); +- this.spawnChance = serverLevelData.getWanderingTraderSpawnChance(); +- if (this.spawnDelay == 0 && this.spawnChance == 0) { +- this.spawnDelay = 24000; +- serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay); +- this.spawnChance = 25; +- serverLevelData.setWanderingTraderSpawnChance(this.spawnChance); +- } ++ // Paper start - Add Wandering Trader spawn rate config options ++ this.tickDelay = Integer.MIN_VALUE; ++ // this.spawnDelay = serverLevelData.getWanderingTraderSpawnDelay(); ++ // this.spawnChance = serverLevelData.getWanderingTraderSpawnChance(); ++ // if (this.spawnDelay == 0 && this.spawnChance == 0) { ++ // this.spawnDelay = 24000; ++ // serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay); ++ // this.spawnChance = 25; ++ // serverLevelData.setWanderingTraderSpawnChance(this.spawnChance); ++ // } ++ // Paper end - Add Wandering Trader spawn rate config options + } + + @Override + public int tick(ServerLevel level, boolean spawnHostiles, boolean spawnPassives) { ++ // Paper start - Add Wandering Trader spawn rate config options ++ if (this.tickDelay == Integer.MIN_VALUE) { ++ this.tickDelay = level.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; ++ this.spawnDelay = level.paperConfig().entities.spawning.wanderingTrader.spawnDayLength; ++ this.spawnChance = level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin; ++ } + if (!level.getGameRules().getBoolean(GameRules.RULE_DO_TRADER_SPAWNING)) { + return 0; +- } else if (--this.tickDelay > 0) { ++ } else if (--this.tickDelay - 1 > 0) { ++ this.tickDelay = this.tickDelay - 1; + return 0; + } else { +- this.tickDelay = 1200; +- this.spawnDelay -= 1200; +- this.serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay); ++ this.tickDelay = level.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; ++ this.spawnDelay = this.spawnDelay - level.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; ++ //this.serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways + if (this.spawnDelay > 0) { + return 0; + } else { +- this.spawnDelay = 24000; ++ this.spawnDelay = level.paperConfig().entities.spawning.wanderingTrader.spawnDayLength; + if (!level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { + return 0; + } else { + int i = this.spawnChance; +- this.spawnChance = Mth.clamp(this.spawnChance + 25, 25, 75); +- this.serverLevelData.setWanderingTraderSpawnChance(this.spawnChance); ++ this.spawnChance = Mth.clamp(this.spawnChance + level.paperConfig().entities.spawning.wanderingTrader.spawnChanceFailureIncrement, level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin, level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMax); ++ //this.serverLevelData.setWanderingTraderSpawnChance(this.spawnChance); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways + if (this.random.nextInt(100) > i) { + return 0; + } else if (this.spawn(level)) { +- this.spawnChance = 25; ++ this.spawnChance = level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin; + return 1; + } else { + return 0; +@@ -100,14 +_,14 @@ + return false; + } + +- WanderingTrader wanderingTrader = EntityType.WANDERING_TRADER.spawn(serverLevel, blockPos2, EntitySpawnReason.EVENT); ++ WanderingTrader wanderingTrader = EntityType.WANDERING_TRADER.spawn(serverLevel, trader -> trader.setDespawnDelay(48000), blockPos2, EntitySpawnReason.EVENT, false, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit // Paper - set despawnTimer before spawn events called + if (wanderingTrader != null) { + for (int i1 = 0; i1 < 2; i1++) { + this.tryToSpawnLlamaFor(serverLevel, wanderingTrader, 4); + } + + this.serverLevelData.setWanderingTraderId(wanderingTrader.getUUID()); +- wanderingTrader.setDespawnDelay(48000); ++ //wanderingTrader.setDespawnDelay(48000); // CraftBukkit - moved to EntityVillagerTrader constructor. This lets the value be modified by plugins on CreatureSpawnEvent + wanderingTrader.setWanderTarget(blockPos1); + wanderingTrader.restrictTo(blockPos1, 16); + return true; +@@ -121,7 +_,7 @@ + private void tryToSpawnLlamaFor(ServerLevel serverLevel, WanderingTrader trader, int maxDistance) { + BlockPos blockPos = this.findSpawnPositionNear(serverLevel, trader.blockPosition(), maxDistance); + if (blockPos != null) { +- TraderLlama traderLlama = EntityType.TRADER_LLAMA.spawn(serverLevel, blockPos, EntitySpawnReason.EVENT); ++ TraderLlama traderLlama = EntityType.TRADER_LLAMA.spawn(serverLevel, blockPos, EntitySpawnReason.EVENT, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit + if (traderLlama != null) { + traderLlama.setLeashedTo(trader, true); + } diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/Villager.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/npc/Villager.java.patch deleted file mode 100644 index cb4983a642..0000000000 --- a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/Villager.java.patch +++ /dev/null @@ -1,211 +0,0 @@ ---- a/net/minecraft/world/entity/npc/Villager.java -+++ b/net/minecraft/world/entity/npc/Villager.java -@@ -93,6 +93,14 @@ - import net.minecraft.world.phys.AABB; - import org.slf4j.Logger; - -+// CraftBukkit start -+import org.bukkit.Bukkit; -+import org.bukkit.craftbukkit.event.CraftEventFactory; -+import org.bukkit.event.entity.EntityRemoveEvent; -+import org.bukkit.event.entity.EntityTransformEvent; -+import org.bukkit.event.entity.VillagerReplenishTradeEvent; -+// CraftBukkit end -+ - public class Villager extends AbstractVillager implements ReputationEventHandler, VillagerDataHolder { - - private static final Logger LOGGER = LogUtils.getLogger(); -@@ -150,7 +158,7 @@ - - @Override - public Brain getBrain() { -- return super.getBrain(); -+ return (Brain) super.getBrain(); // CraftBukkit - decompile error - } - - @Override -@@ -216,7 +224,18 @@ - return this.assignProfessionWhenSpawned; - } - -+ // Spigot Start - @Override -+ public void inactiveTick() { -+ // SPIGOT-3874, SPIGOT-3894, SPIGOT-3846, SPIGOT-5286 :( -+ if (this.level().spigotConfig.tickInactiveVillagers && this.isEffectiveAi()) { -+ this.customServerAiStep((ServerLevel) this.level()); -+ } -+ super.inactiveTick(); -+ } -+ // Spigot End -+ -+ @Override - protected void customServerAiStep(ServerLevel world) { - ProfilerFiller gameprofilerfiller = Profiler.get(); - -@@ -235,7 +254,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 - } - } - -@@ -360,7 +379,13 @@ - while (iterator.hasNext()) { - MerchantOffer merchantrecipe = (MerchantOffer) iterator.next(); - -- merchantrecipe.resetUses(); -+ // CraftBukkit start -+ VillagerReplenishTradeEvent event = new VillagerReplenishTradeEvent((org.bukkit.entity.Villager) this.getBukkitEntity(), merchantrecipe.asBukkit()); -+ Bukkit.getPluginManager().callEvent(event); -+ if (!event.isCancelled()) { -+ merchantrecipe.resetUses(); -+ } -+ // CraftBukkit end - } - - this.resendOffersToTradingPlayer(); -@@ -429,7 +454,13 @@ - while (iterator.hasNext()) { - MerchantOffer merchantrecipe = (MerchantOffer) iterator.next(); - -- merchantrecipe.resetUses(); -+ // CraftBukkit start -+ VillagerReplenishTradeEvent event = new VillagerReplenishTradeEvent((org.bukkit.entity.Villager) this.getBukkitEntity(), merchantrecipe.asBukkit()); -+ Bukkit.getPluginManager().callEvent(event); -+ if (!event.isCancelled()) { -+ merchantrecipe.resetUses(); -+ } -+ // CraftBukkit end - } - } - -@@ -459,6 +490,7 @@ - - while (iterator.hasNext()) { - MerchantOffer merchantrecipe = (MerchantOffer) iterator.next(); -+ if (merchantrecipe.ignoreDiscounts) continue; // Paper - Add ignore discounts API - - merchantrecipe.addToSpecialPriceDiff(-Mth.floor((float) i * merchantrecipe.getPriceMultiplier())); - } -@@ -471,6 +503,7 @@ - - while (iterator1.hasNext()) { - MerchantOffer merchantrecipe1 = (MerchantOffer) iterator1.next(); -+ if (merchantrecipe1.ignoreDiscounts) continue; // Paper - Add ignore discounts API - double d0 = 0.3D + 0.0625D * (double) j; - int k = (int) Math.floor(d0 * (double) merchantrecipe1.getBaseCostA().getCount()); - -@@ -489,7 +522,7 @@ - @Override - public void addAdditionalSaveData(CompoundTag nbt) { - super.addAdditionalSaveData(nbt); -- DataResult dataresult = VillagerData.CODEC.encodeStart(NbtOps.INSTANCE, this.getVillagerData()); -+ DataResult dataresult = VillagerData.CODEC.encodeStart(NbtOps.INSTANCE, this.getVillagerData()); // CraftBukkit - decompile error - Logger logger = Villager.LOGGER; - - Objects.requireNonNull(logger); -@@ -512,7 +545,7 @@ - public void readAdditionalSaveData(CompoundTag nbt) { - super.readAdditionalSaveData(nbt); - if (nbt.contains("VillagerData", 10)) { -- DataResult dataresult = VillagerData.CODEC.parse(NbtOps.INSTANCE, nbt.get("VillagerData")); -+ DataResult dataresult = VillagerData.CODEC.parse(new Dynamic(NbtOps.INSTANCE, nbt.get("VillagerData"))); - Logger logger = Villager.LOGGER; - - Objects.requireNonNull(logger); -@@ -599,7 +632,7 @@ - } - - if (offer.shouldRewardExp()) { -- this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i)); -+ this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.getTradingPlayer(), this)); // Paper - } - - } -@@ -618,7 +651,7 @@ - - @Override - public void die(DamageSource damageSource) { -- Villager.LOGGER.info("Villager {} died, message: '{}'", this, damageSource.getLocalizedDeathMessage(this).getString()); -+ if (org.spigotmc.SpigotConfig.logVillagerDeaths) Villager.LOGGER.info("Villager {} died, message: '{}'", this, damageSource.getLocalizedDeathMessage(this).getString()); // Spigot - Entity entity = damageSource.getEntity(); - - if (entity != null) { -@@ -803,12 +836,19 @@ - @Override - public void thunderHit(ServerLevel world, LightningBolt lightning) { - if (world.getDifficulty() != Difficulty.PEACEFUL) { -- Villager.LOGGER.info("Villager {} was struck by lightning {}.", this, lightning); -+ // Paper - Add EntityZapEvent; move log down, event can cancel - Witch entitywitch = (Witch) this.convertTo(EntityType.WITCH, ConversionParams.single(this, false, false), (entitywitch1) -> { -+ // Paper start - Add EntityZapEvent -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityZapEvent(this, lightning, entitywitch1).isCancelled()) { -+ return false; -+ } -+ if (org.spigotmc.SpigotConfig.logVillagerDeaths) Villager.LOGGER.info("Villager {} was struck by lightning {}.", this, lightning); // Move down -+ // Paper end - Add EntityZapEvent - entitywitch1.finalizeSpawn(world, world.getCurrentDifficultyAt(entitywitch1.blockPosition()), EntitySpawnReason.CONVERSION, (SpawnGroupData) null); - entitywitch1.setPersistenceRequired(); - this.releaseAllPois(); -- }); -+ return true; // Paper start - Add EntityZapEvent -+ }, EntityTransformEvent.TransformReason.LIGHTNING, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit - - if (entitywitch == null) { - super.thunderHit(world, lightning); -@@ -855,6 +895,12 @@ - - @Override - protected void updateTrades() { -+ // Paper start - More vanilla friendly methods to update trades -+ updateTrades(TRADES_PER_LEVEL); -+ } -+ -+ public boolean updateTrades(int amount) { -+ // Paper end - More vanilla friendly methods to update trades - VillagerData villagerdata = this.getVillagerData(); - Int2ObjectMap int2objectmap; - -@@ -872,9 +918,11 @@ - if (avillagertrades_imerchantrecipeoption != null) { - MerchantOffers merchantrecipelist = this.getOffers(); - -- this.addOffersFromItemListings(merchantrecipelist, avillagertrades_imerchantrecipeoption, 2); -+ this.addOffersFromItemListings(merchantrecipelist, avillagertrades_imerchantrecipeoption, 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 world, Villager villager, long time) { -@@ -906,7 +954,7 @@ - }).limit(5L).toList(); - - if (list1.size() >= requiredCount) { -- if (!SpawnUtil.trySpawnMob(EntityType.IRON_GOLEM, EntitySpawnReason.MOB_SUMMONED, world, this.blockPosition(), 10, 8, 6, SpawnUtil.Strategy.LEGACY_IRON_GOLEM, false).isEmpty()) { -+ if (SpawnUtil.trySpawnMob(EntityType.IRON_GOLEM, EntitySpawnReason.MOB_SUMMONED, world, this.blockPosition(), 10, 8, 6, SpawnUtil.Strategy.LEGACY_IRON_GOLEM, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_DEFENSE, () -> {GolemSensor.golemDetected(this);}).isPresent()) { // CraftBukkit // Paper - Set Golem Last Seen to stop it from spawning another one - list.forEach(GolemSensor::golemDetected); - } - } -@@ -963,7 +1011,7 @@ - @Override - public void startSleeping(BlockPos pos) { - super.startSleeping(pos); -- this.brain.setMemory(MemoryModuleType.LAST_SLEPT, (Object) this.level().getGameTime()); -+ this.brain.setMemory(MemoryModuleType.LAST_SLEPT, this.level().getGameTime()); // CraftBukkit - decompile error - this.brain.eraseMemory(MemoryModuleType.WALK_TARGET); - this.brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE); - } -@@ -971,7 +1019,7 @@ - @Override - public void stopSleeping() { - super.stopSleeping(); -- this.brain.setMemory(MemoryModuleType.LAST_WOKEN, (Object) this.level().getGameTime()); -+ this.brain.setMemory(MemoryModuleType.LAST_WOKEN, this.level().getGameTime()); // CraftBukkit - decompile error - } - - private boolean golemSpawnConditionsMet(long worldTime) { diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch deleted file mode 100644 index 5b4f2d10a7..0000000000 --- a/paper-server/patches/unapplied/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch +++ /dev/null @@ -1,100 +0,0 @@ ---- a/net/minecraft/world/entity/npc/WanderingTraderSpawner.java -+++ b/net/minecraft/world/entity/npc/WanderingTraderSpawner.java -@@ -40,43 +40,53 @@ - - public WanderingTraderSpawner(ServerLevelData properties) { - this.serverLevelData = properties; -- this.tickDelay = 1200; -- this.spawnDelay = properties.getWanderingTraderSpawnDelay(); -- this.spawnChance = properties.getWanderingTraderSpawnChance(); -- if (this.spawnDelay == 0 && this.spawnChance == 0) { -- this.spawnDelay = 24000; -- properties.setWanderingTraderSpawnDelay(this.spawnDelay); -- this.spawnChance = 25; -- properties.setWanderingTraderSpawnChance(this.spawnChance); -- } -+ // Paper start - Add Wandering Trader spawn rate config options -+ this.tickDelay = Integer.MIN_VALUE; -+ //this.spawnDelay = properties.getWanderingTraderSpawnDelay(); // Paper - This value is read from the world file only for the first spawn, after which vanilla uses a hardcoded value -+ //this.spawnChance = properties.getWanderingTraderSpawnChance(); // Paper - This value is read from the world file only for the first spawn, after which vanilla uses a hardcoded value -+ //if (this.spawnDelay == 0 && this.spawnChance == 0) { -+ // this.spawnDelay = 24000; -+ // properties.setWanderingTraderSpawnDelay(this.spawnDelay); -+ // this.spawnChance = 25; -+ // properties.setWanderingTraderSpawnChance(this.spawnChance); -+ //} -+ // Paper end - Add Wandering Trader spawn rate config options - - } - - @Override - public int tick(ServerLevel world, boolean spawnMonsters, boolean spawnAnimals) { -+ // Paper start - Add Wandering Trader spawn rate config options -+ if (this.tickDelay == Integer.MIN_VALUE) { -+ this.tickDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; -+ this.spawnDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnDayLength; -+ this.spawnChance = world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin; -+ } - if (!world.getGameRules().getBoolean(GameRules.RULE_DO_TRADER_SPAWNING)) { - return 0; -- } else if (--this.tickDelay > 0) { -+ } else if (this.tickDelay - 1 > 0) { -+ this.tickDelay = this.tickDelay - 1; - return 0; - } else { -- this.tickDelay = 1200; -- this.spawnDelay -= 1200; -- this.serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay); -+ this.tickDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; -+ this.spawnDelay = this.spawnDelay - world.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; -+ //this.serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways - if (this.spawnDelay > 0) { - return 0; - } else { -- this.spawnDelay = 24000; -+ this.spawnDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnDayLength; - if (!world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { - return 0; - } else { - int i = this.spawnChance; - -- this.spawnChance = Mth.clamp(this.spawnChance + 25, 25, 75); -- this.serverLevelData.setWanderingTraderSpawnChance(this.spawnChance); -+ // this.serverLevelData.setWanderingTraderSpawnChance(this.spawnChance); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways -+ this.spawnChance = Mth.clamp(i + world.paperConfig().entities.spawning.wanderingTrader.spawnChanceFailureIncrement, world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin, world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMax); - if (this.random.nextInt(100) > i) { - return 0; - } else if (this.spawn(world)) { -- this.spawnChance = 25; -+ this.spawnChance = world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin; -+ // Paper end - Add Wandering Trader spawn rate config options - return 1; - } else { - return 0; -@@ -110,7 +120,7 @@ - return false; - } - -- WanderingTrader entityvillagertrader = (WanderingTrader) EntityType.WANDERING_TRADER.spawn(world, blockposition2, EntitySpawnReason.EVENT); -+ WanderingTrader entityvillagertrader = (WanderingTrader) EntityType.WANDERING_TRADER.spawn(world, trader -> trader.setDespawnDelay(48000), blockposition2, EntitySpawnReason.EVENT, false, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit // Paper - set despawnTimer before spawn events called - - if (entityvillagertrader != null) { - for (int i = 0; i < 2; ++i) { -@@ -118,7 +128,7 @@ - } - - this.serverLevelData.setWanderingTraderId(entityvillagertrader.getUUID()); -- entityvillagertrader.setDespawnDelay(48000); -+ // entityvillagertrader.setDespawnDelay(48000); // CraftBukkit - moved to EntityVillagerTrader constructor. This lets the value be modified by plugins on CreatureSpawnEvent - entityvillagertrader.setWanderTarget(blockposition1); - entityvillagertrader.restrictTo(blockposition1, 16); - return true; -@@ -133,7 +143,7 @@ - BlockPos blockposition = this.findSpawnPositionNear(world, wanderingTrader.blockPosition(), range); - - if (blockposition != null) { -- TraderLlama entityllamatrader = (TraderLlama) EntityType.TRADER_LLAMA.spawn(world, blockposition, EntitySpawnReason.EVENT); -+ TraderLlama entityllamatrader = (TraderLlama) EntityType.TRADER_LLAMA.spawn(world, blockposition, EntitySpawnReason.EVENT, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit - - if (entityllamatrader != null) { - entityllamatrader.setLeashedTo(wanderingTrader, true);