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 63894e16..b54c40da 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 @@ -60,6 +60,7 @@ import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; import java.util.*; import java.util.logging.Level; +import java.util.stream.Collectors; public class PlayerData extends OfflinePlayerData implements Closable, ExperienceTableClaimer { @@ -376,6 +377,10 @@ public class PlayerData extends OfflinePlayerData implements Closable, Experienc // Close quest data questData.close(); + + // Stop skill casting + if (isCasting()) + leaveSkillCasting(); } public MMOPlayerData getMMOPlayerData() { @@ -786,19 +791,26 @@ public class PlayerData extends OfflinePlayerData implements Closable, Experienc if (value <= 0) return; - if (hasReachedMaxLevel()) { - setExperience(0); - return; - } - // Splitting exp through party members AbstractParty party; if (splitExp && (party = getParty()) != null) { - List onlineMembers = party.getOnlineMembers(); - value /= onlineMembers.size(); - for (PlayerData member : onlineMembers) - if (!equals(member)) - member.giveExperience(value, source, null, false); + final List nearbyMembers = party.getOnlineMembers().stream() + .filter(pd -> { + if (equals(pd) || pd.hasReachedMaxLevel()) + return false; + + final double maxDis = MMOCore.plugin.configManager.partyMaxExpSplitRange; + return maxDis <= 0 || pd.getPlayer().getLocation().distanceSquared(getPlayer().getLocation()) < maxDis * maxDis; + }).collect(Collectors.toList()); + value /= (nearbyMembers.size() + 1); + for (PlayerData member : nearbyMembers) + member.giveExperience(value, source, null, false); + } + + // Must be placed after exp splitting + if (hasReachedMaxLevel()) { + setExperience(0); + return; } // Apply buffs AFTER splitting exp 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 new file mode 100644 index 00000000..9822148b --- /dev/null +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/attribute/MMOCoreAttributeStatHandler.java @@ -0,0 +1,52 @@ +package net.Indyuce.mmocore.api.player.attribute; + +import io.lumine.mythic.lib.api.stat.StatMap; +import io.lumine.mythic.lib.api.stat.handler.StatHandler; +import net.Indyuce.mmocore.MMOCore; +import net.Indyuce.mmocore.api.player.PlayerData; + +/** + * This fixes an issue where registering new stat modifiers in ML + * to add extra attribute points does NOT update the stats granted + * by the attribute. + *

+ * 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 { + private final PlayerAttribute attr; + private final String statName; + + public MMOCoreAttributeStatHandler(PlayerAttribute attr) { + this.attr = attr; + this.statName = "ADDITIONAL_" + attr.getId().toUpperCase().replace("-", "_"); + } + + public String getStat() { + return statName; + } + + /** + * This method is called on login but the MMOCore playerData + * is not loaded yet, hence the try/catch clause + */ + @Override + public void runUpdate(StatMap statMap) { + try { + final PlayerData playerData = MMOCore.plugin.dataProvider.getDataManager().get(statMap.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/attribute/PlayerAttributes.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/attribute/PlayerAttributes.java index 6c26cf9c..ba103c5e 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/attribute/PlayerAttributes.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/attribute/PlayerAttributes.java @@ -22,202 +22,206 @@ import java.util.Set; import java.util.logging.Level; public class PlayerAttributes { - private final PlayerData data; - private final Map instances = new HashMap<>(); + private final PlayerData data; + private final Map instances = new HashMap<>(); - public PlayerAttributes(PlayerData data) { - this.data = data; - } + public PlayerAttributes(PlayerData data) { + this.data = data; + } - public void load(ConfigurationSection config) { - for (String key : config.getKeys(false)) - try { - String id = key.toLowerCase().replace("_", "-").replace(" ", "-"); - Validate.isTrue(MMOCore.plugin.attributeManager.has(id), "Could not find attribute '" + id + "'"); + public void load(ConfigurationSection config) { + for (String key : config.getKeys(false)) + try { + String id = key.toLowerCase().replace("_", "-").replace(" ", "-"); + Validate.isTrue(MMOCore.plugin.attributeManager.has(id), "Could not find attribute '" + id + "'"); - PlayerAttribute attribute = MMOCore.plugin.attributeManager.get(id); - AttributeInstance ins = new AttributeInstance(attribute.getId()); - ins.setBase(config.getInt(key)); - instances.put(id, ins); - } catch (IllegalArgumentException exception) { - data.log(Level.WARNING, exception.getMessage()); - } - } + PlayerAttribute attribute = MMOCore.plugin.attributeManager.get(id); + AttributeInstance ins = new AttributeInstance(attribute.getId()); + ins.setBase(config.getInt(key)); + instances.put(id, ins); + } catch (IllegalArgumentException exception) { + data.log(Level.WARNING, exception.getMessage()); + } + } - public void save(ConfigurationSection config) { - instances.values().forEach(ins -> config.set(ins.id, ins.getBase())); - } + public void save(ConfigurationSection config) { + instances.values().forEach(ins -> config.set(ins.id, ins.getBase())); + } - public String toJsonString() { - JsonObject json = new JsonObject(); - for (AttributeInstance ins : instances.values()) - json.addProperty(ins.getId(), ins.getBase()); - return json.toString(); - } + public String toJsonString() { + JsonObject json = new JsonObject(); + for (AttributeInstance ins : instances.values()) + json.addProperty(ins.getId(), ins.getBase()); + return json.toString(); + } - public void load(String json) { - Gson parser = new Gson(); - JsonObject jo = parser.fromJson(json, JsonObject.class); - for (Entry entry : jo.entrySet()) { - try { - String id = entry.getKey().toLowerCase().replace("_", "-").replace(" ", "-"); - Validate.isTrue(MMOCore.plugin.attributeManager.has(id), "Could not find attribute '" + id + "'"); + public void load(String json) { + Gson parser = new Gson(); + JsonObject jo = parser.fromJson(json, JsonObject.class); + for (Entry entry : jo.entrySet()) { + try { + String id = entry.getKey().toLowerCase().replace("_", "-").replace(" ", "-"); + Validate.isTrue(MMOCore.plugin.attributeManager.has(id), "Could not find attribute '" + id + "'"); - PlayerAttribute attribute = MMOCore.plugin.attributeManager.get(id); - AttributeInstance ins = new AttributeInstance(attribute.getId()); - ins.setBase(entry.getValue().getAsInt()); - instances.put(id, ins); - } catch (IllegalArgumentException exception) { - data.log(Level.WARNING, exception.getMessage()); - } - } - } + PlayerAttribute attribute = MMOCore.plugin.attributeManager.get(id); + AttributeInstance ins = new AttributeInstance(attribute.getId()); + ins.setBase(entry.getValue().getAsInt()); + instances.put(id, ins); + } catch (IllegalArgumentException exception) { + data.log(Level.WARNING, exception.getMessage()); + } + } + } - public PlayerData getData() { - return data; - } + public PlayerData getData() { + return data; + } - public int getAttribute(PlayerAttribute attribute) { - return getInstance(attribute).getTotal(); - } + public int getAttribute(PlayerAttribute attribute) { + return getInstance(attribute).getTotal(); + } - public Collection getInstances() { - return instances.values(); - } + public Collection getInstances() { + return instances.values(); + } - public Map mapPoints() { - Map map = new HashMap<>(); - instances.values().forEach(ins -> map.put(ins.id, ins.spent)); - return map; - } + public Map mapPoints() { + Map map = new HashMap<>(); + instances.values().forEach(ins -> map.put(ins.id, ins.spent)); + return map; + } - @NotNull - public AttributeInstance getInstance(String attribute) { - return instances.computeIfAbsent(attribute, AttributeInstance::new); - } + @NotNull + public AttributeInstance getInstance(String attribute) { + return instances.computeIfAbsent(attribute, AttributeInstance::new); + } - @NotNull - public AttributeInstance getInstance(PlayerAttribute attribute) { - return getInstance(attribute.getId()); - } + @NotNull + public AttributeInstance getInstance(PlayerAttribute attribute) { + return getInstance(attribute.getId()); + } - @Deprecated - public int countSkillPoints() { - return countPoints(); - } + @Deprecated + public int countSkillPoints() { + return countPoints(); + } - public int countPoints() { - int n = 0; - for (AttributeInstance ins : instances.values()) - n += ins.getBase(); - return n; - } + public int countPoints() { + int n = 0; + for (AttributeInstance ins : instances.values()) + n += ins.getBase(); + return n; + } - public class AttributeInstance { - private int spent; + public class AttributeInstance { + private int spent; - private final String id, enumName; - private final Map map = new HashMap<>(); + private final String id, enumName; + private final Map map = new HashMap<>(); - public AttributeInstance(String id) { - this.id = id; - this.enumName = UtilityMethods.enumName(this.id); - } + public AttributeInstance(String id) { + this.id = id; + this.enumName = UtilityMethods.enumName(this.id); + } - public int getBase() { - return spent; - } + public int getBase() { + return spent; + } - public void setBase(int value) { - spent = Math.max(0, value); + public void setBase(int value) { + spent = Math.max(0, value); - if (data.isOnline()) - update(); - } + if (data.isOnline()) + updateStats(); + } - public void addBase(int value) { - setBase(spent + value); - } + public void addBase(int value) { + setBase(spent + value); + } - /* - * 1) two types of attributes: flat attributes which add X to the value, - * and relative attributes which add X% and which must be applied - * afterwards 2) the 'd' parameter lets you choose if the relative - * attributes also apply on the base stat, or if they only apply on the - * instances stat value - */ - public int getTotal() { - double d = spent; + /* + * 1) two types of attributes: flat attributes which add X to the value, + * and relative attributes which add X% and which must be applied + * afterwards 2) the 'd' parameter lets you choose if the relative + * attributes also apply on the base stat, or if they only apply on the + * instances stat value + */ + public int getTotal() { + double d = spent; - for (AttributeModifier attr : map.values()) - if (attr.getType() == ModifierType.FLAT) - d += attr.getValue(); + for (AttributeModifier attr : map.values()) + if (attr.getType() == ModifierType.FLAT) + d += attr.getValue(); - d += data.getMMOPlayerData().getStatMap().getStat("ADDITIONAL_" + enumName); + d += data.getMMOPlayerData().getStatMap().getStat("ADDITIONAL_" + enumName); - for (AttributeModifier attr : map.values()) - if (attr.getType() == ModifierType.RELATIVE) - d *= attr.getValue(); + for (AttributeModifier attr : map.values()) + if (attr.getType() == ModifierType.RELATIVE) + d *= attr.getValue(); - d *= 1 + data.getMMOPlayerData().getStatMap().getStat("ADDITIONAL_" + enumName + "_PERCENT") / 100; + d *= 1 + data.getMMOPlayerData().getStatMap().getStat("ADDITIONAL_" + enumName + "_PERCENT") / 100; - // cast to int at the last moment - return (int) d; - } + // cast to int at the last moment + return (int) d; + } - public AttributeModifier getModifier(String key) { - return map.get(key); - } + public AttributeModifier getModifier(String key) { + return map.get(key); + } - public AttributeModifier addModifier(String key, double value) { - return addModifier(new AttributeModifier(key, id, value, ModifierType.FLAT, EquipmentSlot.OTHER, ModifierSource.OTHER)); - } + public AttributeModifier addModifier(String key, double value) { + return addModifier(new AttributeModifier(key, id, value, ModifierType.FLAT, EquipmentSlot.OTHER, ModifierSource.OTHER)); + } - public AttributeModifier addModifier(AttributeModifier modifier) { - AttributeModifier mod = map.put(modifier.getKey(), modifier); - update(); - return mod; - } + public AttributeModifier addModifier(AttributeModifier modifier) { + final AttributeModifier current = map.put(modifier.getKey(), modifier); - public Set getKeys() { - return map.keySet(); - } + if (current != null && current instanceof Closeable) + ((Closeable) current).close(); - public boolean contains(String key) { - return map.containsKey(key); - } + updateStats(); + return current; + } - public AttributeModifier removeModifier(String key) { - AttributeModifier mod = map.remove(key); + public Set getKeys() { + return map.keySet(); + } - /* - * Closing stat is really important with temporary stats because - * otherwise the runnable will try to remove the key from the map - * even though the attribute was cancelled before hand - */ - if (mod != null) { - if (mod instanceof Closeable) - ((Closeable) mod).close(); - update(); - } - return mod; - } + public boolean contains(String key) { + return map.containsKey(key); + } - public void update() { - PlayerAttribute attr = MMOCore.plugin.attributeManager.get(id); - int total = getTotal(); - attr.getBuffs().forEach(buff -> buff.multiply(total).register(data.getMMOPlayerData())); - } + public AttributeModifier removeModifier(String key) { + final AttributeModifier mod = map.remove(key); - public String getId() { - return id; - } - } + /* + * Closing stat is really important with temporary stats because + * otherwise the runnable will try to remove the key from the map + * even though the attribute was cancelled before hand + */ + if (mod != null) { + if (mod instanceof Closeable) + ((Closeable) mod).close(); + updateStats(); + } + return mod; + } - public void setBaseAttribute(String id, int value) { - getInstances().forEach(ins -> { - if (ins.getId().equals(id)) - ins.setBase(value); - }); - } + public void updateStats() { + final PlayerAttribute attr = MMOCore.plugin.attributeManager.get(id); + final int total = getTotal(); + attr.getBuffs().forEach(buff -> buff.multiply(total).register(data.getMMOPlayerData())); + } + + public String getId() { + return id; + } + } + + public void setBaseAttribute(String id, int value) { + getInstances().forEach(ins -> { + if (ins.getId().equals(id)) + ins.setBase(value); + }); + } } 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 0aef0d2f..42bb5501 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 @@ -1,45 +1,55 @@ package net.Indyuce.mmocore.manager; +import io.lumine.mythic.lib.MythicLib; +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.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; -import net.Indyuce.mmocore.MMOCore; -import net.Indyuce.mmocore.api.player.attribute.PlayerAttribute; -import net.Indyuce.mmocore.api.ConfigFile; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - public class AttributeManager implements MMOCoreManager { - private final Map map = new HashMap<>(); + private final Map map = new HashMap<>(); - @Nullable - public PlayerAttribute get(String id) { - return map.get(id); - } + @Nullable + public PlayerAttribute get(String id) { + return map.get(id); + } - public boolean has(String id) { - return map.containsKey(id); - } + public boolean has(String id) { + return map.containsKey(id); + } - @NotNull - public Collection getAll() { - return map.values(); - } + @NotNull + public Collection getAll() { + return map.values(); + } - @Override - public void initialize(boolean clearBefore) { - if (clearBefore) - map.clear(); + @Override + public void initialize(boolean clearBefore) { + if (clearBefore) { + map.clear(); + MythicLib.plugin.getStats().clearRegisteredStats(handler -> handler instanceof MMOCoreAttributeStatHandler); + } - ConfigFile config = new ConfigFile("attributes"); - for (String key : config.getConfig().getKeys(false)) - try { - String path = key.toLowerCase().replace("_", "-").replace(" ", "-"); - map.put(path, new PlayerAttribute(config.getConfig().getConfigurationSection(key))); - } catch (IllegalArgumentException exception) { - MMOCore.log(Level.WARNING, "Could not load attribute '" + key + "': " + exception.getMessage()); - } - } + final ConfigFile config = new ConfigFile("attributes"); + for (String key : config.getConfig().getKeys(false)) + try { + String path = key.toLowerCase().replace("_", "-").replace(" ", "-"); + map.put(path, new PlayerAttribute(config.getConfig().getConfigurationSection(key))); + } catch (IllegalArgumentException exception) { + MMOCore.log(Level.WARNING, "Could not load attribute '" + key + "': " + exception.getMessage()); + } + + for (PlayerAttribute attr : getAll()) { + final MMOCoreAttributeStatHandler handler = new MMOCoreAttributeStatHandler(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/ConfigManager.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/ConfigManager.java index 92bbfcbe..babc8ded 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/ConfigManager.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/ConfigManager.java @@ -27,7 +27,7 @@ public class ConfigManager { public ChatColor staminaFull, staminaHalf, staminaEmpty; public long combatLogTimer, lootChestExpireTime, lootChestPlayerCooldown, globalSkillCooldown; public double lootChestsChanceWeight,dropItemsChanceWeight, fishingDropsChanceWeight, partyMaxExpSplitRange; - public int maxPartyLevelDifference, maxBoundActiveSkills, maxBoundPassiveSkills, waypointWarpTime; + public int maxPartyLevelDifference, maxBoundActiveSkills, maxBoundPassiveSkills; private final FileConfiguration messages; @@ -82,8 +82,6 @@ public class ConfigManager { loadDefaultFile("skill-trees","rogue-marksman.yml"); loadDefaultFile("skill-trees","warrior-paladin.yml"); loadDefaultFile("skill-trees","general.yml"); - - } loadDefaultFile("attributes.yml"); 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 c0cd3bc8..f8528eeb 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 @@ -31,15 +31,18 @@ public class SkillBar implements Listener { } @EventHandler - public void a(PlayerKeyPressEvent event) { + public void enterSkillCasting(PlayerKeyPressEvent event) { if (event.getPressed() != mainKey) return; + // Extra option to improve support with other plugins + final Player player = event.getPlayer(); + if (disableSneak && player.isSneaking()) return; + // Always cancel event if (event.getPressed().shouldCancelEvent()) event.setCancelled(true); // Enter spell casting - Player player = event.getData().getPlayer(); - PlayerData playerData = event.getData(); + final PlayerData playerData = event.getData(); if (player.getGameMode() != GameMode.SPECTATOR && (MMOCore.plugin.configManager.canCreativeCast || player.getGameMode() != GameMode.CREATIVE) && !playerData.isCasting() && !playerData.getBoundSkills() .isEmpty()) { playerData.setSkillCasting(new CustomSkillCastingHandler(playerData)); @@ -64,10 +67,9 @@ public class SkillBar implements Listener { public void onSkillCast(PlayerItemHeldEvent event) { if (!event.getPlayer().equals(getCaster().getPlayer())) return; - if (!getCaster().isOnline()) { - getCaster().leaveSkillCasting(); - return; - } + // Extra option to improve support with other plugins + final Player player = event.getPlayer(); + if (disableSneak && player.isSneaking()) return; /* * When the event is cancelled, another playerItemHeldEvent is @@ -76,10 +78,6 @@ public class SkillBar implements Listener { */ if (event.getPreviousSlot() == event.getNewSlot()) return; - // Extra option to improve support with other plugins - final Player player = event.getPlayer(); - if (disableSneak && player.isSneaking()) return; - event.setCancelled(true); int slot = event.getNewSlot() + (event.getNewSlot() >= player.getInventory().getHeldItemSlot() ? -1 : 0); @@ -97,18 +95,23 @@ public class SkillBar implements Listener { @EventHandler public void stopCasting(PlayerKeyPressEvent event) { - Player player = event.getPlayer(); - if (event.getPressed() == mainKey && event.getPlayer().equals(getCaster().getPlayer())) { - MMOCore.plugin.soundManager.getSound(SoundEvent.SPELL_CAST_END).playTo(player); + if (!event.getPlayer().equals(getCaster().getPlayer())) return; - new BukkitRunnable() { - @Override - public void run() { - MMOCore.plugin.configManager.getSimpleMessage("casting.no-longer").send(getCaster().getPlayer()); - } - }.runTask(MMOCore.plugin); - getCaster().leaveSkillCasting(); - } + if (event.getPressed() != mainKey) return; + + // Extra option to improve support with other plugins + final Player player = event.getPlayer(); + if (disableSneak && player.isSneaking()) return; + + MMOCore.plugin.soundManager.getSound(SoundEvent.SPELL_CAST_END).playTo(player); + + new BukkitRunnable() { + @Override + public void run() { + MMOCore.plugin.configManager.getSimpleMessage("casting.no-longer").send(getCaster().getPlayer()); + } + }.runTask(MMOCore.plugin); + getCaster().leaveSkillCasting(); } private String getFormat(PlayerData data) { diff --git a/MMOCore-Dist/src/main/resources/config.yml b/MMOCore-Dist/src/main/resources/config.yml index 31a7eaae..286e3cdb 100644 --- a/MMOCore-Dist/src/main/resources/config.yml +++ b/MMOCore-Dist/src/main/resources/config.yml @@ -90,6 +90,7 @@ should-cobblestone-generators-give-exp: false skill-casting: mode: SKILL_BAR open: SWAP_HANDS + disable-sneak: false loot-chests: