diff --git a/patches/server/Add-PlayerTradeEvent-and-PlayerPurchaseEvent.patch b/patches/server/Add-PlayerTradeEvent-and-PlayerPurchaseEvent.patch new file mode 100644 index 0000000000..6f10f0db5f --- /dev/null +++ b/patches/server/Add-PlayerTradeEvent-and-PlayerPurchaseEvent.patch @@ -0,0 +1,249 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 2 Jul 2020 16:12:10 -0700 +Subject: [PATCH] Add PlayerTradeEvent and PlayerPurchaseEvent + +Co-authored-by: Alexander + +diff --git a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +@@ -0,0 +0,0 @@ public abstract class AbstractVillager extends AgeableMob implements InventoryCa + @Override + public void overrideXp(int experience) {} + ++ // Paper start ++ @Override ++ public void processTrade(MerchantOffer recipe, io.papermc.paper.event.player.PlayerPurchaseEvent event) { // The MerchantRecipe passed in here is the one set by the PlayerPurchaseEvent ++ if (event.willIncreaseTradeUses()) { ++ recipe.increaseUses(); ++ } ++ if (event.isRewardingExp()) { ++ this.rewardTradeXp(recipe); ++ } ++ this.notifyTrade(recipe); ++ } ++ // Paper end ++ + @Override + public void notifyTrade(MerchantOffer offer) { +- offer.increaseUses(); ++ // offer.increaseUses(); // Paper - handled in processTrade + this.ambientSoundTime = -this.getAmbientSoundInterval(); +- this.rewardTradeXp(offer); ++ // this.rewardTradeXp(offer); // Paper - handled in processTrade + if (this.tradingPlayer instanceof ServerPlayer) { + CriteriaTriggers.TRADE.trigger((ServerPlayer) this.tradingPlayer, this, offer.getResult()); + } +diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -0,0 +0,0 @@ public abstract class AbstractContainerMenu { + public abstract boolean stillValid(Player player); + + protected boolean moveItemStackTo(ItemStack stack, int startIndex, int endIndex, boolean fromLast) { ++ // Paper start ++ return this.moveItemStackTo(stack, startIndex, endIndex, fromLast, false); ++ } ++ protected boolean moveItemStackTo(ItemStack stack, int startIndex, int endIndex, boolean fromLast, boolean isCheck) { ++ if (isCheck) { ++ stack = stack.copy(); ++ } ++ // Paper end + boolean flag1 = false; + int k = startIndex; + +@@ -0,0 +0,0 @@ public abstract class AbstractContainerMenu { + + slot = (Slot) this.slots.get(k); + itemstack1 = slot.getItem(); ++ // Paper start - clone if only a check ++ if (isCheck) { ++ itemstack1 = itemstack1.copy(); ++ } ++ // Paper end + if (!itemstack1.isEmpty() && ItemStack.isSameItemSameTags(stack, itemstack1)) { + int l = itemstack1.getCount() + stack.getCount(); + + if (l <= stack.getMaxStackSize()) { + stack.setCount(0); + itemstack1.setCount(l); ++ if (!isCheck) { // Paper - dont update if only a check + slot.setChanged(); ++ } // Paper + flag1 = true; + } else if (itemstack1.getCount() < stack.getMaxStackSize()) { + stack.shrink(stack.getMaxStackSize() - itemstack1.getCount()); + itemstack1.setCount(stack.getMaxStackSize()); ++ if (!isCheck) { // Paper - dont update if only a check + slot.setChanged(); ++ } // Paper + flag1 = true; + } + } +@@ -0,0 +0,0 @@ public abstract class AbstractContainerMenu { + + slot = (Slot) this.slots.get(k); + itemstack1 = slot.getItem(); ++ // Paper start - clone if only a check ++ if (isCheck) { ++ itemstack1 = itemstack1.copy(); ++ } ++ // Paper end + if (itemstack1.isEmpty() && slot.mayPlace(stack)) { + if (stack.getCount() > slot.getMaxStackSize()) { ++ // Paper start - dont set slot if only check ++ if (isCheck) { ++ stack.shrink(slot.getMaxStackSize()); ++ } else { ++ // Paper end + slot.set(stack.split(slot.getMaxStackSize())); ++ } // Paper + } else { ++ // Paper start - dont set slot if only check ++ if (isCheck) { ++ stack.shrink(stack.getCount()); ++ } else { ++ // Paper end + slot.set(stack.split(stack.getCount())); ++ } // Paper + } + ++ if (!isCheck) { // Paper - dont update if only check + slot.setChanged(); ++ } // Paper + flag1 = true; + break; + } +diff --git a/src/main/java/net/minecraft/world/inventory/MerchantMenu.java b/src/main/java/net/minecraft/world/inventory/MerchantMenu.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/inventory/MerchantMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/MerchantMenu.java +@@ -0,0 +0,0 @@ public class MerchantMenu extends AbstractContainerMenu { + + itemstack = itemstack1.copy(); + if (index == 2) { +- if (!this.moveItemStackTo(itemstack1, 3, 39, true)) { ++ if (!this.moveItemStackTo(itemstack1, 3, 39, true, true)) { // Paper + return ItemStack.EMPTY; + } + +- slot.onQuickCraft(itemstack1, itemstack); +- this.playTradeSound(); ++ // slot.onQuickCraft(itemstack1, itemstack); // Paper - moved to after the non-check moveItemStackTo call ++ // this.playTradeSound(); + } else if (index != 0 && index != 1) { + if (index >= 3 && index < 30) { + if (!this.moveItemStackTo(itemstack1, 30, 39, false)) { +@@ -0,0 +0,0 @@ public class MerchantMenu extends AbstractContainerMenu { + return ItemStack.EMPTY; + } + ++ if (index != 2) { // Paper - moved down for slot 2 + if (itemstack1.isEmpty()) { + slot.set(ItemStack.EMPTY); + } else { +@@ -0,0 +0,0 @@ public class MerchantMenu extends AbstractContainerMenu { + } + + slot.onTake(player, itemstack1); ++ } // Paper start - handle slot 2 ++ if (index == 2) { // is merchant result slot ++ slot.onTake(player, itemstack1); ++ if (itemstack1.isEmpty()) { ++ slot.set(ItemStack.EMPTY); ++ return ItemStack.EMPTY; ++ } ++ ++ this.moveItemStackTo(itemstack1, 3, 39, true, false); // This should always succeed because it's checked above ++ ++ slot.onQuickCraft(itemstack1, itemstack); ++ this.playTradeSound(); ++ slot.set(ItemStack.EMPTY); // itemstack1 should ALWAYS be empty ++ } ++ // Paper end + } + + return itemstack; +diff --git a/src/main/java/net/minecraft/world/inventory/MerchantResultSlot.java b/src/main/java/net/minecraft/world/inventory/MerchantResultSlot.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/inventory/MerchantResultSlot.java ++++ b/src/main/java/net/minecraft/world/inventory/MerchantResultSlot.java +@@ -0,0 +0,0 @@ public class MerchantResultSlot extends Slot { + + @Override + public void onTake(Player player, ItemStack stack) { +- this.checkTakeAchievements(stack); ++ // this.checkTakeAchievements(stack); // Paper - move to after event is called and not cancelled + MerchantOffer merchantOffer = this.slots.getActiveOffer(); ++ // Paper start ++ io.papermc.paper.event.player.PlayerPurchaseEvent event = null; ++ if (this.merchant instanceof net.minecraft.world.entity.npc.AbstractVillager && this.merchant.getTradingPlayer().getBukkitEntity() instanceof org.bukkit.entity.Player) { ++ event = new io.papermc.paper.event.player.PlayerTradeEvent((org.bukkit.entity.Player) this.merchant.getTradingPlayer().getBukkitEntity(), (org.bukkit.entity.AbstractVillager) ((net.minecraft.world.entity.npc.AbstractVillager) this.merchant).getBukkitEntity(), merchantOffer.asBukkit(), true, true); ++ } else if (this.merchant instanceof org.bukkit.craftbukkit.inventory.CraftMerchantCustom.MinecraftMerchant && this.merchant.getTradingPlayer().getBukkitEntity() instanceof org.bukkit.entity.Player) { ++ event = new io.papermc.paper.event.player.PlayerPurchaseEvent((org.bukkit.entity.Player) this.merchant.getTradingPlayer().getBukkitEntity(), merchantOffer.asBukkit(), false, true); ++ } ++ if (event != null) { ++ if (!event.callEvent()) { ++ stack.setCount(0); ++ event.getPlayer().updateInventory(); ++ return; ++ } ++ merchantOffer = org.bukkit.craftbukkit.inventory.CraftMerchantRecipe.fromBukkit(event.getTrade()).toMinecraft(); ++ } ++ this.checkTakeAchievements(stack); ++ // Paper end + if (merchantOffer != null) { + ItemStack itemStack = this.slots.getItem(0); + ItemStack itemStack2 = this.slots.getItem(1); + if (merchantOffer.take(itemStack, itemStack2) || merchantOffer.take(itemStack2, itemStack)) { +- this.merchant.notifyTrade(merchantOffer); ++ this.merchant.processTrade(merchantOffer, event); // Paper + player.awardStat(Stats.TRADED_WITH_VILLAGER); + this.slots.setItem(0, itemStack); + this.slots.setItem(1, itemStack2); +diff --git a/src/main/java/net/minecraft/world/item/trading/Merchant.java b/src/main/java/net/minecraft/world/item/trading/Merchant.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/item/trading/Merchant.java ++++ b/src/main/java/net/minecraft/world/item/trading/Merchant.java +@@ -0,0 +0,0 @@ public interface Merchant { + + void overrideOffers(MerchantOffers offers); + ++ default void processTrade(MerchantOffer merchantRecipe, io.papermc.paper.event.player.PlayerPurchaseEvent event) { this.notifyTrade(merchantRecipe); } // Paper + void notifyTrade(MerchantOffer offer); + + void notifyTradeUpdated(ItemStack stack); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java +@@ -0,0 +0,0 @@ public class CraftMerchantCustom extends CraftMerchant { + return this.trades; + } + ++ // Paper start ++ @Override ++ public void processTrade(MerchantOffer merchantRecipe, io.papermc.paper.event.player.PlayerPurchaseEvent event) { // The MerchantRecipe passed in here is the one set by the PlayerPurchaseEvent ++ /** Based on {@link net.minecraft.world.entity.npc.AbstractVillager#processTrade(MerchantOffer, io.papermc.paper.event.player.PlayerPurchaseEvent)} */ ++ if (getTradingPlayer() instanceof net.minecraft.server.level.ServerPlayer) { ++ if (event.willIncreaseTradeUses()) { ++ merchantRecipe.increaseUses(); ++ } ++ if (event.isRewardingExp()) { ++ this.tradingWorld.addFreshEntity(new net.minecraft.world.entity.ExperienceOrb(tradingWorld, tradingPlayer.getX(), tradingPlayer.getY(), tradingPlayer.getZ(), merchantRecipe.getXp(), org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.tradingPlayer, null)); ++ } ++ } ++ this.notifyTrade(merchantRecipe); ++ } ++ // Paper end + @Override + public void notifyTrade(MerchantOffer offer) { + // increase recipe's uses +- offer.increaseUses(); ++ // offer.increaseUses(); // Paper - handled above in processTrade + } + + @Override diff --git a/patches/server/Have-CraftMerchantCustom-emit-PlayerPurchaseEvent.patch b/patches/server/Have-CraftMerchantCustom-emit-PlayerPurchaseEvent.patch deleted file mode 100644 index 456703fca0..0000000000 --- a/patches/server/Have-CraftMerchantCustom-emit-PlayerPurchaseEvent.patch +++ /dev/null @@ -1,46 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Alexander -Date: Thu, 6 May 2021 13:01:25 +0100 -Subject: [PATCH] Have CraftMerchantCustom emit PlayerPurchaseEvent - - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java -@@ -0,0 +0,0 @@ public class CraftMerchantCustom extends CraftMerchant { - - @Override - public void notifyTrade(MerchantOffer offer) { -+ // Paper start -+ /** Based on {@link net.minecraft.world.entity.npc.EntityVillagerAbstract#b(MerchantRecipe)} */ -+ if (getTradingPlayer() instanceof net.minecraft.server.level.ServerPlayer) { -+ final net.minecraft.server.level.ServerPlayer trader = (net.minecraft.server.level.ServerPlayer) getTradingPlayer(); -+ final io.papermc.paper.event.player.PlayerPurchaseEvent event = new io.papermc.paper.event.player.PlayerPurchaseEvent( -+ trader.getBukkitEntity(), -+ offer.asBukkit(), -+ false, // reward xp? -+ true); // should increase uses? -+ event.callEvent(); -+ if (event.isCancelled()) { -+ return; -+ } -+ final org.bukkit.inventory.MerchantRecipe eventTrade = event.getTrade(); -+ if (event.willIncreaseTradeUses()) { -+ eventTrade.setUses(eventTrade.getUses() + 1); -+ } -+ if (event.isRewardingExp() && eventTrade.hasExperienceReward()) { -+ /** Based on {@link net.minecraft.world.entity.npc.EntityVillagerTrader#b(MerchantRecipe)} */ -+ final int xp = 3 + net.minecraft.world.entity.Entity.SHARED_RANDOM.nextInt(4); -+ final Level world = trader.getCommandSenderWorld(); -+ world.addFreshEntity(new net.minecraft.world.entity.ExperienceOrb( -+ world, trader.getX(), trader.getY() + 0.5d, trader.getZ(), xp, -+ org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, trader, null)); -+ } -+ return; -+ } -+ // Paper end -+ - // increase recipe's uses - offer.increaseUses(); - } diff --git a/patches/server/added-PlayerTradeEvent.patch b/patches/server/added-PlayerTradeEvent.patch deleted file mode 100644 index 2b9ae0dfe0..0000000000 --- a/patches/server/added-PlayerTradeEvent.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 2 Jul 2020 16:12:10 -0700 -Subject: [PATCH] added PlayerTradeEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -@@ -0,0 +0,0 @@ public abstract class AbstractVillager extends AgeableMob implements InventoryCa - - @Override - public void notifyTrade(MerchantOffer offer) { -- offer.increaseUses(); -- this.ambientSoundTime = -this.getAmbientSoundInterval(); -- this.rewardTradeXp(offer); -+ // Paper - moved down -+ // Paper start - if (this.tradingPlayer instanceof ServerPlayer) { -- CriteriaTriggers.TRADE.trigger((ServerPlayer) this.tradingPlayer, this, offer.getResult()); -+ io.papermc.paper.event.player.PlayerTradeEvent event = new io.papermc.paper.event.player.PlayerTradeEvent(((ServerPlayer) this.tradingPlayer).getBukkitEntity(), (org.bukkit.entity.AbstractVillager) this.getBukkitEntity(), offer.asBukkit(), true, true); -+ event.callEvent(); -+ if (!event.isCancelled()) { -+ MerchantOffer recipe = CraftMerchantRecipe.fromBukkit(event.getTrade()).toMinecraft(); -+ if (event.willIncreaseTradeUses()) recipe.increaseUses(); -+ this.ambientSoundTime = -this.getAmbientSoundInterval(); -+ if (event.isRewardingExp()) this.rewardTradeXp(recipe); -+ CriteriaTriggers.TRADE.trigger((ServerPlayer) this.tradingPlayer, this, recipe.getResult()); -+ } -+ } else { -+ offer.increaseUses(); -+ this.ambientSoundTime = -getAmbientSoundInterval(); -+ this.rewardTradeXp(offer); - } -- -+ // Paper end - } - - protected abstract void rewardTradeXp(MerchantOffer offer);