Custom merchant handling

This commit is contained in:
Alexander
2021-04-21 17:41:12 +01:00
parent c6fae38671
commit 0f7fce89c7
2 changed files with 141 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
package vg.civcraft.mc.civmodcore.entities.merchant;
import io.papermc.paper.event.player.PlayerTradeEvent;
import java.lang.reflect.Field;
import java.util.logging.Level;
import net.kyori.adventure.text.Component;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftMerchant;
import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftMerchantCustom;
import vg.civcraft.mc.civmodcore.util.CivLogger;
/**
* This is an alternative to {@link Bukkit#createMerchant(Component)} that re-adds
* {@link PlayerTradeEvent} emissions. Just do: {@code new CustomBukkitMerchant(Component)}
*/
public class CustomBukkitMerchant extends CraftMerchantCustom {
private static final CivLogger LOGGER;
private static final Field MERCHANT_FIELD;
static {
LOGGER = CivLogger.getLogger(CustomBukkitMerchant.class);
MERCHANT_FIELD = FieldUtils.getField(CraftMerchant.class, "merchant", true);
FieldUtils.removeFinalModifier(MERCHANT_FIELD);
}
public CustomBukkitMerchant(final Component title) {
super(title);
final var nmsMerchant = new CustomNMSMerchant(this, title);
try {
FieldUtils.writeField(MERCHANT_FIELD, this, nmsMerchant, true);
}
catch (final IllegalAccessException exception) {
LOGGER.log(Level.SEVERE,
"Could not re-set merchant to [" + nmsMerchant + "]",
exception);
}
}
public CustomNMSMerchant getNMSMerchant() {
return (CustomNMSMerchant) super.getMerchant();
}
/**
* Use {@link #getNMSMerchant()} instead. The only reason why this is being kept is because
* the super constructor calls this function and expects this type, not {@link CustomNMSMerchant}.
*/
@Deprecated
@Override
public CraftMerchantCustom.MinecraftMerchant getMerchant() {
return super.getMerchant();
}
@Override
public String toString() {
/** Stolen from {@link Object#toString()} because the super version is garbage */
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
}

View File

@@ -0,0 +1,80 @@
package vg.civcraft.mc.civmodcore.entities.merchant;
import io.papermc.paper.event.player.PlayerTradeEvent;
import java.lang.reflect.Field;
import java.util.Objects;
import net.kyori.adventure.text.Component;
import net.minecraft.server.v1_16_R3.Entity;
import net.minecraft.server.v1_16_R3.EntityExperienceOrb;
import net.minecraft.server.v1_16_R3.EntityPlayer;
import net.minecraft.server.v1_16_R3.EntityVillagerAbstract;
import net.minecraft.server.v1_16_R3.EntityVillagerTrader;
import net.minecraft.server.v1_16_R3.MerchantRecipe;
import net.minecraft.server.v1_16_R3.MerchantRecipeList;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftMerchantCustom;
import org.bukkit.entity.AbstractVillager;
import org.bukkit.entity.ExperienceOrb;
public class CustomNMSMerchant extends CraftMerchantCustom.MinecraftMerchant {
private static final Field TRADES_FIELD;
static {
TRADES_FIELD = FieldUtils.getField(CraftMerchantCustom.MinecraftMerchant.class, "trades", true);
FieldUtils.removeFinalModifier(TRADES_FIELD);
}
CustomNMSMerchant(final CustomBukkitMerchant merchant, final Component title) {
super(title);
this.craftMerchant = Objects.requireNonNull(merchant);
}
/**
* @return Returns the raw offers object (which is mutable) via reflection.
*/
public MerchantRecipeList getRawOffers() {
try {
return (MerchantRecipeList) FieldUtils.readField(TRADES_FIELD, this, true);
}
catch (final IllegalAccessException exception) {
throw new RuntimeException(exception);
}
}
/**
* This is based heavily on {@link EntityVillagerAbstract#a(MerchantRecipe)}. This method is called
* by NMS when a trade is being purchased.
*/
@Override
public void a(final MerchantRecipe trade) {
if (getTrader() instanceof EntityPlayer) {
final var trader = (EntityPlayer) getTrader();
final var event = new PlayerTradeEvent(
trader.getBukkitEntity(),
(AbstractVillager) getCraftMerchant(),
trade.asBukkit(),
true, // reward xp?
true); // should increase uses?
event.callEvent();
if (event.isCancelled()) {
return;
}
final var eventTrade = event.getTrade();
if (event.willIncreaseTradeUses()) {
eventTrade.setUses(eventTrade.getUses() + 1);
}
if (event.isRewardingExp() && eventTrade.hasExperienceReward()) {
/** Based on {@link EntityVillagerTrader#b(MerchantRecipe)} */
final int xp = 3 + Entity.SHARED_RANDOM.nextInt(4);
final var world = trader.getWorld();
world.addEntity(new EntityExperienceOrb(
world, trader.locX(), trader.locY() + 0.5d, trader.locZ(),
xp, ExperienceOrb.SpawnReason.VILLAGER_TRADE, trader, null));
}
return;
}
super.a(trade);
}
}