package com.nisovin.magicspells.spells.targeted;
import org.bukkit.Location;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
import com.nisovin.magicspells.MagicSpells;
import com.nisovin.magicspells.Subspell;
import com.nisovin.magicspells.spelleffects.EffectPosition;
import com.nisovin.magicspells.spells.TargetedEntityFromLocationSpell;
import com.nisovin.magicspells.spells.TargetedEntitySpell;
import com.nisovin.magicspells.spells.TargetedSpell;
import com.nisovin.magicspells.util.BoundingBox;
import com.nisovin.magicspells.util.MagicConfig;
import com.nisovin.magicspells.util.TargetInfo;
public class HomingMissileSpell extends TargetedSpell implements TargetedEntitySpell, TargetedEntityFromLocationSpell {
float projectileVelocity;
float projectileInertia;
int tickInterval;
float ticksPerSecond;
float velocityPerTick;
int specialEffectInterval;
String particleName;
float particleSpeed;
int particleCount;
float particleHorizontalSpread;
float particleVerticalSpread;
int maxDuration;
float hitRadius;
float yOffset;
int renderDistance;
String hitSpellName;
Subspell spell;
HomingMissileSpell thisSpell;
public HomingMissileSpell(MagicConfig config, String spellName) {
super(config, spellName);
thisSpell = this;
projectileVelocity = getConfigFloat("projectile-velocity", 5F);
projectileInertia = getConfigFloat("projectile-inertia", 1.5F);
tickInterval = getConfigInt("tick-interval", 2);
ticksPerSecond = 20F / (float)tickInterval;
velocityPerTick = projectileVelocity / ticksPerSecond;
specialEffectInterval = getConfigInt("special-effect-interval", 0);
particleName = getConfigString("particle-name", "reddust");
particleSpeed = getConfigFloat("particle-speed", 0.3F);
particleCount = getConfigInt("particle-count", 15);
particleHorizontalSpread = getConfigFloat("particle-horizontal-spread", 0.3F);
particleVerticalSpread = getConfigFloat("particle-vertical-spread", 0.3F);
maxDuration = getConfigInt("max-duration", 20) * 1000;
hitRadius = getConfigFloat("hit-radius", 1.5F);
yOffset = getConfigFloat("y-offset", 0.6F);
renderDistance = getConfigInt("render-distance", 32);
hitSpellName = getConfigString("spell", "");
}
@Override
public void initialize() {
super.initialize();
Subspell s = new Subspell(hitSpellName);
if (s.process()) {
spell = s;
} else {
MagicSpells.error("ParticleProjectileSpell " + internalName + " has an invalid spell defined!");
}
}
@Override
public PostCastAction castSpell(Player player, SpellCastState state, float power, String[] args) {
if (state == SpellCastState.NORMAL) {
ValidTargetChecker checker = spell != null ? spell.getSpell().getValidTargetChecker() : null;
TargetInfo<LivingEntity> target = getTargetedEntity(player, power, checker);
if (target == null) {
return noTarget(player);
}
new MissileTracker(player, target.getTarget(), target.getPower());
sendMessages(player, target.getTarget());
return PostCastAction.NO_MESSAGES;
}
return PostCastAction.HANDLE_NORMALLY;
}
@Override
public boolean castAtEntity(Player caster, LivingEntity target, float power) {
if (validTargetList.canTarget(caster, target)) {
new MissileTracker(caster, target, power);
return true;
} else {
return false;
}
}
@Override
public boolean castAtEntity(LivingEntity target, float power) {
if (validTargetList.canTarget(target)) {
new MissileTracker(null, target, power);
return true;
} else {
return false;
}
}
@Override
public boolean castAtEntityFromLocation(Player caster, Location from, LivingEntity target, float power) {
if (validTargetList.canTarget(caster, target)) {
new MissileTracker(caster, from, target, power);
return true;
} else {
return false;
}
}
@Override
public boolean castAtEntityFromLocation(Location from, LivingEntity target, float power) {
if (validTargetList.canTarget(target)) {
new MissileTracker(null, from, target, power);
return true;
} else {
return false;
}
}
class MissileTracker implements Runnable {
Player caster;
LivingEntity target;
float power;
long startTime;
Location currentLocation;
Vector currentVelocity;
int taskId;
int counter = 0;
public MissileTracker(Player caster, LivingEntity target, float power) {
this.currentLocation = caster.getLocation().clone().add(0, yOffset, 0);
this.currentVelocity = currentLocation.getDirection();
init(caster, target, power);
playSpellEffects(EffectPosition.CASTER, caster);
}
public MissileTracker(Player caster, Location startLocation, LivingEntity target, float power) {
this.currentLocation = startLocation.clone().add(0, yOffset, 0);
this.currentVelocity = target.getLocation().toVector().subtract(currentLocation.toVector()).normalize();
init(caster, target, power);
if (caster != null) {
playSpellEffects(EffectPosition.CASTER, caster);
} else {
playSpellEffects(EffectPosition.CASTER, startLocation);
}
}
private void init(Player caster, LivingEntity target, float power) {
this.currentVelocity.multiply(velocityPerTick);
this.caster = caster;
this.target = target;
this.power = power;
this.startTime = System.currentTimeMillis();
this.taskId = MagicSpells.scheduleRepeatingTask(this, 0, tickInterval);
}
@Override
public void run() {
// check for valid and alive caster and target
if ((caster != null && !caster.isValid()) || !target.isValid()) {
stop();
return;
}
// check if target has left the world
if (!currentLocation.getWorld().equals(target.getWorld())) {
stop();
return;
}
// check if duration is up
if (maxDuration > 0 && startTime + maxDuration < System.currentTimeMillis()) {
stop();
return;
}
// move projectile and calculate new vector
currentLocation.add(currentVelocity);
currentVelocity.multiply(projectileInertia);
currentVelocity.add(target.getLocation().add(0, yOffset, 0).subtract(currentLocation).toVector().normalize());
currentVelocity.normalize().multiply(velocityPerTick);
// show particle
MagicSpells.getVolatileCodeHandler().playParticleEffect(currentLocation, particleName, particleHorizontalSpread, particleVerticalSpread, particleSpeed, particleCount, renderDistance, 0F);
// play effects
if (specialEffectInterval > 0 && counter % specialEffectInterval == 0) {
playSpellEffects(EffectPosition.SPECIAL, currentLocation);
}
counter++;
// check for hit
if (hitRadius > 0 && spell != null) {
BoundingBox hitBox = new BoundingBox(currentLocation, hitRadius);
if (hitBox.contains(target.getLocation().add(0, yOffset, 0))) {
if (spell.isTargetedEntitySpell()) {
spell.castAtEntity(caster, target, power);
} else if (spell.isTargetedLocationSpell()) {
spell.castAtLocation(caster, target.getLocation(), power);
}
playSpellEffects(EffectPosition.TARGET, target);
stop();
}
}
}
public void stop() {
playSpellEffects(EffectPosition.DELAYED, currentLocation);
MagicSpells.cancelTask(taskId);
caster = null;
target = null;
currentLocation = null;
currentVelocity = null;
}
}
}