package com.nisovin.magicspells.spells.instant; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.entity.Entity; import org.bukkit.entity.FallingBlock; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.entity.TNTPrimed; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityChangeBlockEvent; import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.event.entity.EntityDamageEvent.DamageCause; import org.bukkit.util.Vector; import com.nisovin.magicspells.MagicSpells; import com.nisovin.magicspells.Spell; import com.nisovin.magicspells.events.SpellTargetEvent; import com.nisovin.magicspells.materials.MagicMaterial; import com.nisovin.magicspells.spelleffects.EffectPosition; import com.nisovin.magicspells.spells.InstantSpell; import com.nisovin.magicspells.spells.TargetedLocationSpell; import com.nisovin.magicspells.util.BlockUtils; import com.nisovin.magicspells.util.MagicConfig; import com.nisovin.magicspells.util.Util; public class ThrowBlockSpell extends InstantSpell implements TargetedLocationSpell { MagicMaterial material; int tntFuse; float velocity; boolean applySpellPowerToVelocity; float verticalAdjustment; float yOffset; int rotationOffset; float fallDamage; int fallDamageMax; boolean dropItem; boolean removeBlocks; boolean preventBlocks; boolean callTargetEvent; boolean checkPlugins; boolean ensureSpellCast; boolean stickyBlocks; String spellOnLand; TargetedLocationSpell spell; Map<Entity, FallingBlockInfo> fallingBlocks; int cleanTask = -1; public ThrowBlockSpell(MagicConfig config, String spellName) { super(config, spellName); String blockTypeInfo = getConfigString("block-type", "anvil"); if (blockTypeInfo.toLowerCase().startsWith("primedtnt:")) { String[] split = blockTypeInfo.split(":"); material = null; tntFuse = Integer.parseInt(split[1]); } else { material = MagicSpells.getItemNameResolver().resolveBlock(blockTypeInfo); tntFuse = 0; } velocity = getConfigFloat("velocity", 1); applySpellPowerToVelocity = getConfigBoolean("apply-spell-power-to-velocity", false); verticalAdjustment = getConfigFloat("vertical-adjustment", 0.5F); yOffset = getConfigFloat("y-offset", 0F); rotationOffset = getConfigInt("rotation-offset", 0); fallDamage = getConfigFloat("fall-damage", 2.0F); fallDamageMax = getConfigInt("fall-damage-max", 20); dropItem = getConfigBoolean("drop-item", false); removeBlocks = getConfigBoolean("remove-blocks", false); preventBlocks = getConfigBoolean("prevent-blocks", false); callTargetEvent = getConfigBoolean("call-target-event", true); checkPlugins = getConfigBoolean("check-plugins", false); ensureSpellCast = getConfigBoolean("ensure-spell-cast", true); stickyBlocks = getConfigBoolean("sticky-blocks", false); spellOnLand = getConfigString("spell-on-land", null); } @Override public void initialize() { super.initialize(); if (material == null && tntFuse == 0) { MagicSpells.error("Invalid block-type for " + internalName + " spell"); } if (spellOnLand != null && !spellOnLand.isEmpty()) { Spell s = MagicSpells.getSpellByInternalName(spellOnLand); if (s != null && s instanceof TargetedLocationSpell) { spell = (TargetedLocationSpell)s; } else { MagicSpells.error("Invalid spell-on-land for " + internalName + " spell"); } } if (fallDamage > 0 || removeBlocks || preventBlocks || spell != null || ensureSpellCast || stickyBlocks) { fallingBlocks = new HashMap<Entity, ThrowBlockSpell.FallingBlockInfo>(); if (material != null) { registerEvents(new ThrowBlockListener(this)); } else if (tntFuse > 0) { registerEvents(new TntListener()); } } } @Override public PostCastAction castSpell(Player player, SpellCastState state, float power, String[] args) { if (state == SpellCastState.NORMAL) { Vector v = getVector(player.getLocation(), power); Location l = player.getEyeLocation().add(v); l.add(0, yOffset, 0); spawnFallingBlock(player, power, l, v); playSpellEffects(EffectPosition.CASTER, player); } return PostCastAction.HANDLE_NORMALLY; } private Vector getVector(Location loc, float power) { Vector v = loc.getDirection(); if (verticalAdjustment != 0) { v.setY(v.getY() + verticalAdjustment); } if (rotationOffset != 0) { Util.rotateVector(v, rotationOffset); } v.normalize().multiply(velocity); if (applySpellPowerToVelocity) { v.multiply(power); } return v; } private void spawnFallingBlock(Player player, float power, Location location, Vector velocity) { Entity entity = null; FallingBlockInfo info = new FallingBlockInfo(player, power); if (material != null) { FallingBlock block = material.spawnFallingBlock(location); block.setVelocity(velocity); block.setDropItem(dropItem); if (fallDamage > 0) { MagicSpells.getVolatileCodeHandler().setFallingBlockHurtEntities(block, fallDamage, fallDamageMax); } if (ensureSpellCast || stickyBlocks) { new ThrowBlockMonitor(block, info); } entity = block; } else if (tntFuse > 0) { TNTPrimed tnt = location.getWorld().spawn(location, TNTPrimed.class); tnt.setFuseTicks(tntFuse); tnt.setVelocity(velocity); entity = tnt; } if (fallingBlocks != null) { fallingBlocks.put(entity, info); if (cleanTask < 0) { startTask(); } } } private void startTask() { cleanTask = Bukkit.getScheduler().scheduleSyncDelayedTask(MagicSpells.plugin, new Runnable() { public void run() { Iterator<Entity> iter = fallingBlocks.keySet().iterator(); while (iter.hasNext()) { Entity entity = iter.next(); if (entity instanceof FallingBlock) { FallingBlock block = (FallingBlock)entity; if (!block.isValid()) { iter.remove(); if (removeBlocks) { Block b = block.getLocation().getBlock(); if (material.equals(b) || (material.getMaterial() == Material.ANVIL && b.getType() == Material.ANVIL)) { b.setType(Material.AIR); } } } } else if (entity instanceof TNTPrimed) { TNTPrimed tnt = (TNTPrimed)entity; if (!tnt.isValid() || tnt.isDead()) { iter.remove(); } } } if (fallingBlocks.size() == 0) { cleanTask = -1; } else { startTask(); } } }, 500); } class ThrowBlockMonitor implements Runnable { FallingBlock block; FallingBlockInfo info; int task; int counter = 0; public ThrowBlockMonitor(FallingBlock block, FallingBlockInfo info) { this.block = block; this.info = info; this.task = MagicSpells.scheduleRepeatingTask(this, 20, 1); } @Override public void run() { if (stickyBlocks && !block.isDead()) { if (block.getVelocity().lengthSquared() < .01) { if (!preventBlocks) { Block b = block.getLocation().getBlock(); if (b.getType() == Material.AIR) { BlockUtils.setBlockFromFallingBlock(b, block, true); } } if (!info.spellActivated && spell != null) { if (info.player != null) { spell.castAtLocation(info.player, block.getLocation(), info.power); } else { spell.castAtLocation(block.getLocation(), info.power); } info.spellActivated = true; } block.remove(); } } if (ensureSpellCast && block.isDead()) { if (!info.spellActivated && spell != null) { if (info.player != null) { spell.castAtLocation(info.player, block.getLocation(), info.power); } else { spell.castAtLocation(block.getLocation(), info.power); } } info.spellActivated = true; MagicSpells.cancelTask(task); } if (counter++ > 1500) { MagicSpells.cancelTask(task); } } } class ThrowBlockListener implements Listener { ThrowBlockSpell thisSpell; public ThrowBlockListener(ThrowBlockSpell spell) { this.thisSpell = spell; } @EventHandler(ignoreCancelled=true) public void onDamage(EntityDamageByEntityEvent event) { FallingBlockInfo info = null; if (removeBlocks || preventBlocks) { info = fallingBlocks.get(event.getDamager()); } else { info = fallingBlocks.remove(event.getDamager()); } if (info != null && event.getEntity() instanceof LivingEntity) { float power = info.power; if (callTargetEvent && info.player != null) { SpellTargetEvent evt = new SpellTargetEvent(thisSpell, info.player, (LivingEntity)event.getEntity(), power); Bukkit.getPluginManager().callEvent(evt); if (evt.isCancelled()) { event.setCancelled(true); return; } else { power = evt.getPower(); } } double damage = event.getDamage() * power; if (checkPlugins && info.player != null) { EntityDamageByEntityEvent evt = new EntityDamageByEntityEvent(info.player, event.getEntity(), DamageCause.ENTITY_ATTACK, damage); Bukkit.getPluginManager().callEvent(evt); if (evt.isCancelled()) { event.setCancelled(true); return; } } event.setDamage(damage); if (spell != null && !info.spellActivated) { if (info.player != null) { spell.castAtLocation(info.player, event.getEntity().getLocation(), power); } else { spell.castAtLocation(event.getEntity().getLocation(), power); } info.spellActivated = true; } } } @EventHandler(ignoreCancelled=true) public void onBlockLand(EntityChangeBlockEvent event) { if (!preventBlocks && spell == null) return; FallingBlockInfo info = fallingBlocks.get(event.getEntity()); if (info != null) { if (preventBlocks) { event.getEntity().remove(); event.setCancelled(true); } if (spell != null && !info.spellActivated) { if (info.player != null) { spell.castAtLocation(info.player, event.getBlock().getLocation().add(.5, .5, .5), info.power); } else { spell.castAtLocation(event.getBlock().getLocation().add(.5, .5, .5), info.power); } info.spellActivated = true; } } } } class TntListener implements Listener { @EventHandler void onExplode(EntityExplodeEvent event) { FallingBlockInfo info = fallingBlocks.get(event.getEntity()); if (info != null) { if (preventBlocks) { event.blockList().clear(); event.setYield(0f); event.setCancelled(true); event.getEntity().remove(); } if (spell != null && !info.spellActivated) { if (info.player != null) { spell.castAtLocation(info.player, event.getEntity().getLocation(), info.power); } else { spell.castAtLocation(event.getEntity().getLocation(), info.power); } info.spellActivated = true; } } } } @Override public void turnOff() { if (fallingBlocks != null) { fallingBlocks.clear(); } } class FallingBlockInfo { Player player; float power; boolean spellActivated; public FallingBlockInfo(Player player, float power) { this.player = player; this.power = power; this.spellActivated = false; } } @Override public boolean castAtLocation(Player caster, Location target, float power) { Vector v = getVector(target, power); spawnFallingBlock(caster, power, target.clone().add(0, yOffset, 0), v); playSpellEffects(EffectPosition.CASTER, target); return true; } @Override public boolean castAtLocation(Location target, float power) { Vector v = getVector(target, power); spawnFallingBlock(null, power, target.clone().add(0, yOffset, 0), v); playSpellEffects(EffectPosition.CASTER, target); return true; } }