com.sucy.skill.manager.AttributeManager Maven / Gradle / Ivy
Show all versions of proskillapi Show documentation
/**
* SkillAPI
* com.sucy.skill.manager.AttributeManager
*
* The MIT License (MIT)
*
* Copyright (c) 2014 Steven Sucy
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software") to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.sucy.skill.manager;
import com.sucy.skill.SkillAPI;
import com.sucy.skill.api.player.PlayerData;
import com.sucy.skill.api.util.DamageLoreRemover;
import com.sucy.skill.api.util.Data;
import com.sucy.skill.data.formula.Formula;
import com.sucy.skill.data.formula.value.CustomValue;
import com.sucy.skill.dynamic.ComponentType;
import com.sucy.skill.dynamic.EffectComponent;
import com.sucy.skill.gui.tool.GUIData;
import com.sucy.skill.gui.tool.GUIPage;
import com.sucy.skill.gui.tool.GUITool;
import com.sucy.skill.gui.tool.IconHolder;
import com.sucy.skill.log.LogType;
import com.sucy.skill.log.Logger;
import mc.promcteam.engine.mccore.config.CommentedConfig;
import mc.promcteam.engine.mccore.config.parse.DataSection;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.Damageable;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.*;
import java.util.stream.Collectors;
/**
* Handles loading and accessing individual
* attributes from the configuration.
*/
public class AttributeManager {
// Keys for supported stat modifiers
public static final String HEALTH = "health";
public static final String MANA = "mana";
public static final String MANA_REGEN = "mana-regen";
public static final String PHYSICAL_DAMAGE = "physical-damage";
public static final String MELEE_DAMAGE = "melee-damage";
public static final String PROJECTILE_DAMAGE = "projectile-damage";
public static final String PHYSICAL_DEFENSE = "physical-defense";
public static final String MELEE_DEFENSE = "melee-defense";
public static final String PROJECTILE_DEFENSE = "projectile-defense";
public static final String SKILL_DAMAGE = "skill-damage";
public static final String SKILL_DEFENSE = "skill-defense";
public static final String MOVE_SPEED = "move-speed";
public static final String ATTACK_SPEED = "attack-speed";
public static final String ARMOR = "armor";
public static final String LUCK = "luck";
public static final String ARMOR_TOUGHNESS = "armor-toughness";
public static final String EXPERIENCE = "exp";
public static final String HUNGER = "hunger";
public static final String HUNGER_HEAL = "hunger-heal";
public static final String COOLDOWN = "cooldown";
public static final String KNOCKBACK_RESIST = "knockback-resist";
private final HashMap attributes = new LinkedHashMap<>();
private final HashMap lookup = new HashMap<>();
private final HashMap> byStat = new HashMap<>();
private final HashMap> byComponent = new HashMap<>();
/**
* Sets up the attribute manager, loading the attribute
* data from the configuration. This is handled by SkillAPI
* automatically so other plugins should not instantiate
* this class.
*
* @param api SkillAPI reference
*/
public AttributeManager(SkillAPI api) {
load(api);
}
/**
* Retrieves an attribute template
*
* @param key attribute key
* @return template for the attribute
*/
public Attribute getAttribute(String key) {
return lookup.get(key.toLowerCase());
}
/**
* Unsafe getter for the attribute data.
*
* Do not use this method or modify it's return value unless
* you know exactly what you are doing.
*
* @return attributes map
*/
public HashMap getAttributes() {
return attributes;
}
public List forStat(final String key) {
return byStat.get(key.toLowerCase());
}
public List forComponent(final EffectComponent component, final String key) {
return byComponent.get(component.getKey() + "-" + key.toLowerCase());
}
/**
* Retrieves the available attribute keys. This
* does not include display names for attributes.
*
* @return set of available attribute keys
*/
public Set getKeys() {
return attributes.keySet();
}
/**
* Retrieves the available attribute keys including
* both display names and config keys.
*
* @return display name and config keys for attributes
*/
public Set getLookupKeys() {
return lookup.keySet();
}
/**
* Normalizes a config key or name into the config key
* for a unified identifier to store stats under.
*
* @param key key to normalize
* @return config key
*/
public String normalize(String key) {
final Attribute attribute = lookup.get(key.toLowerCase());
if (attribute == null) {
throw new IllegalArgumentException("Invalid attribute - " + key);
}
return attribute.getKey();
}
/**
* Loads attribute data from the config
*
* @param api SkillAPI reference
*/
private void load(SkillAPI api) {
CommentedConfig config = new CommentedConfig(api, "attributes");
config.saveDefaultConfig();
DataSection data = config.getConfig();
Logger.log(LogType.ATTRIBUTE_LOAD, 1, "Loading attributes...");
for (String key : data.keys()) {
Logger.log(LogType.ATTRIBUTE_LOAD, 2, " - " + key);
Attribute attribute = new Attribute(data.getSection(key), key);
attributes.put(attribute.getKey(), attribute);
lookup.put(attribute.getKey(), attribute);
lookup.put(attribute.getName().toLowerCase(), attribute);
}
GUIData attribs = GUITool.getAttributesMenu();
if (!attribs.isValid()) {
int i = 0;
GUIPage page = attribs.getPage(0);
for (String key : attributes.keySet()) {
if (i < 54) {
page.set(i++, key);
}
}
attribs.resize((attributes.size() + 8) / 9);
}
}
/**
* A single attribute template
*/
public class Attribute implements IconHolder {
private static final String DISPLAY = "display";
private static final String GLOBAL = "global";
private static final String CONDITION = "condition";
private static final String MECHANIC = "mechanic";
private static final String TARGET = "target";
private static final String STATS = "stats";
private static final String MAX = "max";
// iomatix: cost and the modifier
private static final String COSTBASE = "cost_base";
private static final String COSTMOD = "cost_modifier";
// Attribute description
private String key;
private String display;
private ItemStack icon;
private int max;
// iomatix: cost and the modifier
private int costBase;
private double costModifier;
// Dynamic global modifiers
private Map> dynamicModifiers = new EnumMap<>(ComponentType.class);
// General stat modifiers
private HashMap statModifiers = new HashMap<>();
/**
* Creates a new attribute, loading the settings from the given
* config data.
*
* @param data config data to load from
* @param key the key the attribute was labeled under
*/
public Attribute(DataSection data, String key) {
this.key = key.toLowerCase();
this.display = data.getString(DISPLAY, key);
this.icon = Data.parseIcon(data);
this.max = data.getInt(MAX, 999);
// iomatix: base_cost and the modifier
// e.g. per 0.3 increase -> 0.3=>0, 0.6=>0, 0.9=>0, 1.2=>1 (the first additional cost point) etc.
this.costBase = data.getInt(COSTBASE, 1);
this.costModifier = data.getDouble(COSTMOD, 0.0);
// Load dynamic global settings
DataSection globals = data.getSection(GLOBAL);
if (globals != null) {
loadGroup(globals.getSection(CONDITION), ComponentType.CONDITION);
loadGroup(globals.getSection(MECHANIC), ComponentType.MECHANIC);
loadGroup(globals.getSection(TARGET), ComponentType.TARGET);
}
// Load stat settings
DataSection stats = data.getSection(STATS);
if (stats != null) {
for (String stat : stats.keys()) {
loadStatModifier(stats, stat);
}
}
}
/**
* Retrieves the config key of the attribute
*
* @return config key of the attribute
*/
public String getKey() {
return key;
}
/**
* Retrieves the name for the attribute
*
* @return name of the attribute
*/
public String getName() {
return display;
}
/**
* Retrieves the icon for the attribute
*
* @return icon of the attribute
*/
@Override
public ItemStack getIcon(PlayerData data) {
ItemStack item = new ItemStack(icon.getType());
ItemMeta iconMeta = icon.getItemMeta();
ItemMeta meta = item.getItemMeta();
if (meta != null && iconMeta != null) {
meta.setDisplayName(filter(data, iconMeta.getDisplayName()));
List iconLore = iconMeta.getLore();
List lore = iconLore != null
? iconLore.stream().map(iconLine -> filter(data, iconLine)).collect(Collectors.toList())
: new ArrayList<>();
if (meta instanceof Damageable) {
((Damageable) meta).setDamage(((Damageable) iconMeta).getDamage());
}
if (iconMeta.hasCustomModelData()) {
meta.setCustomModelData(iconMeta.getCustomModelData());
}
meta.setLore(lore);
item.setItemMeta(meta);
}
return DamageLoreRemover.removeAttackDmg(item);
}
@Override
public boolean isAllowed(final Player player) {
return true;
}
/**
* Filters a line of the icon according to the player data
*
* @param data player data to use
* @param text line of text to filter
* @return filtered line
*/
private String filter(PlayerData data, String text) {
return text
.replace("{amount}", "" + data.getInvestedAttribute(key))
.replace("{total}", "" + data.getAttribute(key))
;
}
/**
* @return icon for the attribute for use in the GUI editor
*/
public ItemStack getToolIcon() {
ItemStack icon = new ItemStack(this.icon.getType());
ItemMeta meta = icon.getItemMeta();
ItemMeta iconMeta = this.icon.getItemMeta();
if (meta == null || iconMeta == null) {
return icon;
}
meta.setDisplayName(key);
List lore = iconMeta.hasLore()
? iconMeta.getLore()
: null;
if (lore == null) {
lore = new ArrayList<>();
}
if (iconMeta.hasDisplayName())
lore.add(0, iconMeta.getDisplayName());
meta.setLore(lore);
icon.setItemMeta(meta);
return icon;
}
/**
* Retrieves the max amount the attribute can be raised to
*
* @return max attribute amount
*/
public int getMax() {
return max;
}
// iomatix: base_cost and the modifier
/**
* Retrieves the starting cost of the attribute upgrade.
*
* @return costBase amount
*/
public int getCostBase(){
return costBase;
}
/**
* Retrieves the raw additional cost of the attribute upgrade.
* It should be converted to int e.g. using (int) Math.floor function.
*
* @return costModifier
*/
public double getCostMod(){
return costModifier;
}
/**
* Modifies a dynamic condition's value
*
* @param component component to modify for
* @param key key of the value to modify
* @param value base value
* @param amount amount of attribute points
* @return modified value
*/
public double modify(EffectComponent component, String key, double value, int amount) {
key = component.getKey() + "-" + key.toLowerCase();
final Map map = dynamicModifiers.get(component.getType());
if (map.containsKey(key)) {
AttributeValue[] list = map.get(key);
for (AttributeValue attribValue : list) {
if (attribValue.passes(component)) {
return attribValue.apply(value, amount);
}
}
}
return value;
}
/**
* Modifies a stat value
*
* @param key key of the stat
* @param base base value of the stat
* @param amount amount of attribute points
* @return modified stat value
*/
public double modifyStat(String key, double base, int amount) {
if (statModifiers.containsKey(key)) {
return statModifiers.get(key).compute(base, amount);
}
return base;
}
/**
* Loads a dynamic group globals settings into the given map
*
* @param data config data to load from
* @param type the component type to load for
*/
private void loadGroup(DataSection data, ComponentType type) {
if (data == null) {
return;
}
final Map target = dynamicModifiers.computeIfAbsent(type, t -> new HashMap<>());
for (String key : data.keys()) {
final String lower = key.toLowerCase();
Logger.log(LogType.ATTRIBUTE_LOAD, 2, " SkillMod: " + key);
final String value = data.getString(key);
final String[] formulas = value.split("\\|");
final AttributeValue[] values = new AttributeValue[formulas.length];
int i = 0;
for (final String formula : formulas) {
values[i++] = new AttributeValue(formula);
}
target.put(lower, values);
if (!byComponent.containsKey(lower)) {
byComponent.put(lower, new ArrayList<>());
}
byComponent.get(lower).add(this);
}
}
/**
* Loads a stat modifier from the config data
*
* @param data config data to load from
* @param key key of the stat modifier
*/
private void loadStatModifier(DataSection data, String key) {
if (data.has(key)) {
Logger.log(LogType.ATTRIBUTE_LOAD, 2, " StatMod: " + key);
statModifiers.put(
key,
new Formula(data.getString(key, "v"), new CustomValue("v"), new CustomValue("a")));
if (!byStat.containsKey(key)) {
byStat.put(key, new ArrayList<>());
}
byStat.get(key).add(this);
}
}
}
/**
* Represents one formula modifier for an attribute
* that can have conditions
*/
public class AttributeValue {
private Formula formula;
private HashMap conditions = new HashMap<>();
/**
* Loads the attribute value that starts with the formula
* and can have as many conditions as desired after
*
* @param data data string for the value
*/
public AttributeValue(String data) {
String[] pieces = data.split(":");
formula = new Formula(pieces[0], new CustomValue("v"), new CustomValue("a"));
for (int i = 1; i < pieces.length; i++) {
String[] sides = pieces[i].split("=");
conditions.put(sides[0], sides[1]);
Logger.log(LogType.ATTRIBUTE_LOAD, 3, " Condition: " + sides[0] + " / " + sides[1]);
}
}
/**
* Checks whether the formula should be applied to the component
*
* @param component component to check for conditions against
* @return true if passes the conditions
*/
public boolean passes(EffectComponent component) {
for (String key : conditions.keySet()) {
if (!component.getSettings().getString(key).equalsIgnoreCase(conditions.get(key))) {
return false;
}
}
return true;
}
/**
* Checks the conditions for the given component
*
* @param value base value
* @param amount amount of attribute points
* @return the modified value if the conditions passed or the base value if they failed
*/
public double apply(double value, int amount) {
return formula.compute(value, amount);
}
}
}