diff --git a/lib/MMOLib.jar b/lib/MMOLib.jar index ce864bd5..6a94bb01 100644 Binary files a/lib/MMOLib.jar and b/lib/MMOLib.jar differ diff --git a/src/main/java/net/Indyuce/mmocore/MMOCore.java b/src/main/java/net/Indyuce/mmocore/MMOCore.java index c45c45db..eae67ebc 100644 --- a/src/main/java/net/Indyuce/mmocore/MMOCore.java +++ b/src/main/java/net/Indyuce/mmocore/MMOCore.java @@ -2,6 +2,7 @@ package net.Indyuce.mmocore; import java.io.File; import java.lang.reflect.Field; +import java.util.HashSet; import java.util.logging.Level; import org.bukkit.Bukkit; @@ -13,6 +14,7 @@ import org.bukkit.scheduler.BukkitRunnable; import net.Indyuce.mmocore.api.ConfigFile; import net.Indyuce.mmocore.api.PlayerActionBar; +import net.Indyuce.mmocore.api.loot.LootChest; import net.Indyuce.mmocore.api.player.PlayerData; import net.Indyuce.mmocore.api.player.profess.resource.PlayerResource; import net.Indyuce.mmocore.api.player.social.guilds.Guild; @@ -72,7 +74,7 @@ import net.Indyuce.mmocore.manager.DropTableManager; import net.Indyuce.mmocore.manager.EntityManager; import net.Indyuce.mmocore.manager.ExperienceManager; import net.Indyuce.mmocore.manager.InventoryManager; -import net.Indyuce.mmocore.manager.LootableChestManager; +import net.Indyuce.mmocore.manager.LootChestManager; import net.Indyuce.mmocore.manager.MMOLoadManager; import net.Indyuce.mmocore.manager.QuestManager; import net.Indyuce.mmocore.manager.RestrictionManager; @@ -101,7 +103,6 @@ public class MMOCore extends JavaPlugin { public ConfigManager configManager; public WaypointManager waypointManager; public RestrictionManager restrictionManager; - public LootableChestManager chestManager; public RequestManager requestManager; public ConfigItemManager configItems; public SkillManager skillManager; @@ -122,6 +123,7 @@ public class MMOCore extends JavaPlugin { public final ProfessionManager professionManager = new ProfessionManager(); public final EntityManager entities = new EntityManager(); public final ExperienceManager experience = new ExperienceManager(); + public final LootChestManager lootChests = new LootChestManager(); /* * professions @@ -235,6 +237,20 @@ public class MMOCore extends JavaPlugin { } }.runTaskTimer(MMOCore.plugin, 100, 20); + /* + * clean active loot chests every 5 minutes. cannot register this + * runnable in the loot chest manager because it is instanced when the + * plugin loads + */ + new BukkitRunnable() { + public void run() { + for (LootChest chest : new HashSet<>(lootChests.getActive())) + if (chest.shouldExpire()) + chest.unregister(false); + + } + }.runTaskTimer(this, 5 * 60 * 20, 5 * 60 * 20); + /* * For the sake of the lord, make sure they aren't using MMOItems Mana * and Stamina Addon...This should prevent a couple error reports @@ -360,6 +376,8 @@ public class MMOCore extends JavaPlugin { dataProvider.getGuildManager().save(guild); mineManager.resetRemainingBlocks(); + + lootChests.getActive().forEach(chest -> chest.unregister(false)); } public void reloadPlugin() { @@ -396,7 +414,8 @@ public class MMOCore extends JavaPlugin { questManager.clear(); questManager.reload(); - chestManager = new LootableChestManager(new ConfigFile("chests").getConfig()); + lootChests.reload(); + waypointManager = new WaypointManager(new ConfigFile("waypoints").getConfig()); restrictionManager = new RestrictionManager(new ConfigFile("restrictions").getConfig()); requestManager = new RequestManager(); diff --git a/src/main/java/net/Indyuce/mmocore/api/block/BlockInfo.java b/src/main/java/net/Indyuce/mmocore/api/block/BlockInfo.java index 16c64292..ae94a12a 100644 --- a/src/main/java/net/Indyuce/mmocore/api/block/BlockInfo.java +++ b/src/main/java/net/Indyuce/mmocore/api/block/BlockInfo.java @@ -13,6 +13,7 @@ import org.bukkit.inventory.ItemStack; import net.Indyuce.mmocore.MMOCore; import net.Indyuce.mmocore.api.droptable.DropTable; import net.Indyuce.mmocore.api.load.MMOLoadException; +import net.Indyuce.mmocore.api.loot.LootBuilder; import net.Indyuce.mmocore.api.quest.trigger.ExperienceTrigger; import net.Indyuce.mmocore.api.quest.trigger.Trigger; import net.mmogroup.mmolib.api.MMOLineConfig; @@ -76,8 +77,8 @@ public class BlockInfo { return table; } - public List collectDrops() { - return hasDropTable() ? table.collect() : new ArrayList<>(); + public List collectDrops(LootBuilder builder) { + return hasDropTable() ? table.collect(builder) : new ArrayList<>(); } public boolean hasDropTable() { diff --git a/src/main/java/net/Indyuce/mmocore/api/droptable/DropTable.java b/src/main/java/net/Indyuce/mmocore/api/droptable/DropTable.java index 36329a7a..f54719d5 100644 --- a/src/main/java/net/Indyuce/mmocore/api/droptable/DropTable.java +++ b/src/main/java/net/Indyuce/mmocore/api/droptable/DropTable.java @@ -1,7 +1,6 @@ package net.Indyuce.mmocore.api.droptable; -import java.util.ArrayList; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -12,11 +11,12 @@ import org.bukkit.inventory.ItemStack; import net.Indyuce.mmocore.MMOCore; import net.Indyuce.mmocore.api.droptable.dropitem.DropItem; import net.Indyuce.mmocore.api.load.MMOLoadException; +import net.Indyuce.mmocore.api.loot.LootBuilder; import net.mmogroup.mmolib.api.MMOLineConfig; public class DropTable { private final String id; - private final Set drops = new HashSet<>(); + private final Set drops = new LinkedHashSet<>(); /* * cached in order to load other items. @@ -52,13 +52,14 @@ public class DropTable { return id; } - public List collect() { - List total = new ArrayList<>(); + public List collect(LootBuilder builder) { for (DropItem item : drops) - if (item.rollChance()) - item.collect(total); + if (item.rollChance() && builder.getCapacity() >= item.getWeight()) { + item.collect(builder); + builder.reduceCapacity(item.getWeight()); + } - return total; + return builder.getLoot(); } } \ No newline at end of file diff --git a/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/DropItem.java b/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/DropItem.java index 94ceae76..2edda920 100644 --- a/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/DropItem.java +++ b/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/DropItem.java @@ -1,22 +1,21 @@ package net.Indyuce.mmocore.api.droptable.dropitem; -import java.util.List; import java.util.Random; -import org.bukkit.inventory.ItemStack; - +import net.Indyuce.mmocore.api.loot.LootBuilder; import net.Indyuce.mmocore.api.util.math.formula.RandomAmount; import net.mmogroup.mmolib.api.MMOLineConfig; public abstract class DropItem { protected static final Random random = new Random(); - private double chance; - private RandomAmount amount; + private final double chance, weight; + private final RandomAmount amount; 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, 0); + weight = config.args().length > 2 ? Double.parseDouble(config.args()[2]) : 0; } public RandomAmount getAmount() { @@ -27,6 +26,10 @@ public abstract class DropItem { return chance; } + public double getWeight() { + return weight; + } + public int rollAmount() { return amount.calculateInt(); } @@ -35,5 +38,5 @@ public abstract class DropItem { return random.nextDouble() < chance; } - public abstract void collect(List total); + public abstract void collect(LootBuilder builder); } diff --git a/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/DropTableDropItem.java b/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/DropTableDropItem.java index 975c1caa..d664ba0d 100644 --- a/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/DropTableDropItem.java +++ b/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/DropTableDropItem.java @@ -1,16 +1,14 @@ package net.Indyuce.mmocore.api.droptable.dropitem; -import java.util.List; - import org.apache.commons.lang.Validate; -import org.bukkit.inventory.ItemStack; import net.Indyuce.mmocore.MMOCore; import net.Indyuce.mmocore.api.droptable.DropTable; +import net.Indyuce.mmocore.api.loot.LootBuilder; import net.mmogroup.mmolib.api.MMOLineConfig; public class DropTableDropItem extends DropItem { - private DropTable dropTable; + private final DropTable dropTable; public DropTableDropItem(MMOLineConfig config) { super(config); @@ -23,8 +21,8 @@ public class DropTableDropItem extends DropItem { } @Override - public void collect(List total) { + public void collect(LootBuilder builder) { for (int j = 0; j < rollAmount(); j++) - total.addAll(dropTable.collect()); + builder.addLoot(dropTable.collect(builder)); } } diff --git a/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/GoldDropItem.java b/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/GoldDropItem.java index 3e621a34..db70e947 100644 --- a/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/GoldDropItem.java +++ b/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/GoldDropItem.java @@ -1,9 +1,6 @@ package net.Indyuce.mmocore.api.droptable.dropitem; -import java.util.List; - -import org.bukkit.inventory.ItemStack; - +import net.Indyuce.mmocore.api.loot.LootBuilder; import net.Indyuce.mmocore.api.util.item.CurrencyItem; import net.mmogroup.mmolib.api.MMOLineConfig; @@ -13,7 +10,7 @@ public class GoldDropItem extends DropItem { } @Override - public void collect(List total) { - total.add(new CurrencyItem("GOLD_COIN", 1, rollAmount()).build()); + public void collect(LootBuilder builder) { + builder.addLoot(new CurrencyItem("GOLD_COIN", 1, rollAmount()).build()); } } diff --git a/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/MMDropTableDropItem.java b/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/MMDropTableDropItem.java index d001626e..08c95270 100644 --- a/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/MMDropTableDropItem.java +++ b/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/MMDropTableDropItem.java @@ -1,10 +1,6 @@ package net.Indyuce.mmocore.api.droptable.dropitem; -import java.util.List; import java.util.NoSuchElementException; -import java.util.logging.Level; - -import org.bukkit.inventory.ItemStack; import io.lumine.xikage.mythicmobs.MythicMobs; import io.lumine.xikage.mythicmobs.adapters.bukkit.BukkitAdapter; @@ -13,12 +9,13 @@ import io.lumine.xikage.mythicmobs.drops.DropMetadata; import io.lumine.xikage.mythicmobs.drops.DropTable; import io.lumine.xikage.mythicmobs.drops.IItemDrop; import io.lumine.xikage.mythicmobs.drops.LootBag; -import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.loot.LootBuilder; import net.mmogroup.mmolib.api.MMOLineConfig; public class MMDropTableDropItem extends DropItem { - private DropTable dropTable; - private DropMetadata metadata = new DropMetadata(null, null); + private final DropTable dropTable; + + private static final DropMetadata metadata = new DropMetadata(null, null); public MMDropTableDropItem(MMOLineConfig config) { super(config); @@ -28,19 +25,16 @@ public class MMDropTableDropItem extends DropItem { try { dropTable = MythicMobs.inst().getDropManager().getDropTable(id).get(); - } catch(NoSuchElementException e) { - MMOCore.log(Level.WARNING, "Could not find MM drop table" + id); + } catch (NoSuchElementException exception) { + throw new IllegalArgumentException("Could not find MM drop table with ID '" + id + "'"); } } @Override - public void collect(List total) { + public void collect(LootBuilder builder) { LootBag lootBag = dropTable.generate(metadata); - - for(Drop type : lootBag.getDrops()) { - if(type instanceof IItemDrop) { - total.add(BukkitAdapter.adapt(((IItemDrop)type).getDrop(metadata))); - } - } + for (Drop type : lootBag.getDrops()) + if (type instanceof IItemDrop) + builder.addLoot(BukkitAdapter.adapt(((IItemDrop) type).getDrop(metadata))); } } diff --git a/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/NoteDropItem.java b/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/NoteDropItem.java index 77a69e9d..c37f5025 100644 --- a/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/NoteDropItem.java +++ b/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/NoteDropItem.java @@ -1,14 +1,11 @@ package net.Indyuce.mmocore.api.droptable.dropitem; -import java.util.List; - -import org.bukkit.inventory.ItemStack; - +import net.Indyuce.mmocore.api.loot.LootBuilder; import net.Indyuce.mmocore.api.util.item.CurrencyItem; import net.mmogroup.mmolib.api.MMOLineConfig; public class NoteDropItem extends DropItem { - private int min, max; + private final int min, max; public NoteDropItem(MMOLineConfig config) { super(config); @@ -20,7 +17,7 @@ public class NoteDropItem extends DropItem { } @Override - public void collect(List total) { - total.add(new CurrencyItem("NOTE", random.nextInt(max - min + 1) + min, rollAmount()).build()); + public void collect(LootBuilder builder) { + builder.addLoot(new CurrencyItem("NOTE", random.nextInt(max - min + 1) + min, rollAmount()).build()); } } diff --git a/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/VanillaDropItem.java b/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/VanillaDropItem.java index 4ebd1c81..cecf92d1 100644 --- a/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/VanillaDropItem.java +++ b/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/VanillaDropItem.java @@ -1,10 +1,9 @@ package net.Indyuce.mmocore.api.droptable.dropitem; -import java.util.List; - import org.bukkit.Material; import org.bukkit.inventory.ItemStack; +import net.Indyuce.mmocore.api.loot.LootBuilder; import net.mmogroup.mmolib.api.MMOLineConfig; public class VanillaDropItem extends DropItem { @@ -16,13 +15,13 @@ public class VanillaDropItem extends DropItem { config.validate("type"); this.material = Material.valueOf(config.getString("type")); } - + public Material getMaterial() { return material; } @Override - public void collect(List total) { - total.add(new ItemStack(material, rollAmount())); + public void collect(LootBuilder builder) { + builder.addLoot(new ItemStack(material, rollAmount())); } } diff --git a/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/fishing/FishingDropItem.java b/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/fishing/FishingDropItem.java index 1ac7359f..55adfad2 100644 --- a/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/fishing/FishingDropItem.java +++ b/src/main/java/net/Indyuce/mmocore/api/droptable/dropitem/fishing/FishingDropItem.java @@ -1,12 +1,10 @@ package net.Indyuce.mmocore.api.droptable.dropitem.fishing; -import java.util.ArrayList; -import java.util.List; - import org.bukkit.inventory.ItemStack; import net.Indyuce.mmocore.MMOCore; import net.Indyuce.mmocore.api.droptable.dropitem.DropItem; +import net.Indyuce.mmocore.api.loot.LootBuilder; import net.Indyuce.mmocore.api.util.math.formula.RandomAmount; import net.mmogroup.mmolib.api.MMOLineConfig; @@ -58,8 +56,8 @@ public class FishingDropItem { } public ItemStack collect() { - List collect = new ArrayList<>(); - dropItem.collect(collect); - return collect.stream().findAny().get(); + LootBuilder builder = new LootBuilder(null, 0); + dropItem.collect(builder); + return builder.getLoot().stream().findAny().get(); } } diff --git a/src/main/java/net/Indyuce/mmocore/api/event/CustomBlockMineEvent.java b/src/main/java/net/Indyuce/mmocore/api/event/CustomBlockMineEvent.java index 5ee1cf5f..a72228d6 100644 --- a/src/main/java/net/Indyuce/mmocore/api/event/CustomBlockMineEvent.java +++ b/src/main/java/net/Indyuce/mmocore/api/event/CustomBlockMineEvent.java @@ -9,6 +9,7 @@ import org.bukkit.inventory.ItemStack; import net.Indyuce.mmocore.api.block.BlockInfo; import net.Indyuce.mmocore.api.experience.ExperienceInfo; +import net.Indyuce.mmocore.api.loot.LootBuilder; import net.Indyuce.mmocore.api.player.PlayerData; public class CustomBlockMineEvent extends PlayerDataEvent implements Cancellable { @@ -24,7 +25,7 @@ public class CustomBlockMineEvent extends PlayerDataEvent implements Cancellable super(player); this.block = block; - this.drops = info.collectDrops(); + this.drops = info.collectDrops(new LootBuilder(player, 0)); this.experience = info.hasExperience() ? info.getExperience().newInfo() : null; } diff --git a/src/main/java/net/Indyuce/mmocore/api/event/LootChestSpawnEvent.java b/src/main/java/net/Indyuce/mmocore/api/event/LootChestSpawnEvent.java new file mode 100644 index 00000000..df5e75c2 --- /dev/null +++ b/src/main/java/net/Indyuce/mmocore/api/event/LootChestSpawnEvent.java @@ -0,0 +1,51 @@ +package net.Indyuce.mmocore.api.event; + +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +import net.Indyuce.mmocore.api.loot.LootBuilder; +import net.Indyuce.mmocore.api.loot.LootChest; +import net.Indyuce.mmocore.api.player.PlayerData; + +public class LootChestSpawnEvent extends PlayerDataEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + private final LootChest chest; + private final LootBuilder loot; + + private boolean cancelled; + + public LootChestSpawnEvent(PlayerData playerData, LootChest chest, LootBuilder loot) { + super(playerData); + + this.chest = chest; + this.loot = loot; + } + + public LootChest getChest() { + return chest; + } + + public LootBuilder getLoot() { + return loot; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/net/Indyuce/mmocore/api/loot/ChestAlgorithmOptions.java b/src/main/java/net/Indyuce/mmocore/api/loot/ChestAlgorithmOptions.java new file mode 100644 index 00000000..bd305605 --- /dev/null +++ b/src/main/java/net/Indyuce/mmocore/api/loot/ChestAlgorithmOptions.java @@ -0,0 +1,50 @@ +package net.Indyuce.mmocore.api.loot; + +import org.apache.commons.lang.Validate; +import org.bukkit.configuration.ConfigurationSection; + +public class ChestAlgorithmOptions { + + /* + * min and max range represents the range at which the chest can spawn + * around the player. height is the Z delta in which the chest can spawn, + * relative to the player's altitude + */ + public final double minRange, maxRange, height; + + /* + * maximum amount of trials the algorithm will run in order to find a + * suitable location for a chest around the player. + */ + public final int iterations; + + public static final ChestAlgorithmOptions DEFAULT = new ChestAlgorithmOptions(10, 30, 8, 15); + + /* + * this is purely to let server owners tweak the chest random location + * finder algorithm. + */ + public ChestAlgorithmOptions(ConfigurationSection config) { + Validate.notNull(config, "Config cannot be nulm"); + + minRange = config.getDouble("min-range", DEFAULT.minRange); + maxRange = config.getDouble("max-range", DEFAULT.maxRange); + height = config.getDouble("height", DEFAULT.height); + iterations = config.getInt("iterations", DEFAULT.iterations); + + Validate.isTrue(minRange < maxRange, "Max range must be greater than min range"); + Validate.isTrue(height > 0, "Height must be strictly positive"); + Validate.isTrue(iterations > 0, "Iterations must be strictly positive"); + } + + /* + * can be used to register loot chest regions with external plugins, and + * used by the default alg options instance + */ + public ChestAlgorithmOptions(double minRange, double maxRange, double height, int iterations) { + this.minRange = minRange; + this.maxRange = maxRange; + this.height = height; + this.iterations = iterations; + } +} diff --git a/src/main/java/net/Indyuce/mmocore/api/loot/ChestTier.java b/src/main/java/net/Indyuce/mmocore/api/loot/ChestTier.java index 364be184..b547177a 100644 --- a/src/main/java/net/Indyuce/mmocore/api/loot/ChestTier.java +++ b/src/main/java/net/Indyuce/mmocore/api/loot/ChestTier.java @@ -1,24 +1,32 @@ package net.Indyuce.mmocore.api.loot; -import org.apache.commons.lang.Validate; -import org.bukkit.Location; -import org.bukkit.Particle; import org.bukkit.configuration.ConfigurationSection; -import net.Indyuce.mmocore.api.util.math.particle.ChestParticleEffect; +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.droptable.DropTable; +import net.Indyuce.mmocore.api.player.PlayerData; +import net.mmogroup.mmolib.api.math.ScalingFormula; public class ChestTier { private final TierEffect effect; - private final int weight; + private final ScalingFormula capacity; + private final DropTable table; + + public final double chance; public ChestTier(ConfigurationSection config) { - effect = config.isConfigurationSection("effect") ? new TierEffect(config.getConfigurationSection("effect")) - : null; - weight = config.getInt("weight", 1); + 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 int getWeight() { - return weight; + + public double rollCapacity(PlayerData player) { + return capacity.calculate(player.getLevel()); + } + + public DropTable getDropTable() { + return table; } public boolean hasEffect() { @@ -28,21 +36,4 @@ public class ChestTier { public TierEffect getEffect() { return effect; } - - public class TierEffect { - private final ChestParticleEffect type; - private final Particle particle; - - public TierEffect(ConfigurationSection config) { - Validate.notNull(config, "Could not load tier config"); - type = ChestParticleEffect - .valueOf(config.getString("type", "OFFSET").toUpperCase().replace("-", "_").replace(" ", "_")); - particle = Particle - .valueOf(config.getString("particle", "FLAME").toUpperCase().replace("-", "_").replace(" ", "_")); - } - - public void play(Location loc) { - type.play(loc, particle); - } - } } diff --git a/src/main/java/net/Indyuce/mmocore/api/loot/LootBuilder.java b/src/main/java/net/Indyuce/mmocore/api/loot/LootBuilder.java new file mode 100644 index 00000000..873573f7 --- /dev/null +++ b/src/main/java/net/Indyuce/mmocore/api/loot/LootBuilder.java @@ -0,0 +1,48 @@ +package net.Indyuce.mmocore.api.loot; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.inventory.ItemStack; + +import net.Indyuce.mmocore.api.player.PlayerData; + +public class LootBuilder { + private final PlayerData player; + private final List loot = new ArrayList<>(); + + private double capacity; + + /* + * instance which saves what entity is currently rolling a loot table and + * how much item capacity the table has left + */ + public LootBuilder(PlayerData player, double capacity) { + this.player = player; + this.capacity = capacity; + } + + public PlayerData getEntity() { + return player; + } + + public List getLoot() { + return loot; + } + + public double getCapacity() { + return capacity; + } + + 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/src/main/java/net/Indyuce/mmocore/api/loot/LootChest.java b/src/main/java/net/Indyuce/mmocore/api/loot/LootChest.java index 41ba5119..7d4aff55 100644 --- a/src/main/java/net/Indyuce/mmocore/api/loot/LootChest.java +++ b/src/main/java/net/Indyuce/mmocore/api/loot/LootChest.java @@ -2,20 +2,37 @@ package net.Indyuce.mmocore.api.loot; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; import org.bukkit.block.Block; +import org.bukkit.block.Chest; import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +import net.Indyuce.mmocore.MMOCore; public class LootChest { + private final ChestTier tier; private final LootChestRegion region; + private final ReplacedBlock block; + private final BukkitRunnable effectRunnable; + private final long date = System.currentTimeMillis(); /* - * saves data of block replaced + * instance generated when a loot chest is placed (as a bukkit block), and + * used to save the data of the block which has been replaced (can replace + * non-solid blocks) */ - private final ReplacedBlock block; - - public LootChest(LootChestRegion region, Block block) { + public LootChest(ChestTier tier, LootChestRegion region, Block block) { + this.tier = tier; this.region = region; this.block = new ReplacedBlock(block); + this.effectRunnable = tier.hasEffect() ? tier.getEffect().startNewRunnable(block.getLocation().add(.5, .5, .5)) : null; + } + + public ChestTier getTier() { + return tier; } public ReplacedBlock getBlock() { @@ -26,6 +43,40 @@ public class LootChest { return region; } + public boolean hasPlayerNearby() { + for (Player player : block.loc.getWorld().getPlayers()) + if (player.getLocation().distanceSquared(block.loc) < 625) + return true; + return false; + } + + public boolean shouldExpire() { + return System.currentTimeMillis() - date > MMOCore.plugin.configManager.lootChestExpireTime; + } + + public void unregister(boolean player) { + + /* + * if a player is responsible of closing the chest, close it with sound + */ + if (player) { + block.loc.getWorld().playSound(block.loc, Sound.ITEM_ARMOR_EQUIP_LEATHER, 1, 1); + block.loc.getWorld().spawnParticle(Particle.CRIT, block.loc.clone().add(.5, .5, .5), 16, 0, 0, 0, .5); + MMOCore.plugin.lootChests.unregister(this); + } + + /* + * must clean block inventory before replacing block otherwise loots fly + * off and accumulate on the ground (+during dev phase) + */ + else + ((Chest) block.loc.getBlock().getState()).getBlockInventory().clear(); + + block.restore(); + if (effectRunnable != null) + effectRunnable.cancel(); + } + public class ReplacedBlock { private final Material material; private final BlockData data; @@ -34,7 +85,16 @@ public class LootChest { public ReplacedBlock(Block block) { this.material = block.getType(); this.data = block.getBlockData(); - loc = block.getLocation(); + this.loc = block.getLocation(); + } + + public Location getLocoation() { + return loc; + } + + public boolean matches(Location loc) { + return this.loc.getWorld().equals(loc.getWorld()) && this.loc.getBlockX() == loc.getBlockX() && this.loc.getBlockY() == loc.getBlockY() + && this.loc.getBlockZ() == loc.getBlockZ(); } public void restore() { diff --git a/src/main/java/net/Indyuce/mmocore/api/loot/LootChestRegion.java b/src/main/java/net/Indyuce/mmocore/api/loot/LootChestRegion.java index 5d078160..586cffe3 100644 --- a/src/main/java/net/Indyuce/mmocore/api/loot/LootChestRegion.java +++ b/src/main/java/net/Indyuce/mmocore/api/loot/LootChestRegion.java @@ -1,31 +1,44 @@ package net.Indyuce.mmocore.api.loot; -import java.util.HashSet; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Random; import java.util.Set; import java.util.logging.Level; import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Chest; import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.Vector; import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.event.LootChestSpawnEvent; +import net.Indyuce.mmocore.api.player.PlayerData; public class LootChestRegion { private final String id; private final long chestSpawnPeriod; private final RegionBounds bounds; - private final Set tiers = new HashSet<>(); - - /* - * last time - */ + private final ChestAlgorithmOptions algOptions; + private final Set tiers = new LinkedHashSet<>(); + private final BukkitRunnable runnable; + + private static final Random random = new Random(); public LootChestRegion(ConfigurationSection config) { Validate.notNull(config, "Could not load config"); - id = config.getName(); + id = config.getName().toLowerCase().replace("_", "-").replace(" ", "-"); bounds = new RegionBounds(config.getConfigurationSection("bounds")); - chestSpawnPeriod = config.getInt("spawn-period"); + chestSpawnPeriod = config.getLong("spawn-period", 5 * 60); + algOptions = config.contains("algorithm-options") ? new ChestAlgorithmOptions(config.getConfigurationSection("algorithm-options")) + : ChestAlgorithmOptions.DEFAULT; Validate.isTrue(config.isConfigurationSection("tiers"), "Could not find chest tiers"); for (String key : config.getConfigurationSection("tiers").getKeys(false)) @@ -35,6 +48,10 @@ public class LootChestRegion { MMOCore.plugin.getLogger().log(Level.WARNING, "Could not load tier '" + key + "' from chest region '" + id + "': " + exception.getMessage()); } + + Validate.isTrue(!tiers.isEmpty(), "Your region must have at least one chest tier"); + + runnable = new LootChestRunnable(this); } public String getId() { @@ -52,4 +69,101 @@ public class LootChestRegion { public long getChestSpawnPeriod() { return chestSpawnPeriod; } + + public BukkitRunnable getRunnable() { + return runnable; + } + + public void spawnChest(PlayerData player) { + + // first randomly determine the chest tier + ChestTier tier = rollTier(); + + // find a random location, 20 trials max + Location location = getRandomLocation(player.getPlayer().getLocation()); + if (location == null) + return; + + LootChest lootChest = new LootChest(tier, this, location.getBlock()); + LootBuilder builder = new LootBuilder(player, tier.rollCapacity(player)); + tier.getDropTable().collect(builder); + + LootChestSpawnEvent event = new LootChestSpawnEvent(player, lootChest, builder); + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) + return; + + List slots = new ArrayList<>(); + for (int j = 0; j < 27; j++) + slots.add(j); + + location.getBlock().setType(Material.CHEST); + Chest chest = (Chest) location.getBlock().getState(); + tier.getDropTable().collect(builder).forEach(item -> { + Integer slot = slots.get(random.nextInt(slots.size())); + chest.getInventory().setItem(slot, item); + slots.remove(slot); + }); + + MMOCore.plugin.lootChests.register(lootChest); + } + + // TODO improve + public ChestTier rollTier() { + + double s = 0; + for (ChestTier tier : tiers) { + if (random.nextDouble() < tier.chance / (1 - s)) + return tier; + s += tier.chance; + + } + + return tiers.stream().findAny().get(); + } + + public Location getRandomLocation(Location center) { + + for (int j = 0; j < algOptions.iterations; j++) { + Location random = tryRandomDirection(center); + if (random != null) + return random; + } + + /* + * no location has been found after the X iterations, return null and + * cancel chest spawning. worst case scenario, should not happen too + * often except if the player is in a really NARROW zone + */ + return null; + } + + public Location tryRandomDirection(Location center) { + + /* + * chooses a random direction and get the block in that direction which + * has the same height as the player + */ + double a = random.nextDouble() * 2 * Math.PI; + Vector dir = new Vector(Math.cos(a), 0, Math.sin(a)) + .multiply(algOptions.minRange + random.nextDouble() * (algOptions.maxRange - algOptions.minRange)); + Location random = center.add(dir); + + /* + * go up and down at the same time till it finds a non-solid block with + * a solid block underneath + */ + for (int h = 0; h <= algOptions.height * 2; h++) { + int z = h % 2 == 0 ? h / 2 : -(h + 1) / 2; // bijective from N to Z + Location checked = random.clone().add(0, z, 0); + if (isSuitable(checked)) + return checked; + } + + return null; + } + + private boolean isSuitable(Location loc) { + return !loc.getBlock().getType().isSolid() && loc.clone().add(0, -1, 0).getBlock().getType().isSolid(); + } } diff --git a/src/main/java/net/Indyuce/mmocore/api/loot/LootChestRunnable.java b/src/main/java/net/Indyuce/mmocore/api/loot/LootChestRunnable.java new file mode 100644 index 00000000..7e341a08 --- /dev/null +++ b/src/main/java/net/Indyuce/mmocore/api/loot/LootChestRunnable.java @@ -0,0 +1,28 @@ +package net.Indyuce.mmocore.api.loot; + +import java.util.Optional; + +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.PlayerData; + +public class LootChestRunnable extends BukkitRunnable { + private final LootChestRegion region; + + public LootChestRunnable(LootChestRegion region) { + this.region = region; + + runTaskTimer(MMOCore.plugin, region.getChestSpawnPeriod() * 20, region.getChestSpawnPeriod() * 20); + } + + // TODO add option so that players cannot have more than X chests every X + // time + @Override + public void run() { + Optional found = region.getBounds().getPlayers().findAny(); + if (found.isPresent()) + region.spawnChest(PlayerData.get(found.get())); + } +} diff --git a/src/main/java/net/Indyuce/mmocore/api/loot/RegionBounds.java b/src/main/java/net/Indyuce/mmocore/api/loot/RegionBounds.java index a59871de..c162aa37 100644 --- a/src/main/java/net/Indyuce/mmocore/api/loot/RegionBounds.java +++ b/src/main/java/net/Indyuce/mmocore/api/loot/RegionBounds.java @@ -1,7 +1,6 @@ package net.Indyuce.mmocore.api.loot; -import java.util.Optional; -import java.util.Random; +import java.util.stream.Stream; import org.apache.commons.lang.Validate; import org.bukkit.Bukkit; @@ -14,27 +13,28 @@ public class RegionBounds { private final World world; private final int x1, z1, x2, z2; - private static final Random random = new Random(); - public RegionBounds(ConfigurationSection config) { Validate.notNull(config, "Could not load config"); - Validate.notNull(world = Bukkit.getWorld(config.getString("world")), - "Could not find world " + config.getString("world")); - x1 = config.getInt("x1"); - z1 = config.getInt("z1"); + Validate.notNull(world = Bukkit.getWorld(config.getString("world")), "Could not find world " + config.getString("world")); + x1 = Math.min(config.getInt("x1"), config.getInt("x2")); + x2 = Math.max(config.getInt("x1"), config.getInt("x2")); - x2 = config.getInt("x2"); - z2 = config.getInt("z2"); + z1 = Math.min(config.getInt("z1"), config.getInt("z2")); + z2 = Math.max(config.getInt("z1"), config.getInt("z2")); } public RegionBounds(Location loc1, Location loc2) { Validate.isTrue(loc1.getWorld().equals(loc2.getWorld()), "Locations must be in the same world"); world = loc1.getWorld(); - x1 = loc1.getBlockX(); - z1 = loc1.getBlockZ(); + x1 = Math.min(loc1.getBlockX(), loc2.getBlockX()); + x2 = Math.max(loc1.getBlockX(), loc2.getBlockX()); - x2 = loc2.getBlockX(); - z2 = loc2.getBlockZ(); + z1 = Math.min(loc1.getBlockZ(), loc2.getBlockZ()); + z2 = Math.max(loc1.getBlockZ(), loc2.getBlockZ()); + } + + public Stream getPlayers() { + return world.getPlayers().stream().filter(player -> isInRegion(player)); } public boolean isInRegion(Player player) { @@ -42,12 +42,4 @@ public class RegionBounds { int z = player.getLocation().getBlockZ(); return player.getWorld().equals(world) && x1 <= x && x2 >= x && z1 <= z && z2 >= z; } - - public Location findChestLocation() { - - Optional player = world.getPlayers().stream().filter(check -> isInRegion(check)).findAny(); - - // TODO - return null; - } } diff --git a/src/main/java/net/Indyuce/mmocore/api/loot/TierEffect.java b/src/main/java/net/Indyuce/mmocore/api/loot/TierEffect.java new file mode 100644 index 00000000..03cbdd68 --- /dev/null +++ b/src/main/java/net/Indyuce/mmocore/api/loot/TierEffect.java @@ -0,0 +1,37 @@ +package net.Indyuce.mmocore.api.loot; + +import org.apache.commons.lang.Validate; +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.scheduler.BukkitRunnable; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.util.math.particle.ChestParticleEffect; + +public class TierEffect { + private final ChestParticleEffect type; + private final Particle particle; + private final int period; + + public TierEffect(ConfigurationSection config) { + Validate.notNull(config, "Could not load tier config"); + type = ChestParticleEffect.valueOf(config.getString("type", "OFFSET").toUpperCase().replace("-", "_").replace(" ", "_")); + particle = Particle.valueOf(config.getString("particle", "FLAME").toUpperCase().replace("-", "_").replace(" ", "_")); + period = Math.max(20, config.getInt("period", 5 * 20)); + } + + public void play(Location loc) { + type.play(loc, particle); + } + + public BukkitRunnable startNewRunnable(Location loc) { + BukkitRunnable runnable = new BukkitRunnable() { + public void run() { + type.play(loc, particle); + } + }; + runnable.runTaskTimer(MMOCore.plugin, 0, period); + return runnable; + } +} \ No newline at end of file diff --git a/src/main/java/net/Indyuce/mmocore/api/player/social/Party.java b/src/main/java/net/Indyuce/mmocore/api/player/social/Party.java index d5c7379d..6e99c540 100644 --- a/src/main/java/net/Indyuce/mmocore/api/player/social/Party.java +++ b/src/main/java/net/Indyuce/mmocore/api/player/social/Party.java @@ -20,19 +20,23 @@ public class Party { private final PartyMembers members = new PartyMembers(); private final Map invites = new HashMap<>(); + // used to check if two parties are the same + private final UUID id = UUID.randomUUID(); + /* * owner changes when the old owner leaves party */ private PlayerData owner; - // used to check if two parties are the same - // private UUID uuid = UUID.randomUUID(); - public Party(PlayerData owner) { this.owner = owner; addMember(owner); } + public UUID getUniqueId() { + return id; + } + public PlayerData getOwner() { return owner; } @@ -50,7 +54,8 @@ public class Party { } public void removeMember(PlayerData data) { - if (data.isOnline() && data.getPlayer().getOpenInventory() != null && data.getPlayer().getOpenInventory().getTopInventory().getHolder() instanceof PartyViewInventory) + if (data.isOnline() && data.getPlayer().getOpenInventory() != null + && data.getPlayer().getOpenInventory().getTopInventory().getHolder() instanceof PartyViewInventory) InventoryManager.PARTY_CREATION.newInventory(data).open(); members.remove(data); @@ -83,20 +88,22 @@ public class Party { public void reopenInventories() { for (PlayerData member : members.members) - if (member.isOnline() && member.getPlayer().getOpenInventory() != null && member.getPlayer().getOpenInventory().getTopInventory().getHolder() instanceof PartyViewInventory) + if (member.isOnline() && member.getPlayer().getOpenInventory() != null + && member.getPlayer().getOpenInventory().getTopInventory().getHolder() instanceof PartyViewInventory) ((PluginInventory) member.getPlayer().getOpenInventory().getTopInventory().getHolder()).open(); } public void sendPartyInvite(PlayerData inviter, PlayerData target) { invites.put(target.getUniqueId(), System.currentTimeMillis()); Request request = new PartyInvite(this, inviter, target); - new ConfigMessage("party-invite").addPlaceholders("player", inviter.getPlayer().getName(), "uuid", request.getUniqueId().toString()).sendAsJSon(target.getPlayer()); + new ConfigMessage("party-invite").addPlaceholders("player", inviter.getPlayer().getName(), "uuid", request.getUniqueId().toString()) + .sendAsJSon(target.getPlayer()); MMOCore.plugin.requestManager.registerRequest(request); } @Override public boolean equals(Object obj) { - return obj instanceof Party && ((Party) obj).owner.getUniqueId().equals(owner.getUniqueId()); + return obj instanceof Party && ((Party) obj).getUniqueId().equals(getUniqueId()); } /* @@ -136,7 +143,8 @@ public class Party { } private void applyAttributes(PlayerData player) { - MMOCore.plugin.partyManager.getBonuses().forEach(stat -> player.getStats().getInstance(stat).addModifier("mmocoreParty", MMOCore.plugin.partyManager.getBonus(stat).multiply(members.size() - 1))); + MMOCore.plugin.partyManager.getBonuses().forEach(stat -> player.getStats().getInstance(stat).addModifier("mmocoreParty", + MMOCore.plugin.partyManager.getBonus(stat).multiply(members.size() - 1))); } private void clearAttributes(PlayerData player) { diff --git a/src/main/java/net/Indyuce/mmocore/api/player/stats/PlayerStats.java b/src/main/java/net/Indyuce/mmocore/api/player/stats/PlayerStats.java index ed8416cb..18b2e1f2 100644 --- a/src/main/java/net/Indyuce/mmocore/api/player/stats/PlayerStats.java +++ b/src/main/java/net/Indyuce/mmocore/api/player/stats/PlayerStats.java @@ -38,11 +38,11 @@ public class PlayerStats { * applies relative attributes on the base stat too */ public double getStat(StatType stat) { - return getInstance(stat).getTotal(getBase(stat)); + return getInstance(stat).getTotal(); } public double getBase(StatType stat) { - return data.getProfess().calculateStat(stat, stat.hasProfession() ? data.getCollectionSkills().getLevel(stat.getProfession()) : data.getLevel()); + return getInstance(stat).getBase(); } /* diff --git a/src/main/java/net/Indyuce/mmocore/api/player/stats/StatType.java b/src/main/java/net/Indyuce/mmocore/api/player/stats/StatType.java index 449ff797..535b4820 100644 --- a/src/main/java/net/Indyuce/mmocore/api/player/stats/StatType.java +++ b/src/main/java/net/Indyuce/mmocore/api/player/stats/StatType.java @@ -51,6 +51,12 @@ public enum StatType { PROJECTILE_DAMAGE, WEAPON_DAMAGE, SKILL_DAMAGE, + + DAMAGE_REDUCTION, + PHYSICAL_DAMAGE_REDUCTION, + PROJECTILE_DAMAGE_REDUCTION, + WEAPON_DAMAGE_REDUCTION, + SKILL_DAMAGE_REDUCTION, // reduces amount of tugs needed to fish FISHING_STRENGTH("fishing"), diff --git a/src/main/java/net/Indyuce/mmocore/api/skill/result/LocationSkillResult.java b/src/main/java/net/Indyuce/mmocore/api/skill/result/LocationSkillResult.java index 34d6c31a..4a6bf89b 100644 --- a/src/main/java/net/Indyuce/mmocore/api/skill/result/LocationSkillResult.java +++ b/src/main/java/net/Indyuce/mmocore/api/skill/result/LocationSkillResult.java @@ -20,7 +20,7 @@ public class LocationSkillResult extends SkillResult { if (isSuccessful()) { - RayTraceResult result = data.getPlayer().getWorld().rayTrace(data.getPlayer().getEyeLocation(), data.getPlayer().getEyeLocation().getDirection(), range, FluidCollisionMode.ALWAYS, true, 1.0D, entity -> MMOCoreUtils.canTarget(data.getPlayer(), entity)); + RayTraceResult result = data.getPlayer().getWorld().rayTrace(data.getPlayer().getEyeLocation(), data.getPlayer().getEyeLocation().getDirection(), range, FluidCollisionMode.ALWAYS, true, 1.0D, entity -> MMOCoreUtils.canTarget(data, entity)); if (result == null) abort(CancelReason.OTHER); else diff --git a/src/main/java/net/Indyuce/mmocore/api/skill/result/TargetSkillResult.java b/src/main/java/net/Indyuce/mmocore/api/skill/result/TargetSkillResult.java index 1d3f5cfe..e920d6ca 100644 --- a/src/main/java/net/Indyuce/mmocore/api/skill/result/TargetSkillResult.java +++ b/src/main/java/net/Indyuce/mmocore/api/skill/result/TargetSkillResult.java @@ -16,7 +16,7 @@ public class TargetSkillResult extends SkillResult { super(data, skill); if (isSuccessful()) { - MMORayTraceResult result = MMOLib.plugin.getVersion().getWrapper().rayTrace(data.getPlayer(), range, entity -> MMOCoreUtils.canTarget(data.getPlayer(), entity)); + MMORayTraceResult result = MMOLib.plugin.getVersion().getWrapper().rayTrace(data.getPlayer(), range, entity -> MMOCoreUtils.canTarget(data, entity)); if (!result.hasHit()) abort(CancelReason.OTHER); else diff --git a/src/main/java/net/Indyuce/mmocore/api/util/math/formula/RandomAmount.java b/src/main/java/net/Indyuce/mmocore/api/util/math/formula/RandomAmount.java index 32d2a714..87975926 100644 --- a/src/main/java/net/Indyuce/mmocore/api/util/math/formula/RandomAmount.java +++ b/src/main/java/net/Indyuce/mmocore/api/util/math/formula/RandomAmount.java @@ -3,7 +3,7 @@ package net.Indyuce.mmocore.api.util.math.formula; import java.util.Random; public class RandomAmount { - private double min, max; + private final double min, max; private static final Random random = new Random(); @@ -15,8 +15,7 @@ public class RandomAmount { public RandomAmount(String value) { String[] split = value.split("\\-"); min = Double.parseDouble(split[0]); - if (split.length > 1) - max = Double.parseDouble(split[1]); + max = split.length > 1 ? Double.parseDouble(split[1]) : 0; } public double getMax() { diff --git a/src/main/java/net/Indyuce/mmocore/listener/LootableChestsListener.java b/src/main/java/net/Indyuce/mmocore/listener/LootableChestsListener.java index 147bdeb8..bde1ae20 100644 --- a/src/main/java/net/Indyuce/mmocore/listener/LootableChestsListener.java +++ b/src/main/java/net/Indyuce/mmocore/listener/LootableChestsListener.java @@ -6,7 +6,7 @@ import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryCloseEvent; import net.Indyuce.mmocore.MMOCore; -import net.Indyuce.mmocore.manager.LootableChestManager.LootableChest; +import net.Indyuce.mmocore.api.loot.LootChest; public class LootableChestsListener implements Listener { @EventHandler @@ -15,8 +15,8 @@ public class LootableChestsListener implements Listener { return; Chest chest = (Chest) event.getInventory().getHolder(); - LootableChest lootable = MMOCore.plugin.chestManager.getLootableChest(chest.getLocation()); - if (lootable != null) - lootable.whenClosed(true); + LootChest lootChest = MMOCore.plugin.lootChests.getChest(chest.getLocation()); + if (lootChest != null) + lootChest.unregister(true); } } diff --git a/src/main/java/net/Indyuce/mmocore/manager/ConfigManager.java b/src/main/java/net/Indyuce/mmocore/manager/ConfigManager.java index 3e64039f..488c58ac 100644 --- a/src/main/java/net/Indyuce/mmocore/manager/ConfigManager.java +++ b/src/main/java/net/Indyuce/mmocore/manager/ConfigManager.java @@ -27,7 +27,7 @@ public class ConfigManager { public double expPartyBuff, regenPartyBuff; public String partyChatPrefix; public ChatColor manaFull, manaHalf, manaEmpty, staminaFull, staminaHalf, staminaEmpty; - public int combatLogTimer; + public int combatLogTimer, lootChestExpireTime; public final DecimalFormatSymbols formatSymbols = new DecimalFormatSymbols(); public final DecimalFormat decimal = new DecimalFormat("0.#", formatSymbols), decimals = new DecimalFormat("0.##", formatSymbols); @@ -86,7 +86,7 @@ public class ConfigManager { loadDefaultFile("stats.yml"); loadDefaultFile("waypoints.yml"); loadDefaultFile("restrictions.yml"); - loadDefaultFile("chests.yml"); + // loadDefaultFile("chests.yml"); loadDefaultFile("commands.yml"); loadDefaultFile("guilds.yml"); @@ -100,6 +100,7 @@ public class ConfigManager { partyChatPrefix = MMOCore.plugin.getConfig().getString("party.chat-prefix"); formatSymbols.setDecimalSeparator(getFirstChar(MMOCore.plugin.getConfig().getString("number-format.decimal-separator"), ',')); combatLogTimer = MMOCore.plugin.getConfig().getInt("combat-log.timer"); + lootChestExpireTime = Math.max(MMOCore.plugin.getConfig().getInt("loot-chest-expire-time"), 1) * 1000; manaFull = getColorOrDefault("mana-whole", ChatColor.BLUE); manaHalf = getColorOrDefault("mana-half", ChatColor.AQUA); diff --git a/src/main/java/net/Indyuce/mmocore/manager/DropTableManager.java b/src/main/java/net/Indyuce/mmocore/manager/DropTableManager.java index ca2b4a81..5a703a33 100644 --- a/src/main/java/net/Indyuce/mmocore/manager/DropTableManager.java +++ b/src/main/java/net/Indyuce/mmocore/manager/DropTableManager.java @@ -15,7 +15,7 @@ import net.Indyuce.mmocore.MMOCore; import net.Indyuce.mmocore.api.droptable.DropTable; public class DropTableManager extends MMOManager { - private Map map = new HashMap<>(); + private final Map map = new HashMap<>(); public void register(DropTable table) { map.put(table.getId(), table); @@ -47,11 +47,8 @@ public class DropTableManager extends MMOManager { if (obj instanceof String) return get((String) obj); - if (obj instanceof ConfigurationSection) { - DropTable table = new DropTable((ConfigurationSection) obj); - table.load(); - return table; - } + if (obj instanceof ConfigurationSection) + return new DropTable((ConfigurationSection) obj).load(); throw new IllegalArgumentException("Could not parse drop table."); } diff --git a/src/main/java/net/Indyuce/mmocore/manager/LootChestManager.java b/src/main/java/net/Indyuce/mmocore/manager/LootChestManager.java new file mode 100644 index 00000000..787a8947 --- /dev/null +++ b/src/main/java/net/Indyuce/mmocore/manager/LootChestManager.java @@ -0,0 +1,75 @@ +package net.Indyuce.mmocore.manager; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; + +import org.bukkit.Location; +import org.bukkit.configuration.file.FileConfiguration; + +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.ConfigFile; +import net.Indyuce.mmocore.api.loot.LootChest; +import net.Indyuce.mmocore.api.loot.LootChestRegion; + +public class LootChestManager { + + /* + * all active loot chests in the server + */ + private final Set active = new HashSet<>(); + + private final Map regions = new HashMap<>(); + + public boolean hasRegion(String id) { + return regions.containsKey(id); + } + + public LootChestRegion getRegion(String id) { + return regions.get(id); + } + + public Collection getRegions() { + return regions.values(); + } + + public Set getActive() { + return active; + } + + public void register(LootChest chest) { + active.add(chest); + } + + public void unregister(LootChest chest) { + active.remove(chest); + } + + public LootChest getChest(Location loc) { + + for (LootChest chest : active) + if (chest.getBlock().matches(loc)) + return chest; + + return null; + } + + public void reload() { + regions.values().forEach(region -> region.getRunnable().cancel()); + regions.clear(); + + FileConfiguration config = new ConfigFile("loot-chests").getConfig(); + for (String key : config.getKeys(false)) + try { + LootChestRegion region = new LootChestRegion(config.getConfigurationSection(key)); + regions.put(region.getId(), region); + } catch (IllegalArgumentException exception) { + MMOCore.plugin.getLogger().log(Level.WARNING, + "An error occured while trying to load loot chest region '" + key + "': " + exception.getMessage()); + } + + } +} diff --git a/src/main/java/net/Indyuce/mmocore/manager/LootableChestManager.java b/src/main/java/net/Indyuce/mmocore/manager/LootableChestManager.java deleted file mode 100644 index 15134998..00000000 --- a/src/main/java/net/Indyuce/mmocore/manager/LootableChestManager.java +++ /dev/null @@ -1,169 +0,0 @@ -package net.Indyuce.mmocore.manager; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Random; -import java.util.Set; -import java.util.logging.Level; - -import org.apache.commons.lang.Validate; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.World; -import org.bukkit.block.Chest; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; - -import net.Indyuce.mmocore.MMOCore; -import net.Indyuce.mmocore.api.droptable.DropTable; -import net.Indyuce.mmocore.api.util.math.particle.ChestParticleEffect; - -public class LootableChestManager { - private Set map = new HashSet<>(); - - private static BukkitRunnable runnable; - private static final Random random = new Random(); - - public LootableChestManager(FileConfiguration config) { - for (String key : config.getKeys(false)) - try { - register(new LootableChest(config.getConfigurationSection(key))); - } catch (IllegalArgumentException | IndexOutOfBoundsException exception) { - MMOCore.plugin.getLogger().log(Level.WARNING, "Could not register loot chest '" + key + "': " + exception.getMessage()); - } - - if (runnable != null) - runnable.cancel(); - - (runnable = new BukkitRunnable() { - public void run() { - map.forEach(chest -> { - if (chest.hasEffect() && chest.isSpawned() && chest.hasPlayerNearby()) - chest.playEffect(); - }); - } - }).runTaskTimerAsynchronously(MMOCore.plugin, 100, 4 * 20); - } - - public void register(LootableChest chest) { - map.add(chest); - chest.whenClosed(false); - } - - public LootableChest getLootableChest(Location loc) { - for (LootableChest chest : map) - if (blockCheck(chest.getLocation(), loc)) - return chest; - return null; - } - - private boolean blockCheck(Location loc1, Location loc2) { - return loc1.getWorld().equals(loc2.getWorld()) && loc1.getBlockX() == loc2.getBlockX() && loc1.getBlockY() == loc2.getBlockY() && loc1.getBlockZ() == loc2.getBlockZ(); - } - - public class LootableChest { - private final Location loc; - private final DropTable table; - private final int regenTime; - private final Particle effectParticle; - private final ChestParticleEffect effect; - - private long lastDisappear; - - public LootableChest(ConfigurationSection config) { - loc = readLocation(config.getName()); - regenTime = config.getInt("regen-time"); - table = MMOCore.plugin.dropTableManager.loadDropTable(config.get("drop-table")); - - if (config.contains("effect")) { - String format = config.getString("effect.particle"); - Validate.notNull(format, "Particle is missing particle"); - effectParticle = Particle.valueOf(format.toUpperCase().replace("-", "_")); - - format = config.getString("effect.type"); - Validate.notNull(format, "Particle is missing effect type"); - effect = ChestParticleEffect.valueOf(format.toUpperCase().replace("-", "_")); - - } else { - effectParticle = null; - effect = null; - } - } - - public boolean hasEffect() { - return effectParticle != null && effect != null; - } - - public boolean isSpawned() { - return System.currentTimeMillis() > lastDisappear + 50 * regenTime; - } - - public Location getLocation() { - return loc; - } - - public DropTable getDropTable() { - return table; - } - - public int getRegenTime() { - return regenTime; - } - - public void playEffect() { - effect.play(loc.clone().add(.5, .5, .5), effectParticle); - } - - public void whenClosed(boolean sound) { - if (sound) { - loc.getWorld().playSound(loc, Sound.ITEM_ARMOR_EQUIP_LEATHER, 1, 1); - loc.getWorld().spawnParticle(Particle.CRIT, loc.clone().add(.5, .5, .5), 16, 0, 0, 0, .5); - } - if (loc.getBlock().getState() instanceof Chest) - ((Chest) loc.getBlock().getState()).getBlockInventory().clear(); - loc.getBlock().setType(Material.AIR); - lastDisappear = System.currentTimeMillis(); - Bukkit.getScheduler().scheduleSyncDelayedTask(MMOCore.plugin, () -> whenSpawn(), regenTime); - } - - public boolean hasPlayerNearby() { - for (Player player : loc.getWorld().getPlayers()) - if (player.getLocation().distanceSquared(loc) < 625) - return true; - return false; - } - - public void whenSpawn() { - List slots = new ArrayList<>(); - for (int j = 0; j < 27; j++) - slots.add(j); - - loc.getBlock().setType(Material.CHEST); - Chest chest = (Chest) loc.getBlock().getState(); - table.collect().forEach(item -> { - Integer slot = slots.get(random.nextInt(slots.size())); - chest.getInventory().setItem(slot, item); - slots.remove(slot); - }); - } - - private Location readLocation(String string) { - String[] split = string.split("\\ "); - - World world = Bukkit.getWorld(split[0]); - Validate.notNull(world, "Could not find world '" + split[0] + "'"); - - double x = Double.parseDouble(split[1]); - double y = Double.parseDouble(split[2]); - double z = Double.parseDouble(split[3]); - - return new Location(world, x, y, z); - } - } -} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 794f4b44..5f5de156 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -34,14 +34,22 @@ lootsplosion: offset: .2 height: .6 +# Time in seconds it takes for a loot chest to +# expire after it was spawned. 600 is 10 minutes. +loot-chest-expire-time: 600 + # Settings for the default action bar action-bar: + # Whether or not to use the default action bar. (This doesn't change any other action bars provided by MMOCore.) enabled: true + # The decimal format for stats (not including stat formats in stats.yml) decimal: "0.#" + # The amount of ticks before updating the info ticks-to-update: 5 + # How to display the data. format: "&c❤ {health}/{max_health} &f| &9⭐ {mana}/{max_mana} &f| &7⛨ {armor}" diff --git a/src/main/resources/default/professions/mining.yml b/src/main/resources/default/professions/mining.yml index dac0324c..8be90143 100644 --- a/src/main/resources/default/professions/mining.yml +++ b/src/main/resources/default/professions/mining.yml @@ -35,8 +35,6 @@ on-mine: temp-block: 'skull{value="eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTU0ODE4MjMzYzgxMTg3M2U4NWY1YTRlYTQ0MjliNzVmMjNiNmFlMGVhNmY1ZmMwZjdiYjQyMGQ3YzQ3MSJ9fX0="}' triggers: - 'exp{profession=mining;amount=20}' - - emerald: material: vanilla{type=EMERALD_ORE}