diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/PlayerData.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/PlayerData.java index 7e9ee45d..bb20021f 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/PlayerData.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/PlayerData.java @@ -46,8 +46,8 @@ import net.Indyuce.mmocore.skill.binding.BoundSkillInfo; import net.Indyuce.mmocore.skill.binding.SkillSlot; import net.Indyuce.mmocore.skill.cast.SkillCastingInstance; import net.Indyuce.mmocore.skill.cast.SkillCastingMode; -import net.Indyuce.mmocore.skilltree.SkillTreeStatus; import net.Indyuce.mmocore.skilltree.SkillTreeNode; +import net.Indyuce.mmocore.skilltree.SkillTreeStatus; import net.Indyuce.mmocore.skilltree.tree.SkillTree; import net.Indyuce.mmocore.waypoint.Waypoint; import net.Indyuce.mmocore.waypoint.WaypointOption; @@ -351,7 +351,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD /** * @return If the item is unlocked by the player - * This is used for skills that can be locked & unlocked. + * This is used for skills that can be locked & unlocked. */ public boolean hasUnlocked(Unlockable unlockable) { return unlockable.isUnlockedByDefault() || unlockedItems.contains(unlockable.getUnlockNamespacedKey()); @@ -745,6 +745,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD final int y = getPlayer().getLocation().getBlockY(); final int z = getPlayer().getLocation().getBlockZ(); final int warpTime = target.getWarpTime(); + final boolean hasPerm = getPlayer().hasPermission("mmocore.bypass-waypoint-wait"); int t; public void run() { @@ -757,7 +758,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD } MMOCore.plugin.configManager.getSimpleMessage("warping-comencing", "left", String.valueOf((warpTime - t) / 20)).send(getPlayer()); - if (t++ >= warpTime) { + if (hasPerm || t++ >= warpTime) { getPlayer().teleport(target.getLocation()); getPlayer().addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 20, 1, false, false)); MMOCore.plugin.soundManager.getSound(SoundEvent.WARP_TELEPORT).playTo(getPlayer()); @@ -1003,29 +1004,30 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD return skillCasting != null; } - /** - * @return true if the PlayerEnterCastingModeEvent successfully put the player into casting mode, otherwise if the event is cancelled, returns false. - * @apiNote Changed to a boolean to reflect the cancellation state of the event being fired - */ + @Deprecated public boolean setSkillCasting(@NotNull SkillCastingInstance skillCasting) { Validate.isTrue(!isCasting(), "Player already in casting mode"); PlayerEnterCastingModeEvent event = new PlayerEnterCastingModeEvent(getPlayer()); Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return false; - if (event.isCancelled()){ - skillCasting.close(); - return false; - } - this.skillCasting = skillCasting; + skillCasting.close(); + setSkillCasting(); return true; } /** - * API Method + * @return true if the PlayerEnterCastingModeEvent successfully put the player into casting mode, otherwise if the event is cancelled, returns false. + * @apiNote Changed to a boolean to reflect the cancellation state of the event being fired */ - public void setSkillCasting() { + public boolean setSkillCasting() { Validate.isTrue(!isCasting(), "Player already in casting mode"); - setSkillCasting(SkillCastingMode.getCurrent().newInstance(this)); + PlayerEnterCastingModeEvent event = new PlayerEnterCastingModeEvent(getPlayer()); + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return false; + + this.skillCasting = SkillCastingMode.getCurrent().newInstance(this); + return true; } @NotNull @@ -1035,27 +1037,25 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD /** * API Method to leave casting mode and fire the PlayerExitCastingModeEvent + * * @return true if the skill casting mode was left, or false if the event was cancelled, keeping the player in casting mode. */ - public boolean leaveSkillCasting(){ - return this.leaveSkillCasting(false); + public boolean leaveSkillCasting() { + return leaveSkillCasting(false); } /** * @param skipEvent Skip Firing the PlayerExitCastingModeEvent * @return true if the PlayerExitCastingModeEvent is not cancelled, or if the event is skipped. - * */ public boolean leaveSkillCasting(boolean skipEvent) { Validate.isTrue(isCasting(), "Player not in casting mode"); if (!skipEvent) { PlayerExitCastingModeEvent event = new PlayerExitCastingModeEvent(getPlayer()); Bukkit.getPluginManager().callEvent(event); - - if (event.isCancelled()) { - return false; - } + if (event.isCancelled()) return false; } + skillCasting.close(); this.skillCasting = null; setLastActivity(PlayerActivity.ACTION_BAR_MESSAGE, 0); // Reset action bar @@ -1221,7 +1221,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD * checks if they could potentially upgrade to one of these * * @return If the player can change its current class to - * a subclass + * a subclass */ @Deprecated public boolean canChooseSubclass() { diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/attribute/MMOCoreAttributeStatHandler.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/attribute/MMOCoreAttributeStatHandler.java index 9822148b..8cebc77b 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/attribute/MMOCoreAttributeStatHandler.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/attribute/MMOCoreAttributeStatHandler.java @@ -1,9 +1,10 @@ package net.Indyuce.mmocore.api.player.attribute; -import io.lumine.mythic.lib.api.stat.StatMap; +import io.lumine.mythic.lib.api.stat.StatInstance; import io.lumine.mythic.lib.api.stat.handler.StatHandler; import net.Indyuce.mmocore.MMOCore; import net.Indyuce.mmocore.api.player.PlayerData; +import org.bukkit.configuration.ConfigurationSection; /** * This fixes an issue where registering new stat modifiers in ML @@ -13,17 +14,13 @@ import net.Indyuce.mmocore.api.player.PlayerData; * This stat handler MAY call subsequent stat handlers. There might * be infinite recursion problems if another attr. grants extra attribute pts. */ -public class MMOCoreAttributeStatHandler implements StatHandler { +public class MMOCoreAttributeStatHandler extends StatHandler { private final PlayerAttribute attr; - private final String statName; - public MMOCoreAttributeStatHandler(PlayerAttribute attr) { + public MMOCoreAttributeStatHandler(ConfigurationSection config, PlayerAttribute attr) { + super(config, "ADDITIONAL_" + attr.getId().toUpperCase().replace("-", "_")); + this.attr = attr; - this.statName = "ADDITIONAL_" + attr.getId().toUpperCase().replace("-", "_"); - } - - public String getStat() { - return statName; } /** @@ -31,22 +28,12 @@ public class MMOCoreAttributeStatHandler implements StatHandler { * is not loaded yet, hence the try/catch clause */ @Override - public void runUpdate(StatMap statMap) { + public void runUpdate(StatInstance instance) { try { - final PlayerData playerData = MMOCore.plugin.dataProvider.getDataManager().get(statMap.getPlayerData().getUniqueId()); + final PlayerData playerData = MMOCore.plugin.dataProvider.getDataManager().get(instance.getMap().getPlayerData().getUniqueId()); playerData.getAttributes().getInstance(attr).updateStats(); } catch (NullPointerException exception) { // Player data is not loaded yet so there's nothing to update. } } - - @Override - public double getBaseValue(StatMap statMap) { - return 0; - } - - @Override - public double getTotalValue(StatMap statMap) { - return statMap.getStat(statName); - } } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/profess/PlayerClass.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/profess/PlayerClass.java index 53ad651a..115a91cb 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/profess/PlayerClass.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/profess/PlayerClass.java @@ -222,7 +222,7 @@ public class PlayerClass extends PostLoadObject implements ExperienceObject { for (String key : config.getStringList("main-exp-sources")) try { MMOCore.plugin.experience.registerSource(MMOCore.plugin.loadManager.loadExperienceSource(new MMOLineConfig(key), this)); - } catch (IllegalArgumentException exception) { + } catch (RuntimeException exception) { MMOCore.plugin.getLogger().log(Level.WARNING, "Could not load exp source '" + key + "' from class '" + id + "': " + exception.getMessage()); } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/util/MMOCoreUtils.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/util/MMOCoreUtils.java index 6419845f..752ddea1 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/util/MMOCoreUtils.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/util/MMOCoreUtils.java @@ -39,8 +39,14 @@ public class MMOCoreUtils { : caseOnWords(item.getType().name().replace("_", " ")); } + /** + * @param current Current value of resource + * @param maxStat Maximum value of resource + * @return Clamped resource value. If the provided current value is 0, + * this function will return the maximum resource value. + */ public static double fixResource(double current, double maxStat) { - return current == 0 ? maxStat : Math.min(current, maxStat); + return current == 0 ? maxStat : Math.max(0, Math.min(current, maxStat)); } public static String caseOnWords(String s) { @@ -62,7 +68,6 @@ public class MMOCoreUtils { } /** - * * @param value an integer you want to convert * @return the string representing the integer but with roman letters */ @@ -82,23 +87,25 @@ public class MMOCoreUtils { roman_numerals.put("IV", 4); roman_numerals.put("I", 1); String res = ""; - for(Map.Entry entry : roman_numerals.entrySet()){ - int matches = value/entry.getValue(); + for (Map.Entry entry : roman_numerals.entrySet()) { + int matches = value / entry.getValue(); res += repeat(entry.getKey(), matches); value = value % entry.getValue(); } return res; } + private static String repeat(String s, int n) { - if(s == null) { + if (s == null) { return null; } final StringBuilder sb = new StringBuilder(); - for(int i = 0; i < n; i++) { + for (int i = 0; i < n; i++) { sb.append(s); } return sb.toString(); } + /** * Displays an in game indicator using a hologram. This uses * LumineUtils hologramFactory to summon holograms @@ -260,8 +267,9 @@ public class MMOCoreUtils { * @param damage Damage that needs to be applied */ public static void decreaseDurability(Player player, EquipmentSlot slot, int damage) { + ItemStack item = player.getInventory().getItem(slot); - if (item == null || item.getType().getMaxDurability() == 0 || !item.hasItemMeta() || !(item.getItemMeta() instanceof Damageable) || item.getItemMeta().isUnbreakable()) + if (item == null || item.getType().getMaxDurability() == 0 || item.getItemMeta().isUnbreakable()) return; PlayerItemDamageEvent event = new PlayerItemDamageEvent(player, item, damage); @@ -270,11 +278,12 @@ public class MMOCoreUtils { return; ItemMeta meta = item.getItemMeta(); - if (event.getDamage() + ((Damageable) meta).getDamage() >= item.getType().getMaxDurability()) { + final int newDamage = event.getDamage() + ((Damageable) meta).getDamage(); + if (newDamage >= item.getType().getMaxDurability()) { player.playSound(player.getLocation(), Sound.ENTITY_ITEM_BREAK, 1F, 1F); player.getInventory().setItem(slot, null); } else { - ((Damageable) meta).setDamage(((Damageable) meta).getDamage() + event.getDamage()); + ((Damageable) meta).setDamage(newDamage); item.setItemMeta(meta); } } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/experience/source/KillMobExperienceSource.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/experience/source/KillMobExperienceSource.java index 70b29076..7a72c5b8 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/experience/source/KillMobExperienceSource.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/experience/source/KillMobExperienceSource.java @@ -2,7 +2,8 @@ package net.Indyuce.mmocore.experience.source; import io.lumine.mythic.lib.MythicLib; import io.lumine.mythic.lib.api.MMOLineConfig; -import io.lumine.mythic.lib.api.event.PlayerKillEntityEvent; +import io.lumine.mythic.lib.api.event.PlayerAttackEvent; +import io.lumine.mythic.lib.util.FlushableRegistry; import net.Indyuce.mmocore.MMOCore; import net.Indyuce.mmocore.api.player.PlayerData; import net.Indyuce.mmocore.api.util.MMOCoreUtils; @@ -15,9 +16,11 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.persistence.PersistentDataType; import javax.annotation.Nullable; +import java.util.UUID; public class KillMobExperienceSource extends SpecificExperienceSource { private final EntityType type; @@ -37,17 +40,36 @@ public class KillMobExperienceSource extends SpecificExperienceSource { public ExperienceSourceManager newManager() { return new ExperienceSourceManager() { - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void a(PlayerKillEntityEvent event) { - Bukkit.getScheduler().runTaskLater(MMOCore.plugin, () -> { - if (event.getTarget().isDead() && !event.getTarget().getPersistentDataContainer().has(new NamespacedKey(MMOCore.plugin, "spawner_spawned"), PersistentDataType.STRING)) { - PlayerData data = PlayerData.get(event.getPlayer()); + /** + * This map is used to keep track of the last player who + * hit some entity. It is flushed on entity death. + */ + private final FlushableRegistry registry = new FlushableRegistry<>((entity, attacker) -> Bukkit.getEntity(entity) == null, 20 * 60); - for (KillMobExperienceSource source : getSources()) - if (source.matches(data, event.getTarget())) - source.giveExperience(data, 1, MMOCoreUtils.getCenterLocation(event.getTarget())); - } - }, 2); + @Override + public void whenClosed() { + registry.close(); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void registerLastAttacker(PlayerAttackEvent event) { + registry.getRegistry().put(event.getEntity().getUniqueId(), event.getAttacker().getData().getUniqueId()); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void giveExp(EntityDeathEvent event) { + + // Always remove entry from map + final @Nullable UUID lastAttacker = this.registry.getRegistry().remove(event.getEntity().getUniqueId()); + if (lastAttacker == null) return; + + if (event.getEntity().getPersistentDataContainer().has(new NamespacedKey(MMOCore.plugin, "spawner_spawned"), PersistentDataType.STRING)) + return; + + final PlayerData data = PlayerData.get(lastAttacker); + for (KillMobExperienceSource source : getSources()) + if (source.matches(data, event.getEntity())) + source.giveExperience(data, 1, MMOCoreUtils.getCenterLocation(event.getEntity())); } }; } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/chest/condition/BiomeCondition.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/chest/condition/BiomeCondition.java index 1c55c5fc..9f9824b6 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/chest/condition/BiomeCondition.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/chest/condition/BiomeCondition.java @@ -8,18 +8,17 @@ import org.bukkit.block.Biome; import io.lumine.mythic.lib.api.MMOLineConfig; public class BiomeCondition extends Condition { - private final List names; + private final List names; - public BiomeCondition(MMOLineConfig config) { - super(config); + public BiomeCondition(MMOLineConfig config) { + super(config); - config.validate("name"); - names = Arrays.asList(config.getString("name").toUpperCase().split(",")); - } + config.validate("name"); + names = Arrays.asList(config.getString("name").toUpperCase().split(",")); + } - @Override - public boolean isMet(ConditionInstance entity) { - Biome currentBiome = entity.getEntity().getLocation().getBlock().getBiome(); - return names.contains(currentBiome.name()); - } + @Override + public boolean isMet(ConditionInstance instance) { + return names.contains(instance.getLocation().getBlock().getBiome().name()); + } } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/chest/condition/ConditionInstance.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/chest/condition/ConditionInstance.java index 02629763..7721e9df 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/chest/condition/ConditionInstance.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/chest/condition/ConditionInstance.java @@ -6,36 +6,44 @@ import java.util.stream.Stream; import net.Indyuce.mmocore.MMOCore; import org.bukkit.Location; import org.bukkit.entity.Entity; +import org.jetbrains.annotations.NotNull; public class ConditionInstance { - private final Entity entity; - private final Location applied; - private final List regions; + private final Entity entity; + private final Location applied; + private final List regions; - public ConditionInstance(Entity entity) { - this(entity, entity.getLocation()); - } + public ConditionInstance(@NotNull Entity entity) { + this(entity, entity.getLocation()); + } - public ConditionInstance(Entity entity, Location applied) { - this.entity = entity; - this.regions = MMOCore.plugin.regionHandler.getRegions(this.applied = applied); - - regions.add("__global__"); - } + public ConditionInstance(@NotNull Entity entity, @NotNull Location applied) { + this.entity = entity; + this.regions = MMOCore.plugin.regionHandler.getRegions(this.applied = applied); - public boolean isInRegion(String name) { - return regions.contains(name); - } + regions.add("__global__"); + } - public Location getAppliedLocation() { - return applied; - } + public boolean isInRegion(String name) { + return regions.contains(name); + } - public Entity getEntity() { - return entity; - } + @Deprecated + public Location getAppliedLocation() { + return applied; + } - public Stream getRegionStream() { - return regions.stream(); - } + @NotNull + public Location getLocation() { + return applied; + } + + @NotNull + public Entity getEntity() { + return entity; + } + + public Stream getRegionStream() { + return regions.stream(); + } } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/chest/condition/DistanceCondition.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/chest/condition/DistanceCondition.java index 21e46f8f..668f14d2 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/chest/condition/DistanceCondition.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/chest/condition/DistanceCondition.java @@ -6,7 +6,7 @@ import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.entity.Entity; -public class DistanceCondition extends Condition{ +public class DistanceCondition extends Condition { private final Location location; private final double distance; @@ -17,18 +17,14 @@ public class DistanceCondition extends Condition{ Validate.isTrue(config.contains("y")); Validate.isTrue(config.contains("z")); Validate.isTrue(config.contains("distance")); - Validate.isTrue(Bukkit.getWorld(config.getString("world"))!=null,"This world doesn't exist"); - location=new Location(Bukkit.getWorld(config.getString("world")),config.getDouble("x"), - config.getDouble("y"),config.getDouble("z")); - distance=config.getDouble("distance"); + Validate.isTrue(Bukkit.getWorld(config.getString("world")) != null, "This world doesn't exist"); + location = new Location(Bukkit.getWorld(config.getString("world")), config.getDouble("x"), + config.getDouble("y"), config.getDouble("z")); + distance = config.getDouble("distance"); } @Override - public boolean isMet(ConditionInstance entity) { - Entity entity1=entity.getEntity(); - return entity1.getWorld().equals(location.getWorld())&&location.distance(entity1.getLocation()) min && time < max; - } else { - // Allows for wrapping times, such as min=20000 max=6000 - return time > min || time < max; - } + if (min < max) { + return time > min && time < max; + } else { + // Allows for wrapping times, such as min=20000 max=6000 + return time > min || time < max; } - - return false; } } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/chest/condition/WeatherCondition.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/chest/condition/WeatherCondition.java index 37285a1f..ff4a1eb3 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/chest/condition/WeatherCondition.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/chest/condition/WeatherCondition.java @@ -18,15 +18,13 @@ public class WeatherCondition extends Condition { @Override public boolean isMet(ConditionInstance entity) { - if (entity.getEntity() instanceof Player player) { - boolean isClear = player.getWorld().isClearWeather(); - boolean hasStorm = player.getWorld().hasStorm(); + boolean isClear = entity.getLocation().getWorld().isClearWeather(); + boolean hasStorm = entity.getLocation().getWorld().hasStorm(); - if (condition.equalsIgnoreCase("clear")) { - return isClear; - } else if (condition.equalsIgnoreCase("stormy")) { - return hasStorm; - } + if (condition.equalsIgnoreCase("clear")) { + return isClear; + } else if (condition.equalsIgnoreCase("stormy")) { + return hasStorm; } return false; diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/chest/condition/WorldCondition.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/chest/condition/WorldCondition.java index 97c58cc4..332ef4da 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/chest/condition/WorldCondition.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/loot/chest/condition/WorldCondition.java @@ -17,6 +17,6 @@ public class WorldCondition extends Condition { @Override public boolean isMet(ConditionInstance entity) { - return names.contains(entity.getEntity().getWorld().getName()) || names.contains("__global__"); + return names.contains(entity.getLocation().getWorld().getName()) || names.contains("__global__"); } } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/AttributeManager.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/AttributeManager.java index 42bb5501..604a6231 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/AttributeManager.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/AttributeManager.java @@ -5,6 +5,7 @@ import net.Indyuce.mmocore.MMOCore; import net.Indyuce.mmocore.api.ConfigFile; import net.Indyuce.mmocore.api.player.attribute.MMOCoreAttributeStatHandler; import net.Indyuce.mmocore.api.player.attribute.PlayerAttribute; +import org.bukkit.configuration.ConfigurationSection; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -46,8 +47,9 @@ public class AttributeManager implements MMOCoreManager { MMOCore.log(Level.WARNING, "Could not load attribute '" + key + "': " + exception.getMessage()); } + final ConfigurationSection statsConfig = new ConfigFile(MythicLib.plugin, "", "stats").getConfig(); for (PlayerAttribute attr : getAll()) { - final MMOCoreAttributeStatHandler handler = new MMOCoreAttributeStatHandler(attr); + final MMOCoreAttributeStatHandler handler = new MMOCoreAttributeStatHandler(statsConfig, attr); MythicLib.plugin.getStats().registerStat(handler.getStat(), handler); MythicLib.plugin.getStats().registerStat(handler.getStat() + "_PERCENT", handler); } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/ExperienceManager.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/ExperienceManager.java index 6714cb23..a1d70b4d 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/ExperienceManager.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/ExperienceManager.java @@ -9,7 +9,7 @@ import net.Indyuce.mmocore.manager.profession.ExperienceSourceManager; import org.apache.commons.lang.Validate; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; import java.io.File; @@ -25,10 +25,8 @@ public class ExperienceManager implements MMOCoreManager { * Experience sources from the exp-sources.yml config file where you can * input any exp source which can later be used along with the 'from' * exp source anywhere in the plugin. - *

- * TODO First needs to edit the exp-source current structure. This is going to break a lot of things * - * @deprecated See TODO + * @deprecated TODO First needs to edit the exp-source current structure. This is going to break a lot of things */ @Deprecated private final Map>> publicExpSources = new HashMap<>(); @@ -55,6 +53,7 @@ public class ExperienceManager implements MMOCoreManager { return expCurves.containsKey(id); } + @NotNull public ExpCurve getCurveOrThrow(String id) { Validate.isTrue(hasCurve(id), "Could not find exp curve with ID '" + id + "'"); return expCurves.get(id); @@ -70,10 +69,12 @@ public class ExperienceManager implements MMOCoreManager { return expTables.containsKey(id); } + @NotNull public ExperienceTable getTableOrThrow(String id) { return Objects.requireNonNull(expTables.get(id), "Could not find exp table with ID '" + id + "'"); } + @NotNull public ExperienceTable loadExperienceTable(Object obj) { if (obj instanceof ConfigurationSection) @@ -99,7 +100,7 @@ public class ExperienceManager implements MMOCoreManager { expCurves.clear(); expTables.clear(); - managers.values().forEach(HandlerList::unregisterAll); + managers.forEach((c, manager) -> manager.close()); managers.clear(); } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/RestrictionManager.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/RestrictionManager.java index 4d06cdc6..6cc6f02b 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/RestrictionManager.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/RestrictionManager.java @@ -115,8 +115,9 @@ public class RestrictionManager implements MMOCoreManager { String parentFormat = formatId(config.getString("parent")); parent = Objects.requireNonNull(map.get(parentFormat), "Could not find parent with ID '" + parentFormat + "'"); } - for (String key : config.getStringList("can-mine")) - mineable.add(MMOCore.plugin.loadManager.loadBlockType(new MMOLineConfig(key)).generateKey()); + if (config.contains("can-mine")) + for (String key : config.getStringList("can-mine")) + mineable.add(MMOCore.plugin.loadManager.loadBlockType(new MMOLineConfig(key)).generateKey()); } /** diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/data/sql/MMOCoreDataSynchronizer.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/data/sql/MMOCoreDataSynchronizer.java index 264666a1..30736a42 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/data/sql/MMOCoreDataSynchronizer.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/data/sql/MMOCoreDataSynchronizer.java @@ -78,15 +78,12 @@ public class MMOCoreDataSynchronizer extends SQLDataSynchronizer { getData().setUnlockedItems(unlockedItems); if (!isEmpty(result.getString("guild"))) { final Guild guild = MMOCore.plugin.dataProvider.getGuildManager().getGuild(result.getString("guild")); - if (guild != null) - getData().setGuild(guild.hasMember(getData().getUniqueId()) ? guild : null); + if (guild != null) getData().setGuild(guild.hasMember(getData().getUniqueId()) ? guild : null); } - if (!isEmpty(result.getString("attributes"))) - getData().getAttributes().load(result.getString("attributes")); + if (!isEmpty(result.getString("attributes"))) getData().getAttributes().load(result.getString("attributes")); if (!isEmpty(result.getString("professions"))) getData().getCollectionSkills().load(result.getString("professions")); - if (!isEmpty(result.getString("quests"))) - getData().getQuestData().load(result.getString("quests")); + if (!isEmpty(result.getString("quests"))) getData().getQuestData().load(result.getString("quests")); getData().getQuestData().updateBossBar(); if (!isEmpty(result.getString("waypoints"))) getData().getWaypoints().addAll(MMOCoreUtils.jsonArrayToList(result.getString("waypoints"))); @@ -101,8 +98,7 @@ public class MMOCoreDataSynchronizer extends SQLDataSynchronizer { JsonObject object = MythicLib.plugin.getGson().fromJson(result.getString("bound_skills"), JsonObject.class); for (Map.Entry entry : object.entrySet()) { ClassSkill skill = getData().getProfess().getSkill(entry.getValue().getAsString()); - if (skill != null) - getData().bindSkill(Integer.parseInt(entry.getKey()), skill); + if (skill != null) getData().bindSkill(Integer.parseInt(entry.getKey()), skill); } } @@ -123,15 +119,17 @@ public class MMOCoreDataSynchronizer extends SQLDataSynchronizer { * These should be loaded after to make sure that the * MAX_MANA, MAX_STAMINA & MAX_STELLIUM stats are already loaded. */ - double health = result.getDouble("health"); getData().setMana(result.getDouble("mana")); getData().setStamina(result.getDouble("stamina")); getData().setStellium(result.getDouble("stellium")); - if (getData().isOnline()) { - //If the player is not dead and the health is 0, this means that the data was - //missing from the data base and it gives full health to the player. - health = health == 0 && !getData().getPlayer().isDead() ? getData().getPlayer().getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue() : health; - health = Math.max(Math.min(health, getData().getPlayer().getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue()), 0); + if (getData().isOnline() && !getData().getPlayer().isDead()) { + + /* + * If the player is not dead and the health is 0, this means that the data was + * missing from the data base and it gives full health to the player. If the + * player is dead however, it must not account for that subtle edge case. + */ + final double health = MMOCoreUtils.fixResource(result.getDouble("health"), getData().getPlayer().getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue()); getData().getPlayer().setHealth(health); } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/data/yaml/YAMLPlayerDataHandler.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/data/yaml/YAMLPlayerDataHandler.java index 0d11a606..8cd0d6cd 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/data/yaml/YAMLPlayerDataHandler.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/data/yaml/YAMLPlayerDataHandler.java @@ -134,7 +134,7 @@ public class YAMLPlayerDataHandler extends YAMLSynchronizedDataHandler implements Listener { +public abstract class ExperienceSourceManager implements Listener, Closeable { /** * List of all active experience sources */ private final Set sources = new HashSet<>(); + private boolean open = true; public ExperienceSourceManager() { Bukkit.getPluginManager().registerEvents(this, MMOCore.plugin); @@ -23,6 +27,19 @@ public abstract class ExperienceSourceManager implem sources.add(source); } + public void whenClosed() { + // Nothing by default + } + + @Override + public void close() { + Validate.isTrue(open, "Manager is already closed"); + open = false; + + HandlerList.unregisterAll(this); + whenClosed(); + } + public Set getSources() { return sources; } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/profession/FishingManager.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/profession/FishingManager.java index aad6403d..48c79e28 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/profession/FishingManager.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/profession/FishingManager.java @@ -10,6 +10,9 @@ import net.Indyuce.mmocore.loot.fishing.FishingDropItem; import org.apache.commons.lang.Validate; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Entity; +import org.bukkit.entity.FishHook; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; import java.util.*; import java.util.logging.Level; @@ -36,8 +39,9 @@ public class FishingManager extends SpecificProfessionManager { MMOCore.plugin.statManager.registerProfession("CRITICAL_FISHING_FAILURE_CHANCE", getLinkedProfession()); } - public FishingDropTable calculateDropTable(Entity entity) { - ConditionInstance conditionEntity = new ConditionInstance(entity); + @NotNull + public FishingDropTable calculateDropTable(@NotNull Player player, @NotNull FishHook hook) { + ConditionInstance conditionEntity = new ConditionInstance(player, hook.getLocation()); for (FishingDropTable table : tables) if (table.areConditionsMet(conditionEntity)) diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/skill/cast/listener/KeyCombos.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/skill/cast/listener/KeyCombos.java index e05758e4..8f780afe 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/skill/cast/listener/KeyCombos.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/skill/cast/listener/KeyCombos.java @@ -16,7 +16,6 @@ import org.bukkit.GameMode; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; @@ -74,6 +73,9 @@ public class KeyCombos implements SkillCastingListener { if (player.getGameMode() == GameMode.CREATIVE && !MMOCore.plugin.configManager.canCreativeCast) return; + // Don't start combos if no skills are bound + if (playerData.getBoundSkills().isEmpty()) return; + // Start combo when there is an initializer key if (!event.getData().isCasting() && initializerKey != null) { if (event.getPressed() == initializerKey) { @@ -82,7 +84,7 @@ public class KeyCombos implements SkillCastingListener { if (event.getPressed().shouldCancelEvent()) event.setCancelled(true); // Start combo - if (playerData.setSkillCasting(new CustomSkillCastingInstance(playerData)) && beginComboSound != null) + if (playerData.setSkillCasting() && beginComboSound != null) beginComboSound.playTo(player); } @@ -97,10 +99,9 @@ public class KeyCombos implements SkillCastingListener { // Start combo when there is NO initializer key else { final @NotNull ComboMap comboMap = Objects.requireNonNullElse(playerData.getProfess().getComboMap(), this.comboMap); - if (comboMap.isComboStart(event.getPressed())) { - casting = new CustomSkillCastingInstance(playerData); - if (playerData.setSkillCasting(new CustomSkillCastingInstance(playerData)) && beginComboSound != null) - beginComboSound.playTo(player); + if (comboMap.isComboStart(event.getPressed()) && playerData.setSkillCasting()) { + casting = (CustomSkillCastingInstance) playerData.getSkillCasting(); + if (beginComboSound != null) beginComboSound.playTo(player); } } @@ -174,7 +175,8 @@ public class KeyCombos implements SkillCastingListener { @Override public void onTick() { - if (actionBarOptions != null) if (actionBarOptions.isSubtitle) + if (getCaster().getBoundSkills().isEmpty()) close(); + else if (actionBarOptions != null) if (actionBarOptions.isSubtitle) getCaster().getPlayer().sendTitle(" ", actionBarOptions.format(this), 0, 20, 0); else getCaster().displayActionBar(actionBarOptions.format(this)); } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/skill/cast/listener/SkillBar.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/skill/cast/listener/SkillBar.java index 6f338ff1..3601009e 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/skill/cast/listener/SkillBar.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/skill/cast/listener/SkillBar.java @@ -55,11 +55,9 @@ public class SkillBar implements SkillCastingListener { // Enter spell casting final PlayerData playerData = event.getData(); - if (player.getGameMode() != GameMode.SPECTATOR && (MMOCore.plugin.configManager.canCreativeCast || player.getGameMode() != GameMode.CREATIVE) && !playerData.isCasting() && !playerData.getBoundSkills().isEmpty()) { - if (playerData.setSkillCasting(new CustomSkillCastingInstance(playerData))) { + if (player.getGameMode() != GameMode.SPECTATOR && (MMOCore.plugin.configManager.canCreativeCast || player.getGameMode() != GameMode.CREATIVE) && !playerData.isCasting() && !playerData.getBoundSkills().isEmpty()) + if (playerData.setSkillCasting()) MMOCore.plugin.soundManager.getSound(SoundEvent.SPELL_CAST_BEGIN).playTo(player); - } - } } public class CustomSkillCastingInstance extends SkillCastingInstance { diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/skill/cast/listener/SkillScroller.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/skill/cast/listener/SkillScroller.java index 48c55fef..6d5a66c7 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/skill/cast/listener/SkillScroller.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/skill/cast/listener/SkillScroller.java @@ -60,6 +60,7 @@ public class SkillScroller implements SkillCastingListener { Player player = playerData.getPlayer(); if (player.getGameMode() == GameMode.CREATIVE && !MMOCore.plugin.configManager.canCreativeCast) return; + if (event.getPressed() == enterKey) { // Leave casting mode @@ -80,7 +81,7 @@ public class SkillScroller implements SkillCastingListener { if (event.getPressed().shouldCancelEvent()) event.setCancelled(true); // Enter casting mode - if (!playerData.setSkillCasting(new CustomSkillCastingInstance(playerData))) { + if (!playerData.setSkillCasting()) { return; } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/skill/list/Ambers.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/skill/list/Ambers.java index 848f1e2e..de1551a4 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/skill/list/Ambers.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/skill/list/Ambers.java @@ -53,7 +53,7 @@ public class Ambers extends SkillHandler implements Listener if (passive == null) return; - passive.getTriggeredSkill().cast(new TriggerMetadata(event.getAttacker(), event.getAttack(), event.getEntity())); + passive.getTriggeredSkill().cast(new TriggerMetadata(event)); } public static class Amber extends BukkitRunnable { diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/skill/list/Sneaky_Picky.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/skill/list/Sneaky_Picky.java index 96a11b7d..b1347bf9 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/skill/list/Sneaky_Picky.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/skill/list/Sneaky_Picky.java @@ -45,6 +45,6 @@ public class Sneaky_Picky extends SkillHandler implements Lis if (skill == null) return; - skill.getTriggeredSkill().cast(new TriggerMetadata(event.getAttacker(), event.getAttack(), event.getEntity())); + skill.getTriggeredSkill().cast(new TriggerMetadata(event)); } } 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 47e78d5b..cbbae34f 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 @@ -45,7 +45,7 @@ public class FishingListener implements Listener { * Checks for drop tables. If no drop table, just plain vanilla * fishing OTHERWISE initialize fishing, register other listener. */ - FishingDropTable table = MMOCore.plugin.fishingManager.calculateDropTable(player); + FishingDropTable table = MMOCore.plugin.fishingManager.calculateDropTable(player, hook); if (table == null) return; diff --git a/MMOCore-Dist/src/main/resources/plugin.yml b/MMOCore-Dist/src/main/resources/plugin.yml index 73f9f2c2..15f9782a 100644 --- a/MMOCore-Dist/src/main/resources/plugin.yml +++ b/MMOCore-Dist/src/main/resources/plugin.yml @@ -24,6 +24,9 @@ permissions: mmocore.currency: description: Access to /deposit and /withdraw default: op + mmocore.bypass-waypoint-wait: + description: Bypass waypoint waiting time + default: op mmocore.class-select: description: Access to /class default: op