PriceProvider changed to abstract & cache price
Needs refinement, but works kinda well Took 3 hours 46 minutes
This commit is contained in:
parent
6bba0b739a
commit
796aaae5a9
@ -21,6 +21,7 @@ import java.util.logging.Level;
|
|||||||
public final class PainfulLoss extends JavaPlugin {
|
public final class PainfulLoss extends JavaPlugin {
|
||||||
public static final Locale DEFAULT_LOCALE = Locale.ENGLISH;
|
public static final Locale DEFAULT_LOCALE = Locale.ENGLISH;
|
||||||
public static final String DEFAULT_LOCALE_TAG = "en-US";
|
public static final String DEFAULT_LOCALE_TAG = "en-US";
|
||||||
|
private static boolean debug = false;
|
||||||
@Getter private static PainfulLoss instance = null;
|
@Getter private static PainfulLoss instance = null;
|
||||||
@Getter private static PriceProvider priceProvider = null;
|
@Getter private static PriceProvider priceProvider = null;
|
||||||
|
|
||||||
@ -29,6 +30,7 @@ public final class PainfulLoss extends JavaPlugin {
|
|||||||
PainfulLoss.instance = this;
|
PainfulLoss.instance = this;
|
||||||
Message.init(DEFAULT_LOCALE);
|
Message.init(DEFAULT_LOCALE);
|
||||||
configurationInit();
|
configurationInit();
|
||||||
|
PainfulLoss.debug = PropertiesEnum.DEBUG.getBoolean();
|
||||||
langInit();
|
langInit();
|
||||||
registerCommands();
|
registerCommands();
|
||||||
initPriceProvider();
|
initPriceProvider();
|
||||||
@ -109,6 +111,7 @@ public final class PainfulLoss extends JavaPlugin {
|
|||||||
|
|
||||||
public void reload() {
|
public void reload() {
|
||||||
reloadConfig();
|
reloadConfig();
|
||||||
|
PainfulLoss.debug = PropertiesEnum.DEBUG.getBoolean();
|
||||||
Message.clearCache();
|
Message.clearCache();
|
||||||
langInit();
|
langInit();
|
||||||
initPriceProvider();
|
initPriceProvider();
|
||||||
@ -121,4 +124,8 @@ public final class PainfulLoss extends JavaPlugin {
|
|||||||
public static void info(String message) {
|
public static void info(String message) {
|
||||||
PainfulLoss.log(Level.INFO, message);
|
PainfulLoss.log(Level.INFO, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void debug(String message) {
|
||||||
|
if (PainfulLoss.debug) Bukkit.getLogger().log(Level.INFO, "[Painful Loss DEBUG] {0}", message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -76,8 +76,12 @@ public class PainfulLossCommand implements CommandExecutor, TabCompleter {
|
|||||||
|
|
||||||
private boolean commandWorth(CommandSender commandSender, String[] args) {
|
private boolean commandWorth(CommandSender commandSender, String[] args) {
|
||||||
if (args.length == 1 && commandSender instanceof Player p && commandSender.hasPermission("painfulloss.worth")) {
|
if (args.length == 1 && commandSender instanceof Player p && commandSender.hasPermission("painfulloss.worth")) {
|
||||||
|
double t1 = System.currentTimeMillis();
|
||||||
|
PainfulLoss.debug("Starting clock for worth...");
|
||||||
commandSender.sendMessage(Message.of("command.worth", PriceCalculator.estimate(p)
|
commandSender.sendMessage(Message.of("command.worth", PriceCalculator.estimate(p)
|
||||||
+ (PropertiesEnum.ENCHANT_ENABLED.getBoolean() ? PriceCalculator.enchantEstimate(p) : 0)));
|
+ (PropertiesEnum.ENCHANT_ENABLED.getBoolean() ? PriceCalculator.enchantEstimate(p) : 0)));
|
||||||
|
double t2 = System.currentTimeMillis();
|
||||||
|
PainfulLoss.debug("It took " + (t2-t1) + "ms to get the job done !");
|
||||||
} else if (args.length == 2 && commandSender.hasPermission("painfulloss.worth.other")) {
|
} else if (args.length == 2 && commandSender.hasPermission("painfulloss.worth.other")) {
|
||||||
Player p = PainfulLoss.getInstance().getServer().getPlayer(args[1]);
|
Player p = PainfulLoss.getInstance().getServer().getPlayer(args[1]);
|
||||||
if (p == null) {
|
if (p == null) {
|
||||||
@ -89,7 +93,7 @@ public class PainfulLossCommand implements CommandExecutor, TabCompleter {
|
|||||||
} else {
|
} else {
|
||||||
return commandNotAutorised(commandSender);
|
return commandNotAutorised(commandSender);
|
||||||
}
|
}
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -7,19 +7,19 @@ import javax.annotation.Nullable;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class CountPriceProvider implements PriceProvider{
|
public class CountPriceProvider extends PriceProvider{
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "Count";
|
return "Count";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Double> getPrice(ItemStack stack, @Nullable Player player) {
|
public Optional<Double> getProviderPrice(ItemStack stack, @Nullable Player player) {
|
||||||
return Optional.of((double) stack.getAmount());
|
return Optional.of((double) stack.getAmount());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Double> getPrices(List<ItemStack> stack, @org.jetbrains.annotations.Nullable Player player) {
|
public Optional<Double> getProviderPrices(List<ItemStack> stack, @org.jetbrains.annotations.Nullable Player player) {
|
||||||
return Optional.of(stack.stream().mapToDouble(ItemStack::getAmount).sum());
|
return Optional.of(stack.stream().mapToDouble(ItemStack::getAmount).sum());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import javax.annotation.Nullable;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class EssentialsPriceProvider implements PriceProvider {
|
public class EssentialsPriceProvider extends PriceProvider {
|
||||||
private final IEssentials plugin;
|
private final IEssentials plugin;
|
||||||
private final Worth worth;
|
private final Worth worth;
|
||||||
private boolean ready = false;
|
private boolean ready = false;
|
||||||
@ -25,13 +25,13 @@ public class EssentialsPriceProvider implements PriceProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Double> getPrice(ItemStack stack, @Nullable Player player) {
|
public Optional<Double> getProviderPrice(ItemStack stack, @Nullable Player player) {
|
||||||
if (stack == null || stack.getAmount() <= 0) return Optional.empty();
|
if (stack == null || stack.getAmount() <= 0) return Optional.empty();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
BigDecimal price = worth.getPrice(plugin, stack);
|
BigDecimal price = worth.getPrice(plugin, stack);
|
||||||
if (price == null) return Optional.empty();
|
if (price == null) return Optional.empty();
|
||||||
if (price.signum() < 0) return Optional.empty();
|
if (price.doubleValue() <= 0) return Optional.empty();
|
||||||
return Optional.of(price.doubleValue());
|
return Optional.of(price.doubleValue());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
|
|||||||
@ -1,20 +1,149 @@
|
|||||||
package fr.molzonas.painfulloss.provider;
|
package fr.molzonas.painfulloss.provider;
|
||||||
|
|
||||||
|
import fr.molzonas.painfulloss.PainfulLoss;
|
||||||
|
import fr.molzonas.painfulloss.utils.PropertiesEnum;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Material;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.*;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.List;
|
import java.math.BigDecimal;
|
||||||
import java.util.Optional;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
public interface PriceProvider {
|
public abstract class PriceProvider {
|
||||||
String getName();
|
private final Map<Material, BigDecimal> cachedPrices = new ConcurrentHashMap<>();
|
||||||
Optional<Double> getPrice(ItemStack stack, @Nullable Player player);
|
private final Set<Material> tested = java.util.Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||||
default Optional<Double> getPrices(List<ItemStack> stack, @Nullable Player player) {
|
|
||||||
|
public abstract String getName();
|
||||||
|
protected abstract Optional<Double> getProviderPrice(ItemStack stack, @Nullable Player player);
|
||||||
|
protected Optional<Double> getProviderPrices(List<ItemStack> stack, @Nullable Player player) {
|
||||||
|
if (stack.isEmpty()) return Optional.empty();
|
||||||
|
return Optional.of(stack.stream().mapToDouble(x -> this.getProviderPrice(x, player).orElse(0d)).sum());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Double> getPrice(ItemStack item, @Nullable Player player) {
|
||||||
|
if (item == null || item.getAmount() <= 0 || item.getType().isAir()) return Optional.empty();
|
||||||
|
ItemStack stack = item.clone();
|
||||||
|
stack.setAmount(1);
|
||||||
|
if (cachedPrices.containsKey(stack.getType())) {
|
||||||
|
BigDecimal price = cachedPrices.get(stack.getType());
|
||||||
|
return price.doubleValue() > 0d ? Optional.of(price.doubleValue() * item.getAmount()) : Optional.empty();
|
||||||
|
}
|
||||||
|
PainfulLoss.debug("Item not cached : " + stack.getType().name() + " ! Searching for values.");
|
||||||
|
Optional<Double> priceFromProvider = getProviderPrice(stack, player);
|
||||||
|
if (priceFromProvider.isPresent() && priceFromProvider.orElse(-1d) > 0) {
|
||||||
|
cachedPrices.put(stack.getType(), BigDecimal.valueOf(priceFromProvider.get()));
|
||||||
|
PainfulLoss.debug("Found " + stack.getType().name() + " price in the " + this.getName() + " provider for "+ priceFromProvider.get() + "$ per unit.");
|
||||||
|
return Optional.of(priceFromProvider.orElse(0d) * item.getAmount());
|
||||||
|
}
|
||||||
|
if (PropertiesEnum.RECIPE_COST_IF_NO_PRICE_FOUND.getBoolean()) {
|
||||||
|
// Anti-loop guard - if the material was already tested, return empty
|
||||||
|
if (!tested.add(stack.getType())) return Optional.empty();
|
||||||
|
PainfulLoss.debug("Time to search components of " + stack.getType().name() + " !");
|
||||||
|
try {
|
||||||
|
RecipePrice recipePrice = new RecipePrice(stack.clone(), player);
|
||||||
|
double priceFromIngredients = recipePrice.getLowestRecipePrice();
|
||||||
|
if (priceFromIngredients > 0) {
|
||||||
|
PainfulLoss.debug("And the components total price for " + stack.getType().name() + " is " + priceFromIngredients + "$ !");
|
||||||
|
cachedPrices.put(stack.getType(), BigDecimal.valueOf(priceFromIngredients));
|
||||||
|
return Optional.of(priceFromIngredients * item.getAmount());
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
PainfulLoss.debug("Let's get back from " + stack.getType().name() + " after this search.");
|
||||||
|
tested.remove(stack.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If nothing is found : the price is NULL
|
||||||
|
cachedPrices.put(stack.getType(), BigDecimal.valueOf(-1));
|
||||||
|
PainfulLoss.debug("No price found for " + item.getType().name() + ". Registering it to 0$ !");
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Double> getPrices(List<ItemStack> stack, @Nullable Player player) {
|
||||||
if (stack.isEmpty()) return Optional.empty();
|
if (stack.isEmpty()) return Optional.empty();
|
||||||
return Optional.of(stack.stream().mapToDouble(x -> this.getPrice(x, player).orElse(0d)).sum());
|
return Optional.of(stack.stream().mapToDouble(x -> this.getPrice(x, player).orElse(0d)).sum());
|
||||||
}
|
}
|
||||||
default boolean isReady() { return true; }
|
public boolean isReady() {
|
||||||
default boolean isCountBased() { return false; }
|
return true;
|
||||||
default void reload() { }
|
}
|
||||||
|
public boolean isCountBased() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public void reload() {
|
||||||
|
this.cachedPrices.clear();
|
||||||
|
}
|
||||||
|
class RecipePrice {
|
||||||
|
final ItemStack item;
|
||||||
|
final Player player;
|
||||||
|
final int finalAmount;
|
||||||
|
|
||||||
|
RecipePrice(ItemStack item, @Nullable Player player) {
|
||||||
|
this.item = new ItemStack(item.getType(), 1);
|
||||||
|
this.player = player;
|
||||||
|
this.finalAmount = item.getAmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
double getLowestRecipePrice() {
|
||||||
|
double lowest = 0d;
|
||||||
|
for (Recipe r : Bukkit.getRecipesFor(item)) {
|
||||||
|
double total = switch (r) {
|
||||||
|
case ShapedRecipe sr -> getShapedRecipePrice(sr);
|
||||||
|
case ShapelessRecipe sr -> getShapelessRecipePrice(sr);
|
||||||
|
case CookingRecipe<?> sr -> getRecipeChoicePrice(sr.getInputChoice());
|
||||||
|
default -> 0d;
|
||||||
|
};
|
||||||
|
if (total > 0d) {
|
||||||
|
double unit = total / Math.max(1, r.getResult().getAmount());
|
||||||
|
if (lowest == 0 || unit < lowest) lowest = unit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PainfulLoss.debug("And the lowest price for the recipes was " + lowest + "$.");
|
||||||
|
return lowest;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getShapedRecipePrice(ShapedRecipe sr) {
|
||||||
|
double sum = 0d;
|
||||||
|
Map<Character, RecipeChoice> map = sr.getChoiceMap();
|
||||||
|
Map<RecipeChoice, Integer> nbOfUnit = new HashMap<>();
|
||||||
|
|
||||||
|
for (String s : sr.getShape()) {
|
||||||
|
for (Character c : s.toCharArray()) {
|
||||||
|
RecipeChoice rc = map.getOrDefault(c, null);
|
||||||
|
if (c == ' ' || rc == null) continue;
|
||||||
|
nbOfUnit.put(rc, nbOfUnit.getOrDefault(rc, 0) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<RecipeChoice, Integer> entry : nbOfUnit.entrySet()) {
|
||||||
|
double price = getRecipeChoicePrice(entry.getKey()) * entry.getValue();
|
||||||
|
sum += price;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getShapelessRecipePrice(ShapelessRecipe sr) {
|
||||||
|
return sr.getChoiceList().stream().mapToDouble(this::getRecipeChoicePrice).sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
double getRecipeChoicePrice(RecipeChoice choice) {
|
||||||
|
if (choice == null) return 0d;
|
||||||
|
double lowest = 0d;
|
||||||
|
List<ItemStack> stacks = new ArrayList<>();
|
||||||
|
if (choice instanceof RecipeChoice.MaterialChoice c) {
|
||||||
|
stacks = c.getChoices().stream().map(x -> new ItemStack(x, 1)).toList();
|
||||||
|
}
|
||||||
|
if (choice instanceof RecipeChoice.ExactChoice c) {
|
||||||
|
stacks = c.getChoices();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ItemStack stack : stacks) {
|
||||||
|
double price = getPrice(stack, player).orElse(0d);
|
||||||
|
if (price > 0 && (lowest == 0 || lowest > price)) lowest = price;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lowest;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import java.util.Map;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
public class UltimateShopPriceProvider implements PriceProvider {
|
public class UltimateShopPriceProvider extends PriceProvider {
|
||||||
private final Reflect reflect;
|
private final Reflect reflect;
|
||||||
private String economyProvider;
|
private String economyProvider;
|
||||||
private boolean ready = true;
|
private boolean ready = true;
|
||||||
@ -39,9 +39,10 @@ public class UltimateShopPriceProvider implements PriceProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Double> getPrice(ItemStack stack, @Nullable Player player) {
|
public Optional<Double> getProviderPrice(ItemStack stack, @Nullable Player player) {
|
||||||
try {
|
try {
|
||||||
return Optional.of(reflect.priceFor(stack, player, economyProvider));
|
double price = reflect.priceFor(stack, player, economyProvider);
|
||||||
|
return price > 0 ? Optional.of(price) : Optional.empty();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,8 @@ public enum PropertiesEnum {
|
|||||||
PRICE_PROVIDER("priceProvider"),
|
PRICE_PROVIDER("priceProvider"),
|
||||||
ENCHANT_ENABLED("estimation.enchant.enabled"),
|
ENCHANT_ENABLED("estimation.enchant.enabled"),
|
||||||
ENCHANT_DEFAULT_PER_LEVEL("estimation.enchant.default_per_level"),
|
ENCHANT_DEFAULT_PER_LEVEL("estimation.enchant.default_per_level"),
|
||||||
ECONOMY_PROVIDER("economyProvider", "Vault")
|
ECONOMY_PROVIDER("economyProvider", "Vault"),
|
||||||
|
RECIPE_COST_IF_NO_PRICE_FOUND("recipeCostIfNoPriceFound", "false")
|
||||||
;
|
;
|
||||||
|
|
||||||
@Getter private final String path;
|
@Getter private final String path;
|
||||||
|
|||||||
@ -11,6 +11,10 @@ priceProvider:
|
|||||||
# Used to do estimations with UltimateShop, can only be Vault... for now.
|
# Used to do estimations with UltimateShop, can only be Vault... for now.
|
||||||
economyProvider: Vault
|
economyProvider: Vault
|
||||||
|
|
||||||
|
# If the product price isn't found, use the components of this product to get the price
|
||||||
|
# Disable this if you already defined every item or if the plugin is laggy
|
||||||
|
recipeCostIfNoPriceFound: true
|
||||||
|
|
||||||
estimation:
|
estimation:
|
||||||
# Is enchants on items used in worth calculation ?
|
# Is enchants on items used in worth calculation ?
|
||||||
enchant:
|
enchant:
|
||||||
|
|||||||
@ -2,7 +2,7 @@ name: PainfulLoss
|
|||||||
version: '1.0'
|
version: '1.0'
|
||||||
main: fr.molzonas.painfulloss.PainfulLoss
|
main: fr.molzonas.painfulloss.PainfulLoss
|
||||||
api-version: '1.20'
|
api-version: '1.20'
|
||||||
prefix: PainfulLoss
|
prefix: Painful Loss
|
||||||
softdepend: [Essentials, UltimateShop]
|
softdepend: [Essentials, UltimateShop]
|
||||||
author: Molzonas
|
author: Molzonas
|
||||||
commands:
|
commands:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user