package com.nisovin.magicspells.spells.targeted; import java.util.ArrayList; import java.util.List; import java.util.Random; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.entity.Ageable; import org.bukkit.entity.Enderman; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.entity.Projectile; import org.bukkit.entity.Skeleton; import org.bukkit.entity.Tameable; import org.bukkit.entity.Zombie; import org.bukkit.event.EventHandler; import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.entity.EntityTargetEvent; import org.bukkit.inventory.EntityEquipment; import org.bukkit.inventory.ItemStack; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import com.nisovin.magicspells.MagicSpells; import com.nisovin.magicspells.Subspell; import com.nisovin.magicspells.spells.TargetedEntityFromLocationSpell; import com.nisovin.magicspells.spells.TargetedLocationSpell; import com.nisovin.magicspells.spells.TargetedSpell; import com.nisovin.magicspells.util.BlockUtils; import com.nisovin.magicspells.util.EntityData; import com.nisovin.magicspells.util.TargetInfo; import com.nisovin.magicspells.util.Util; import com.nisovin.magicspells.util.MagicConfig; public class SpawnMonsterSpell extends TargetedSpell implements TargetedLocationSpell, TargetedEntityFromLocationSpell { private String location; private EntityData entityData; private boolean allowSpawnInMidair; private boolean baby; private boolean tamed; private ItemStack holding; private ItemStack helmet; private ItemStack chestplate; private ItemStack leggings; private ItemStack boots; private float holdingDropChance; private float helmetDropChance; private float chestplateDropChance; private float leggingsDropChance; private float bootsDropChance; private List<PotionEffect> potionEffects; private int fireTicks; private int duration; private String nameplateText; private boolean useCasterName; private boolean removeAI; private boolean noAI; private boolean addLookAtPlayerAI; private String[] attributeTypes; private double[] attributeValues; private int[] attributeOperations; private Subspell attackSpell; private int retargetRange; private int targetInterval; private int targetRange; private Random random = new Random(); public SpawnMonsterSpell(MagicConfig config, String spellName) { super(config, spellName); location = getConfigString("location", "target"); entityData = new EntityData(getConfigString("entity-type", "wolf")); allowSpawnInMidair = getConfigBoolean("allow-spawn-in-midair", false); baby = getConfigBoolean("baby", false); tamed = getConfigBoolean("tamed", false); holding = Util.getItemStackFromString(getConfigString("holding", "0")); if (holding != null && holding.getType() != Material.AIR) { holding.setAmount(1); } helmet = Util.getItemStackFromString(getConfigString("helmet", "0")); if (helmet != null && helmet.getType() != Material.AIR) { helmet.setAmount(1); } chestplate = Util.getItemStackFromString(getConfigString("chestplate", "0")); if (chestplate != null && chestplate.getType() != Material.AIR) { chestplate.setAmount(1); } leggings = Util.getItemStackFromString(getConfigString("leggings", "0")); if (leggings != null && leggings.getType() != Material.AIR) { leggings.setAmount(1); } boots = Util.getItemStackFromString(getConfigString("boots", "0")); if (boots != null && boots.getType() != Material.AIR) { boots.setAmount(1); } holdingDropChance = getConfigFloat("holding-drop-chance", 0) / 100F; helmetDropChance = getConfigFloat("helmet-drop-chance", 0) / 100F; chestplateDropChance = getConfigFloat("chestplate-drop-chance", 0) / 100F; leggingsDropChance = getConfigFloat("leggings-drop-chance", 0) / 100F; bootsDropChance = getConfigFloat("boots-drop-chance", 0) / 100F; List<String> list = getConfigStringList("potion-effects", null); if (list != null && list.size() > 0) { potionEffects = new ArrayList<PotionEffect>(); for (String data : list) { String[] split = data.split(" "); try { PotionEffectType type = Util.getPotionEffectType(split[0]); if (type == null) throw new Exception(""); int duration = 600; if (split.length > 1) duration = Integer.parseInt(split[1]); int strength = 0; if (split.length > 2) strength = Integer.parseInt(split[2]); boolean ambient = false; if (split.length > 3 && split[3].equalsIgnoreCase("ambient")) ambient = true; potionEffects.add(new PotionEffect(type, duration, strength, ambient)); } catch (Exception e) { MagicSpells.debug("Invalid potion effect string on '" + internalName + "' spell: " + data); } } } fireTicks = getConfigInt("fire-ticks", 0); duration = getConfigInt("duration", 0); nameplateText = getConfigString("nameplate-text", ""); removeAI = getConfigBoolean("remove-ai", false); noAI = getConfigBoolean("no-ai", false); addLookAtPlayerAI = getConfigBoolean("add-look-at-player-ai", false); List<String> attributes = getConfigStringList("attributes", null); if (attributes != null && attributes.size() > 0) { attributeTypes = new String[attributes.size()]; attributeValues = new double[attributes.size()]; attributeOperations = new int[attributes.size()]; for (int i = 0; i < attributes.size(); i++) { String s = attributes.get(i); try { String[] data = s.split(" "); String type = data[0]; double val = Double.parseDouble(data[1]); int op = 0; if (data.length > 2) { if (data[2].equalsIgnoreCase("mult")) { op = 1; } else if (data[2].toLowerCase().contains("add") && data[2].toLowerCase().contains("perc")) { op = 2; } } attributeTypes[i] = type; attributeValues[i] = val; attributeOperations[i] = op; } catch (Exception e) { MagicSpells.error("Invalid attribute on '" + spellName + "' spell: " + s); } } } String attackSpellName = getConfigString("attack-spell", null); if (attackSpellName != null && !attackSpellName.isEmpty()) { attackSpell = new Subspell(attackSpellName); } retargetRange = getConfigInt("retarget-range", 50); targetInterval = getConfigInt("target-interval", -1); targetRange = getConfigInt("target-range", 20); if (entityData.getType() == null || !entityData.getType().isAlive()) { MagicSpells.error("SpawnMonster spell '" + spellName + "' has an invalid entity-type!"); } } @Override public void initialize() { super.initialize(); if (attackSpell != null) { if (!attackSpell.process()) { MagicSpells.error("SpawnMonsterSpell '" + internalName + "' has invalid attack-spell"); } } } @Override public PostCastAction castSpell(Player player, SpellCastState state, float power, String[] args) { if (state == SpellCastState.NORMAL) { Location loc = null; LivingEntity target = null; if (location.equalsIgnoreCase("focus")) { loc = getRandomLocationFrom(player.getLocation(), 3); TargetInfo<LivingEntity> targetInfo = getTargetedEntity(player, power); if (targetInfo == null) { return noTarget(player); } target = targetInfo.getTarget(); power = targetInfo.getPower(); } else if (location.equalsIgnoreCase("target")) { Block block = getTargetedBlock(player, power); if (block != null && block.getType() != Material.AIR) { if (BlockUtils.isPathable(block)) { loc = block.getLocation(); } else if (BlockUtils.isPathable(block.getRelative(BlockFace.UP))) { loc = block.getLocation().add(0, 1, 0); } } } else if (location.equalsIgnoreCase("caster")) { loc = player.getLocation(); } else if (location.equalsIgnoreCase("random")) { loc = getRandomLocationFrom(player.getLocation(), getRange(power)); } else if (location.startsWith("casteroffset:")) { String[] split = location.split(":"); float y = Float.parseFloat(split[1]); loc = player.getLocation().add(0, y, 0); loc.setPitch(0); } if (loc == null) { return noTarget(player); } spawnMob(player, player.getLocation(), loc, target, power); } return PostCastAction.HANDLE_NORMALLY; } private Location getRandomLocationFrom(Location location, int range) { World world = location.getWorld(); int attempts = 0; int x, y, z; Block block, block2; while (attempts < 10) { x = location.getBlockX() + random.nextInt(range * 2) - range; y = location.getBlockY() + 2; z = location.getBlockZ() + random.nextInt(range * 2) - range; block = world.getBlockAt(x, y, z); if (block.getType() == Material.STATIONARY_WATER || block.getType() == Material.WATER) { return block.getLocation(); } else if (BlockUtils.isPathable(block)) { if (allowSpawnInMidair) { return block.getLocation(); } int c = 0; while (c < 5) { block2 = block.getRelative(BlockFace.DOWN); if (BlockUtils.isPathable(block2)) { block = block2; } else { return block.getLocation(); } c++; } } attempts++; } return null; } private void spawnMob(final Player player, Location source, Location loc, LivingEntity target, float power) { if (entityData.getType() != null) { // spawn it loc.setYaw((float) (Math.random() * 360)); final Entity entity = entityData.spawn(loc.add(.5, .1, .5)); // prep prepMob(player, entity); // add potion effects if (potionEffects != null) { ((LivingEntity)entity).addPotionEffects(potionEffects); } // set on fire if (fireTicks > 0) { ((LivingEntity)entity).setFireTicks(fireTicks); } // add attributes if (attributeTypes != null && attributeTypes.length > 0) { for (int i = 0; i < attributeTypes.length; i++) { if (attributeTypes[i] != null) { MagicSpells.getVolatileCodeHandler().addEntityAttribute((LivingEntity)entity, attributeTypes[i], attributeValues[i], attributeOperations[i]); } } } // set AI if (removeAI) { MagicSpells.getVolatileCodeHandler().removeAI((LivingEntity)entity); if (addLookAtPlayerAI) { MagicSpells.getVolatileCodeHandler().addAILookAtPlayer((LivingEntity)entity, 10); } } if (noAI) { MagicSpells.getVolatileCodeHandler().setNoAIFlag((LivingEntity)entity); } // set target if (target != null) { MagicSpells.getVolatileCodeHandler().setTarget((LivingEntity)entity, target); } if (targetInterval > 0) { new Targeter(player, (LivingEntity)entity); } // setup attack spell if (attackSpell != null) { final AttackMonitor monitor = new AttackMonitor(player, (LivingEntity)entity, target, power); MagicSpells.registerEvents(monitor); MagicSpells.scheduleDelayedTask(new Runnable() { public void run() { HandlerList.unregisterAll(monitor); } }, duration > 0 ? duration : 12000); } // play effects if (player != null) { playSpellEffects(player, entity); } else { playSpellEffects(source, entity); } // schedule removal if (duration > 0) { MagicSpells.scheduleDelayedTask(new Runnable() { public void run() { entity.remove(); } }, duration); } } } void prepMob(Player player, Entity entity) { // set as tamed if (tamed && entity instanceof Tameable && player != null) { ((Tameable)entity).setTamed(true); ((Tameable)entity).setOwner(player); } // set as baby if (baby) { if (entity instanceof Ageable) { ((Ageable)entity).setBaby(); } } if (entity instanceof Zombie) { ((Zombie)entity).setBaby(baby); } // set held item if (holding != null && holding.getType() != Material.AIR) { if (entity instanceof Enderman) { ((Enderman)entity).setCarriedMaterial(holding.getData()); } else if (entity instanceof Skeleton || entity instanceof Zombie) { final EntityEquipment equip = ((LivingEntity)entity).getEquipment(); equip.setItemInHand(holding.clone()); equip.setItemInHandDropChance(holdingDropChance); } } // set armor final EntityEquipment equip = ((LivingEntity)entity).getEquipment(); equip.setHelmet(helmet); equip.setChestplate(chestplate); equip.setLeggings(leggings); equip.setBoots(boots); equip.setHelmetDropChance(helmetDropChance); equip.setChestplateDropChance(chestplateDropChance); equip.setLeggingsDropChance(leggingsDropChance); equip.setBootsDropChance(bootsDropChance); // set nameplate text if (entity instanceof LivingEntity) { if (useCasterName && player != null) { ((LivingEntity)entity).setCustomName(player.getDisplayName()); ((LivingEntity)entity).setCustomNameVisible(true); } else if (nameplateText != null && !nameplateText.isEmpty()) { ((LivingEntity)entity).setCustomName(nameplateText); ((LivingEntity)entity).setCustomNameVisible(true); } } } @Override public boolean castAtLocation(Player caster, Location target, float power) { if (location.equalsIgnoreCase("target")) { spawnMob(caster, caster.getLocation(), target, null, power); } else if (location.equalsIgnoreCase("caster")) { spawnMob(caster, caster.getLocation(), caster.getLocation(), null, power); } else if (location.equalsIgnoreCase("random")) { Location loc = getRandomLocationFrom(target, getRange(power)); if (loc != null) { spawnMob(caster, caster.getLocation(), loc, null, power); } } else if (location.startsWith("offset:")) { String[] split = location.split(":"); float y = Float.parseFloat(split[1]); Location loc = target.clone().add(0, y, 0); loc.setPitch(0); spawnMob(caster, caster.getLocation(), loc, null, power); } return true; } @Override public boolean castAtLocation(Location target, float power) { if (location.equalsIgnoreCase("target")) { spawnMob(null, target, target, null, power); } else if (location.equalsIgnoreCase("caster")) { spawnMob(null, target, target, null, power); } else if (location.equalsIgnoreCase("random")) { Location loc = getRandomLocationFrom(target, getRange(power)); if (loc != null) { spawnMob(null, target, loc, null, power); } } else if (location.startsWith("offset:")) { String[] split = location.split(":"); float y = Float.parseFloat(split[1]); Location loc = target.clone().add(0, y, 0); loc.setPitch(0); spawnMob(null, target, loc, null, power); } return true; } @Override public boolean castAtEntityFromLocation(Player caster, Location from, LivingEntity target, float power) { if (location.equals("focus")) { spawnMob(caster, from, from, target, power); } else { castAtLocation(caster, from, power); } return true; } @Override public boolean castAtEntityFromLocation(Location from, LivingEntity target, float power) { if (location.equals("focus")) { spawnMob(null, from, from, target, power); } else { castAtLocation(from, power); } return true; } class AttackMonitor implements Listener { Player caster; LivingEntity monster; LivingEntity target; float power; public AttackMonitor(Player caster, LivingEntity monster, LivingEntity target, float power) { this.caster = caster; this.monster = monster; this.target = target; this.power = power; } @EventHandler(ignoreCancelled = true) void onDamage(EntityDamageByEntityEvent event) { if (attackSpell.getSpell().onCooldown(caster)) { return; } Entity damager = event.getDamager(); if (damager instanceof Projectile) { if (((Projectile)damager).getShooter() != null && ((Projectile)damager).getShooter() instanceof Entity) { damager = (Entity)((Projectile)damager).getShooter(); } } if (event.getEntity() instanceof LivingEntity && damager == monster) { if (attackSpell.isTargetedEntityFromLocationSpell()) { attackSpell.castAtEntityFromLocation(caster, monster.getLocation(), (LivingEntity)event.getEntity(), power); } else if (attackSpell.isTargetedEntitySpell()) { attackSpell.castAtEntity(caster, (LivingEntity)event.getEntity(), power); } else if (attackSpell.isTargetedLocationSpell()) { attackSpell.castAtLocation(caster, event.getEntity().getLocation(), power); } else { attackSpell.cast(caster, power); } event.setCancelled(true); } } @EventHandler void onTarget(EntityTargetEvent event) { if (event.getEntity() == monster && event.getTarget() == caster) { event.setCancelled(true); } else if (event.getTarget() == null) { retarget(null); } else if (target != null && event.getTarget() != target) { event.setTarget(target); } } @EventHandler void onDeath(EntityDeathEvent event) { if (event.getEntity() == target) { target = null; retarget(event.getEntity()); } } void retarget(LivingEntity ignore) { LivingEntity t = null; int r = retargetRange * retargetRange; for (Entity e : monster.getNearbyEntities(retargetRange, retargetRange, retargetRange)) { if (e instanceof LivingEntity && validTargetList.canTarget(caster, (LivingEntity)e) && e != caster && e != ignore) { if (e instanceof Player) { Player p = (Player)e; if (p.getGameMode() == GameMode.CREATIVE || p.getGameMode() == GameMode.SPECTATOR) { continue; } } int rr = (int)monster.getLocation().distanceSquared(e.getLocation()); if (rr < r) { r = rr; t = (LivingEntity)e; if (r < 25) { break; } } } } target = t; MagicSpells.getVolatileCodeHandler().setTarget(monster, t); } } class Targeter implements Runnable { Player caster; LivingEntity entity; int taskId; public Targeter(Player caster, LivingEntity entity) { this.caster = caster; this.entity = entity; this.taskId = MagicSpells.scheduleRepeatingTask(this, 1, targetInterval); } public void run() { if (entity.isDead() || !entity.isValid()) { MagicSpells.cancelTask(taskId); return; } List<Entity> list = entity.getNearbyEntities(targetRange, targetRange, targetRange); List<LivingEntity> targetable = new ArrayList<LivingEntity>(); for (Entity e : list) { if (e instanceof LivingEntity && validTargetList.canTarget(caster, (LivingEntity)e)) { targetable.add((LivingEntity)e); } } if (targetable.size() > 0) { LivingEntity target = targetable.get(random.nextInt(targetable.size())); MagicSpells.getVolatileCodeHandler().setTarget(entity, target); } } } }