| @@ -0,0 +1,85 @@ | ||
| package org.bukkit.potion; | ||
|
|
||
| import org.apache.commons.lang.Validate; | ||
| import org.bukkit.entity.LivingEntity; | ||
|
|
||
| /** | ||
| * Represents a potion effect, that can be added to a {@link LivingEntity}. A | ||
| * potion effect has a duration that it will last for, an amplifier that will | ||
| * enhance its effects, and a {@link PotionEffectType}, that represents its | ||
| * effect on an entity. | ||
| */ | ||
| public class PotionEffect { | ||
| private final int amplifier; | ||
| private final int duration; | ||
| private final PotionEffectType type; | ||
|
|
||
| public PotionEffect(PotionEffectType type, int duration, int amplifier) { | ||
| Validate.notNull(type, "effect type cannot be null"); | ||
| this.type = type; | ||
| this.duration = duration; | ||
| this.amplifier = amplifier; | ||
| } | ||
|
|
||
| /** | ||
| * Attempts to add the effect represented by this object to the given | ||
| * {@link LivingEntity}. | ||
| * | ||
| * @see LivingEntity#addPotionEffect(PotionEffect) | ||
| * @param entity | ||
| * The entity to add this effect to | ||
| */ | ||
| public boolean apply(LivingEntity entity) { | ||
| return entity.addPotionEffect(this); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object obj) { | ||
| if (this == obj) { | ||
| return true; | ||
| } | ||
| if (obj == null || getClass() != obj.getClass()) { | ||
| return false; | ||
| } | ||
| PotionEffect other = (PotionEffect) obj; | ||
| if (type == null) { | ||
| if (other.type != null) { | ||
| return false; | ||
| } | ||
| } else if (!type.equals(other.type)) { | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the amplifier of this effect. A higher amplifier means the potion | ||
| * effect happens more often over its duration and in some cases has more | ||
| * effect on its target. | ||
| */ | ||
| public int getAmplifier() { | ||
| return amplifier; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the duration (in ticks) that this effect will run for when | ||
| * applied to a {@link LivingEntity}. | ||
| */ | ||
| public int getDuration() { | ||
| return duration; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the {@link PotionEffectType} of this effect. | ||
| * | ||
| * @return The potion type of this effect | ||
| */ | ||
| public PotionEffectType getType() { | ||
| return type; | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| return 31 + ((type == null) ? 0 : type.hashCode()); | ||
| }; | ||
| } |
| @@ -0,0 +1,239 @@ | ||
| package org.bukkit.potion; | ||
|
|
||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
|
|
||
| import org.apache.commons.lang.Validate; | ||
|
|
||
| /** | ||
| * Represents a type of potion and its effect on an entity. | ||
| */ | ||
| public abstract class PotionEffectType { | ||
| /** | ||
| * Increases movement speed. | ||
| */ | ||
| public static PotionEffectType SPEED = new PotionEffectTypeWrapper(1); | ||
|
|
||
| /** | ||
| * Decreases movement speed. | ||
| */ | ||
| public static PotionEffectType SLOW = new PotionEffectTypeWrapper(2); | ||
|
|
||
| /** | ||
| * Increases dig speed. | ||
| */ | ||
| public static PotionEffectType FAST_DIGGING = new PotionEffectTypeWrapper(3); | ||
|
|
||
| /** | ||
| * Decreases dig speed. | ||
| */ | ||
| public static PotionEffectType SLOW_DIGGING = new PotionEffectTypeWrapper(4); | ||
|
|
||
| /** | ||
| * Increases damage dealt. | ||
| */ | ||
| public static PotionEffectType INCREASE_DAMAGE = new PotionEffectTypeWrapper(5); | ||
|
|
||
| /** | ||
| * Heals an entity. | ||
| */ | ||
| public static PotionEffectType HEAL = new PotionEffectTypeWrapper(6); | ||
|
|
||
| /** | ||
| * Hurts an entity. | ||
| */ | ||
| public static PotionEffectType HARM = new PotionEffectTypeWrapper(7); | ||
|
|
||
| /** | ||
| * Increases jump height. | ||
| */ | ||
| public static PotionEffectType JUMP = new PotionEffectTypeWrapper(8); | ||
|
|
||
| /** | ||
| * Warps vision on the client. | ||
| */ | ||
| public static PotionEffectType CONFUSION = new PotionEffectTypeWrapper(9); | ||
|
|
||
| /** | ||
| * Regenerates health. | ||
| */ | ||
| public static PotionEffectType REGENERATION = new PotionEffectTypeWrapper(10); | ||
|
|
||
| /** | ||
| * Decreases damage dealt to an entity. | ||
| */ | ||
| public static PotionEffectType DAMAGE_RESISTANCE = new PotionEffectTypeWrapper(11); | ||
|
|
||
| /** | ||
| * Stops fire damage. | ||
| */ | ||
| public static PotionEffectType FIRE_RESISTANCE = new PotionEffectTypeWrapper(12); | ||
|
|
||
| /** | ||
| * Allows breathing underwater. | ||
| */ | ||
| public static PotionEffectType WATER_BREATHING = new PotionEffectTypeWrapper(13); | ||
|
|
||
| /** | ||
| * Grants invisibility. | ||
| */ | ||
| @Deprecated | ||
| public static PotionEffectType INVISIBILITY = new PotionEffectTypeWrapper(14); // unimplemented | ||
|
|
||
| /** | ||
| * Blinds an entity. | ||
| */ | ||
| public static PotionEffectType BLINDNESS = new PotionEffectTypeWrapper(15); | ||
|
|
||
| /** | ||
| * Allows an entity to see in the dark. | ||
| */ | ||
| @Deprecated | ||
| public static PotionEffectType NIGHT_VISION = new PotionEffectTypeWrapper(16); // unimplemented | ||
|
|
||
| /** | ||
| * Increases hunger. | ||
| */ | ||
| public static PotionEffectType HUNGER = new PotionEffectTypeWrapper(17); | ||
|
|
||
| /** | ||
| * Decreases damage dealt by an entity. | ||
| */ | ||
| public static PotionEffectType WEAKNESS = new PotionEffectTypeWrapper(18); | ||
|
|
||
| /** | ||
| * Deals damage to an entity over time. | ||
| */ | ||
| public static PotionEffectType POISON = new PotionEffectTypeWrapper(19); | ||
|
|
||
| private final int id; | ||
|
|
||
| protected PotionEffectType(int id) { | ||
| this.id = id; | ||
| } | ||
|
|
||
| public PotionEffect createEffect(int duration, int amplifier) { | ||
| return Potion.getBrewer().createEffect(this, duration, amplifier); | ||
| } | ||
|
|
||
| /** | ||
| * Returns the duration modifier applied to effects of this type. | ||
| * | ||
| * @return duration modifier | ||
| */ | ||
| public abstract double getDurationModifier(); | ||
|
|
||
| /** | ||
| * Returns the unique ID of this type. | ||
| * | ||
| * @return Unique ID | ||
| */ | ||
| public int getId() { | ||
| return id; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the name of this effect type. | ||
| * | ||
| * @return The name of this effect type | ||
| */ | ||
| public abstract String getName(); | ||
|
|
||
| /** | ||
| * Returns whether the effect of this type happens once, immediately. | ||
| * | ||
| * @return whether this type is normally instant | ||
| */ | ||
| public abstract boolean isInstant(); | ||
|
|
||
| @Override | ||
| public boolean equals(Object obj) { | ||
| if (obj == null) { | ||
| return false; | ||
| } | ||
| if (!(obj instanceof PotionEffectType)) { | ||
| return false; | ||
| } | ||
| final PotionEffectType other = (PotionEffectType) obj; | ||
| if (this.id != other.id) { | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| return id; | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return "PotionEffectType[" + id + ", " + getName() + "]"; | ||
| } | ||
|
|
||
| private static final PotionEffectType[] byId = new PotionEffectType[20]; | ||
| private static final Map<String, PotionEffectType> byName = new HashMap<String, PotionEffectType>(); | ||
| // will break on updates. | ||
| private static boolean acceptingNew = true; | ||
|
|
||
| /** | ||
| * Gets the effect type specified by the unique id. | ||
| * | ||
| * @param id | ||
| * Unique ID to fetch | ||
| * @return Resulting type, or null if not found. | ||
| */ | ||
| public static PotionEffectType getById(int id) { | ||
| if (id >= byId.length || id < 0) | ||
| return null; | ||
| return byId[id]; | ||
| } | ||
|
|
||
| /** | ||
| * Gets the effect type specified by the given name. | ||
| * | ||
| * @param name | ||
| * Name of PotionEffectType to fetch | ||
| * @return Resulting PotionEffectType, or null if not found. | ||
| */ | ||
| public static PotionEffectType getByName(String name) { | ||
| Validate.notNull(name, "name cannot be null"); | ||
| return byName.get(name.toLowerCase()); | ||
| } | ||
|
|
||
| /** | ||
| * Registers an effect type with the given object. | ||
| * <p> | ||
| * Generally not to be used from within a plugin. | ||
| * | ||
| * @param potionType | ||
| * PotionType to register | ||
| */ | ||
| public static void registerPotionEffectType(PotionEffectType type) { | ||
| if (byId[type.id] != null || byName.containsKey(type.getName().toLowerCase())) { | ||
| throw new IllegalArgumentException("Cannot set already-set type"); | ||
| } else if (!acceptingNew) { | ||
| throw new IllegalStateException( | ||
| "No longer accepting new potion effect types (can only be done by the server implementation)"); | ||
| } | ||
|
|
||
| byId[type.id] = type; | ||
| byName.put(type.getName().toLowerCase(), type); | ||
| } | ||
|
|
||
| /** | ||
| * Stops accepting any effect type registrations. | ||
| */ | ||
| public static void stopAcceptingRegistrations() { | ||
| acceptingNew = false; | ||
| } | ||
|
|
||
| /** | ||
| * Returns an array of all the registered {@link PotionEffectType}s. | ||
| * | ||
| * @return Array of types. | ||
| */ | ||
| public static PotionEffectType[] values() { | ||
| return byId.clone(); | ||
| } | ||
| } |
| @@ -0,0 +1,29 @@ | ||
| package org.bukkit.potion; | ||
|
|
||
| public class PotionEffectTypeWrapper extends PotionEffectType { | ||
| protected PotionEffectTypeWrapper(int id) { | ||
| super(id); | ||
| } | ||
|
|
||
| @Override | ||
| public double getDurationModifier() { | ||
| return getType().getDurationModifier(); | ||
| } | ||
|
|
||
| @Override | ||
| public String getName() { | ||
| return getType().getName(); | ||
| } | ||
|
|
||
| /** | ||
| * Get the potion type bound to this wrapper. | ||
| */ | ||
| public PotionEffectType getType() { | ||
| return PotionEffectType.getById(getId()); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isInstant() { | ||
| return getType().isInstant(); | ||
| } | ||
| } |
| @@ -0,0 +1,45 @@ | ||
| package org.bukkit.potion; | ||
|
|
||
| public enum PotionType { | ||
| REGEN(1, PotionEffectType.REGENERATION), | ||
| SPEED(2, PotionEffectType.SPEED), | ||
| FIRE_RESISTANCE(3, PotionEffectType.FIRE_RESISTANCE), | ||
| POISON(4, PotionEffectType.POISON), | ||
| INSTANT_HEAL(5, PotionEffectType.HEAL), | ||
| WEAKNESS(8, PotionEffectType.SPEED), | ||
| STRENGTH(9, PotionEffectType.INCREASE_DAMAGE), | ||
| SLOWNESS(10, PotionEffectType.SLOW), | ||
| INSTANT_DAMAGE(12, PotionEffectType.HARM); | ||
|
|
||
| private final int damageValue; | ||
| private final PotionEffectType effect; | ||
|
|
||
| PotionType(int damageValue, PotionEffectType effect) { | ||
| this.damageValue = damageValue; | ||
| this.effect = effect; | ||
| } | ||
|
|
||
| public PotionEffectType getEffectType() { | ||
| return effect; | ||
| } | ||
|
|
||
| protected int getDamageValue() { | ||
| return damageValue; | ||
| } | ||
|
|
||
| public static PotionType getByDamageValue(int damage) { | ||
| for (PotionType type : PotionType.values()) { | ||
| if (type.damageValue == damage) | ||
| return type; | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| public static PotionType getByEffect(PotionEffectType effectType) { | ||
| for (PotionType type : PotionType.values()) { | ||
| if (type.effect.equals(effectType)) | ||
| return type; | ||
| } | ||
| return null; | ||
| } | ||
| } |
| @@ -0,0 +1,109 @@ | ||
| package org.bukkit.potion; | ||
|
|
||
| import static org.junit.Assert.*; | ||
| import static org.hamcrest.Matchers.is; | ||
|
|
||
| import org.bukkit.Material; | ||
| import org.bukkit.entity.LivingEntity; | ||
| import org.bukkit.inventory.ItemStack; | ||
| import org.bukkit.potion.Potion.Tier; | ||
| import org.junit.Test; | ||
|
|
||
| public class PotionTest { | ||
| @Test | ||
| public void applyToItemStack() { | ||
| Potion potion = new Potion(PotionType.POISON); | ||
| ItemStack stack = new ItemStack(Material.POTION, 1); | ||
| potion.apply(stack); | ||
| assertTrue(stack.getDurability() == potion.toDamageValue()); | ||
| } | ||
|
|
||
| @Test | ||
| public void fromDamage() { | ||
| Potion potion = Potion.fromDamage(PotionType.POISON.getDamageValue()); | ||
| assertTrue(potion.getType() == PotionType.POISON); | ||
| potion = Potion.fromDamage(PotionType.POISON.getDamageValue() | SPLASH_BIT); | ||
| assertTrue(potion.getType() == PotionType.POISON && potion.isSplash()); | ||
| potion = Potion.fromDamage(0x25 /* Potion of Healing II */); | ||
| assertTrue(potion.getType() == PotionType.INSTANT_HEAL && potion.getTier() == Tier.TWO); | ||
| } | ||
|
|
||
| @Test(expected = IllegalArgumentException.class) | ||
| public void illegalApplyToItemStack() { | ||
| Potion potion = new Potion(PotionType.POISON); | ||
| potion.apply(new ItemStack(Material.AIR, 1)); | ||
| } | ||
|
|
||
| @Test | ||
| public void ItemStackConversion() { | ||
| Potion potion = new Potion(PotionType.POISON); | ||
| ItemStack itemstack = potion.toItemStack(1); | ||
| assertThat(itemstack.getType(), is(Material.POTION)); | ||
| assertTrue(itemstack.getAmount() == 1); | ||
| assertTrue(itemstack.getDurability() == potion.toDamageValue()); | ||
| } | ||
|
|
||
| @Test | ||
| public void setExtended() { | ||
| Potion potion = new Potion(PotionType.POISON); | ||
| assertFalse(potion.hasExtendedDuration()); | ||
| potion.setHasExtendedDuration(true); | ||
| assertTrue(potion.hasExtendedDuration()); | ||
| assertTrue((potion.toDamageValue() & EXTENDED_BIT) != 0); | ||
| } | ||
|
|
||
| @Test | ||
| public void setSplash() { | ||
| Potion potion = new Potion(PotionType.POISON); | ||
| assertFalse(potion.isSplash()); | ||
| potion.setSplash(true); | ||
| assertTrue(potion.isSplash()); | ||
| assertTrue((potion.toDamageValue() & SPLASH_BIT) != 0); | ||
| } | ||
|
|
||
| @Test | ||
| public void setTier() { | ||
| Potion potion = new Potion(PotionType.POISON); | ||
| assertThat(potion.getTier(), is(Tier.ONE)); | ||
| potion.setTier(Tier.TWO); | ||
| assertThat(potion.getTier(), is(Tier.TWO)); | ||
| assertTrue(potion.toDamageValue() == (PotionType.POISON.getDamageValue() | potion.getTier().getDamageBit())); | ||
| } | ||
|
|
||
| @Test | ||
| public void useNulls() { | ||
| try { | ||
| new Potion(null); | ||
| fail("cannot use null type in constructor"); | ||
| } catch (IllegalArgumentException ex) { | ||
| } | ||
|
|
||
| try { | ||
| new Potion(PotionType.POISON, null); | ||
| fail("cannot use null tier in constructor"); | ||
| } catch (IllegalArgumentException ex) { | ||
| } | ||
|
|
||
| Potion potion = new Potion(PotionType.POISON); | ||
| try { | ||
| potion.setTier(null); | ||
| fail("cannot set a null tier"); | ||
| } catch (IllegalArgumentException ex) { | ||
| } | ||
|
|
||
| try { | ||
| potion.apply((ItemStack) null); | ||
| fail("cannot apply to a null itemstack"); | ||
| } catch (IllegalArgumentException ex) { | ||
| } | ||
|
|
||
| try { | ||
| potion.apply((LivingEntity) null); | ||
| fail("cannot apply to a null entity"); | ||
| } catch (IllegalArgumentException ex) { | ||
| } | ||
| } | ||
|
|
||
| private static final int EXTENDED_BIT = 0x40; | ||
| private static final int SPLASH_BIT = 0x4000; | ||
| } |