package com.nisovin.magicspells.spelleffects;
import java.util.HashMap;
import java.util.List;
import org.bukkit.Location;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
import com.nisovin.magicspells.MagicSpells;
import com.nisovin.magicspells.castmodifiers.ModifierSet;
/**
*
* Represents a graphical effect that can be used with the 'effects' option of a spell.
*
*/
public abstract class SpellEffect {
// for normal/line
double heightOffset = 0;
double forwardOffset = 0;
int delay = 0;
// for line
double distanceBetween = 1;
// for buff
int effectInterval = 20;
// for orbit
float orbitRadius = 1;
float secondsPerRevolution = 3;
boolean counterClockwise = false;
int tickInterval = 2;
float ticksPerSecond;
float distancePerTick;
int ticksPerRevolution;
float orbitYOffset = 0;
ModifierSet modifiers = null;
int taskId = -1;
public abstract void loadFromString(String string);
public final void loadFromConfiguration(ConfigurationSection config) {
heightOffset = config.getDouble("height-offset", heightOffset);
forwardOffset = config.getDouble("forward-offset", forwardOffset);
delay = config.getInt("delay", delay);
distanceBetween = config.getDouble("distance-between", distanceBetween);
effectInterval = config.getInt("effect-interval", effectInterval);
orbitRadius = (float)config.getDouble("orbit-radius", orbitRadius);
secondsPerRevolution = (float)config.getDouble("orbit-seconds-per-revolution", secondsPerRevolution);
counterClockwise = config.getBoolean("orbit-counter-clockwise", counterClockwise);
tickInterval = config.getInt("orbit-tick-interval", tickInterval);
ticksPerSecond = 20F / (float)tickInterval;
distancePerTick = 6.28F / (ticksPerSecond * secondsPerRevolution);
ticksPerRevolution = Math.round(ticksPerSecond * secondsPerRevolution);
orbitYOffset = (float)config.getDouble("orbit-y-offset", orbitYOffset);
List<String> list = config.getStringList("modifiers");
if (list != null) {
modifiers = new ModifierSet(list);
}
loadFromConfig(config);
}
protected abstract void loadFromConfig(ConfigurationSection config);
/**
* Plays an effect on the specified entity.
* @param entity the entity to play the effect on
* @param param the parameter specified in the spell config (can be ignored)
*/
public final void playEffect(final Entity entity) {
if (delay <= 0) {
playEffectEntity(entity);
} else {
MagicSpells.scheduleDelayedTask(new Runnable() {
public void run() {
playEffectEntity(entity);
}
}, delay);
}
}
protected void playEffectEntity(Entity entity) {
playEffectLocationReal(entity == null ? null : entity.getLocation());
}
/**
* Plays an effect at the specified location.
* @param location location to play the effect at
* @param param the parameter specified in the spell config (can be ignored)
*/
public final void playEffect(final Location location) {
if (delay <= 0) {
playEffectLocationReal(location);
} else {
MagicSpells.scheduleDelayedTask(new Runnable() {
public void run() {
playEffectLocationReal(location);
}
}, delay);
}
}
private void playEffectLocationReal(Location location) {
if (location == null) {
playEffectLocation(null);
} else if (heightOffset != 0 || forwardOffset != 0) {
Location loc = location.clone();
if (heightOffset != 0) {
loc.setY(loc.getY() + heightOffset);
}
if (forwardOffset != 0) {
loc.add(loc.getDirection().setY(0).normalize().multiply(forwardOffset));
}
playEffectLocation(loc);
} else {
playEffectLocation(location);
}
}
protected void playEffectLocation(Location location) {
}
/**
* Plays an effect between two locations (such as a smoke trail type effect).
* @param location1 the starting location
* @param location2 the ending location
* @param param the parameter specified in the spell config (can be ignored)
*/
public final void playEffect(final Location location1, final Location location2) {
if (delay <= 0) {
playEffectLine(location1, location2);
} else {
MagicSpells.scheduleDelayedTask(new Runnable() {
public void run() {
playEffectLine(location1, location2);
}
}, delay);
}
}
protected void playEffectLine(Location location1, Location location2) {
int c = (int)Math.ceil(location1.distance(location2) / distanceBetween) - 1;
if (c <= 0) return;
Vector v = location2.toVector().subtract(location1.toVector()).normalize().multiply(distanceBetween);
Location l = location1.clone();
if (heightOffset != 0) {
l.setY(l.getY() + heightOffset);
}
for (int i = 0; i < c; i++) {
l.add(v);
playEffect(l);
}
}
public void playEffectWhileActiveOnEntity(final Entity entity, final SpellEffectActiveChecker checker) {
taskId = MagicSpells.scheduleRepeatingTask(new Runnable() {
public void run() {
if (checker.isActive(entity)) {
playEffect(entity);
}
}
}, 0, effectInterval);
}
public void playEffectWhileActiveOrbit(final Entity entity, final SpellEffectActiveChecker checker) {
new OrbitTracker(entity, checker);
}
public interface SpellEffectActiveChecker {
public boolean isActive(Entity entity);
}
class OrbitTracker implements Runnable {
Entity entity;
SpellEffectActiveChecker checker;
Vector currentPosition;
int taskId;
int counter = 0;
public OrbitTracker(Entity entity, SpellEffectActiveChecker checker) {
this.entity = entity;
this.checker = checker;
this.currentPosition = entity.getLocation().getDirection().setY(0);
this.taskId = MagicSpells.scheduleRepeatingTask(this, 0, tickInterval);
}
@Override
public void run() {
// check for valid and alive caster and target
if (!entity.isValid()) {
stop();
return;
}
// check if duration is up
if (counter++ % ticksPerRevolution == 0 && !checker.isActive(entity)) {
stop();
return;
}
// move projectile and calculate new vector
Location loc = getLocation();
// show effect
playEffect(loc);
}
private Location getLocation() {
Vector perp;
if (counterClockwise) {
perp = new Vector(currentPosition.getZ(), 0, -currentPosition.getX());
} else {
perp = new Vector(-currentPosition.getZ(), 0, currentPosition.getX());
}
currentPosition.add(perp.multiply(distancePerTick)).normalize();
return entity.getLocation().add(0, orbitYOffset, 0).add(currentPosition.clone().multiply(orbitRadius));
}
public void stop() {
MagicSpells.cancelTask(taskId);
entity = null;
currentPosition = null;
}
}
private static HashMap<String, Class<? extends SpellEffect>> effects = new HashMap<String, Class<? extends SpellEffect>>();
/**
* Gets the GraphicalEffect by the provided name.
* @param name the name of the effect
* @return
*/
public static SpellEffect createNewEffectByName(String name) {
Class<? extends SpellEffect> clazz = effects.get(name.toLowerCase());
if (clazz != null) {
try {
return clazz.newInstance();
} catch (Exception e) {
return null;
}
}
return null;
}
/**
* Adds an effect with the provided name to the list of available effects.
* This will replace an existing effect if the same name is used.
* @param name the name of the effect
* @param effect the effect to add
*/
public static void addEffect(String name, Class<? extends SpellEffect> effect) {
effects.put(name.toLowerCase(), effect);
}
static {
effects.put("actionbartext", ActionBarTextEffect.class);
effects.put("angry", AngryEffect.class);
effects.put("bigsmoke", BigSmokeEffect.class);
effects.put("blockbreak", BlockBreakEffect.class);
effects.put("bluesparkle", BlueSparkleEffect.class);
effects.put("broadcast", BroadcastEffect.class);
effects.put("cloud", CloudEffect.class);
effects.put("dragondeath", DragonDeathEffect.class);
effects.put("ender", EnderSignalEffect.class);
effects.put("explosion", ExplosionEffect.class);
effects.put("fireworks", FireworksEffect.class);
effects.put("greensparkle", GreenSparkleEffect.class);
effects.put("hearts", HeartsEffect.class);
effects.put("itemcooldown", ItemCooldownEffect.class);
effects.put("itemspray", ItemSprayEffect.class);
effects.put("lightning", LightningEffect.class);
effects.put("nova", NovaEffect.class);
effects.put("particles", ParticlesEffect.class);
effects.put("particlecloud", ParticleCloudEffect.class);
effects.put("particleline", ParticleLineEffect.class);
effects.put("potion", PotionEffect.class);
effects.put("smoke", SmokeEffect.class);
effects.put("smokeswirl", SmokeSwirlEffect.class);
effects.put("smoketrail", SmokeTrailEffect.class);
effects.put("sound", SoundEffect.class);
effects.put("soundpersonal", SoundPersonalEffect.class);
effects.put("spawn", MobSpawnerEffect.class);
effects.put("splash", SplashPotionEffect.class);
effects.put("title", TitleEffect.class);
}
}