package com.nisovin.magicspells.spells.instant; import java.util.ArrayList; import java.util.List; import java.util.Random; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.entity.Item; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.Action; import org.bukkit.event.entity.ItemSpawnEvent; import org.bukkit.event.inventory.InventoryOpenEvent; import org.bukkit.event.player.PlayerDropItemEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerPickupItemEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.util.Vector; import com.nisovin.magicspells.MagicSpells; import com.nisovin.magicspells.Spell; import com.nisovin.magicspells.spelleffects.EffectPosition; import com.nisovin.magicspells.spells.InstantSpell; import com.nisovin.magicspells.spells.TargetedEntitySpell; import com.nisovin.magicspells.spells.TargetedLocationSpell; import com.nisovin.magicspells.spells.command.ScrollSpell; import com.nisovin.magicspells.spells.command.TomeSpell; import com.nisovin.magicspells.util.MagicConfig; import com.nisovin.magicspells.util.Util; public class ConjureSpell extends InstantSpell implements TargetedEntitySpell, TargetedLocationSpell { Random rand = new Random(); static ExpirationHandler expirationHandler = null; private boolean addToInventory; private boolean addToEnderChest; private boolean dropIfInventoryFull; private boolean powerAffectsQuantity; private boolean powerAffectsChance; private boolean calculateDropsIndividually; private boolean autoEquip; private boolean stackExisting; private boolean ignoreMaxStackSize; private boolean forceUpdateInventory; //private boolean allowParameters; private double expiration; private int requiredSlot; private int preferredSlot; private boolean offhand; List<String> itemList; private ItemStack[] itemTypes; private int[] itemMinQuantities; private int[] itemMaxQuantities; private double[] itemChances; private float randomVelocity; private int delay; public ConjureSpell(MagicConfig config, String spellName) { super(config, spellName); addToInventory = getConfigBoolean("add-to-inventory", false); addToEnderChest = getConfigBoolean("add-to-ender-chest", false); dropIfInventoryFull = getConfigBoolean("drop-if-inventory-full", true); powerAffectsQuantity = getConfigBoolean("power-affects-quantity", false); powerAffectsChance = getConfigBoolean("power-affects-chance", true); calculateDropsIndividually = getConfigBoolean("calculate-drops-individually", true); autoEquip = getConfigBoolean("auto-equip", false); stackExisting = getConfigBoolean("stack-existing", true); ignoreMaxStackSize = getConfigBoolean("ignore-max-stack-size", false); forceUpdateInventory = getConfigBoolean("force-update-inventory", true); //allowParameters = getConfigBoolean("allow-parameters", true); expiration = getConfigDouble("expiration", 0L); requiredSlot = getConfigInt("required-slot", -1); preferredSlot = getConfigInt("preferred-slot", -1); offhand = getConfigBoolean("offhand", false); itemList = getConfigStringList("items", null); randomVelocity = getConfigFloat("random-velocity", 0); delay = getConfigInt("delay", -1); } @Override public void initialize() { super.initialize(); if (expiration > 0 && expirationHandler == null) { expirationHandler = new ExpirationHandler(); } if (itemList != null && itemList.size() > 0) { itemTypes = new ItemStack[itemList.size()]; itemMinQuantities = new int[itemList.size()]; itemMaxQuantities = new int[itemList.size()]; itemChances = new double[itemList.size()]; for (int i = 0; i < itemList.size(); i++) { try { String[] data = Util.splitParams(itemList.get(i)); String[] quantityData = data.length == 1 ? new String[]{"1"} : data[1].split("-"); if (data[0].startsWith("TOME:")) { String[] tomeData = data[0].split(":"); TomeSpell tomeSpell = (TomeSpell)MagicSpells.getSpellByInternalName(tomeData[1]); Spell spell = MagicSpells.getSpellByInternalName(tomeData[2]); int uses = tomeData.length > 3 ? Integer.parseInt(tomeData[3]) : -1; itemTypes[i] = tomeSpell.createTome(spell, uses, null); } else if (data[0].startsWith("SCROLL:")) { String[] scrollData = data[0].split(":"); ScrollSpell scrollSpell = (ScrollSpell)MagicSpells.getSpellByInternalName(scrollData[1]); Spell spell = MagicSpells.getSpellByInternalName(scrollData[2]); int uses = scrollData.length > 3 ? Integer.parseInt(scrollData[3]) : -1; itemTypes[i] = scrollSpell.createScroll(spell, uses, null); } else { itemTypes[i] = Util.getItemStackFromString(data[0]); } if (itemTypes[i] == null) { MagicSpells.error("Conjure spell '" + internalName + "' has specified invalid item (e1): " + itemList.get(i)); continue; } if (quantityData.length == 1) { itemMinQuantities[i] = Integer.parseInt(quantityData[0]); itemMaxQuantities[i] = itemMinQuantities[i]; } else { itemMinQuantities[i] = Integer.parseInt(quantityData[0]); itemMaxQuantities[i] = Integer.parseInt(quantityData[1]); } if (data.length > 2) { itemChances[i] = Double.parseDouble(data[2].replace("%", "")); } else { itemChances[i] = 100; } } catch (Exception e) { MagicSpells.error("Conjure spell '" + internalName + "' has specified invalid item (e2): " + itemList.get(i)); itemTypes[i] = null; } } } itemList = null; } @Override public PostCastAction castSpell(final Player player, SpellCastState state, final float power, String[] args) { if (itemTypes == null) return PostCastAction.ALREADY_HANDLED; if (state == SpellCastState.NORMAL) { if (delay >= 0) { MagicSpells.scheduleDelayedTask(new Runnable() { public void run() { conjureItems(player, power); } }, delay); } else { conjureItems(player, power); } } return PostCastAction.HANDLE_NORMALLY; } @SuppressWarnings("deprecation") private void conjureItems(Player player, float power) { // get items to drop List<ItemStack> items = new ArrayList<ItemStack>(); if (calculateDropsIndividually) { individual(items, rand, power); } else { together(items, rand, power); } // drop items Location loc = player.getEyeLocation().add(player.getLocation().getDirection()); boolean updateInv = false; for (ItemStack item : items) { boolean added = false; PlayerInventory inv = player.getInventory(); if (autoEquip && item.getAmount() == 1) { if (item.getType().name().endsWith("HELMET") && inv.getHelmet() == null) { inv.setHelmet(item); added = true; } else if (item.getType().name().endsWith("CHESTPLATE") && inv.getChestplate() == null) { inv.setChestplate(item); added = true; } else if (item.getType().name().endsWith("LEGGINGS") && inv.getLeggings() == null) { inv.setLeggings(item); added = true; } else if (item.getType().name().endsWith("BOOTS") && inv.getBoots() == null) { inv.setBoots(item); added = true; } } if (!added) { if (addToEnderChest) { added = Util.addToInventory(player.getEnderChest(), item, stackExisting, ignoreMaxStackSize); } if (!added && addToInventory) { if (offhand) { MagicSpells.getVolatileCodeHandler().setOffhand(player, item); } else if (requiredSlot >= 0) { ItemStack old = inv.getItem(requiredSlot); if (old != null && old.isSimilar(item)) { item.setAmount(item.getAmount() + old.getAmount()); } inv.setItem(requiredSlot, item); added = true; updateInv = true; } else if (preferredSlot >= 0 && inv.getItem(preferredSlot) == null) { inv.setItem(preferredSlot, item); added = true; updateInv = true; } else if (preferredSlot >= 0 && inv.getItem(preferredSlot).isSimilar(item) && inv.getItem(preferredSlot).getAmount() + item.getAmount() < item.getType().getMaxStackSize()) { item.setAmount(item.getAmount() + inv.getItem(preferredSlot).getAmount()); inv.setItem(preferredSlot, item); added = true; updateInv = true; } else if (!added) { added = Util.addToInventory(inv, item, stackExisting, ignoreMaxStackSize); if (added) updateInv = true; } } if (!added && (dropIfInventoryFull || !addToInventory)) { player.getWorld().dropItem(loc, item).setItemStack(item); } } else { updateInv = true; } } if (updateInv && forceUpdateInventory) { player.updateInventory(); } playSpellEffects(EffectPosition.CASTER, player); } private void individual(List<ItemStack> items, Random rand, float power) { for (int i = 0; i < itemTypes.length; i++) { double r = rand.nextDouble() * 100; if (powerAffectsChance) r = r / power; if (itemTypes[i] != null && r < itemChances[i]) { addItem(i, items, rand, power); } } } private void together(List<ItemStack> items, Random rand, float power) { double r = rand.nextDouble() * 100; double m = 0; for (int i = 0; i < itemTypes.length; i++) { if (itemTypes[i] != null && r < itemChances[i] + m) { addItem(i, items, rand, power); return; } else { m += itemChances[i]; } } } private void addItem(int i, List<ItemStack> items, Random rand, float power) { int quant = itemMinQuantities[i]; if (itemMaxQuantities[i] > itemMinQuantities[i]) { quant = rand.nextInt(itemMaxQuantities[i] - itemMinQuantities[i]) + itemMinQuantities[i]; } if (powerAffectsQuantity) { quant = Math.round(quant * power); } if (quant > 0) { ItemStack item = itemTypes[i].clone(); item.setAmount(quant); if (expiration > 0) { expirationHandler.addExpiresLine(item, expiration); } items.add(item); } } @Override public boolean castAtLocation(Player caster, Location target, float power) { return castAtLocation(target, power); } @Override public boolean castAtLocation(Location target, float power) { List<ItemStack> items = new ArrayList<ItemStack>(); if (calculateDropsIndividually) { individual(items, rand, power); } else { together(items, rand, power); } Location loc = target.clone(); if (loc.getBlock().getType() != Material.AIR) { loc.add(0, 1, 0); } if (loc.getBlock().getType() != Material.AIR) { loc.add(0, 1, 0); } for (ItemStack item : items) { Item dropped = loc.getWorld().dropItem(loc, item); dropped.setItemStack(item); if (randomVelocity > 0) { Vector v = new Vector(rand.nextDouble() - .5, rand.nextDouble() / 2, rand.nextDouble() - .5); v.normalize().multiply(randomVelocity); dropped.setVelocity(v); } } return true; } @Override public boolean castAtEntity(Player caster, LivingEntity target, float power) { return castAtEntity(target, power); } @Override public boolean castAtEntity(LivingEntity target, float power) { if (target instanceof Player) { conjureItems((Player)target, power); return true; } else { return false; } } @Override public void turnOff() { expirationHandler = null; } class ExpirationHandler implements Listener { private final String expPrefix = ChatColor.BLACK.toString() + ChatColor.MAGIC.toString() + "MSExp:"; public ExpirationHandler() { MagicSpells.registerEvents(this); } public void addExpiresLine(ItemStack item, double expireHours) { ItemMeta meta = item.getItemMeta(); List<String> lore; if (meta.hasLore()) { lore = new ArrayList<String>(meta.getLore()); } else { lore = new ArrayList<String>(); } long expiresAt = System.currentTimeMillis() + (long)(expireHours * 60L * 60L * 1000L); lore.add(getExpiresText(expiresAt)); lore.add(expPrefix + expiresAt); meta.setLore(lore); item.setItemMeta(meta); } @EventHandler(priority = EventPriority.LOWEST) void onJoin(PlayerJoinEvent event) { PlayerInventory inv = event.getPlayer().getInventory(); processInventory(inv); ItemStack[] armor = inv.getArmorContents(); processInventoryContents(armor); inv.setArmorContents(armor); } @EventHandler(priority = EventPriority.LOWEST) void onInvOpen(InventoryOpenEvent event) { processInventory(event.getInventory()); } @EventHandler(priority = EventPriority.LOWEST) void onRightClick(PlayerInteractEvent event) { if (event.hasItem()) { ItemStack item = event.getPlayer().getItemInHand(); ExpirationResult result = updateExpiresLineIfNeeded(item); if (result == ExpirationResult.EXPIRED) { event.getPlayer().setItemInHand(null); event.setCancelled(true); } } } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) void onPickup(PlayerPickupItemEvent event) { processItemDrop(event.getItem()); if (event.getItem().isDead()) { event.setCancelled(true); } } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) void onDrop(PlayerDropItemEvent event) { processItemDrop(event.getItemDrop()); } @EventHandler(priority = EventPriority.LOWEST) void onItemSpawn(ItemSpawnEvent event) { processItemDrop(event.getEntity()); } private void processInventory(Inventory inv) { ItemStack[] contents = inv.getContents(); processInventoryContents(contents); inv.setContents(contents); } private void processInventoryContents(ItemStack[] contents) { for (int i = 0; i < contents.length; i++) { ExpirationResult result = updateExpiresLineIfNeeded(contents[i]); if (result == ExpirationResult.EXPIRED) { contents[i] = null; } } } private boolean processItemDrop(Item drop) { ItemStack item = drop.getItemStack(); ExpirationResult result = updateExpiresLineIfNeeded(item); if (result == ExpirationResult.UPDATE) { drop.setItemStack(item); } else if (result == ExpirationResult.EXPIRED) { drop.remove(); return true; } return false; } private ExpirationResult updateExpiresLineIfNeeded(ItemStack item) { if (item == null) return ExpirationResult.NO_UPDATE; if (!item.hasItemMeta()) return ExpirationResult.NO_UPDATE; ItemMeta meta = item.getItemMeta(); if (!meta.hasLore()) return ExpirationResult.NO_UPDATE; ArrayList<String> lore = new ArrayList<String>(meta.getLore()); if (lore.size() < 2) return ExpirationResult.NO_UPDATE; String lastLine = lore.get(lore.size() - 1); if (!lastLine.startsWith(expPrefix)) return ExpirationResult.NO_UPDATE; long expiresAt = Long.parseLong(lastLine.replace(expPrefix, "")); if (expiresAt < System.currentTimeMillis()) { return ExpirationResult.EXPIRED; } else { lore.set(lore.size() - 2, getExpiresText(expiresAt)); meta.setLore(lore); item.setItemMeta(meta); return ExpirationResult.UPDATE; } } private String getExpiresText(long expiresAt) { if (expiresAt < System.currentTimeMillis()) { return ChatColor.GRAY + "Expired"; } else { double hours = (expiresAt - System.currentTimeMillis()) / 3600000D; if (hours / 24 >= 15) { return ChatColor.GRAY + "Expires in " + ChatColor.WHITE + ((long)hours / 168L) + ChatColor.GRAY + " weeks"; } else if (hours / 24 >= 3) { return ChatColor.GRAY + "Expires in " + ChatColor.WHITE + ((long)hours / 24L) + ChatColor.GRAY + " days"; } else if (hours >= 2) { return ChatColor.GRAY + "Expires in " + ChatColor.WHITE + (long)hours + ChatColor.GRAY + " hours"; } else { return ChatColor.GRAY + "Expires in " + ChatColor.WHITE + "1" + ChatColor.GRAY + " hour"; } } } } private enum ExpirationResult { NO_UPDATE, UPDATE, EXPIRED } }