VillagerTradeLimiter/src/com/pretzel/dev/villagertradelimiter/listeners/PlayerListener.java

253 lines
12 KiB
Java

package com.pretzel.dev.villagertradelimiter.listeners;
import com.pretzel.dev.villagertradelimiter.VillagerTradeLimiter;
import com.pretzel.dev.villagertradelimiter.data.Cooldown;
import com.pretzel.dev.villagertradelimiter.data.PlayerData;
import com.pretzel.dev.villagertradelimiter.settings.Settings;
import com.pretzel.dev.villagertradelimiter.wrappers.*;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import org.bukkit.entity.Villager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import java.time.Instant;
import java.util.Date;
import java.util.List;
public class PlayerListener implements Listener {
private final VillagerTradeLimiter instance;
private final Settings settings;
/**
* @param instance The instance of VillagerTradeLimiter.java
* @param settings The settings instance
*/
public PlayerListener(final VillagerTradeLimiter instance, final Settings settings) {
this.instance = instance;
this.settings = settings;
}
/** Handles when a player begins trading with a villager */
@EventHandler
public void onPlayerBeginTrading(final PlayerInteractEntityEvent event) {
if(event.isCancelled()) return; //Skips when event is already cancelled
if(!(event.getRightClicked() instanceof Villager)) return; //Skips non-villager entities
final Player player = event.getPlayer();
final Villager villager = (Villager)event.getRightClicked();
//Skips when player is holding an ignored item
Material heldItemType = player.getInventory().getItem(event.getHand()).getType();
for(String ignoredType : instance.getCfg().getStringList("IgnoredHeldItems")) {
if(heldItemType.equals(Material.matchMaterial(ignoredType))) {
event.setCancelled(true);
return;
}
}
if(settings.shouldSkipNPC(event.getPlayer()) || settings.shouldSkipNPC(villager)) return; //Skips NPCs
if(villager.getProfession() == Villager.Profession.NONE || villager.getProfession() == Villager.Profession.NITWIT || villager.getRecipeCount() == 0) return; //Skips non-trading villagers
//DisableTrading feature
if(instance.getCfg().isBoolean("DisableTrading")) {
//If all trading is disabled
if(instance.getCfg().getBoolean("DisableTrading", false)) {
event.setCancelled(true);
return;
}
} else {
//If trading in the world the player is in is disabled
final List<String> disabledWorlds = instance.getCfg().getStringList("DisableTrading");
final String world = event.getPlayer().getWorld().getName();
for(String disabledWorld : disabledWorlds) {
if(world.equals(disabledWorld)) {
event.setCancelled(true);
return;
}
}
}
//Cancel the original event, and open the adjusted trade view
event.setCancelled(true);
if(!instance.getPlayerData().containsKey(player.getUniqueId())) {
instance.getPlayerData().put(player.getUniqueId(), new PlayerData());
}
if(!instance.getPlayerData().containsKey(villager.getUniqueId())) {
instance.getPlayerData().put(villager.getUniqueId(), new PlayerData());
}
this.see(villager, player, player);
}
/**
* 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) {
//Wraps the villager and player into wrapper classes
final VillagerWrapper villagerWrapper = new VillagerWrapper(villager);
final PlayerWrapper otherWrapper = new PlayerWrapper(other);
final Player otherPlayer = otherWrapper.getPlayer();
if(settings.shouldSkipNPC(player) || settings.shouldSkipNPC(villager) || otherPlayer == null || settings.shouldSkipNPC(otherPlayer)) return; //Skips NPCs
final PlayerData playerData = instance.getPlayerData().get(other.getUniqueId());
if(playerData != null) playerData.setTradingVillager(villagerWrapper);
//Checks if the version is old, before the 1.16 UUID changes
String version = instance.getServer().getClass().getPackage().getName();
boolean isOld = version.contains("1_13_") || version.contains("1_14_") || version.contains("1_15_");
//Calculates the player's total reputation and Hero of the Village discount
int totalReputation = villagerWrapper.getTotalReputation(villagerWrapper, otherWrapper, isOld);
double hotvDiscount = getHotvDiscount(otherWrapper);
//Adjusts the recipe prices, MaxUses, and ingredients
final List<RecipeWrapper> recipes = villagerWrapper.getRecipes();
for(RecipeWrapper recipe : recipes) {
//Set the special price (discount)
recipe.setSpecialPrice(getDiscount(recipe, totalReputation, hotvDiscount));
//Set ingredient materials and amounts
final ConfigurationSection override = settings.getOverride(recipe.getItemStack("buy"), recipe.getItemStack("sell"));
if(override != null) {
setIngredient(override.getConfigurationSection("Item1"), recipe.getIngredient1());
setIngredient(override.getConfigurationSection("Item2"), recipe.getIngredient2());
setIngredient(override.getConfigurationSection("Result"), recipe.getResult());
}
//Set the maximum number of uses (trades/day)
recipe.setMaxUses(getMaxUses(recipe, other));
}
//Open the villager's trading menu
player.openMerchant(villager, false);
}
/**
* @param recipe The recipe to get the base price for
* @return The initial price of a recipe/trade, before any discounts are applied
*/
private int getBasePrice(final RecipeWrapper recipe) {
int basePrice = recipe.getIngredient1().getAmount();
basePrice = settings.fetchInt(recipe, "Item1.Amount", basePrice);
return Math.min(Math.max(basePrice, 1), 64);
}
/**
* @param recipe The recipe to get the demand for
* @return The current value of the demand for the given recipe
*/
private int getDemand(final RecipeWrapper recipe) {
int demand = recipe.getDemand();
int maxDemand = settings.fetchInt(recipe, "MaxDemand", -1);
if(maxDemand >= 0 && demand > maxDemand) return maxDemand;
return demand;
}
/**
* @param recipe The recipe to get the discount for
* @param totalReputation The player's total reputation from a villager's gossips
* @param hotvDiscount The total discount from the Hero of the Village effect
* @return The total discount for the recipe, which is added to the base price to get the final price
*/
private int getDiscount(final RecipeWrapper recipe, int totalReputation, double hotvDiscount) {
//Calculates the total discount
int basePrice = getBasePrice(recipe);
int demand = getDemand(recipe);
float priceMultiplier = recipe.getPriceMultiplier();
int discount = -(int)(totalReputation * priceMultiplier) - (int)(hotvDiscount * basePrice) + Math.max(0, (int)(demand * priceMultiplier * basePrice));
double maxDiscount = settings.fetchDouble(recipe, "MaxDiscount", 0.3);
if(maxDiscount >= 0.0 && maxDiscount <= 1.0) {
//Change the discount to the smaller MaxDiscount
if(basePrice + discount < basePrice * (1.0 - maxDiscount)) {
discount = -(int)(basePrice * maxDiscount);
}
} else if(maxDiscount > 1.0) {
//Change the discount to the larger MaxDiscount
//TODO: Allow for better fine-tuning
discount = (int)(discount * maxDiscount);
}
return discount;
}
/**
* @param recipe The recipe to get the MaxUses for
* @return The current maximum number of times a player can make a trade before the villager restocks
*/
private int getMaxUses(final RecipeWrapper recipe, final OfflinePlayer player) {
int uses = recipe.getMaxUses();
int maxUses = settings.fetchInt(recipe, "MaxUses", -1);
boolean disabled = settings.fetchBoolean(recipe, "Disabled", false);
//Disables the trade if the player has an active cooldown for the trade
final PlayerData playerData = instance.getPlayerData().get(player.getUniqueId());
if(playerData != null && playerData.getTradingVillager() != null) {
final ConfigurationSection overrides = instance.getCfg().getConfigurationSection("Overrides");
if(overrides != null) {
final String type = settings.getType(recipe.getItemStack("sell"), recipe.getItemStack("buy"), recipe.getItemStack("buyB"));
final String global = instance.getCfg().getString("Cooldown", "0");
final String local = overrides.getString(type+".Cooldown", global);
if(type != null && !local.equals("0")) {
if(playerData.getTradingCooldowns().containsKey(type)) {
final Date now = Date.from(Instant.now());
final Date lastTrade = Cooldown.parseTime(playerData.getTradingCooldowns().get(type));
long cooldown = Cooldown.parseCooldown(local);
if(lastTrade != null && (now.getTime()/1000L >= lastTrade.getTime()/1000L + cooldown)) {
playerData.getTradingCooldowns().remove(type);
} else {
maxUses = 0;
}
}
}
}
}
if(maxUses < 0) maxUses = uses;
if(disabled) maxUses = 0;
return maxUses;
}
/**
* @param playerWrapper The wrapped player to check the hotv effect for
* @return The Hero of the Village discount factor, adjusted by config
*/
private double getHotvDiscount(final PlayerWrapper playerWrapper) {
final Player player = playerWrapper.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.getCfg().getInt("MaxHeroLevel", -1);
if(maxHeroLevel == 0 || heroLevel == 0) return 0.0;
if(maxHeroLevel > 0 && heroLevel > maxHeroLevel) {
heroLevel = maxHeroLevel;
}
return 0.0625*(heroLevel-1) + 0.3;
}
/**
* @param item The config section that contains the settings for Item1, Item2, or Result items in the trade
* @param ingredient The respective ingredient to change, based on config.yml
*/
private void setIngredient(final ConfigurationSection item, final IngredientWrapper ingredient) {
if(item == null) return;
ingredient.setMaterialId("minecraft:"+item.getString("Material", ingredient.getMaterialId()).toLowerCase().replace("minecraft:",""));
ingredient.setAmount(item.getInt("Amount", ingredient.getAmount()));
}
}