Recalculate damage modifiers in event for old method. Fixes BUKKIT-5681

When we added the new API in EntityDamageEvent to give control over the
various things that modify the final damage done we caused a change in
behavior for users of the old #setDamage(double) method. Before changing
the damage would happen before the modifiers were calculated so they would
be based on the final damage value from the event. Now they are calculated
at the beginning so changing the damage does not change the modifiers.

To allow the old style and the new to coexist we now expose the vanilla
modifer calculations to the event in the form of Function objects. These
are used in #setDamage(double) to calculate the difference in the modifier
between the old damage and the new and apply this difference to the current
modifier. The difference is between the vanilla values for both damage
values and is applied on top of the event's modifier value as this should
make old and new API usage work together in a way that isn't surprising.

By: Travis Watkins <amaranth@ubuntu.com>
This commit is contained in:
Bukkit/Spigot 2014-07-09 16:19:27 -05:00
parent bbc211faba
commit 7396e8f7c4
3 changed files with 45 additions and 7 deletions

View File

@ -2,6 +2,7 @@ package org.bukkit.event.entity;
import java.util.Map;
import com.google.common.base.Function;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
@ -22,8 +23,8 @@ public class EntityDamageByBlockEvent extends EntityDamageEvent {
this.damager = damager;
}
public EntityDamageByBlockEvent(final Block damager, final Entity damagee, final DamageCause cause, final Map<DamageModifier, Double> modifiers) {
super(damagee, cause, modifiers);
public EntityDamageByBlockEvent(final Block damager, final Entity damagee, final DamageCause cause, final Map<DamageModifier, Double> modifiers, final Map<DamageModifier, ? extends Function<? super Double, Double>> modifierFunctions) {
super(damagee, cause, modifiers, modifierFunctions);
this.damager = damager;
}

View File

@ -2,6 +2,7 @@ package org.bukkit.event.entity;
import java.util.Map;
import com.google.common.base.Function;
import org.bukkit.entity.Entity;
/**
@ -21,8 +22,8 @@ public class EntityDamageByEntityEvent extends EntityDamageEvent {
this.damager = damager;
}
public EntityDamageByEntityEvent(final Entity damager, final Entity damagee, final DamageCause cause, final Map<DamageModifier, Double> modifiers) {
super(damagee, cause, modifiers);
public EntityDamageByEntityEvent(final Entity damager, final Entity damagee, final DamageCause cause, final Map<DamageModifier, Double> modifiers, final Map<DamageModifier, ? extends Function<? super Double, Double>> modifierFunctions) {
super(damagee, cause, modifiers, modifierFunctions);
this.damager = damager;
}

View File

@ -10,6 +10,8 @@ import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.util.NumberConversions;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.collect.ImmutableMap;
/**
@ -18,7 +20,9 @@ import com.google.common.collect.ImmutableMap;
public class EntityDamageEvent extends EntityEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private static final DamageModifier[] MODIFIERS = DamageModifier.values();
private static final Function<? super Double, Double> ZERO = Functions.constant(-0.0);
private final Map<DamageModifier, Double> modifiers;
private final Map<DamageModifier, ? extends Function<? super Double, Double>> modifierFunctions;
private final Map<DamageModifier, Double> originals;
private boolean cancelled;
private final DamageCause cause;
@ -30,16 +34,20 @@ public class EntityDamageEvent extends EntityEvent implements Cancellable {
@Deprecated
public EntityDamageEvent(final Entity damagee, final DamageCause cause, final double damage) {
this(damagee, cause, new EnumMap<DamageModifier, Double>(ImmutableMap.of(DamageModifier.BASE, damage)));
this(damagee, cause, new EnumMap<DamageModifier, Double>(ImmutableMap.of(DamageModifier.BASE, damage)), new EnumMap<DamageModifier, Function<? super Double, Double>>(ImmutableMap.of(DamageModifier.BASE, ZERO)));
}
public EntityDamageEvent(final Entity damagee, final DamageCause cause, final Map<DamageModifier, Double> modifiers) {
public EntityDamageEvent(final Entity damagee, final DamageCause cause, final Map<DamageModifier, Double> modifiers, final Map<DamageModifier, ? extends Function<? super Double, Double>> modifierFunctions) {
super(damagee);
Validate.isTrue(modifiers.containsKey(DamageModifier.BASE), "BASE DamageModifier missing");
Validate.isTrue(!modifiers.containsKey(null), "Cannot have null DamageModifier");
Validate.noNullElements(modifiers.values(), "Cannot have null modifier values");
Validate.isTrue(modifiers.keySet().equals(modifierFunctions.keySet()), "Must have a modifier function for each DamageModifier");
Validate.noNullElements(modifierFunctions.values(), "Cannot have null modifier function");
this.originals = new EnumMap<DamageModifier, Double>(modifiers);
this.cause = cause;
this.modifiers = modifiers;
this.modifierFunctions = modifierFunctions;
}
public boolean isCancelled() {
@ -149,11 +157,39 @@ public class EntityDamageEvent extends EntityEvent implements Cancellable {
}
/**
* Sets the raw amount of damage caused by the event
* Sets the raw amount of damage caused by the event.
* <p>
* For compatibility this also recalculates the modifiers and scales
* them by the difference between the modifier for the previous damage
* value and the new one.
*
* @param damage The raw amount of damage caused by the event
*/
public void setDamage(double damage) {
// These have to happen in the same order as the server calculates them, keep the enum sorted
double remaining = damage;
double oldRemaining = getDamage(DamageModifier.BASE);
for (DamageModifier modifier : MODIFIERS) {
if (!isApplicable(modifier)) {
continue;
}
Function<? super Double, Double> modifierFunction = modifierFunctions.get(modifier);
double newVanilla = modifierFunction.apply(remaining);
double oldVanilla = modifierFunction.apply(oldRemaining);
double difference = oldVanilla - newVanilla;
// Don't allow value to cross zero, assume zero values should be negative
double old = getDamage(modifier);
if (old > 0) {
setDamage(modifier, Math.max(0, old - difference));
} else {
setDamage(modifier, Math.min(0, old - difference));
}
remaining += newVanilla;
oldRemaining += oldVanilla;
}
setDamage(DamageModifier.BASE, damage);
}