@@ -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;
}
}
@@ -1,6 +1,7 @@
package org.bukkit.plugin.messaging;

import java.net.InetSocketAddress;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -23,6 +24,8 @@
import org.bukkit.permissions.PermissionAttachment;
import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.plugin.Plugin;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Vector;

public class TestPlayer implements Player {
@@ -621,4 +624,28 @@ public void showPlayer(Player player) {
public boolean canSee(Player player) {
throw new UnsupportedOperationException("Not supported yet.");
}

public boolean addPotionEffect(PotionEffect effect) {
throw new UnsupportedOperationException("Not supported yet.");
}

public boolean addPotionEffect(PotionEffect effect, boolean force) {
throw new UnsupportedOperationException("Not supported yet.");
}

public boolean addPotionEffects(Collection<PotionEffect> effects) {
throw new UnsupportedOperationException("Not supported yet.");
}

public boolean hasPotionEffect(PotionEffectType type) {
throw new UnsupportedOperationException("Not supported yet.");
}

public void removePotionEffect(PotionEffectType type) {
throw new UnsupportedOperationException("Not supported yet.");
}

public Collection<PotionEffect> getActivePotionEffects() {
throw new UnsupportedOperationException("Not supported yet.");
}
}
@@ -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;
}