package de.slikey.effectlib;
import de.slikey.effectlib.util.Disposable;
import de.slikey.effectlib.util.DynamicLocation;
import de.slikey.effectlib.util.ParticleEffect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Entity;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.NumberConversions;
import org.bukkit.util.Vector;
/**
* Dispose the EffectManager if you don't need him anymore.
*
* @author Kevin
*
*/
public final class EffectManager implements Disposable {
private static List<EffectManager> effectManagers;
private static Map<String, Class<? extends Effect>> effectClasses = new HashMap<String, Class<? extends Effect>>();
private final Plugin owningPlugin;
private final Map<Effect, BukkitTask> effects;
private boolean disposed;
private boolean disposeOnTermination;
private boolean debug = false;
private int visibleRange = 32;
public EffectManager(Plugin owningPlugin) {
ParticleEffect.ParticlePacket.initialize();
this.owningPlugin = owningPlugin;
effects = new HashMap<Effect, BukkitTask>();
disposed = false;
disposeOnTermination = false;
}
public void start(Effect effect) {
if (disposed) {
throw new IllegalStateException("EffectManager is disposed and not able to accept any effects.");
}
if (disposeOnTermination) {
throw new IllegalStateException("EffectManager is awaiting termination to dispose and not able to accept any effects.");
}
if (effects.containsKey(effect)) {
effect.cancel(false);
}
if (!owningPlugin.isEnabled()) return;
BukkitScheduler s = Bukkit.getScheduler();
BukkitTask task = null;
switch (effect.getType()) {
case INSTANT:
if(effect.isAsynchronous()) {
task = s.runTaskAsynchronously(owningPlugin, effect);
} else {
task = s.runTask(owningPlugin, effect);
}
break;
case DELAYED:
if (effect.isAsynchronous()) {
task = s.runTaskLaterAsynchronously(owningPlugin, effect, effect.getDelay());
} else {
task = s.runTaskLater(owningPlugin, effect, effect.getDelay());
}
break;
case REPEATING:
if (effect.isAsynchronous()) {
task = s.runTaskTimerAsynchronously(owningPlugin, effect, effect.getDelay(), effect.getPeriod());
} else {
task = s.runTaskTimer(owningPlugin, effect, effect.getDelay(), effect.getPeriod());
}
break;
}
synchronized (this) {
effects.put(effect, task);
}
}
public Effect start(String effectClass, ConfigurationSection parameters, Location origin, Entity originEntity) {
return start(effectClass, parameters, origin, null, originEntity, null, null);
}
public Effect start(String effectClass, ConfigurationSection parameters, Entity originEntity) {
return start(effectClass, parameters, originEntity == null ? null : originEntity.getLocation(), null, originEntity, null, null);
}
public Effect start(String effectClass, ConfigurationSection parameters, Location origin) {
return start(effectClass, parameters, origin, null, null, null, null);
}
/**
* Start an Effect from a Configuration map of parameters.
*
* @param effectClass The name of the Effect class to instantiate. If unqualified, defaults to the de.slikey.effectlib.effect namespace.
* @param parameters A Configuration-driven map of key/value parameters. Each of these will be applied directly to the corresponding field in the Effect instance.
* @param origin The origin Location
* @param target The target Location, only used in some Effects (like LineEffect)
* @param originEntity The origin Entity, the effect will attach to the Entity's Location
* @param targetEntity The target Entity, only used in some Effects
* @param parameterMap A map of parameter values to replace. These must start with the "$" character, values in the parameters map that contain a $key will be replaced with the value in this parameterMap.
* @return
*/
public Effect start(String effectClass, ConfigurationSection parameters, Location origin, Location target, Entity originEntity, Entity targetEntity, Map<String, String> parameterMap) {
return start(effectClass, parameters, new DynamicLocation(origin, originEntity), new DynamicLocation(target, targetEntity), parameterMap);
}
/**
* Start an Effect from a Configuration map of parameters.
*
* @param effectClass The name of the Effect class to instantiate. If unqualified, defaults to the de.slikey.effectlib.effect namespace.
* @param parameters A Configuration-driven map of key/value parameters. Each of these will be applied directly to the corresponding field in the Effect instance.
* @param origin The origin Location
* @param target The target Location, only used in some Effects (like LineEffect)
* @param parameterMap A map of parameter values to replace. These must start with the "$" character, values in the parameters map that contain a $key will be replaced with the value in this parameterMap.
* @return
*/
public Effect start(String effectClass, ConfigurationSection parameters, DynamicLocation origin, DynamicLocation target, Map<String, String> parameterMap) {
Class<? extends Effect> effectLibClass;
try {
// A shaded manager may provide a fully-qualified path.
if (!effectClass.contains(".")) {
effectClass = "de.slikey.effectlib.effect." + effectClass;
if (!effectClass.endsWith("Effect")) {
effectClass = effectClass + "Effect";
}
}
effectLibClass = effectClasses.get(effectClass);
if (effectLibClass == null) {
effectLibClass = (Class<? extends Effect>) Class.forName(effectClass);
effectClasses.put(effectClass, effectLibClass);
}
} catch (Throwable ex) {
owningPlugin.getLogger().info("Error loading EffectLib class: " + effectClass + ": " + ex.getMessage());
return null;
}
Effect effect = null;
try {
Constructor constructor = effectLibClass.getConstructor(EffectManager.class);
effect = (Effect) constructor.newInstance(this);
} catch (Exception ex) {
owningPlugin.getLogger().warning("Error creating Effect class: " + effectClass);
}
if (effect == null) {
return null;
}
Collection<String> keys = parameters.getKeys(false);
for (String key : keys) {
if (key.equals("class")) {
continue;
}
if (!setField(effect, key, parameters, parameterMap) && debug) {
owningPlugin.getLogger().warning("Unable to assign EffectLib property " + key + " of class " + effectLibClass.getName());
}
}
effect.setDynamicOrigin(origin);
effect.setDynamicTarget(target);
effect.start();
return effect;
}
public void cancel(boolean callback) {
List<Effect> allEffects = new ArrayList<Effect>(effects.keySet());
for (Effect effect : allEffects) {
effect.cancel(callback);
}
}
public void done(Effect effect) {
synchronized (this) {
BukkitTask existingTask = effects.get(effect);
if (existingTask != null) {
existingTask.cancel();
}
effects.remove(effect);
}
if (effect.callback != null && owningPlugin.isEnabled()) {
Bukkit.getScheduler().runTask(owningPlugin, effect.callback);
}
if (disposeOnTermination && effects.isEmpty()) {
dispose();
}
}
@Override
public void dispose() {
if (disposed) {
return;
}
disposed = true;
cancel(false);
if (effectManagers != null) {
effectManagers.remove(this);
}
}
public void disposeOnTermination() {
disposeOnTermination = true;
if (effects.isEmpty()) {
dispose();
}
}
public void enableDebug(boolean enable) {
debug = enable;
}
public boolean isDebugEnabled() {
return debug;
}
public void onError(Throwable ex) {
if (debug) {
owningPlugin.getLogger().log(Level.WARNING, "Particle Effect error", ex);
}
}
public int getParticleRange() {
return visibleRange;
}
public void setParticleRange(int range) {
visibleRange = range;
}
public Plugin getOwningPlugin() {
return owningPlugin;
}
protected boolean setField(Object effect, String key, ConfigurationSection section, Map<String, String> parameterMap) {
try {
String value = section.getString(key);
if (parameterMap != null && !parameterMap.isEmpty() && value.startsWith("$")) {
String parameterValue = parameterMap.get(value);
value = parameterValue == null ? value : parameterValue;
}
Field field = effect.getClass().getField(key);
if (field.getType().equals(Integer.TYPE) || field.getType().equals(Integer.class)) {
field.set(effect, NumberConversions.toInt(value));
} else if (field.getType().equals(Float.TYPE) || field.getType().equals(Float.class)) {
field.set(effect, NumberConversions.toFloat(value));
} else if (field.getType().equals(Double.TYPE) || field.getType().equals(Double.class)) {
field.set(effect, NumberConversions.toDouble(value));
} else if (field.getType().equals(Boolean.TYPE) || field.getType().equals(Boolean.class)) {
field.set(effect, value.equalsIgnoreCase("true"));
} else if (field.getType().equals(Long.TYPE) || field.getType().equals(Long.class)) {
field.set(effect, NumberConversions.toLong(value));
} else if (field.getType().equals(Short.TYPE) || field.getType().equals(Short.class)) {
field.set(effect, NumberConversions.toShort(value));
} else if (field.getType().equals(Byte.TYPE) || field.getType().equals(Byte.class)) {
field.set(effect, NumberConversions.toByte(value));
} else if (field.getType().isAssignableFrom(String.class)) {
field.set(effect, value);
} else if (field.getType().isAssignableFrom(ParticleEffect.class)) {
ParticleEffect particleType = ParticleEffect.valueOf(value.toUpperCase());
field.set(effect, particleType);
} else if (field.getType().isAssignableFrom(EffectType.class)) {
EffectType effectType = EffectType.valueOf(value.toUpperCase());
field.set(effect, effectType);
} else if (field.getType().equals(Sound.class)) {
try {
Sound sound = Sound.valueOf(value.toUpperCase());
field.set(effect, sound);
} catch (Exception ex) {
onError(ex);
}
} else if (field.getType().equals(Material.class)) {
try {
Material material = Material.valueOf(value.toUpperCase());
field.set(effect, material);
} catch (Exception ex) {
onError(ex);
}
} else if (field.getType().equals(Color.class)) {
try {
Integer rgb = Integer.parseInt(value, 16);
Color color = Color.fromRGB(rgb);
field.set(effect, color);
} catch (Exception ex) {
onError(ex);
}
} else if (field.getType().equals(Vector.class)) {
double x = 0;
double y = 0;
double z = 0;
try {
String[] pieces = value.split(",");
x = pieces.length > 0 ? Double.parseDouble(pieces[0]) : 0;
y = pieces.length > 1 ? Double.parseDouble(pieces[1]) : 0;
z = pieces.length > 2 ? Double.parseDouble(pieces[2]) : 0;
} catch (Exception ex) {
onError(ex);
}
field.set(effect, new Vector(x, y, z));
} else {
return false;
}
return true;
} catch (Exception ex) {
this.onError(ex);
}
return false;
}
public static void initialize() {
effectManagers = new ArrayList<EffectManager>();
}
public static List<EffectManager> getManagers() {
if (effectManagers == null) {
initialize();
}
return effectManagers;
}
public static void disposeAll() {
if (effectManagers != null) {
for (Iterator<EffectManager> i = effectManagers.iterator(); i.hasNext();) {
EffectManager em = i.next();
i.remove();
em.dispose();
}
}
}
}