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..83bc2253
--- /dev/null
+++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/api/player/attribute/MMOCoreAttributeStatHandler.java
@@ -0,0 +1,44 @@
+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;
+ }
+
+ @Override
+ public void runUpdate(StatMap statMap) {
+ final PlayerData playerData = MMOCore.plugin.dataProvider.getDataManager().get(statMap.getPlayerData().getUniqueId());
+ playerData.getAttributes().getInstance(attr).updateStats();
+ }
+
+ @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);
+ }
+ }
}