package com.nisovin.magicspells.spells.instant; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.util.Vector; import com.nisovin.magicspells.MagicSpells; import com.nisovin.magicspells.spelleffects.EffectPosition; import com.nisovin.magicspells.spells.InstantSpell; import com.nisovin.magicspells.util.MagicConfig; import com.nisovin.magicspells.util.Util; public class FlightPathSpell extends InstantSpell { static FlightHandler flightHandler; float targetX; float targetY; float targetZ; int cruisingAltitude; float speed; EntityType mount; public FlightPathSpell(MagicConfig config, String spellName) { super(config, spellName); targetX = getConfigFloat("x", 0); targetY = getConfigFloat("y", 70); targetZ = getConfigFloat("z", 0); cruisingAltitude = getConfigInt("cruising-altitude", 150); speed = getConfigFloat("speed", 1.5f); mount = Util.getEntityType(getConfigString("mount", "")); if (flightHandler == null) { flightHandler = new FlightHandler(); } } @Override public void initialize() { flightHandler.init(); } @Override public PostCastAction castSpell(Player player, SpellCastState state, float power, String[] args) { if (state == SpellCastState.NORMAL) { ActiveFlight flight = new ActiveFlight(player, mount, this); flightHandler.addFlight(flight); } return PostCastAction.HANDLE_NORMALLY; } @Override public void turnOff() { if (flightHandler != null) { flightHandler.turnOff(); flightHandler = null; } } class FlightHandler implements Runnable, Listener { boolean inited = false; Map<String, ActiveFlight> flights = new HashMap<String, ActiveFlight>(); int task = -1; public void addFlight(ActiveFlight flight) { flights.put(flight.player.getName(), flight); flight.start(); if (task < 0) { task = MagicSpells.scheduleRepeatingTask(this, 0, 5); } } void init() { if (!inited) { inited = true; MagicSpells.registerEvents(this); } } void cancel(Player player) { ActiveFlight flight = flights.remove(player.getName()); if (flight != null) { flight.cancel(); } } void turnOff() { for (ActiveFlight flight : flights.values()) { flight.cancel(); } MagicSpells.cancelTask(task); flights.clear(); } @EventHandler void onTeleport(PlayerTeleportEvent event) { cancel(event.getPlayer()); } @EventHandler void onPlayerDeath(PlayerDeathEvent event) { cancel(event.getEntity()); } @EventHandler void onQuit(PlayerQuitEvent event) { cancel(event.getPlayer()); } @Override public void run() { Iterator<ActiveFlight> iter = flights.values().iterator(); while (iter.hasNext()) { ActiveFlight flight = iter.next(); if (flight.isDone()) { iter.remove(); } else { flight.fly(); } } if (flights.size() == 0) { MagicSpells.cancelTask(task); task = -1; } } } class ActiveFlight { Player player; EntityType mountType; Entity mount; Entity entityToPush; FlightPathSpell spell; FlightState state; boolean wasFlyingAllowed; boolean wasFlying; Location lastLocation; int sameLocCount = 0; public ActiveFlight(Player player, EntityType mountType, FlightPathSpell spell) { this.player = player; this.mountType = mountType; this.spell = spell; this.state = FlightState.TAKE_OFF; this.wasFlyingAllowed = player.getAllowFlight(); this.wasFlying = player.isFlying(); this.lastLocation = player.getLocation(); } void start() { player.setAllowFlight(true); spell.playSpellEffects(EffectPosition.CASTER, player); if (mountType == null) { entityToPush = player; } else { mount = player.getWorld().spawnEntity(player.getLocation(), mountType); entityToPush = mount; if (player.getVehicle() != null) { player.getVehicle().eject(); } mount.setPassenger(player); } } void fly() { if (state == FlightState.DONE) return; // check for stuck if (player.getLocation().distanceSquared(lastLocation) < 0.4) { sameLocCount++; } if (sameLocCount > 12) { MagicSpells.error("Flight stuck '" + spell.getInternalName() + "' at " + player.getLocation()); cancel(); return; } lastLocation = player.getLocation(); // do flight if (state == FlightState.TAKE_OFF) { player.setFlying(false); double y = entityToPush.getLocation().getY(); if (y >= cruisingAltitude) { entityToPush.setVelocity(new Vector(0, 0, 0)); state = FlightState.CRUISING; } else { entityToPush.setVelocity(new Vector(0, 2, 0)); } } else if (state == FlightState.CRUISING) { player.setFlying(true); double x = entityToPush.getLocation().getX(); double z = entityToPush.getLocation().getZ(); if ((targetX - 1 <= x && x <= targetX + 1) && (targetZ - 1 <= z && z <= targetZ + 1)) { entityToPush.setVelocity(new Vector(0, 0, 0)); state = FlightState.LANDING; } else { Vector t = new Vector(targetX, cruisingAltitude, targetZ); Vector v = t.subtract(entityToPush.getLocation().toVector()); double len = v.lengthSquared(); v.normalize().multiply(len > 25 ? speed : 0.3); entityToPush.setVelocity(v); } } else if (state == FlightState.LANDING) { player.setFlying(false); Location l = entityToPush.getLocation(); if (l.getBlock().getType() != Material.AIR || l.subtract(0, 1, 0).getBlock().getType() != Material.AIR || l.subtract(0, 2, 0).getBlock().getType() != Material.AIR) { player.setFallDistance(0f); cancel(); } else { entityToPush.setVelocity(new Vector(0, -1, 0)); player.setFallDistance(0f); } } spell.playSpellEffects(EffectPosition.SPECIAL, player); } void cancel() { if (state != FlightState.DONE) { state = FlightState.DONE; player.setFlying(wasFlying); player.setAllowFlight(wasFlyingAllowed); if (mount != null) { mount.eject(); mount.remove(); } spell.playSpellEffects(EffectPosition.DELAYED, player); player = null; mount = null; entityToPush = null; spell = null; } } boolean isDone() { return state == FlightState.DONE; } } enum FlightState { TAKE_OFF, CRUISING, LANDING, DONE } }