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); + } + } }