package com.pretzel.dev.villagertradelimiter.listeners; import com.pretzel.dev.villagertradelimiter.VillagerTradeLimiter; import com.pretzel.dev.villagertradelimiter.data.CustomRecipe; import com.pretzel.dev.villagertradelimiter.data.PlayerData; import com.pretzel.dev.villagertradelimiter.lib.Util; import com.pretzel.dev.villagertradelimiter.nms.NMSVillager; import org.bukkit.*; import org.bukkit.entity.*; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryType; import org.bukkit.event.player.PlayerInteractEntityEvent; import org.bukkit.inventory.*; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import java.util.*; public class PlayerListener implements Listener { private final VillagerTradeLimiter instance; private final HashMap> customRecipes; /** * @param instance The instance of VillagerTradeLimiter.java */ public PlayerListener(final VillagerTradeLimiter instance) { this.instance = instance; this.customRecipes = new HashMap<>(); } /** Handles when a player begins trading with a villager */ @EventHandler public void onPlayerBeginTrading(final PlayerInteractEntityEvent event) { if(event.isCancelled()) return; //Skips when the event is already cancelled if(!(event.getRightClicked() instanceof final Villager villager)) return; //Skips non-villager entity interactions if(villager.getRecipeCount() == 0) return; //Skips when the villager has no trades final Player player = event.getPlayer(); final ItemStack heldItem = player.getInventory().getItem(event.getHand()); if(instance.getSettings().shouldSkipNPC(player, villager)) return; //Skips when the player or villager is a Citizens NPC or Shopkeeper if(heldItem != null && instance.getSettings().getIgnoreHeldItems().contains(heldItem.getType().name().toLowerCase())) return; //Skips when player is holding an ignored item if(instance.getSettings().getIgnoreWorlds().contains(villager.getWorld().getName())) return; //Skips when the villager is in an ignored world //Skips and cancels the event when the player is in a disabled world if(instance.getSettings().getDisableTrading().contains(player.getWorld().getName())) { Util.sendMsg(instance.getLang("trading.noworld"), player); event.setCancelled(true); return; } //Skips and cancels the event when the villager has a disabled profession String profession = villager.getProfession().name().toLowerCase(); if(instance.getSettings().getDisableProfessions().contains(profession)) { Util.sendMsg(instance.getLang("trading.noprofession").replace("{profession}", profession), player); event.setCancelled(true); return; } //Cancel the original event, and open the adjusted trade view event.setCancelled(true); instance.getPlayerData().putIfAbsent(player.getUniqueId(), new PlayerData()); instance.getPlayerData().putIfAbsent(villager.getUniqueId(), new PlayerData()); this.see(villager, player, player); } /** Handles when a player stops trading with a villager */ @EventHandler public void onPlayerStopTrading(final InventoryCloseEvent event) { //Don't do anything unless the player is actually finished trading with a villager if(event.getInventory().getType() != InventoryType.MERCHANT) return; //Skips non-merchant inventories if(!(event.getPlayer() instanceof final Player player)) return; //Skips non-players if(!(event.getInventory().getHolder() instanceof final Villager villager)) return; //Skips non-villagers if(instance.getSettings().shouldSkipNPC(player, villager)) return; //Skips NPCs //Reset the villager's recipes to vanilla when a player is finished trading resetVillager(villager); } private void resetVillager(final Villager villager) { if(!customRecipes.containsKey(villager)) return; ArrayList recipes = customRecipes.get(villager); for(int i = 0; i < recipes.size(); i++) { recipes.get(i).reset(); villager.setRecipe(i, recipes.get(i).getOriginal()); } } public void onDisable() { //Reset the villager's recipes to vanilla when the plugin is disabled for(Villager villager : customRecipes.keySet()) resetVillager(villager); customRecipes.clear(); } /** * Opens the villager's trading menu, with the adjusted trades of another player (or the same player) * @param villager The villager whose trades you want to see * @param player The player who calls the command, or the player that has begun trading * @param other The other player to view trades for, or the player that has just begun trading */ public void see(final Villager villager, final Player player, final OfflinePlayer other) { //Skips when the villager is in a disabled world if(instance.getSettings().getDisableTrading().contains(villager.getWorld().getName())) { Util.sendMsg(instance.getLang("see.noworld"), player); return; } //Wraps the villager and player into wrapper classes if(other == null || instance.getSettings().shouldSkipNPC(player, villager)) return; //Skips NPCs //Calculates the player's total reputation and Hero of the Village discount final NMSVillager nmsVillager = new NMSVillager(villager); int totalReputation = nmsVillager.getTotalReputation(other); double hotvDiscount = getHotvDiscount(other); //Adjusts the recipe prices, MaxUses, and ingredients final ArrayList originalRecipes = new ArrayList<>(villager.getRecipes()); final ArrayList recipes = new ArrayList<>(); for(int i = 0; i < originalRecipes.size(); i++) { recipes.add(new CustomRecipe(instance, other, villager, originalRecipes.get(i))); villager.setRecipe(i, recipes.get(i).getAdjusted()); } customRecipes.put(villager, recipes); //Opens the trading window boolean multipleTraders = instance.getSettings().getMultipleTraders(); int delay = instance.getSettings().getTradeDelay(); if(delay >= 0) { Bukkit.getScheduler().runTaskLater(instance, () -> player.openMerchant(villager, multipleTraders), delay); } else { player.openMerchant(villager, multipleTraders); } //Updates the prices for(int i = 0; i < villager.getRecipes().size(); i++) { int discount = recipes.get(i).getDiscount(totalReputation, hotvDiscount); villager.getRecipe(i).setSpecialPrice(discount); } } /** * @param other The player to check the hotv effect for * @return The Hero of the Village discount factor, adjusted by config */ private double getHotvDiscount(final OfflinePlayer other) { final Player player = other.getPlayer(); if(player == null) return 0.0; final PotionEffectType effectType = PotionEffectType.HERO_OF_THE_VILLAGE; if(!player.hasPotionEffect(effectType)) return 0.0; final PotionEffect effect = player.getPotionEffect(effectType); if(effect == null) return 0.0; //Calculates the discount factor from the player's current effect level or the defined maximum int heroLevel = effect.getAmplifier()+1; final int maxHeroLevel = instance.getGroupManager().getGroup(other).getMaxHeroLevel();; if(maxHeroLevel == 0 || heroLevel == 0) return 0.0; if(maxHeroLevel > 0 && heroLevel > maxHeroLevel) { heroLevel = maxHeroLevel; } return 0.0625*(heroLevel-1) + 0.3; } /** * @param villager The villager * @return The list of original recipes for the villager */ public ArrayList getCustomRecipes(final Villager villager) { return this.customRecipes.get(villager); } }