Skip to content

Commit

Permalink
Add event support for attribute value changed as well as modifier cha…
Browse files Browse the repository at this point in the history
…nges
  • Loading branch information
steffen-wilke committed Dec 8, 2024
1 parent 39f4240 commit 317830b
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 55 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package de.gurkenlabs.litiengine.attributes;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
* An attribute is a numerical representation of a property that can be adjusted using {@link AttributeModifier}s.
Expand All @@ -11,70 +13,82 @@
* registered modifications. This is e.g. useful when a property might only be changed for a certain period of time or
* we need to know the original value of a property.
* </p>
*
*
* <p>
* <i> An example use-case are player stats that might be affected throughout the game (e.g. via certain skills,
* upgrades or level-ups). </i>
* </p>
*
* @param <T>
* The type of the attribute value.
* @param <T> The type of the attribute value.
*/
public class Attribute<T extends Number> {

private final List<AttributeModifier<T>> modifiers;
private final Collection<AttributeValueListener> listeners = ConcurrentHashMap.newKeySet();
private final AttributeModifierListener attributeModifierListener = this::evaluateValue;

private T baseValue;

private T value;

/**
* Initializes a new instance of the {@code Attribute} class.
*
* @param initialValue
* The initial value
* @param initialValue The initial value
*/
public Attribute(final T initialValue) {
this.modifiers = new ArrayList<>();
this.baseValue = initialValue;
this.value = this.applyModifiers(this.getBase());
}

/**
* Adds the specified modifier to this attribute.
*
* @param modifier
* The modifier to be added to this instance.
*
* @param modifier The modifier to be added to this instance.
*/
public void addModifier(final AttributeModifier<T> modifier) {
if (this.getModifiers().contains(modifier)) {
return;
}

this.getModifiers().add(modifier);
modifier.onChanged(attributeModifierListener);
Collections.sort(this.getModifiers());
this.evaluateValue();
}

public void onValueChanged(AttributeValueListener listener) {
this.listeners.add(listener);
}

public void removeListener(AttributeValueListener listener) {
this.listeners.add(listener);
}

/**
* Removes the specified modifier from this attribute.
*
* @param modifier
* The modifier to be removed from this instance.
*
* @param modifier The modifier to be removed from this instance.
*/
public void removeModifier(final AttributeModifier<T> modifier) {
this.getModifiers().remove(modifier);
modifier.removeListener(attributeModifierListener);
Collections.sort(this.getModifiers());
this.evaluateValue();
}

/**
* Gets the current value of this attribute, respecting all the registered {@code AttributeModifier}s.
*
*
* @return The current value of this attribute.
*/
public T get() {
return this.applyModifiers(this.getBase());
return this.value;
}

/**
* Gets the raw base value of this attribute without applying any modifications.
*
*
* @return The raw base value of this attribute.
*/
public T getBase() {
Expand All @@ -83,7 +97,7 @@ public T getBase() {

/**
* Gets all modifiers added to this instance.
*
*
* @return All modifiers added to this instance.
*/
public List<AttributeModifier<T>> getModifiers() {
Expand All @@ -92,9 +106,8 @@ public List<AttributeModifier<T>> getModifiers() {

/**
* Determines whether the specified modifier instance is added to this attribute instance.
*
* @param modifier
* The modifier to check for.
*
* @param modifier The modifier to check for.
* @return True if the modifier was added to this attribute instance; otherwise false.
*/
public boolean isModifierApplied(final AttributeModifier<T> modifier) {
Expand All @@ -103,44 +116,48 @@ public boolean isModifierApplied(final AttributeModifier<T> modifier) {

/**
* Adjusts the base value of this attribute once with the specified modifier.
*
* @param modifier
* The modifier used to adjust this attribute's base value.
*
*
* @param modifier The modifier used to adjust this attribute's base value.
* @see #getBase()
* @see #setBaseValue(Number)
*/
public void modifyBaseValue(final AttributeModifier<T> modifier) {
this.baseValue = modifier.modify(this.getBase());
this.setBaseValue(modifier.modify(this.getBase()));
}

/**
* Adjusts the base value of this attribute once with the specified modifier.
*
* @param modification
* The modification type.
* @param value
* The modification value to be applied.
* @param modification The modification type.
* @param value The modification value to be applied.
*/
public void modifyBaseValue(final Modification modification, double value) {
this.baseValue = new AttributeModifier<T>(modification, value).modify(this.getBase());
this.modifyBaseValue(new AttributeModifier<>(modification, value));
}

/**
* Sets the base value of this attribute.
*
* @param baseValue
* The base value to be set.
*
* @param baseValue The base value to be set.
*/
public void setBaseValue(final T baseValue) {
this.baseValue = baseValue;
this.evaluateValue();
}

protected void evaluateValue() {
final T previousValue = this.value;
this.value = this.applyModifiers(this.getBase());

if (previousValue != null && !previousValue.equals(this.value)) {
this.fireValueChangedEvent();
}
}

/**
* Apply modifiers.
*
* @param baseValue
* the base value
* @param baseValue the base value
* @return the t
*/
protected T applyModifiers(final T baseValue) {
Expand All @@ -152,6 +169,12 @@ protected T applyModifiers(final T baseValue) {
return currentValue;
}

private void fireValueChangedEvent() {
for (var listener : this.listeners) {
listener.valueChanged();
}
}

@Override
public String toString() {
return this.get() == null ? null : this.get().toString();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package de.gurkenlabs.litiengine.attributes;


import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;

/**
* An attribute modifier allows to modify attributes by the specified Modification and modify value.
*
Expand All @@ -8,6 +12,7 @@
* @see Attribute#modifyBaseValue(AttributeModifier)
*/
public class AttributeModifier<T extends Number> implements Comparable<AttributeModifier<T>> {
private final Collection<AttributeModifierListener> listeners = ConcurrentHashMap.newKeySet();
private final Modification modification;
private double modifyValue;
private boolean active;
Expand Down Expand Up @@ -50,14 +55,12 @@ public boolean equals(Object obj) {
return super.equals(obj);
}

/**
* Generates a hash code for this attribute modifier.
*
* @return The hash code.
*/
@Override
public int hashCode() {
return super.hashCode();
public void onChanged(AttributeModifierListener listener) {
this.listeners.add(listener);
}

public void removeListener(AttributeModifierListener listener) {
this.listeners.add(listener);
}

/**
Expand Down Expand Up @@ -114,7 +117,12 @@ public T modify(final T modvalue) {
* @param value The new modify value.
*/
public void setModifyValue(double value) {
var previous = this.modifyValue;
this.modifyValue = value;

if (previous != this.modifyValue) {
this.fireChangedEvent();
}
}

/**
Expand All @@ -123,7 +131,12 @@ public void setModifyValue(double value) {
* @param active True to activate, false to deactivate.
*/
public void setActive(boolean active) {
var previous = this.active;
this.active = active;

if (previous != this.active) {
this.fireChangedEvent();
}
}

@SuppressWarnings("unchecked")
Expand All @@ -144,4 +157,10 @@ private T ensureType(final Double modValue, final T originalValue) {

return null;
}

private void fireChangedEvent() {
for (var listener : this.listeners) {
listener.modifierChanged();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package de.gurkenlabs.litiengine.attributes;

import java.util.EventListener;

@FunctionalInterface
public interface AttributeModifierListener extends EventListener {
void modifierChanged();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package de.gurkenlabs.litiengine.attributes;

import java.util.EventListener;

@FunctionalInterface
public interface AttributeValueListener extends EventListener {
void valueChanged();
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ public class RangeAttribute<T extends Number> extends Attribute<T> {
private final List<AttributeModifier<T>> minModifiers;
private final List<AttributeModifier<T>> maxModifiers;
private T minBaseValue;

private T maxBaseValue;
private T minValue;
private T maxValue;

/**
* Initializes a new instance of the {@code RangeAttribute} class.
Expand All @@ -28,6 +29,7 @@ public RangeAttribute(final T maxValue, final T minValue, final T baseValue) {
this.maxModifiers = new CopyOnWriteArrayList<>();
this.maxBaseValue = maxValue;
this.minBaseValue = minValue;
this.evaluateValue();
}

public void addMinModifier(final AttributeModifier<T> modifier) {
Expand All @@ -37,6 +39,7 @@ public void addMinModifier(final AttributeModifier<T> modifier) {

this.getMinModifiers().add(modifier);
Collections.sort(this.getMinModifiers());
this.evaluateValue();
}

public void addMaxModifier(final AttributeModifier<T> modifier) {
Expand All @@ -46,20 +49,20 @@ public void addMaxModifier(final AttributeModifier<T> modifier) {

this.getMaxModifiers().add(modifier);
Collections.sort(this.getMaxModifiers());
this.evaluateValue();
}

@Override
public T get() {
final T current = this.applyModifiers(this.getBase());
return this.valueInRange(current);
return this.valueInRange(super.get());
}

public T getMin() {
return this.applyMinModifiers(this.minBaseValue);
return this.minValue;
}

public T getMax() {
return this.applyMaxModifiers(this.maxBaseValue);
return this.maxValue;
}

public float getRelativeCurrentValue() {
Expand All @@ -69,10 +72,12 @@ public float getRelativeCurrentValue() {
@Override
public void modifyBaseValue(final AttributeModifier<T> modifier) {
this.setBaseValue(this.valueInRange(modifier.modify(this.getBase())));
this.evaluateValue();
}

public void modifyMaxBaseValue(final AttributeModifier<T> modifier) {
this.maxBaseValue = modifier.modify(this.maxBaseValue);
this.evaluateValue();
}

public void setToMin() {
Expand All @@ -85,10 +90,12 @@ public void setToMax() {

public void setMaxBaseValue(final T maxValue) {
this.maxBaseValue = maxValue;
this.evaluateValue();
}

public void setMinBaseValue(final T minValue) {
this.minBaseValue = minValue;
this.evaluateValue();
}

protected List<AttributeModifier<T>> getMinModifiers() {
Expand Down Expand Up @@ -117,9 +124,16 @@ protected T applyMaxModifiers(final T maxValue) {
return currentValue;
}

@Override
protected void evaluateValue() {
super.evaluateValue();
this.minValue = this.applyMinModifiers(this.minBaseValue);
this.maxValue = this.applyMaxModifiers(this.maxBaseValue);
}

private T valueInRange(final T value) {
if (value.doubleValue() < this.minBaseValue.doubleValue()) {
return this.minBaseValue;
if (value.doubleValue() < this.getMin().doubleValue()) {
return this.getMin();
} else if (value.doubleValue() > this.getMax().doubleValue()) {
return this.getMax();
}
Expand Down
Loading

0 comments on commit 317830b

Please sign in to comment.