diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/block/BlockInfo.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/block/BlockInfo.java index 20292a86..204808c4 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/block/BlockInfo.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/block/BlockInfo.java @@ -84,6 +84,7 @@ public class BlockInfo { return table != null; } + @Deprecated public List collectDrops(LootBuilder builder) { return table != null ? table.collect(builder) : new ArrayList<>(); } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/LootBuilder.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/LootBuilder.java index c23136ce..af0f24ff 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/LootBuilder.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/LootBuilder.java @@ -1,54 +1,59 @@ package net.Indyuce.mmocore.loot; +import net.Indyuce.mmocore.api.player.PlayerData; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + import java.util.ArrayList; import java.util.List; -import net.Indyuce.mmocore.api.player.PlayerData; -import org.bukkit.inventory.ItemStack; - public class LootBuilder { - private final PlayerData player; - private final List loot = new ArrayList<>(); + private final PlayerData player; + private final List loot = new ArrayList<>(); - private double capacity; + private double capacity; - /** - * Used to create loot from a drop table - * - * @param player - * Player looting - * @param capacity - * Capacity is the maximum amount of item weight generated using - * this table. If capacity is set to 10, this table cannot drop - * an item with 5 weight and another with 6 weight at the saeme - * time. - */ - public LootBuilder(PlayerData player, double capacity) { - this.player = player; - this.capacity = capacity; - } + public static double DEFAULT_CAPACITY = 100; - public PlayerData getEntity() { - return player; - } + public LootBuilder(PlayerData player) { + this(player, DEFAULT_CAPACITY); + } - public List getLoot() { - return loot; - } + /** + * Used to create loot from a drop table + * + * @param player Player looting + * @param capacity Capacity is the maximum amount of item weight generated using + * this table. If capacity is set to 10, this table cannot drop + * an item with 5 weight and another with 6 weight at the saeme + * time. + */ + public LootBuilder(@NotNull PlayerData player, double capacity) { + this.player = player; + this.capacity = capacity; + } - public double getCapacity() { - return capacity; - } + public PlayerData getEntity() { + return player; + } - public void addLoot(ItemStack item) { - loot.add(item); - } + public List getLoot() { + return loot; + } - public void addLoot(List items) { - loot.addAll(items); - } + public double getCapacity() { + return capacity; + } - public void reduceCapacity(double value) { - this.capacity = Math.max(0, capacity - value); - } + public void addLoot(ItemStack item) { + loot.add(item); + } + + public void addLoot(List items) { + loot.addAll(items); + } + + public void reduceCapacity(double value) { + this.capacity = Math.max(0, capacity - value); + } } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/RandomWeightedRoll.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/RandomWeightedRoll.java index 32e611c2..77efd79a 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/RandomWeightedRoll.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/RandomWeightedRoll.java @@ -1,6 +1,7 @@ package net.Indyuce.mmocore.loot; import net.Indyuce.mmocore.api.player.PlayerData; +import net.Indyuce.mmocore.loot.droptable.dropitem.DropItem; import org.jetbrains.annotations.NotNull; import java.util.Collection; @@ -17,23 +18,24 @@ public class RandomWeightedRoll { private final T rolled; private static final Random RANDOM = new Random(); - private static final double CHANCE_COEFFICIENT = 7. / 100; public RandomWeightedRoll(PlayerData player, Collection collection, double chanceWeight) { this.collection = collection; double partialSum = 0; - final double randomCoefficient = RANDOM.nextDouble(), chance = chanceWeight * player.getStats().getStat("CHANCE"), sum = weightedSum(chance); + final double randomCoefficient = RANDOM.nextDouble(), + effectiveLuck = DropItem.CHANCE_FACTOR * chanceWeight * player.getStats().getStat("CHANCE"), + sum = weightedSum(effectiveLuck); for (T item : collection) { - partialSum += computeRealWeight(item, chance); + partialSum += computeRealWeight(item, effectiveLuck); if (partialSum >= randomCoefficient * sum) { rolled = item; return; } } - throw new RuntimeException("Could not roll item, the chance is :"+chance); + throw new RuntimeException("Could not roll item, effective luck is " + effectiveLuck); } /** @@ -47,10 +49,10 @@ public class RandomWeightedRoll { return rolled; } - private double weightedSum(double chance) { + private double weightedSum(double effectiveLuck) { double sum = 0; for (T item : collection) - sum += computeRealWeight(item, chance); + sum += computeRealWeight(item, effectiveLuck); return sum; } @@ -62,8 +64,8 @@ public class RandomWeightedRoll { * * @return The real weight of an item considering the player's chance stat. */ - private double computeRealWeight(T item, double chance) { - return Math.pow(item.getWeight(), 1 / Math.pow(1 + CHANCE_COEFFICIENT * chance, 1. / 3.)); + private double computeRealWeight(T item, double effectiveLuck) { + return Math.pow(item.getWeight(), Math.pow(1 + effectiveLuck, -DropItem.CHANCE_POWER)); } /* diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/chest/ChestTier.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/chest/ChestTier.java index 0e82e44d..b7cc3981 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/chest/ChestTier.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/chest/ChestTier.java @@ -1,47 +1,55 @@ package net.Indyuce.mmocore.loot.chest; import io.lumine.mythic.lib.api.math.ScalingFormula; +import io.lumine.mythic.lib.util.annotation.BackwardsCompatibility; import net.Indyuce.mmocore.MMOCore; import net.Indyuce.mmocore.api.player.PlayerData; import net.Indyuce.mmocore.loot.Weighted; import net.Indyuce.mmocore.loot.droptable.DropTable; import org.bukkit.configuration.ConfigurationSection; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public class ChestTier implements Weighted { - private final TierEffect effect; - private final ScalingFormula capacity; - private final DropTable table; - private final double chance; + private final TierEffect effect; + // TODO Capacity should be inherent to drop table + // TODO make capacity any numeric formula, parsed with a player + @BackwardsCompatibility(version = "1.12.1") + @Nullable + private final ScalingFormula capacity; + private final DropTable table; + private final double chance; - public ChestTier(ConfigurationSection config) { - effect = config.isConfigurationSection("effect") ? new TierEffect(config.getConfigurationSection("effect")) : null; - capacity = new ScalingFormula(config.get("capacity")); - chance = config.getDouble("chance"); - table = MMOCore.plugin.dropTableManager.loadDropTable(config.get("drops")); - } + public ChestTier(ConfigurationSection config) { + effect = config.isConfigurationSection("effect") ? new TierEffect(config.getConfigurationSection("effect")) : null; + capacity = config.contains("capacity") ? new ScalingFormula(config.get("capacity")) : null; + chance = config.getDouble("chance"); + table = MMOCore.plugin.dropTableManager.loadDropTable(config.get("drops")); + } - public double rollCapacity(PlayerData player) { - return capacity.calculate(player.getLevel()); - } + public double rollCapacity(@NotNull PlayerData player) { + return capacity == null ? table.getCapacity() : capacity.calculate(player.getLevel()); + } - public double getChance() { - return chance; - } + public double getChance() { + return chance; + } - @Override - public double getWeight() { - return chance; - } + @Override + public double getWeight() { + return chance; + } - public DropTable getDropTable() { - return table; - } + @NotNull + public DropTable getDropTable() { + return table; + } - public boolean hasEffect() { - return effect != null; - } + public boolean hasEffect() { + return effect != null; + } - public TierEffect getEffect() { - return effect; - } + public TierEffect getEffect() { + return effect; + } } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/droptable/DropTable.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/droptable/DropTable.java index 71f800e7..956b5bf5 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/droptable/DropTable.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/droptable/DropTable.java @@ -13,15 +13,17 @@ import org.bukkit.configuration.ConfigurationSection; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; -import java.util.LinkedHashSet; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import java.util.Set; import java.util.logging.Level; public class DropTable implements PreloadedObject { private final String id; - private final Set drops = new LinkedHashSet<>(); - private final Set conditions = new LinkedHashSet<>(); + private final double capacity; + private final boolean shuffle; + private final List drops = new ArrayList<>(); + private final List conditions = new ArrayList<>(); private final PostLoadAction postLoadAction; @@ -30,11 +32,16 @@ public class DropTable implements PreloadedObject { this.postLoadAction.cacheConfig(config); this.id = config.getName(); + this.shuffle = config.getBoolean("shuffle"); + this.capacity = config.getDouble("capacity", LootBuilder.DEFAULT_CAPACITY); + Validate.isTrue(capacity >= 0, "Capacity must be positive"); } public DropTable(String id) { this.postLoadAction = generatePostLoadAction(); this.id = id; + this.capacity = 100; + this.shuffle = false; } private PostLoadAction generatePostLoadAction() { @@ -76,13 +83,27 @@ public class DropTable implements PreloadedObject { drops.add(item); } - public Set getDrops() { + public double getCapacity() { + return capacity; + } + + @NotNull + public List getDrops() { return drops; } + @NotNull public List collect(LootBuilder builder) { - for (DropItem item : drops) + // Shuffle items? + final List items; + if (shuffle) { + items = new ArrayList<>(drops); + Collections.shuffle(items); + } else items = drops; + + // Collect items + for (DropItem item : items) if (item.rollChance(builder.getEntity()) && builder.getCapacity() >= item.getWeight()) { item.collect(builder); builder.reduceCapacity(item.getWeight()); @@ -91,7 +112,8 @@ public class DropTable implements PreloadedObject { return builder.getLoot(); } - public Set getConditions() { + @NotNull + public List getConditions() { return conditions; } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/droptable/dropitem/DropItem.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/droptable/dropitem/DropItem.java index 8559101c..13ac352a 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/droptable/dropitem/DropItem.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/droptable/dropitem/DropItem.java @@ -14,8 +14,6 @@ public abstract class DropItem { private final double chance, weight; private final RandomAmount amount; - private static final double CHANCE_COEFFICIENT = 7. / 100; - public DropItem(MMOLineConfig config) { chance = config.args().length > 0 ? Double.parseDouble(config.args()[0]) : 1; amount = config.args().length > 1 ? new RandomAmount(config.args()[1]) : new RandomAmount(1, 1); @@ -38,16 +36,14 @@ public abstract class DropItem { return amount.calculateInt(); } - /** - * CHANCE stat = 0 | tier chances are unchanged - * CHANCE stat = +inf | uniform law for any drop item - * CHANCE stat = 100 | all tier chances are taken their square root - * - * @return The real weight of an item considering the player's CHANCE stat. - */ + /// TODO make it configurable + @Deprecated + public static final double CHANCE_FACTOR = 7. / 100, CHANCE_POWER = 0.33333333333; + public boolean rollChance(PlayerData player) { - double value = random.nextDouble(); - return value < Math.pow(chance, 1 / Math.pow(1 + CHANCE_COEFFICIENT * MMOCore.plugin.configManager.dropItemsChanceWeight* player.getStats().getStat("CHANCE"), 1.0 / 3.0)); + final double effectiveLuck = CHANCE_FACTOR * MMOCore.plugin.configManager.dropItemsChanceWeight * player.getStats().getStat("CHANCE"); + final double randomValue = random.nextDouble(); + return randomValue < Math.pow(chance, Math.pow(1 + effectiveLuck, CHANCE_POWER)); } public abstract void collect(LootBuilder builder); diff --git a/MMOCore-Dist/src/main/java/net/Indyuce/mmocore/listener/BlockListener.java b/MMOCore-Dist/src/main/java/net/Indyuce/mmocore/listener/BlockListener.java index 9e509ffb..7ab70709 100644 --- a/MMOCore-Dist/src/main/java/net/Indyuce/mmocore/listener/BlockListener.java +++ b/MMOCore-Dist/src/main/java/net/Indyuce/mmocore/listener/BlockListener.java @@ -87,7 +87,7 @@ public class BlockListener implements Listener { // Find the block drops boolean conditionsMet = !info.hasDropTable() || info.getDropTable().areConditionsMet(new ConditionInstance(player)); - List drops = conditionsMet && info.hasDropTable() ? info.getDropTable().collect(new LootBuilder(PlayerData.get(player), 0)) : new ArrayList<>(); + List drops = conditionsMet && info.hasDropTable() ? info.getDropTable().collect(new LootBuilder(PlayerData.get(player), info.getDropTable().getCapacity())) : new ArrayList<>(); /* * Calls the event and listen for cancel & for drops changes... also diff --git a/MMOCore-Dist/src/main/java/net/Indyuce/mmocore/listener/profession/FishingListener.java b/MMOCore-Dist/src/main/java/net/Indyuce/mmocore/listener/profession/FishingListener.java index 8d14dd2a..cf3daa81 100644 --- a/MMOCore-Dist/src/main/java/net/Indyuce/mmocore/listener/profession/FishingListener.java +++ b/MMOCore-Dist/src/main/java/net/Indyuce/mmocore/listener/profession/FishingListener.java @@ -170,7 +170,7 @@ public class FishingListener implements Listener { } // Find looted item - ItemStack collect = caught.collect(new LootBuilder(playerData, 0)); + ItemStack collect = caught.collect(new LootBuilder(playerData)); if (collect == null) { hook.getWorld().spawnParticle(VParticle.SMOKE.get(), location, 24, 0, 0, 0, .08); return;