/* * Project: UHC * Class: gg.uhc.uhc.modules.death.DeathStandsModule * * The MIT License (MIT) * * Copyright (c) 2015 Graham Howden <graham_howden1 at yahoo.co.uk>. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package gg.uhc.uhc.modules.death; import gg.uhc.uhc.modules.DisableableModule; import gg.uhc.uhc.modules.ModuleRegistry; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Maps; import org.bukkit.*; import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.player.PlayerArmorStandManipulateEvent; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import org.bukkit.metadata.MetadataValue; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.bukkit.util.EulerAngle; import org.bukkit.util.Vector; import java.util.EnumMap; import java.util.Iterator; import java.util.List; import java.util.Map; public class DeathStandsModule extends DisableableModule implements Listener { protected static final int DEATH_ANIMATION_TIME = 18; protected static final int TICKS_PER_SECOND = 20; protected static final double PLAYER_VELOCITY_MULTIPLIER = 1.5D; protected static final double PLAYER_VELOICY_Y_ADDITIONAL = .2D; protected static final Predicate<ItemStack> EMPTY_ITEM = new Predicate<ItemStack>() { @Override public boolean apply(ItemStack input) { return input == null || input.getType() == Material.AIR; } }; protected static final String ICON_NAME = "Death armour stands"; protected static final String STAND_PREFIX = ChatColor.RED + "RIP: " + ChatColor.RESET; public DeathStandsModule() { setId("DeathStands"); this.iconName = ICON_NAME; this.icon.setType(Material.ARMOR_STAND); this.icon.setWeight(ModuleRegistry.CATEGORY_DEATH); } protected boolean isProtectedArmourStand(Entity entity) { final String customName = entity.getCustomName(); return customName != null && customName.startsWith(STAND_PREFIX); } @SuppressWarnings("Duplicates") protected Map<EquipmentSlot, ItemStack> getItems(ArmorStand stand) { final Map<EquipmentSlot, ItemStack> slots = Maps.newHashMapWithExpectedSize(5); slots.put(EquipmentSlot.HAND, stand.getItemInHand()); slots.put(EquipmentSlot.HEAD, stand.getHelmet()); slots.put(EquipmentSlot.CHEST, stand.getChestplate()); slots.put(EquipmentSlot.LEGS, stand.getLeggings()); slots.put(EquipmentSlot.FEET, stand.getBoots()); return slots; } @SuppressWarnings("Duplicates") protected Map<EquipmentSlot, ItemStack> getItems(PlayerInventory inventory) { final Map<EquipmentSlot, ItemStack> slots = Maps.newHashMapWithExpectedSize(5); slots.put(EquipmentSlot.HAND, inventory.getItemInHand()); slots.put(EquipmentSlot.HEAD, inventory.getHelmet()); slots.put(EquipmentSlot.CHEST, inventory.getChestplate()); slots.put(EquipmentSlot.LEGS, inventory.getLeggings()); slots.put(EquipmentSlot.FEET, inventory.getBoots()); return slots; } protected EnumMap<EquipmentSlot, ItemStack> getSavedSlots(Player player) { for (final MetadataValue value : player.getMetadata(StandItemsMetadata.KEY)) { if (!(value instanceof StandItemsMetadata)) continue; // remove the metadata player.removeMetadata(StandItemsMetadata.KEY, value.getOwningPlugin()); // return the map return ((StandItemsMetadata) value).value(); } return Maps.newEnumMap(EquipmentSlot.class); } protected void removeFirstEquals(Iterable iterable, Object equal) { final Iterator iterator = iterable.iterator(); while (iterator.hasNext()) { if (iterator.next().equals(equal)) { iterator.remove(); return; } } } // run at high so previous events can modify the drops before we do (HeadDrops) @EventHandler(priority = EventPriority.HIGH) public void on(PlayerDeathEvent event) { if (!isEnabled()) return; final Player player = event.getEntity(); // make the player invisible for the duration of their death animation player.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, DEATH_ANIMATION_TIME, 1)); final Location location = player.getLocation(); // create an armour stand at the player final ArmorStand stand = player.getWorld().spawn(location.clone().add(0, .2D, 0), ArmorStand.class); stand.setBasePlate(false); stand.setArms(true); // give the armour stand the death message as a name stand.setCustomName(STAND_PREFIX + event.getDeathMessage()); stand.setCustomNameVisible(true); // face the same direction as the player stand.getLocation().setDirection(location.getDirection()); // set the armour stand helmet to be looking at the same yaw stand.setHeadPose(new EulerAngle(Math.toRadians(location.getPitch()), 0, 0)); // use the player's velocity as a base and apply it to the stand stand.setVelocity( player.getVelocity() .clone() .multiply(PLAYER_VELOCITY_MULTIPLIER) .add(new Vector(0D, PLAYER_VELOICY_Y_ADDITIONAL, 0D)) ); // start with player's existing items in each slot (if exists) Map<EquipmentSlot, ItemStack> toSet = getItems(player.getInventory()); // overide with any saved items in the metadata toSet.putAll(getSavedSlots(player)); // filter out the invalid items toSet = Maps.filterValues(toSet, Predicates.not(EMPTY_ITEM)); final List<ItemStack> drops = event.getDrops(); for (final Map.Entry<EquipmentSlot, ItemStack> entry : toSet.entrySet()) { final ItemStack stack = entry.getValue(); if (stack == null) continue; // remove the first matching stack in the drop list removeFirstEquals(drops, stack); // set the item on the armour stand in the correct slot switch (entry.getKey()) { case HAND: stand.setItemInHand(stack); break; case HEAD: stand.setHelmet(stack); break; case CHEST: stand.setChestplate(stack); break; case LEGS: stand.setLeggings(stack); break; case FEET: stand.setBoots(stack); break; default: } } } @EventHandler public void on(PlayerArmorStandManipulateEvent event) { final ArmorStand stand = event.getRightClicked(); if (!isProtectedArmourStand(stand)) return; final ItemStack players = event.getPlayerItem(); final ItemStack stands = event.getArmorStandItem(); // if the player is holding something it will be a swap if (players == null || players.getType() != Material.AIR) return; // if the stand hasn't got something then the player is adding // items or nothing will happen if (stands == null || stands.getType() == Material.AIR) return; // they're removing an item from the armour stand. If there // is only 1 item on the stand then this is the final item // on the armour stand so kill it (fire optional) if (Maps.filterValues(getItems(stand), Predicates.not(EMPTY_ITEM)).values().size() == 1) { stand.remove(); } } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void on(EntityDamageEvent event) { if (event.getEntityType() != EntityType.ARMOR_STAND) return; if (!isProtectedArmourStand(event.getEntity())) return; // always cancel events, we choose when to break the stand event.setCancelled(true); final ArmorStand stand = (ArmorStand) event.getEntity(); final Location loc = stand.getLocation(); final World world = stand.getWorld(); // for the first 2 seconds don't allow breaking // to avoid accidental breaks after kill if (event.getEntity().getTicksLived() < 2 * TICKS_PER_SECOND) { world.playEffect(stand.getEyeLocation(), Effect.WITCH_MAGIC, 0); return; } // drop each of it's worn items for (final ItemStack stack : Maps.filterValues(getItems(stand), Predicates.not(EMPTY_ITEM)).values()) { world.dropItemNaturally(loc, stack); } // kill the stand now stand.remove(); } @Override protected boolean isEnabledByDefault() { return true; } }