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 73bc45000b5285ccf5b06e2f372d6c82bf843b82..5a591c439c5cef6b7e7e6f836ab813cb4f29b08c 100644 --- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java @@ -137,11 +137,24 @@ public abstract class AbstractVillager extends AgeableMob implements InventoryCa @Override public void overrideXp(int experience) {} + // Paper start + @Override + 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 + if (event == null || event.willIncreaseTradeUses()) { + recipe.increaseUses(); + } + if (event == null || 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 52e57fa3b25dce10c17682964cbd4bcb4c130dc6..c6ac55cdece9e2901e5d17a23408171c6c49ff69 100644 --- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java @@ -760,6 +760,14 @@ 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; @@ -782,18 +790,27 @@ 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; } } @@ -824,14 +841,33 @@ 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.setByPlayer(stack.split(slot.getMaxStackSize())); + } // Paper } else { + // Paper start - dont set slot if only check + if (isCheck) { + stack.shrink(stack.getCount()); + } else { + // Paper end slot.setByPlayer(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 743a2adc465be5477d204185967265389d7102de..8eab7596e1f7d1beb9ab0d70d1310d26822262e9 100644 --- a/src/main/java/net/minecraft/world/inventory/MerchantMenu.java +++ b/src/main/java/net/minecraft/world/inventory/MerchantMenu.java @@ -134,12 +134,12 @@ public class MerchantMenu extends AbstractContainerMenu { itemstack = itemstack1.copy(); if (slot == 2) { - if (!this.moveItemStackTo(itemstack1, 3, 39, true)) { + if (!this.moveItemStackTo(itemstack1, 3, 39, true, true)) { // Paper return ItemStack.EMPTY; } - slot1.onQuickCraft(itemstack1, itemstack); - this.playTradeSound(); + // slot1.onQuickCraft(itemstack1, itemstack); // Paper - moved to after the non-check moveItemStackTo call + // this.playTradeSound(); } else if (slot != 0 && slot != 1) { if (slot >= 3 && slot < 30) { if (!this.moveItemStackTo(itemstack1, 30, 39, false)) { @@ -152,6 +152,7 @@ public class MerchantMenu extends AbstractContainerMenu { return ItemStack.EMPTY; } + if (slot != 2) { // Paper - moved down for slot 2 if (itemstack1.isEmpty()) { slot1.setByPlayer(ItemStack.EMPTY); } else { @@ -163,6 +164,21 @@ public class MerchantMenu extends AbstractContainerMenu { } slot1.onTake(player, itemstack1); + } // Paper start - handle slot 2 + if (slot == 2) { // is merchant result slot + slot1.onTake(player, itemstack1); + if (itemstack1.isEmpty()) { + slot1.set(ItemStack.EMPTY); + return ItemStack.EMPTY; + } + + this.moveItemStackTo(itemstack1, 3, 39, true, false); // This should always succeed because it's checked above + + slot1.onQuickCraft(itemstack1, itemstack); + this.playTradeSound(); + slot1.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 e49bbb803399ef696665c5844a18b55a551654f6..1f2b9a9a3fa167e2ba021c823dd142b0bb18a695 100644 --- a/src/main/java/net/minecraft/world/inventory/MerchantResultSlot.java +++ b/src/main/java/net/minecraft/world/inventory/MerchantResultSlot.java @@ -47,13 +47,32 @@ 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 (merchantOffer != null && player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) { + if (this.merchant instanceof net.minecraft.world.entity.npc.AbstractVillager abstractVillager) { + event = new io.papermc.paper.event.player.PlayerTradeEvent(serverPlayer.getBukkitEntity(), (org.bukkit.entity.AbstractVillager) abstractVillager.getBukkitEntity(), merchantOffer.asBukkit(), true, true); + } else if (this.merchant instanceof org.bukkit.craftbukkit.inventory.CraftMerchantCustom.MinecraftMerchant) { + event = new io.papermc.paper.event.player.PlayerPurchaseEvent(serverPlayer.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 5a350948a4735902f5c612592bc9d100445a0c8a..716b30dcd7e63c66736c448dd136c9f74dc7fe43 100644 --- a/src/main/java/net/minecraft/world/item/trading/Merchant.java +++ b/src/main/java/net/minecraft/world/item/trading/Merchant.java @@ -20,6 +20,7 @@ public interface Merchant { void overrideOffers(MerchantOffers offers); + default void processTrade(MerchantOffer merchantRecipe, @Nullable 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 adf22ce4f0bcd3bd57dc2030c6c92d3df96566e3..05af1f1cfb38e4ae4ea0ecc2d0a943cbc4063c77 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java @@ -74,10 +74,25 @@ public class CraftMerchantCustom extends CraftMerchant { return this.trades; } + // Paper start + @Override + public void processTrade(MerchantOffer merchantRecipe, @javax.annotation.Nullable 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 == null || event.willIncreaseTradeUses()) { + merchantRecipe.increaseUses(); + } + if (event == null || event.isRewardingExp()) { + this.tradingPlayer.level().addFreshEntity(new net.minecraft.world.entity.ExperienceOrb(this.tradingPlayer.level(), this.tradingPlayer.getX(), this.tradingPlayer.getY(), this.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