Merge pull request #978 from jayoevans/wip

Attribute System
This commit is contained in:
Jay 2018-08-17 10:03:20 +10:00 committed by GitHub
commit 19c5681a77
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 181 additions and 279 deletions

View file

@ -3,6 +3,7 @@ package com.projectkorra.projectkorra.ability;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -17,6 +18,8 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ConcurrentSkipListMap;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.tuple.Pair;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.Location; import org.bukkit.Location;
@ -39,6 +42,9 @@ import com.projectkorra.projectkorra.ability.util.ComboManager;
import com.projectkorra.projectkorra.ability.util.MultiAbilityManager; import com.projectkorra.projectkorra.ability.util.MultiAbilityManager;
import com.projectkorra.projectkorra.ability.util.MultiAbilityManager.MultiAbilityInfo; import com.projectkorra.projectkorra.ability.util.MultiAbilityManager.MultiAbilityInfo;
import com.projectkorra.projectkorra.ability.util.PassiveManager; import com.projectkorra.projectkorra.ability.util.PassiveManager;
import com.projectkorra.projectkorra.attribute.Attribute;
import com.projectkorra.projectkorra.attribute.AttributeModifier;
import com.projectkorra.projectkorra.attribute.AttributePriority;
import com.projectkorra.projectkorra.configuration.ConfigManager; import com.projectkorra.projectkorra.configuration.ConfigManager;
import com.projectkorra.projectkorra.event.AbilityEndEvent; import com.projectkorra.projectkorra.event.AbilityEndEvent;
import com.projectkorra.projectkorra.event.AbilityProgressEvent; import com.projectkorra.projectkorra.event.AbilityProgressEvent;
@ -73,6 +79,7 @@ public abstract class CoreAbility implements Ability {
private static final Map<Class<? extends CoreAbility>, CoreAbility> ABILITIES_BY_CLASS = new ConcurrentHashMap<>(); private static final Map<Class<? extends CoreAbility>, CoreAbility> ABILITIES_BY_CLASS = new ConcurrentHashMap<>();
private static final double DEFAULT_COLLISION_RADIUS = 0.3; private static final double DEFAULT_COLLISION_RADIUS = 0.3;
private static final List<String> ADDON_PLUGINS = new ArrayList<>(); private static final List<String> ADDON_PLUGINS = new ArrayList<>();
private static final Map<Class<? extends CoreAbility>, Map<String, Field>> ATTRIBUTE_FIELDS = new HashMap<>();
private static int idCounter; private static int idCounter;
@ -80,12 +87,15 @@ public abstract class CoreAbility implements Ability {
protected BendingPlayer bPlayer; protected BendingPlayer bPlayer;
protected FlightHandler flightHandler; protected FlightHandler flightHandler;
private final Map<String, Map<AttributePriority, Set<Pair<Number, AttributeModifier>>>> attributeModifiers = new HashMap<>();
private final Map<String, Object> attributeValues = new HashMap<>();
private boolean started; private boolean started;
private boolean removed; private boolean removed;
private boolean hidden; private boolean hidden;
private int id; private int id;
private long startTime; private long startTime;
private long startTick; private long startTick;
private boolean attributesModified;
static { static {
idCounter = Integer.MIN_VALUE; idCounter = Integer.MIN_VALUE;
@ -103,6 +113,15 @@ public abstract class CoreAbility implements Ability {
* @see #getAbility(String) * @see #getAbility(String)
*/ */
public CoreAbility() { public CoreAbility() {
for (Field field : getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(Attribute.class)) {
Attribute attribute = field.getAnnotation(Attribute.class);
if (!ATTRIBUTE_FIELDS.containsKey(getClass())) {
ATTRIBUTE_FIELDS.put(getClass(), new HashMap<>());
}
ATTRIBUTE_FIELDS.get(getClass()).put(attribute.value(), field);
}
}
} }
/** /**
@ -151,6 +170,7 @@ public abstract class CoreAbility implements Ability {
this.remove(); this.remove();
return; return;
} }
this.started = true; this.started = true;
this.startTime = System.currentTimeMillis(); this.startTime = System.currentTimeMillis();
final Class<? extends CoreAbility> clazz = this.getClass(); final Class<? extends CoreAbility> clazz = this.getClass();
@ -237,6 +257,10 @@ public abstract class CoreAbility implements Ability {
} }
try { try {
if (!abil.attributesModified) {
abil.modifyAttributes();
abil.attributesModified = true;
}
abil.progress(); abil.progress();
Bukkit.getServer().getPluginManager().callEvent(new AbilityProgressEvent(abil)); Bukkit.getServer().getPluginManager().callEvent(new AbilityProgressEvent(abil));
} }
@ -930,6 +954,76 @@ public abstract class CoreAbility implements Ability {
return locations; return locations;
} }
public CoreAbility addAttributeModifier(String attribute, Number value, AttributeModifier modification) {
return addAttributeModifier(attribute, value, modification, AttributePriority.MEDIUM);
}
public CoreAbility addAttributeModifier(String attribute, Number value, AttributeModifier modificationType, AttributePriority priority) {
Validate.notNull(attribute, "attribute cannot be null");
Validate.notNull(value, "value cannot be null");
Validate.notNull(modificationType, "modifierMethod cannot be null");
Validate.notNull(priority, "priority cannot be null");
Validate.isTrue(ATTRIBUTE_FIELDS.containsKey(getClass()) && ATTRIBUTE_FIELDS.get(getClass()).containsKey(attribute), "Attribute " + attribute + " is not a defined Attribute for " + getName());
if (!attributeModifiers.containsKey(attribute)) {
attributeModifiers.put(attribute, new HashMap<>());
}
if (!attributeModifiers.get(attribute).containsKey(priority)) {
attributeModifiers.get(attribute).put(priority, new HashSet<>());
}
attributeModifiers.get(attribute).get(priority).add(Pair.of(value, modificationType));
return this;
}
public CoreAbility setAttribute(String attribute, Object value) {
Validate.notNull(attribute, "attribute cannot be null");
Validate.notNull(value, "value cannot be null");
Validate.isTrue(ATTRIBUTE_FIELDS.containsKey(getClass()) && ATTRIBUTE_FIELDS.get(getClass()).containsKey(attribute), "Attribute " + attribute + " is not a defined Attribute for " + getName());
attributeValues.put(attribute, value);
return this;
}
private void modifyAttributes() {
System.out.println(attributeModifiers);
for (String attribute : attributeModifiers.keySet()) {
Field field = ATTRIBUTE_FIELDS.get(getClass()).get(attribute);
boolean accessibility = field.isAccessible();
field.setAccessible(true);
try {
for (AttributePriority priority : AttributePriority.values()) {
if (attributeModifiers.get(attribute).containsKey(priority)) {
for (Pair<Number, AttributeModifier> pair : attributeModifiers.get(attribute).get(priority)) {
Object get = field.get(this);
Validate.isTrue(get instanceof Number, "The field " + field.getName() + " cannot algebraically be modified.");
Number oldValue = (Number) field.get(this);
Number newValue = pair.getRight().performModification(oldValue, pair.getLeft());
field.set(this, newValue);
}
}
}
}
catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
finally {
field.setAccessible(accessibility);
}
}
attributeValues.forEach((attribute, value) -> {
Field field = ATTRIBUTE_FIELDS.get(getClass()).get(attribute);
boolean accessibility = field.isAccessible();
field.setAccessible(true);
try {
field.set(this, value);
}
catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
finally {
field.setAccessible(accessibility);
}
});
}
/** /**
* @return the current FileConfiguration for the plugin * @return the current FileConfiguration for the plugin
*/ */

View file

@ -30,7 +30,12 @@ import com.projectkorra.projectkorra.util.DamageHandler;
public class Suffocate extends AirAbility { public class Suffocate extends AirAbility {
public static enum SpiralType { public static enum SpiralType {
HORIZONTAL1, HORIZONTAL2, VERTICAL1, VERTICAL2, DIAGONAL1, DIAGONAL2 HORIZONTAL1,
HORIZONTAL2,
VERTICAL1,
VERTICAL2,
DIAGONAL1,
DIAGONAL2
}; };
private boolean started; private boolean started;

View file

@ -9,19 +9,7 @@ import java.lang.annotation.Target;
@Target(value = { ElementType.FIELD }) @Target(value = { ElementType.FIELD })
public @interface Attribute { public @interface Attribute {
/** public String value();
* This is the attribute name that is added to the CoreAbility name. E.g.
* Returning "Damage" on a FireBlast ability would make the attribute
* "FireBlastDamage"
*/
String value() default "";
/**
* This is for overriding the attribute name if the name you want should not
* come from the CoreAbility name. E.g. Returning "FastSwimSpeed" would make
* the Attribute name "FastSwimSpeed", instead of ability + "FastSwimSpeed"
*/
String attribute() default "";
public static final String SPEED = "Speed"; public static final String SPEED = "Speed";
public static final String RANGE = "Range"; public static final String RANGE = "Range";
@ -34,4 +22,5 @@ public @interface Attribute {
public static final String POWER = "Power"; public static final String POWER = "Power";
public static final String WIDTH = "Width"; public static final String WIDTH = "Width";
public static final String HEIGHT = "Height"; public static final String HEIGHT = "Height";
} }

View file

@ -1,64 +1,82 @@
package com.projectkorra.projectkorra.attribute; package com.projectkorra.projectkorra.attribute;
import org.bukkit.plugin.Plugin; import org.apache.commons.lang.Validate;
import com.projectkorra.projectkorra.ProjectKorra; public enum AttributeModifier {
import com.projectkorra.projectkorra.ability.CoreAbility;
import com.projectkorra.projectkorra.ability.FireAbility;
import com.projectkorra.projectkorra.ability.WaterAbility;
public class AttributeModifier { ADDITION((oldValue, modifier) -> {
if (oldValue instanceof Double || modifier instanceof Double) {
return oldValue.doubleValue() + modifier.doubleValue();
} else if (oldValue instanceof Float || modifier instanceof Float) {
return oldValue.floatValue() + modifier.floatValue();
} else if (oldValue instanceof Long || modifier instanceof Long) {
return oldValue.longValue() + modifier.longValue();
} else if (oldValue instanceof Integer || modifier instanceof Integer) {
return oldValue.intValue() + modifier.intValue();
}
return 0;
}),
SUBTRACTION((oldValue, modifier) -> {
if (oldValue instanceof Double || modifier instanceof Double) {
return oldValue.doubleValue() - modifier.doubleValue();
} else if (oldValue instanceof Float || modifier instanceof Float) {
return oldValue.floatValue() - modifier.floatValue();
} else if (oldValue instanceof Long || modifier instanceof Long) {
return oldValue.longValue() - modifier.longValue();
} else if (oldValue instanceof Integer || modifier instanceof Integer) {
return oldValue.intValue() - modifier.intValue();
}
return 0;
}),
MULTIPLICATION((oldValue, modifier) -> {
if (oldValue instanceof Double || modifier instanceof Double) {
return oldValue.doubleValue() * modifier.doubleValue();
} else if (oldValue instanceof Float || modifier instanceof Float) {
return oldValue.floatValue() * modifier.floatValue();
} else if (oldValue instanceof Long || modifier instanceof Long) {
return oldValue.longValue() * modifier.longValue();
} else if (oldValue instanceof Integer || modifier instanceof Integer) {
return oldValue.intValue() * modifier.intValue();
}
return 0;
}),
DIVISION((oldValue, modifier) -> {
if (oldValue instanceof Double || modifier instanceof Double) {
return oldValue.doubleValue() / modifier.doubleValue();
} else if (oldValue instanceof Float || modifier instanceof Float) {
return oldValue.floatValue() / modifier.floatValue();
} else if (oldValue instanceof Long || modifier instanceof Long) {
return oldValue.longValue() / modifier.longValue();
} else if (oldValue instanceof Integer || modifier instanceof Integer) {
return oldValue.intValue() / modifier.intValue();
}
return 0;
});
private double modifier = 1.0D; private AttributeModifierMethod modifier;
private final AttributeModifierType type;
public enum AttributeModifierType { private AttributeModifier(AttributeModifierMethod modifier) {
MULTIPLY, ADDITION
};
public AttributeModifier(final String name, final double modifier, final AttributeModifierType type, final Plugin plugin) {
this.modifier = modifier; this.modifier = modifier;
this.type = type;
} }
public AttributeModifier(final String name, final AttributeModifierType type, final Plugin plugin) { public AttributeModifierMethod getModifier() {
this(name, 1.0D, type, plugin); return modifier;
} }
protected AttributeModifier(final String name, final AttributeModifierType type, final double modifier) { public Number performModification(Number oldValue, Number modifier) {
this(name, modifier, type, ProjectKorra.plugin); Validate.isTrue(!(this == DIVISION && modifier.doubleValue() == 0), "modifier cannot be 0");
return this.modifier.performModification(oldValue, modifier);
} }
/** /**
* Should return the modifier that should be applied to the Attribute. Is * Functional interface for modifying fields with the {@link Attribute}
* called every time it is applied, so the value doesn't have to be final. * annotation
*
* @return The modifier
*/ */
public double getModifier(final CoreAbility ability) { @FunctionalInterface
return this.modifier; public interface AttributeModifierMethod {
public Number performModification(Number oldValue, Number modifier);
} }
/**
* Returns what type of math should be done with the modifier.
*
* @return The modifier type
*/
public AttributeModifierType getType() {
return this.type;
}
public static AttributeModifier WATERBENDING_NIGHT = new AttributeModifier("WaterbendingNightModifier", AttributeModifierType.MULTIPLY, 1.0) {
@Override
public double getModifier(final CoreAbility ability) {
return WaterAbility.getNightFactor(ability.getPlayer().getWorld());
}
};
public static AttributeModifier FIREBENDING_DAY = new AttributeModifier("FirebendingDayModifier", AttributeModifierType.MULTIPLY, 1.0) {
@Override
public double getModifier(final CoreAbility ability) {
return FireAbility.getDayFactor(1.0, ability.getPlayer().getWorld());
}
};
} }

View file

@ -0,0 +1,9 @@
package com.projectkorra.projectkorra.attribute;
public enum AttributePriority {
LOW,
MEDIUM,
HIGH;
}

View file

@ -1,104 +0,0 @@
package com.projectkorra.projectkorra.attribute;
import java.lang.reflect.Field;
import org.bukkit.Bukkit;
import com.projectkorra.projectkorra.Element;
import com.projectkorra.projectkorra.ability.CoreAbility;
import com.projectkorra.projectkorra.attribute.AttributeModifier.AttributeModifierType;
import com.projectkorra.projectkorra.event.AttributeModifyEvent;
public class Attributes {
/**
* Modifies all abilities of the provided element for the given attribute
* and modifier
*
* @param element The type of abilities being changed. Can be a subelement.
* @param attribute What attribute to modify
* @param modifier The modifier
*/
public static void modify(final Element element, final String attribute, final AttributeModifier modifier) {
for (final CoreAbility ability : CoreAbility.getAbilitiesByElement(element)) {
modify(ability, attribute, modifier);
}
};
/**
* Modifies all the attribute of the provided ability from the given
* attribute and modifier
*
* @param ability The ability to change
* @param attribute What attribute to modify
* @param modifier The modifier
*/
public static void modify(final CoreAbility ability, final String attribute, final AttributeModifier modifier) {
if (ability.getPlayer() == null) {
for (final CoreAbility ability2 : CoreAbility.getAbilities(ability.getClass())) {
modify(ability2, attribute, modifier);
}
return;
}
for (final Field field : ability.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(Attribute.class)) {
final Attribute annotation = field.getAnnotation(Attribute.class);
String attrToTest = ability.getName() + annotation.value();
if (!annotation.attribute().equals("")) {
attrToTest = annotation.attribute();
}
if (attrToTest.equalsIgnoreCase(attribute)) {
final boolean flag = field.isAccessible();
if (!flag) {
field.setAccessible(true);
}
try {
if (field.getDeclaringClass().equals(Double.TYPE.getClass())) {
final double oldValue = field.getDouble(ability);
double newValue = modifier.getType() == AttributeModifierType.MULTIPLY ? oldValue * modifier.getModifier(ability) : oldValue + modifier.getModifier(ability);
final AttributeModifyEvent event = new AttributeModifyEvent(ability, attribute, oldValue, newValue, modifier);
Bukkit.getPluginManager().callEvent(event);
newValue = event.getNewValue();
field.setDouble(ability, newValue);
} else if (field.getDeclaringClass().equals(Long.TYPE.getClass())) {
final long oldValue = field.getLong(ability);
long newValue = (long) (modifier.getType() == AttributeModifierType.MULTIPLY ? oldValue * modifier.getModifier(ability) : oldValue + modifier.getModifier(ability));
final AttributeModifyEvent event = new AttributeModifyEvent(ability, attribute, oldValue, newValue, modifier);
Bukkit.getPluginManager().callEvent(event);
newValue = (long) event.getNewValue();
field.setLong(ability, newValue);
} else if (field.getDeclaringClass().equals(Integer.TYPE.getClass())) {
final int oldValue = field.getInt(ability);
int newValue = (int) (modifier.getType() == AttributeModifierType.MULTIPLY ? oldValue * modifier.getModifier(ability) : oldValue + modifier.getModifier(ability));
final AttributeModifyEvent event = new AttributeModifyEvent(ability, attribute, oldValue, newValue, modifier);
Bukkit.getPluginManager().callEvent(event);
newValue = (int) event.getNewValue();
field.setInt(ability, newValue);
}
}
catch (final IllegalArgumentException e) {
e.printStackTrace();
}
catch (final IllegalAccessException e) {
e.printStackTrace();
}
if (!flag) {
field.setAccessible(false);
}
}
}
}
}
}

View file

@ -1,58 +0,0 @@
package com.projectkorra.projectkorra.event;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import com.projectkorra.projectkorra.ability.CoreAbility;
import com.projectkorra.projectkorra.attribute.AttributeModifier;
public class AttributeModifyEvent extends Event {
private final CoreAbility ability;
private final String attribute;
private final double oldValue;
private double newValue;
private final AttributeModifier modifier;
public AttributeModifyEvent(final CoreAbility ability, final String attribute, final double oldValue, final double newValue, final AttributeModifier modifier) {
this.ability = ability;
this.attribute = attribute;
this.oldValue = oldValue;
this.newValue = newValue;
this.modifier = modifier;
}
public CoreAbility getAbility() {
return this.ability;
}
public AttributeModifier getModifier() {
return this.modifier;
}
public boolean hasModifier() {
return this.modifier != null;
}
public String getAttribute() {
return this.attribute;
}
public double getOldValue() {
return this.oldValue;
}
public double getNewValue() {
return this.newValue;
}
public void setNewValue(final double newValue) {
this.newValue = newValue;
}
@Override
public HandlerList getHandlers() {
return null;
}
}

View file

@ -19,6 +19,7 @@ import com.projectkorra.projectkorra.ProjectKorra;
import com.projectkorra.projectkorra.ability.AirAbility; import com.projectkorra.projectkorra.ability.AirAbility;
import com.projectkorra.projectkorra.ability.FireAbility; import com.projectkorra.projectkorra.ability.FireAbility;
import com.projectkorra.projectkorra.ability.util.Collision; import com.projectkorra.projectkorra.ability.util.Collision;
import com.projectkorra.projectkorra.attribute.Attribute;
import com.projectkorra.projectkorra.avatar.AvatarState; import com.projectkorra.projectkorra.avatar.AvatarState;
import com.projectkorra.projectkorra.firebending.util.FireDamageTimer; import com.projectkorra.projectkorra.firebending.util.FireDamageTimer;
import com.projectkorra.projectkorra.util.DamageHandler; import com.projectkorra.projectkorra.util.DamageHandler;
@ -35,10 +36,15 @@ public class FireBlast extends FireAbility {
private boolean isFireBurst = false; private boolean isFireBurst = false;
private boolean fireBurstIgnite; private boolean fireBurstIgnite;
private int ticks; private int ticks;
@Attribute(Attribute.COOLDOWN)
private long cooldown; private long cooldown;
@Attribute(Attribute.SPEED)
private double speedFactor; private double speedFactor;
@Attribute(Attribute.RANGE)
private double range; private double range;
@Attribute(Attribute.DAMAGE)
private double damage; private double damage;
@Attribute(Attribute.SPEED)
private double speed; private double speed;
private double collisionRadius; private double collisionRadius;
private double fireTicks; private double fireTicks;

View file

@ -1,57 +0,0 @@
package com.projectkorra.projectkorra.util;
import java.lang.reflect.Field;
import com.projectkorra.projectkorra.ProjectKorra;
import com.projectkorra.projectkorra.ability.CoreAbility;
public class Attribute {
public static boolean setField(final CoreAbility ability, final String field, final Object value) {
try {
final Field _field = ability.getClass().getDeclaredField(field);
final boolean oldVisibility = _field.isAccessible();
_field.setAccessible(true);
try {
_field.set(ability, value);
}
catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
ProjectKorra.log.warning(e.getClass().getName() + ": Failed to set field '" + _field.getName() + "' (" + _field.getType().getSimpleName() + ") in " + ability.getClass().getSimpleName() + " to '" + value.toString() + "' (" + value.getClass().getSimpleName() + ") at");
return false;
}
_field.setAccessible(oldVisibility);
}
catch (NoSuchFieldException | SecurityException e) {
e.printStackTrace();
ProjectKorra.log.warning(e.getClass().getName() + ": Failed to set field '" + field + "' in " + ability.getClass().getSimpleName() + " to '" + value.toString() + "' (" + value.getClass().getSimpleName() + ")");
return false;
}
return true;
}
public static Object getField(final CoreAbility ability, final String field) {
try {
final Field _field = ability.getClass().getDeclaredField(field);
final boolean oldVisibility = _field.isAccessible();
_field.setAccessible(true);
try {
final Object object = _field.get(ability);
_field.setAccessible(oldVisibility);
return object;
}
catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
ProjectKorra.log.warning(e.getClass().getName() + ": Failed to get field '" + _field.getName() + "' in " + ability.getClass().getSimpleName());
return null;
}
}
catch (NoSuchFieldException | SecurityException e) {
e.printStackTrace();
ProjectKorra.log.warning(e.getClass().getName() + ": Failed to get field '" + field + "' in " + ability.getClass().getSimpleName());
return null;
}
}
}