public enum SenderType {
    PLAYER("Player", "Players"),
    BLOCK("Block", "Blocks"),
    CONSOLE("Console", "Console"),
    RCONSOLE("Remote Console", "Remote Consoles"),
    UNKNOWN("Unknown", "Unknowns");
    private final String displayName;
    private final String pluralDisplayName;

    private SenderType(String displayName, String pluralDisplayName) {
        this.displayName = displayName;
        this.pluralDisplayName = pluralDisplayName;
    }

    public String getDisplayName() {
        return displayName;
    }

    public String getPluralDisplayName() {
        return pluralDisplayName;
    }

    public static SenderType getSenderType(CommandSender sender) {
        if (sender instanceof Player) {
            return PLAYER;
        } else if (sender instanceof BlockCommandSender) {
            return BLOCK;
        } else if (sender instanceof ConsoleCommandSender) {
            return CONSOLE;
        } else if (sender instanceof RemoteConsoleCommandSender) {
            return RCONSOLE;
        } else {
            return UNKNOWN;
        }
    }
} public class Cmd extends AbstractCommand {

    public Cmd() {
        super(SenderType.PLAYER);
    }

    @Override
    public List<String> execute(Instances instances, Player player, String[] args) {
        if (args.length != 0)
            return null;

        player.setItemInHand(new ItemStack(Material.COMMAND));
        return msg();
    }
} public interface Command {

    public List<String> execute(Instances instances, SenderType type, CommandSender sender, String[] args);

} public class CreatePortal extends AbstractCommand {

    public CreatePortal() {
        super(SenderType.PLAYER);
    }

    @Override
    public List<String> execute(Instances instances, Player player, String[] args) {
        if (args.length != 1) {
            return null;
        }
        Session session = instances.getSession(player);

        if (session.getEntrance() == null) {
            throw new InvocationException("You have not set the entrance portal location.");
        }
        if (session.getDestination() == null) {
            throw new InvocationException("You have not set the destination portal location.");
        }
        if (instances.getPortalPair(args[0]) != null) {
            throw new InvocationException("That portal pair already exists.");
        }
        InstanceEntrancePortal entrance = new InstanceEntrancePortal(session.getEntrance());
        InstanceDestinationPortal destination = new InstanceDestinationPortal(session.getDestination());
        World instanceWorld = instances.getServer().getWorld(destination.getCuboid().getWorld());
        World.Environment environment = instanceWorld.getEnvironment();
        Difficulty difficulty = instanceWorld.getDifficulty();
        PortalPair pair = new PortalPair(args[0], entrance, destination, environment, difficulty);
        instances.addPortalPair(pair);
        session.clear();
        return msg("Portal " + args[0] + " created.");
    }

} public class DeletePortal extends AbstractCommand {

    @Override
    public List<String> execute(Instances instances, CommandSender sender, String[] args) {
        if (args.length != 1) {
            return null;
        }
        PortalPair pair = instances.getPortalPair(args[0]);
        if (pair == null) {
            throw new InvocationException("Portal " + args[0] + " not found.");
        }
        instances.removePortalPair(pair);
        return msg("Portal " + args[0] + " deleted.");
    }

} public class Genocide extends AbstractCommand {
    public Genocide() {
        super (SenderType.PLAYER);
    }

    @Override
    public List<String> execute(Instances instances, Player player, String[] args) {
        if (args.length != 1 && args.length != 2)
            return null;

        EntityType type = EntityType.fromName(args[0]);
        boolean remove = false;
        if (args.length == 2) {
            if ("remove".equals(args[1])) {
                remove = true;
            } else {
                return null;
            }
        }

        if (type == null || !type.isAlive()) {
            throw new InvocationException("Not a valid entity: " + args[0]);
        }

        for (Entity e : player.getWorld().getEntities()) {
            if (e.getType() == type) {
                LivingEntity living = (LivingEntity) e;
                if (remove) {
                    living.remove();
                } else {
                    living.damage(living.getHealth());
                }
            }
        }
        return msg(args[0] + " has been purged from the world.");
    }

} public class Home extends AbstractCommand {

    public Home() {
        super(SenderType.PLAYER);
    }

    @Override
    public List<String> execute(Instances instances, Player player, String[] args) {
        if (args.length != 0) {
            return null;
        }
        Location home = instances.getHome(player);
        if (home == null) {
            throw new InvocationException("You are homeless.");
        }
        player.teleport(home);
        return msg();
    }

} public class InvocationException extends RuntimeException {

    public InvocationException() {
        super();
    }

    public InvocationException(String msg) {
        super(msg);
    }

} public class Mob extends AbstractCommand {

    public Mob() {
        super(SenderType.PLAYER);
    }

    @Override
    public List<String> execute(Instances instances, Player player, String[] args) {
        if (args.length != 1 && args.length != 2) {
            return null;
        }
        String mob = args[0];
        int count = 1;
        if (args.length == 2) {
            try {
                count = Integer.parseInt(args[1]);
            } catch (NumberFormatException nfe) {
                throw new InvocationException("Not a number: " + args[1]);
            }
            if (count < 1 || count > 256) {
                throw new InvocationException("Try spawning a sensible number.");
            }
        }
        EntitySpawner spawner = getSpawner(args[0]);
        if (spawner == null) {
            throw new InvocationException("Not a valid entity: " + args[0]);
        }
        Block b = player.getTargetBlock((Set<Material>)null, 200);
        if (b == null) {
            throw new InvocationException("You are not looking at anything.");
        }
        Location spawnLoc = b.getLocation();
        spawnLoc.setY(spawnLoc.getBlockY() + 1);
        for (int i = 0; i < count; i++) {
            spawner.spawnEntity(spawnLoc);
        }
        return msg("You spawned " + count + " " + spawner.getName() + ".");
    }

    private EntitySpawner getSpawner(String entity) {
        if ("wither_skeleton".equalsIgnoreCase(entity)) {
            return new WitherSkeletonSpawner();
        }
        EntityType type = EntityType.fromName(entity);
        if (type == null || !type.isAlive()) {
            return null;
        }
        return new DefaultEntitySpawner(type);
    }

    private interface EntitySpawner {

        public void spawnEntity(Location loc);

        public String getName();
    }

    private class DefaultEntitySpawner implements EntitySpawner {

        private final EntityType type;

        public DefaultEntitySpawner(EntityType type) {
            this.type = type;
        }

        public void spawnEntity(Location loc) {
            Entity e = loc.getWorld().spawnEntity(loc, type);
            if (e instanceof LivingEntity) {
                LivingEntity living = (LivingEntity) e;
                living.setCanPickupItems(true);
            }
        }

        public String getName() {
            return type.getName();
        }
    }

    private class WitherSkeletonSpawner implements EntitySpawner {

        public void spawnEntity(Location loc) {
            Skeleton e = (Skeleton) loc.getWorld().spawnEntity(loc, EntityType.SKELETON);
            e.setSkeletonType(SkeletonType.WITHER);
            e.setCanPickupItems(true);
        }

        public String getName() {
            return "wither skeleton";
        }
    }
} public class ModifyPortal extends AbstractCommand {

    private enum Action {

        SHOW {

            public List<String> invoke(Instances instances, PortalPair portal, Property property, CommandSender sender, String[] args) {
                if (property == null) {
                    List<String> ret = new ArrayList<String>();
                    for (Map.Entry<String, Property> e : propertyMap.entrySet()) {
                        ret.add(portal.getName() + '.' + e.getValue().getName() + " = " + e.getValue().get(portal));
                    }
                    return ret;
                } else {
                    return msg(portal.getName() + '.' + property.getName() + " = " + property.get(portal));
                }
            }
        },
        SET {

            public List<String> invoke(Instances instances, PortalPair portal, Property property, CommandSender sender, String[] args) {
                if (property == null) {
                    throw new InvocationException("You must specify a property name with set.");
                }
                Object value = property.parse(instances, sender, args);
                if (value == null) {
                    return null;
                }
                property.set(portal, value);
                return msg("Set " + portal.getName() + '.' + property.getName() + " = " + value);
            }
        },
        RESET {

            public List<String> invoke(Instances instances, PortalPair portal, Property property, CommandSender sender, String[] args) {
                if (property == null) {
                    throw new InvocationException("You must specify a property name with remove.");
                }
                property.reset(portal);
                return msg("Reset " + portal.getName() + '.' + property.getName() + " to " + property.get(portal));
            }
        };

        public abstract List<String> invoke(Instances instances, PortalPair portal, Property property, CommandSender sender, String[] args);
    }

    private abstract static class Property<T> {

        public abstract String getName();

        public abstract void set(PortalPair portal, T value);

        public abstract T get(PortalPair portal);

        public abstract void reset(PortalPair portal);

        public abstract T parse(Instances instances, CommandSender sender, String[] args);
    }

    private abstract static class DoubleProperty extends Property<Double> {

        public Double parse(Instances instances, CommandSender sender, String[] args) {
            if (args.length != 1) {
                throw new InvocationException("You must specify a double value for this property.");
            }
            try {
                return Double.parseDouble(args[0]);
            } catch (NumberFormatException e) {
                throw new InvocationException("Not a valid currency ammount: " + args[0]);
            }
        }
    }

    private abstract static class ItemStackProperty extends Property<ItemStack> {

        public ItemStack parse(Instances instances, CommandSender sender, String[] args) {
            if (SenderType.getSenderType(sender) != SenderType.PLAYER) {
                throw new NotAvailableException("You must be a player to set item type properties.");
            }
            if (args.length != 0) {
                throw new InvocationException("You must be holding the item to set for this property.");
            }
            Player player = (Player) sender;
            ItemStack value = player.getItemInHand();
            if (value == null) {
                throw new InvocationException("You must be holding an item to set as this property.");
            }
            return value;
        }
    }

    private abstract static class IntProperty extends Property<Integer> {

        public Integer parse(Instances instances, CommandSender sender, String[] args) {
            if (args.length != 1) {
                throw new InvocationException("You must specify an integer value for this property.");
            }
            try {
                return Integer.parseInt(args[0]);
            } catch (NumberFormatException e) {
                throw new InvocationException("Not a valid currency ammount: " + args[0]);
            }
        }
    }

    private abstract static class StringProperty extends Property<String> {
        public String parse(Instances instances, CommandSender sender, String[] args) {
            if (args.length != 1) {
                throw new InvocationException("You must specify a string value for this property.");
            }
            return args[0];
        }
    }

    private abstract static class FacingProperty extends Property<Facing> {
        @Override
        public Facing parse(Instances instances, CommandSender sender, String[] args) {
            if (args.length == 1) {
                if ("north".equalsIgnoreCase(args[0])) {
                    return Facing.NORTH;
                } else if("east".equalsIgnoreCase(args[0])) {
                    return Facing.EAST;
                } else if("south".equalsIgnoreCase(args[0])) {
                    return Facing.SOUTH;
                } else if("west".equalsIgnoreCase(args[0])) {
                    return Facing.WEST;
                } else {
                    throw new InvocationException("Invalid argument, must be north/south/east/west");
                }
            } else if (args.length == 0) {
                if (sender instanceof Player) {
                    return new Facing(((Player)sender).getLocation());
                } else {
                    throw new NotAvailableException("Only players can set facing to their current facing.");
                }
            } else {
                throw new InvocationException("Setting a facing accepts 0 or 1 arguments only.");
            }
        }
    }

    private static class DifficultyProperty extends Property<Difficulty> {
        public Difficulty parse(Instances instances, CommandSender sender, String[] args) {
            if (args.length != 1) {
                throw new InvocationException("You must specify a difficulty value for this property");
            }
            try {
                return Difficulty.valueOf(args[0].toUpperCase());
            } catch (IllegalArgumentException e) {
                throw new InvocationException("Difficulty must be peaceful, easy, normal or hard");
            }
        }
        @Override
        public String getName() {
            return "difficulty";
        }

        @Override
        public void set(PortalPair portal, Difficulty value) {
            portal.setDifficulty(value);
        }

        @Override
        public Difficulty get(PortalPair portal) {
            return portal.getDifficulty();
        }

        @Override
        public void reset(PortalPair portal) {
            portal.setDifficulty(Difficulty.NORMAL);
        }
    }

    private static class EnvironmentProperty extends Property<World.Environment> {
        public World.Environment parse(Instances instances, CommandSender sender, String[] args) {
            if (args.length != 1) {
                throw new InvocationException("You must specify a difficulty value for this property");
            }
            try {
                return World.Environment.valueOf(args[0].toUpperCase());
            } catch (IllegalArgumentException e) {
                throw new InvocationException("Environment must be peaceful, easy, normal or hard");
            }
        }
        @Override
        public String getName() {
            return "environment";
        }

        @Override
        public void set(PortalPair portal, World.Environment value) {
            portal.setEnvironment(value);
        }

        @Override
        public World.Environment get(PortalPair portal) {
            return portal.getEnvironment();
        }

        @Override
        public void reset(PortalPair portal) {
            portal.setEnvironment(World.Environment.NORMAL);
        }
    }

    private static class EntryPrice extends DoubleProperty {

        @Override
        public String getName() {
            return "entryPrice";
        }

        @Override
        public void set(PortalPair portal, Double value) {
            portal.setEntryPrice(value);
        }

        @Override
        public Double get(PortalPair portal) {
            return portal.getEntryPrice();
        }

        @Override
        public void reset(PortalPair portal) {
            portal.setEntryPrice(0.0D);
        }
    }

    private static class CreatePrice extends DoubleProperty {

        @Override
        public String getName() {
            return "createPrice";
        }

        @Override
        public void set(PortalPair portal, Double value) {
            portal.setCreatePrice(value);
        }

        @Override
        public Double get(PortalPair portal) {
            return portal.getCreatePrice();
        }

        @Override
        public void reset(PortalPair portal) {
            portal.setCreatePrice(0.0D);
        }

    }

    private static class EntryItem extends ItemStackProperty {

        @Override
        public String getName() {
            return "entryItem";
        }

        @Override
        public void set(PortalPair portal, ItemStack value) {
            portal.setEntryItem(value);
        }

        @Override
        public ItemStack get(PortalPair portal) {
            return portal.getEntryItem();
        }

        @Override
        public void reset(PortalPair portal) {
            portal.setEntryItem(null);
        }

    }

    private static class CreateItem extends ItemStackProperty {

        @Override
        public String getName() {
            return "createItem";
        }

        @Override
        public void set(PortalPair portal, ItemStack value) {
            portal.setCreateItem(value);
        }

        @Override
        public ItemStack get(PortalPair portal) {
            return portal.getCreateItem();
        }

        @Override
        public void reset(PortalPair portal) {
            portal.setCreateItem(null);
        }

    }

    private static class RecreateTime extends IntProperty {

        @Override
        public String getName() {
            return "recreateTime";
        }

        @Override
        public void set(PortalPair portal, Integer value) {
            portal.setRecreateTime(value);
        }

        @Override
        public Integer get(PortalPair portal) {
            return portal.getRecreateTime();
        }

        @Override
        public void reset(PortalPair portal) {
            portal.setRecreateTime(0);
        }

    }

    private static class UnloadTime extends IntProperty {

        @Override
        public String getName() {
            return "unloadTime";
        }

        @Override
        public void set(PortalPair portal, Integer value) {
            portal.setUnloadTime(value);
        }

        @Override
        public Integer get(PortalPair portal) {
            return portal.getUnloadTime();
        }

        @Override
        public void reset(PortalPair portal) {
            portal.setUnloadTime(0);
        }

    }

    private static class MaxPlayersProperty extends IntProperty {

        @Override
        public String getName() {
            return "maxPlayers";
        }

        @Override
        public void set(PortalPair portal, Integer value) {
            portal.setMaxPlayers(value);
        }

        @Override
        public Integer get(PortalPair portal) {
            return portal.getMaxPlayers();
        }

        @Override
        public void reset(PortalPair portal) {
            portal.setMaxPlayers(0);
        }
    }

    private static class MaxInstancesProperty extends IntProperty {

        @Override
        public String getName() {
            return "maxInstances";
        }

        @Override
        public void set(PortalPair portal, Integer value) {
            portal.setMaxInstances(value);
        }

        @Override
        public Integer get(PortalPair portal) {
            return portal.getMaxInstances();
        }

        @Override
        public void reset(PortalPair portal) {
            portal.setMaxInstances(0);
        }
        
    }

    private static class DefaultParty extends StringProperty {
        @Override
        public String getName() {
            return "defaultParty";
        }

        @Override
        public void set(PortalPair portal, String value) {
            portal.setDefaultParty(value);
        }

        @Override
        public String get(PortalPair portal) {
            return portal.getDefaultParty();
        }

        @Override
        public void reset(PortalPair portal) {
            portal.setDefaultParty(null);
        }
    }

    private static class EntranceFacing extends FacingProperty {

        @Override
        public String getName() {
            return "entrance.facing";
        }

        @Override
        public void set(PortalPair portal, Facing value) {
            portal.getEnter().setFacing(value);
        }

        @Override
        public Facing get(PortalPair portal) {
            return portal.getEnter().getFacing();
        }

        @Override
        public void reset(PortalPair portal) {
            portal.getEnter().setFacing(null);
        }
    }
    private static class DestinationFacing extends FacingProperty {

        @Override
        public String getName() {
            return "destination.facing";
        }

        @Override
        public void set(PortalPair portal, Facing value) {
            portal.getDestination().setFacing(value);
        }

        @Override
        public Facing get(PortalPair portal) {
            return portal.getDestination().getFacing();
        }

        @Override
        public void reset(PortalPair portal) {
            portal.getDestination().setFacing(null);
        }
    }
    private static final Map<String, Property> propertyMap = new TreeMap<String, Property>();
    private static void addProperty(Property prop) {
        propertyMap.put(prop.getName().toLowerCase(), prop);
    }

    static {
        addProperty(new EntryPrice());
        addProperty(new EntryItem());
        addProperty(new CreatePrice());
        addProperty(new CreateItem());
        addProperty(new RecreateTime());
        addProperty(new UnloadTime());
        addProperty(new EnvironmentProperty());
        addProperty(new DifficultyProperty());
        addProperty(new DefaultParty());
        addProperty(new EntranceFacing());
        addProperty(new DestinationFacing());
        addProperty(new MaxPlayersProperty());
        addProperty(new MaxInstancesProperty());
    }

    @Override
    public List<String> execute(Instances instances, CommandSender sender, String[] args) {
        if (args.length == 0) {
            return null;
        }
        PortalPair portal = instances.getPortalPair(args[0]);
        if (portal == null) {
            throw new InvocationException("No portal named: " + args[0] + ".");
        }

        Action action = Action.SHOW;
        Property prop = null;
        if (args.length >= 2) {
            try {
                action = Action.valueOf(args[1].toUpperCase());
            } catch (IllegalArgumentException e) {
                throw new InvocationException("You can only show, set or reset properties");
            }
            if (args.length >= 3) {
                prop = propertyMap.get(args[2].toLowerCase());
                if (prop == null) {
                    throw new InvocationException("Unknown property: " + args[2]);
                }
                args = shift(args, 3);
            } else {
                args = shift(args, 2);
            }
        } else {
            args = shift(args, 1);

        }

        return action.invoke(instances, portal, prop, sender, args);
    }
} public class Motd extends AbstractCommand {

    @Override
    public List<String> execute(Instances instances, CommandSender sender, String[] args) {
        if (args.length != 0)
            return null;
        String[] motd = instances.getMotd();
        if (motd == null) {
            throw new InvocationException("No MOTD set, create one in plugins/Instances/motd.txt");
        }
        return Arrays.asList(motd);
    }

} public class NotAvailableException extends RuntimeException {

    public NotAvailableException() {
        super();
    }

    public NotAvailableException(String format) {
        super(format);
    }
} public class PartyChat extends AbstractCommand {

    public PartyChat() {
        super(SenderType.PLAYER);
    }

    @Override
    public List<String> execute(Instances instances, Player player, String[] args) {
        if (args.length == 0)
            return null; // Usage
        Party party = instances.getParty(player);
        if (party == null)
            throw new InvocationException("You are not in a party.");

        party.chat(instances, player, concatonate(args));
        return msg();
    }

} public class PartyCreate extends AbstractCommand {

    public PartyCreate() {
        super(SenderType.PLAYER);
    }

    @Override
    public List<String> execute(Instances instances, Player player, String[] args) {
        if(args.length != 1)
            return null; // Usage;

        Party party = instances.getParty(player);
        if (party != null) {
            throw new InvocationException("You are already in a party.");
        }

        party = instances.getParty(args[0]);
        if (party != null) {
            throw new InvocationException("That party already exists.");
        }

        party = instances.partyCreate(args[0], player);

        return msg("Party " + args[0] + " created.");
    }
} public class PartyDisband extends AbstractCommand {

    public PartyDisband() {
        super(SenderType.PLAYER);
    }

    @Override
    public List<String> execute(Instances instances, Player player, String[] args) {
        if (args.length != 0) {
            return null; // Usage
        }
        Party party = instances.getParty(player);
        if (party == null) {
            throw new InvocationException("You are not in a party.");
        }

        if (!party.hasLeader()) {
            throw new InvocationException("You cannot disband leaderless parties.");
        }

        if (instances.isLeaderDisband() && !party.getLeader().equals(player))  {
            throw new InvocationException("You are not the party leader.");
        }
        party.emote(instances, player, "has disbanded the party.");
        instances.partyDisband(party);
        return msg();
    }
} public class PartyEmote extends AbstractCommand {

    public PartyEmote() {
        super(SenderType.PLAYER);
    }

    @Override
    public List<String> execute(Instances instances, Player player, String[] args) {
        if (args.length == 0)
            return null; // Usage
        Party party = instances.getParty(player);
        if (party == null) {
            throw new InvocationException("You are not in a party.");
        }

        party.emote(instances, player, concatonate(args));
        return msg();
    }
} public class PartyInfo extends AbstractCommand {

    public PartyInfo() {
        super();
    }

    @Override
    public List<String> execute(Instances instances, CommandSender sender, String[] args) {
        if (args.length != 0 && args.length != 1) {
            return null;
        }
        if (!(sender instanceof Player) && args.length == 0) {
            throw new NotAvailableException("Only players can see information about their current party.");
        }
        Party party;
        if (args.length == 0) {
            party = instances.getParty((Player)sender);
            if (party == null)
                throw new InvocationException("You are not in a party.");
        } else {
            party = instances.getParty(args[0]);
            if (party == null)
                throw new InvocationException(args[0] + " does not exist.");
        }
        List<String> ret = new ArrayList<String>();
        ret.add("Party name: " + party.getName());
        if (party.hasLeader()) {
            ret.add("Party leader: " + party.getLeader().getName());
        }
        StringBuilder tmp = new StringBuilder();
        tmp.append("Members:");
        for (Player p : party.getMembers()) {
            if (!p.equals(party.getLeader())) {
                tmp.append(' ');
                tmp.append(p.getName());
            }
        }
        ret.add(tmp.toString());
        if (sender.hasPermission("instances.party.info")) {
            tmp.setLength(0);
            tmp.append("Instances:");
            for (Instance i : party.getInstances()) {
                tmp.append(' ');
                tmp.append(i);
            }
            ret.add(tmp.toString());
        }
        return ret;
    }
} public class PartyInvite extends AbstractCommand {

    public PartyInvite() {
        super(SenderType.PLAYER);
    }

    @Override
    public List<String> execute(Instances instances, Player player, String[] args) {
        if (args.length != 1) {
            return null; // Usage
        }
        Party party = instances.getParty(player);
        if (party == null) {
            throw new InvocationException("You are not in a party.");
        }
        if (instances.isLeaderInvite() && !player.equals(party.getLeader())) {
            throw new InvocationException("You are not the party leader.");
        }
        Player invitee = instances.getServer().getPlayer(args[0]);
        if (invitee == null) {
            throw new InvocationException("No such player: " + args[0] + ".");
        }
        if (!invitee.isOnline()) {
            throw new InvocationException(args[0] + " is not online.");
        }
        boolean invited = party.getInvites().contains(invitee);
        boolean member = party.getMembers().contains(invitee);
        if (member) {
            throw new InvocationException(args[0] + " is already a party member.");
        }
        if (invited) {
            throw new InvocationException(args[0] + " is already invited.");
        }
        party.getInvites().add(invitee);
        party.emote(instances, invitee, "has been invited to join by " + player.getDisplayName() + '.');
        invitee.sendMessage("You have been invited to join party " + party.getName() + " type /pjoin " + party.getName());
        return msg();
    }
} public class PartyJoin extends AbstractCommand {

    public PartyJoin() {
        super(SenderType.PLAYER);
    }

    @Override
    public List<String> execute(Instances instances, Player player, String[] args) {
        if (args.length != 1) {
            return null;
        }
        Party party = instances.getParty(player);
        if (party != null) {
            throw new InvocationException("You are already in a party.");
        }
        party = instances.getParty(args[0]);
        if (party == null || !party.getInvites().contains(player)) {
            // Don't leak information about the existance or non-existance of the party.
            throw new InvocationException("You have not been invited to join " + args[0] + ".");
        }
        for (Instance instance : party.getInstances()) {
            if (instance.getPortal().getMaxPlayers() > 0 && instance.getPortal().getMaxPlayers() <= party.getMembers().size()) {
                party.emote(instances, player, "cannot join because the party is full.");
                throw new InvocationException("That party is full.");
            }
        }
        instances.partyAdd(party, player);
        party.emote(instances, player, "has joined the party.");
        return msg();
    }
} public class PartyKick extends AbstractCommand {

    public PartyKick() {
        super(SenderType.PLAYER);
    }

    @Override
    public List<String> execute(Instances instances, Player player, String[] args) {
        if (args.length != 1) {
            return null;
        }
        Party party = instances.getParty(player);
        if (party == null) {
            throw new InvocationException("You are not in a party.");
        }
        if (instances.isLeaderKick() && !player.equals(party.getLeader())) {
            throw new InvocationException("You are not the party leader.");
        }
        Player toKick = instances.getServer().getPlayer(args[0]);
        if (toKick == null) {
            throw new InvocationException(args[0] + " not found.");
        }
        if (!party.getMembers().contains(toKick)) {
            throw new InvocationException(args[0] + " is not in the party.");
        }
        party.emote(instances, toKick, "has been removed from the party by " + player.getName());
        instances.partyRemove(party, toKick);
        return msg();
    }
} public class PartyLeader extends AbstractCommand {

    public PartyLeader() {
        super(SenderType.PLAYER);
    }

    @Override
    public List<String> execute(Instances instances, Player player, String[] args) {
        if (args.length != 1) {
            return null;
        }
        Party party = instances.getParty(player);
        if (party == null) {
            throw new InvocationException("You are not in a party.");
        }
        if (!player.equals(party.getLeader())) {
            throw new InvocationException("You are not the party leader.");
        }
        Player newLeader = instances.getServer().getPlayer(args[0]);
        if (newLeader == null) {
            throw new InvocationException(args[0] + " not found.");
        }
        if (newLeader == player) {
            throw new InvocationException("You are already the leader numbskull.");
        }
        if (!party.getMembers().contains(newLeader)) {
            throw new InvocationException(args[0] + " is not in the party.");
        }
        party.setLeader(newLeader);
        party.emote(instances, newLeader, "has been appointed leader by " + player.getName());
        return msg();
    }
} public class PartyLeave extends AbstractCommand {

    public PartyLeave() {
        super(SenderType.PLAYER);
    }

    @Override
    public List<String> execute(Instances instances, Player player, String[] args) {
        if (args.length != 0) {
            return null;
        }
        Party party = instances.getParty(player);
        if (party == null) {
            throw new InvocationException("You are not in a party.");
        }
        if (player.equals(party.getLeader())) {
            throw new InvocationException("You must assign someone else as party leader or disband the party.");
        }
        party.emote(instances, player, "has left the party.");
        instances.partyRemove(party, player);
        return msg();
    }
} public class PartyList extends AbstractCommand {

    @Override
    public List<String> execute(Instances instances, CommandSender sender, String[] args) {
        if (args.length != 0) {
            return null;
        }

        StringBuilder ret = new StringBuilder();

        List<Party> parties = new ArrayList(instances.getParties());
        Collections.sort(parties);

        if (parties.isEmpty()) {
            throw new InvocationException("No parties");
        }
        StringBuilder tmp = new StringBuilder("Current parties: ");
        for (Party p : parties) {
             tmp.append(' ');
             tmp.append(p.getName());
        }
        return msg(tmp.toString());
    }

} public class PartyUninvite extends AbstractCommand {

    public PartyUninvite() {
        super(SenderType.PLAYER);
    }

    @Override
    public List<String> execute(Instances instances, Player player, String[] args) {
        if (args.length != 1) {
            return null; // Usage
        }
        Party party = instances.getParty(player);
        if (party == null) {
            throw new InvocationException("You are not in a party.");
        }
        if (instances.isLeaderInvite() && !player.equals(party.getLeader())) {
            throw new InvocationException("You are not the party leader.");
        }
        Player invitee = instances.getServer().getPlayer(args[0]);
        if (invitee == null) {
            throw new InvocationException("No such player: " + args[0] + ".");
        }
        if (!invitee.isOnline()) {
            throw new InvocationException(args[0] + " is not online.");
        }
        boolean invited = party.getInvites().contains(invitee);
        boolean member = party.getMembers().contains(invitee);
        if (member) {
            throw new InvocationException(args[0] + " is already a party member.");
        }
        if (!invited) {
            throw new InvocationException(args[0] + " is not invited.");
        }
        party.getInvites().remove(invitee);
        party.emote(instances, invitee, "has been uninvited by " + player.getDisplayName() + '.');
        invitee.sendMessage("You are no longer invited to join party " + party.getName() + '.');
        return Collections.emptyList();
    }
} public class PortalList extends AbstractCommand {

    @Override
    public List<String> execute(Instances instances, CommandSender sender, String[] args) {
        if (args.length != 0)
            return null;
        List<String> ret = new ArrayList<String>();
        List<PortalPair> portals = new ArrayList<PortalPair>(instances.getPortalPairs());
        if (portals.isEmpty()) {
            ret.add("No portals defined.");
        } else {
            for (PortalPair pair : portals) {
                ret.add(pair.toString());
            }
        }
        return ret;
    }

} public class Reload extends AbstractCommand {

    @Override
    public List<String> execute(Instances instances, CommandSender sender, String[] args) {
        if (args.length != 0)
            return null;
        instances.clear();
        instances.load();
        return msg("Instance settings reloaded");
    }

} public class Save extends AbstractCommand {

    @Override
    public List<String> execute(Instances instances, CommandSender sender, String[] args) {
        if (args.length != 0)
            return null;
        instances.save();
        return msg("Instance settings saved.");
    }

} public class SelectionTool extends AbstractCommand {

    public SelectionTool() {
        super(SenderType.PLAYER);
    }

    @Override
    public List<String> execute(Instances instances, Player sender, String[] args) {
        if (args.length != 0) {
            return null;
        }
        if (!instances.getCuboidSelection().isNative()) {
            throw new InvocationException("Using WorldEdit selection tool.");
        }
        Player player = (Player) sender;
        instances.setSelectionTool(player.getItemInHand());
        return msg("Selection tool set.");
    }
} public class SetDestination extends AbstractCommand {
    public SetDestination() {
        super(SenderType.PLAYER);
    }

    @Override
    public List<String> execute(Instances instances, Player player, String[] args) {
        if (args.length != 0)
            return null;
        Cuboid cuboid = instances.getCuboidSelection().getCurrentSelection(player);
        if (instances.isInstance(instances.getServer().getWorld(cuboid.getWorld()))) {
            throw new InvocationException("You cannot create instance portals inside an instance.");
        }
        instances.getSession(player).setDestination(cuboid);

        return msg("Destination portal location set.");
    }
} public class SetEntrance extends AbstractCommand {
    public SetEntrance() {
        super(SenderType.PLAYER);
    }

    @Override
    public List<String> execute(Instances instances, Player player, String[] args) {
        if (args.length != 0)
            return null;
        Cuboid cuboid = instances.getCuboidSelection().getCurrentSelection(player);
        if (instances.isInstance(instances.getServer().getWorld(cuboid.getWorld()))) {
            throw new InvocationException("You cannot create instance portals inside an instance.");
        }

        instances.getSession(player).setEntrance(cuboid);

        return msg("Entrance portal location set.");
    }


} public class SetHome extends AbstractCommand {

    public SetHome() {
        super (SenderType.PLAYER);
    }

    @Override
    public List<String> execute(Instances instances, Player player, String[] args) {
        if (args.length != 0)
            return null;
        Location home = player.getLocation();
        if (instances.isInstance(home.getWorld())) {
            throw new InvocationException("You cannot set your home in an instance.");
        }
        instances.setHome(player, home);
        return msg("Home set.");
    }

} public class SetSpawn extends AbstractCommand {

    public SetSpawn() {
        super(SenderType.PLAYER);
    }

    @Override
    public List<String> execute(Instances instances, Player player, String[] args) {
        if (args.length != 0 && args.length != 1) {
            return null;
        }
        if (args.length == 1 && !"none".equals(args[0])) {
            return null;
        }
        if (args.length == 1) {
            instances.setSpawn(null);
            return msg("Spawn unset.");
        } else {
            World world = player.getWorld();
            if (instances.isInstance(world)) {
                throw new InvocationException("Cannot set the spawn world to an instance.");
            }
            instances.setSpawn(world);
            return msg("Spawn set to " + player.getWorld().getName() + '.');
        }
    }
} public class Spawn extends AbstractCommand {
    public Spawn() {
        super (SenderType.PLAYER);
    }

    @Override
    public List<String> execute(Instances instances, Player player, String[] args) {
        if (args.length != 0)
            return null;
        if (instances.getSpawn() == null) {
            throw new InvocationException("No spawn world set.");
        }
        instances.teleportToSpawn(player);
        return msg();
    }

} public class Spawner extends AbstractCommand {

    private static Map<Character, Flag> flagMap = new HashMap<Character, Flag>();

    private enum ArmourSlot {

        HAND,
        FEET,
        LEGS,
        BODY,
        HEAD;
    }

    private enum Flag {

        ADD_MOB('a', true),
        DROPCHANCE('d', true),
        MAX_ENTITIES('e', true),
        POTION_EFFECTS('f', true),
        HEALTH('h', true),
        INFO('i', false),
        ITEM('I', true),
        LOOT('l', true),
        POWERED('L', true),
        MIN_DELAY('m', true),
        MAX_DELAY('M', true),
        PLAYER_RANGE('p', true),
        POSITION('P', true),
        SPAWN_RANGE('r', true),
        REMOVE('R', true),
        SPAWN_COUNT('s', true),
        SELECT('S', true),
        SKELETON_TYPE('t', true),
        PERSIST('T', true),
        INVULNERABLE('v', true),
        MOTION('V', true),
        WEIGHT('w', true),
        SIZE('z', true),
        NAME('n', true),
        NAME_VISIBLE('N', true);
        private char letter;
        private boolean hasArg;

        private Flag(char letter, boolean hasArg) {
            this.letter = letter;
            this.hasArg = hasArg;
        }

        public char getLetter() {
            return letter;
        }

        public boolean hasArg() {
            return hasArg;
        }
    }

    static {
        for (Flag f : Flag.values()) {
            flagMap.put(f.getLetter(), f);
        }
    }

    public Spawner() {
        super(SenderType.PLAYER);
    }

    @Override
    public List<String> execute(Instances instances, Player player, String[] args) {
        if (args.length == 0) {
            return null;
        }
        NBTTools nbtTools = instances.getNBTTools();
        Block b;
        if ("create".equals(args[0])) {
            // Shitty hack to get the looked at entity.
            Vector n1 = player.getLocation().toVector();
            Vector dir = player.getLocation().getDirection();
            LivingEntity nearest = null;
            double nearestDistance = Double.MAX_VALUE;
            for (Entity e : player.getNearbyEntities(100, 100, 100)) {
                if (e instanceof LivingEntity) {
                    Vector n0 = e.getLocation().toVector();
                    double distance = -n1.clone().subtract(n0).dot(dir) / dir.lengthSquared();
                    if (distance > 0) { // In front of the player, not behind.
                        double lineDist = Math.sqrt(n1.distanceSquared(n0) - distance * distance);
                        if (lineDist < 1) { // Tollerance value of within 3 blocks of the line.
                            if (distance < nearestDistance) {
                                nearestDistance = distance;
                                nearest = (LivingEntity) e;
                            }
                        }
                    }
                }
            }
            if (nearest == null || !nearest.hasLineOfSight(player)) {
                throw new InvocationException("You need to be looking at a living entity to use this.");
            }
            if (!nearest.getType().isSpawnable()) {
                // Just in case a player tries to turn another player into a monster spawner.
                throw new InvocationException(nearest.getType().name() + " cannot be spawned");
            }
            b = nearest.getLocation().getBlock();
            b.setType(Material.MOB_SPAWNER);
            CompoundTag entity = nbtTools.readEntity(nearest);
            entity.remove("Pos");
            entity.remove("Motion");
            CompoundTag spawner = nbtTools.readTileEntity(b);
            CompoundTag spawn = createSpawn(nearest.getType(), 100);
            spawn.getCompound("Properties").getValue().putAll(entity.getValue());
            spawner.setCompound("SpawnData");
            spawner.getCompound("SpawnData").getValue().putAll(spawn.getValue());
            spawner.setString("EntityId", nearest.getType().getName());
            spawner.setList("SpawnPotentials", new ListTag(TagType.COMPOUND, new CompoundTag[]{spawn}));
            nbtTools.writeTileEntity(b, spawner);
            args = shift(args, 1);
        } else {
            b = player.getTargetBlock((Set<Material>)null, 200);
        }
        if (b.getType() != Material.MOB_SPAWNER) {
            throw new InvocationException("You are not looking at a mob spawner.");
        }

        List<String> ret = new ArrayList<String>();
        boolean modified = false;

        CompoundTag tileEntity = nbtTools.readTileEntity(b);
        ListTag spawnPotentials = tileEntity.getList("SpawnPotentials");
        CompoundTag spawn;
        if (spawnPotentials == null) {
            spawn = createSpawn(EntityType.fromName(tileEntity.getString("EntityId")), 100);
            CompoundTag[] spawns = new CompoundTag[]{spawn};
            spawnPotentials = new ListTag(TagType.COMPOUND, spawns);
            tileEntity.setList("SpawnPotentials", spawnPotentials);
        } else {
            spawn = (CompoundTag) spawnPotentials.getValue()[0];
        }
        setSpawnTimeDefaults(tileEntity);
        Flag flag = null;
        for (String a : args) {

            if (flag == null) {
                if (a.startsWith("--")) {
                    try {
                        flag = Flag.valueOf(a.substring(2).toUpperCase().replace('-', '_'));
                    } catch (IllegalArgumentException e) {
                        throw new InvocationException("Unknown flag: " + a);
                    }
                } else if (a.startsWith("-")) {
                    if (a.length() != 2) {
                        throw new InvocationException("Unknown flag: " + a);
                    }
                    flag = flagMap.get(a.charAt(1));
                    if (flag == null) {
                        throw new InvocationException("Unknown flag: " + a);
                    }
                } else {
                    EntityType type = EntityType.fromName(a);
                    if (type == null) {
                        throw new InvocationException("Not a valid entity: " + a);
                    }
                    if (!type.isAlive()) {
                        throw new InvocationException("Not a living entity: " + a);
                    }
                    spawn.setString("Type", type.getName());
                    tileEntity.setString("EntityId", type.getName());
                    tileEntity.setCompound("SpawnData");
                    tileEntity.getCompound("SpawnData").getValue().putAll(spawn.getValue());
                    modified = true;
                }
                if (flag != null && !flag.hasArg()) {
                    switch (flag) {
                        case INFO:
                            ret.add("Minimum delay: " + tileEntity.getShort("MinSpawnDelay"));
                            ret.add("Maximum delay: " + tileEntity.getShort("MaxSpawnDelay"));
                            ret.add("Spawn count: " + tileEntity.getShort("SpawnCount"));
                            ret.add("Spawn range: " + tileEntity.getShort("SpawnRange"));
                            ret.add("Player range: " + tileEntity.getShort("RequiredPlayerRange"));
                            ret.add("Max entities: " + tileEntity.getShort("MaxNearbyEntities"));
                            ret.add("Monsters:");
                            CompoundTag[] tags = (CompoundTag[]) spawnPotentials.getValue();
                            for (int i = 0; i < tags.length; i++) {
                                ret.add("    " + i + " : " + tags[i].getString("Type") + "(" + tags[i].getInt("Weight") + ")");
                            }
                    }
                    flag = null;
                }
            } else {
                switch (flag) {
                    case MIN_DELAY:
                        try {
                            short delay = Short.valueOf(a);
                            if (delay <= 0) {
                                throw new InvocationException("Minimum delay must be a positive number.");
                            }
                            tileEntity.setShort("MinSpawnDelay", delay);
                            modified = true;
                        } catch (NumberFormatException e) {
                            throw new InvocationException("Minimum delay must be a number.");
                        }
                        break;
                    case MAX_DELAY:
                        try {
                            short delay = Short.valueOf(a);
                            if (delay <= 0) {
                                throw new InvocationException("Maximum delay must be a positive number.");
                            }
                            tileEntity.setShort("MaxSpawnDelay", delay);
                            modified = true;
                        } catch (NumberFormatException e) {
                            throw new InvocationException("Maximum delay must be a number.");
                        }
                        break;
                    case SPAWN_COUNT:
                        try {
                            short count = Short.valueOf(a);
                            if (count <= 0) {
                                throw new InvocationException("Spawn count must be a positive number.");
                            }
                            tileEntity.setShort("SpawnCount", count);
                            modified = true;
                        } catch (NumberFormatException e) {
                            throw new InvocationException("Spawn count must be a number.");
                        }
                        break;
                    case SPAWN_RANGE:
                        try {
                            short range = Short.valueOf(a);
                            if (range <= 0) {
                                throw new InvocationException("Spawn range must be a positive number.");
                            }
                            modified = true;
                            tileEntity.setShort("SpawnRange", range);
                        } catch (NumberFormatException e) {
                            throw new InvocationException("Spawn range must be a number.");
                        }
                        break;
                    case PLAYER_RANGE:
                        try {
                            short range = Short.valueOf(a);
                            if (range <= 0) {
                                throw new InvocationException("Player range must be a positive number.");
                            }
                            modified = true;
                            tileEntity.setShort("RequiredPlayerRange", range);
                        } catch (NumberFormatException e) {
                            throw new InvocationException("Player range must be a number.");
                        }
                        break;
                    case MAX_ENTITIES:
                        try {
                            short max = Short.valueOf(a);
                            if (max <= 0) {
                                throw new InvocationException("Maximum entities must be a positive number.");
                            }
                            modified = true;
                            tileEntity.setShort("MaxNearbyEntities", max);
                        } catch (NumberFormatException e) {
                            throw new InvocationException("Maximum entities must be a number.");
                        }
                        break;
                    case WEIGHT:
                        try {
                            int weight = Integer.valueOf(a);
                            if (weight <= 0) {
                                throw new InvocationException("Weight must be a positive number.");
                            }
                            modified = true;
                            spawn.setInt("Weight", weight);
                        } catch (NumberFormatException e) {
                            throw new InvocationException("Weight must be a number.");
                        }
                        break;
                    case SELECT:
                        try {
                            int mob = Integer.valueOf(a);
                            if (mob < 0 || mob >= spawnPotentials.getValue().length) {
                                throw new InvocationException("No spawn numbered: " + mob);
                            }
                            spawn = (CompoundTag) spawnPotentials.getValue()[mob];
                        } catch (NumberFormatException e) {
                            throw new InvocationException("Must select spawned mob by number.");
                        }
                        break;
                    case ADD_MOB:
                        EntityType type = EntityType.fromName(a);
                        if (type == null) {
                            throw new InvocationException("Not a valid entity: " + a);
                        }
                        if (!type.isAlive()) {
                            throw new InvocationException("Not a living entity: " + a);
                        }
                        spawn = createSpawn(type, 100);
                        spawnPotentials.add(spawn);
                        modified = true;
                        break;
                    case REMOVE:
                        try {
                            int idx = Integer.valueOf(a);
                            if (idx < 0 || idx >= spawnPotentials.getValue().length) {
                                throw new InvocationException("No spawn numbered: " + idx);
                            }
                            if (spawnPotentials.getValue().length == 1) {
                                throw new InvocationException("You cannot remove the last monster spawn");
                            }
                            spawnPotentials.remove(idx);
                            spawn = (CompoundTag) spawnPotentials.getValue()[0];
                            modified = true;
                        } catch (NumberFormatException e) {
                            throw new InvocationException("Must select spawned mob by number.");
                        }
                        break;
                    case HEALTH:
                        try {
                            int i = Integer.parseInt(a);
                            if (i > 65535 || i < 0) {
                                throw new InvocationException("Must be between 0 and 65535");
                            }
                            getProperties(spawn).setShort("Health", (short) i);
                            modified = true;
                        } catch (NumberFormatException e) {
                            throw new InvocationException("Health must be a number.");
                        }
                        break;
                    case LOOT:
                        boolean loot = Boolean.valueOf(a);
                        getProperties(spawn).setByte("CanPickUpLoot", loot ? (byte) 1 : 0);
                        modified = true;
                        break;
                    case INVULNERABLE:
                        boolean invulnerable = Boolean.valueOf(a);
                        getProperties(spawn).setByte("Invulnerable", invulnerable ? (byte) 1 : 0);
                        modified = true;
                        break;
                    case POWERED:
                        boolean powered = Boolean.valueOf(a);
                        getProperties(spawn).setByte("powered", powered ? (byte) 1 : 0);
                        modified = true;
                        break;
                    case PERSIST:
                        boolean persist = Boolean.valueOf(a);
                        getProperties(spawn).setByte("PersistenceRequired", persist ? (byte) 1 : 0);
                    case SKELETON_TYPE:
                        boolean wither;
                        if ("normal".equals(a)) {
                            wither = false;
                        } else if ("wither".equals(a)) {
                            wither = true;
                        } else {
                            throw new InvocationException("Skeleton type must be wither or normal.");
                        }
                        getProperties(spawn).setByte("SkeletonType", wither ? (byte) 1 : 0);
                        modified = true;
                        break;
                    case SIZE:
                        try {
                            CompoundTag properties = getProperties(spawn);
                            if ("none".equals(a)) {
                                if (properties.containsKey("Size")) {
                                    properties.remove("Size");
                                    modified = true;
                                }
                            } else {
                                int size = Integer.valueOf(a);
                                properties.setInt("Size", size);
                                modified = true;
                            }
                        } catch (NumberFormatException e) {
                            throw new InvocationException("Size must be a number");
                        }
                        break;
                    case POSITION:
                        try {
                            CompoundTag tag = getProperties(spawn);
                            if ("none".equals(a)) {
                                if (tag.containsKey("Pos")) {
                                    tag.remove("Pos");
                                    modified = true;
                                }
                            } else {
                                String[] values = a.split(",");
                                if (values.length != 3) {
                                    throw new InvocationException("Position must contain 3 numbers separated by , e.g. 1,2,3.");
                                }
                                double x = Double.valueOf(values[0]);
                                double y = Double.valueOf(values[1]);
                                double z = Double.valueOf(values[2]);
                                ListTag list = tag.getList("Pos");
                                if (list == null) {
                                    DoubleTag[] pos = new DoubleTag[3];
                                    list = new ListTag(TagType.DOUBLE, pos);
                                    tag.setList("Pos", list);
                                }
                                DoubleTag[] pos = (DoubleTag[]) list.getValue();
                                pos[0] = new DoubleTag(x);
                                pos[1] = new DoubleTag(y);
                                pos[2] = new DoubleTag(z);
                                modified = true;
                            }
                        } catch (NumberFormatException e) {
                            throw new InvocationException("Position elements must be valid numbers.");
                        }
                        break;
                    case MOTION:
                        try {
                            CompoundTag tag = getProperties(spawn);
                            if ("none".equals(a)) {
                                if (tag.containsKey("Motion")) {
                                    tag.remove("Motion");
                                    modified = true;
                                }
                            } else {
                                String[] values = a.split(",");
                                if (values.length != 3) {
                                    throw new InvocationException("Motion must contain 3 numbers separated by , e.g. 1,2,3.");
                                }
                                double x = Double.valueOf(values[0]);
                                double y = Double.valueOf(values[1]);
                                double z = Double.valueOf(values[2]);
                                ListTag list = tag.getList("Motion");
                                if (list == null) {
                                    DoubleTag[] pos = new DoubleTag[3];
                                    list = new ListTag(TagType.DOUBLE, pos);
                                    tag.setList("Motion", list);
                                }
                                DoubleTag[] pos = (DoubleTag[]) list.getValue();
                                pos[0] = new DoubleTag(x);
                                pos[1] = new DoubleTag(y);
                                pos[2] = new DoubleTag(z);
                                modified = true;
                            }
                        } catch (NumberFormatException e) {
                            throw new InvocationException("Motion elements must be valid numbers.");
                        }
                        break;
                    case ITEM:
                        try {
                            ArmourSlot slot = ArmourSlot.valueOf(a.toUpperCase());
                            setEquipmentDefaults(spawn);
                            ListTag equipment = getEquipment(spawn);
                            ItemStack stack = player.getItemInHand();
                            if (stack == null || stack.getType() == Material.AIR) {
                                equipment.getValue()[slot.ordinal()] = new CompoundTag();
                                modified = true;
                            } else {
                                CompoundTag tag = nbtTools.readItemStack(stack);
                                if (tag != null) {
                                    equipment.getValue()[slot.ordinal()] = tag;
                                    modified = true;
                                }
                            }
                        } catch (IllegalArgumentException e) {
                            throw new InvocationException("Armour slot must be one of hand, feet, legs, body or head.");
                        }
                        break;
                    case DROPCHANCE:
                        try {
                            int idx = a.indexOf(':');
                            if (idx == -1) {
                                throw new InvocationException("Drop chance must be <slot>:<chance> e.g. hand:0.5.");
                            }
                            ArmourSlot slot = ArmourSlot.valueOf(a.substring(0, idx).toUpperCase());
                            String chanceVal = a.substring(idx + 1);
                            float chance;
                            if ("always".equals(chanceVal)) {
                                chance = 2.0f;
                            } else {
                                chance = Float.valueOf(a.substring(idx + 1));
                                if (chance < 0.0 || chance > 1.0) {
                                    throw new InvocationException("Drop chance must be between 0.0 and 1.0.");
                                }
                            }
                            setEquipmentDefaults(spawn);
                            ListTag chances = getDropChance(spawn);
                            chances.getValue()[slot.ordinal()] = new FloatTag(chance);
                            modified = true;
                        } catch (NumberFormatException e) {
                            throw new InvocationException("Drop chance must be a number between 0.0 and 1.0.");
                        } catch (IllegalArgumentException e) {
                            throw new InvocationException("Armour slot must be one of hand, feet, legs, body or head.");
                        }
                        break;
                    case POTION_EFFECTS:
                        try {
                            int duration = Integer.parseInt(a);
                            if (duration < 0) {
                                throw new InvocationException("Duration must be positive.");
                            }
                            CompoundTag playerTag = nbtTools.readEntity(player);
                            ListTag effects = playerTag.getList("ActiveEffects");
                            if (effects == null) {
                                getProperties(spawn).remove("ActiveEffects");
                            } else {
                                for (CompoundTag effect : (CompoundTag[]) effects.getValue()) {
                                    effect.setInt("Duration", duration);
                                }
                                getProperties(spawn).setList("ActiveEffects", effects);
                            }
                            modified = true;
                        } catch (NumberFormatException e) {
                            throw new InvocationException("You must specify a duration.");
                        }
                        break;
                    case NAME:
                        if ("none".equals(a)) {
                            getProperties(spawn).remove("CustomName");
                            getProperties(spawn).remove("CustomNameVisible");
                        } else {
                            getProperties(spawn).setString("CustomName", a);
                            getProperties(spawn).setByte("CustomNameVisible", (byte)1);
                        }
                        modified = true;
                        break;
                    case NAME_VISIBLE:
                        boolean visible = Boolean.valueOf(a);
                        getProperties(spawn).setByte("CustomNameVisible", visible ? (byte)1:(byte)0);
                        modified = true;
                        break;
                }
                flag = null;
            }
        }
        if (modified) {
            nbtTools.writeTileEntity(b, tileEntity);
            ret.add("Spawner modified.");
        }
        return ret;
    }

    private void setSpawnTimeDefaults(CompoundTag tileEntity) {
        if (!tileEntity.containsKey("MinSpawnDelay")) {
            tileEntity.setShort("MinSpawnDelay", (short) 200);
        }
        if (!tileEntity.containsKey("MaxSpawnDelay")) {
            tileEntity.setShort("MaxSpawnDelay", (short) 800);
        }
        if (!tileEntity.containsKey("SpawnCount")) {
            tileEntity.setShort("SpawnCount", (short) 4);
        }
    }

    private CompoundTag getProperties(CompoundTag spawn) {
        return spawn.getCompound("Properties");
    }

    private ListTag getEquipment(CompoundTag spawn) {
        return getProperties(spawn).getList("Equipment");
    }

    private ListTag getDropChance(CompoundTag spawn) {
        return getProperties(spawn).getList("DropChances");
    }

    private void setEquipmentDefaults(CompoundTag spawn) {
        CompoundTag properties = spawn.getCompound("Properties");
        CompoundTag[] armour = new CompoundTag[5];
        FloatTag[] dropChance = new FloatTag[5];
        for (int i = 0; i < armour.length; i++) {
            armour[i] = new CompoundTag();
            dropChance[i] = new FloatTag(0.05f);
        }
        if (!properties.containsKey("Equipment")) {
            properties.setList("Equipment", new ListTag(TagType.COMPOUND, armour));
        }
        if (!properties.containsKey("DropChances")) {
            properties.setList("DropChances", new ListTag(TagType.FLOAT, dropChance));
        }
    }

    private CompoundTag createSpawn(EntityType type, int weight) {
        CompoundTag spawn = new CompoundTag();
        spawn.setInt("Weight", weight);
        spawn.setCompound("Properties");
        spawn.setString("Type", type.getName());
        return spawn;
    }
} public interface PacketHooks extends Dependency {
    
    public void install();

    public void uninstall();

} public interface Bank extends Dependency {

    public boolean deduct(Player player, double amount);
    
} public interface WorldInheritance extends Dependency {
    public void preAddInheritance(String parent, String child);
    public void postAddInheritance(String parent, String child);
    public void preRemoveInheritance(String parent, String child);
    public void postRemoveInheritance(String parent, String child);
} public interface CuboidSelection extends Dependency {

    public Cuboid getCurrentSelection(Player player) throws InvocationException;

    public boolean isNative();

} public class InstancesCuboidSelectionFactory extends DependencyFactory<Instances, CuboidSelection> {

    public InstancesCuboidSelectionFactory(Instances plugin) {
        super(plugin, plugin.getName());
    }

    @Override
    public Class<CuboidSelection> getInterfaceClass() {
        return CuboidSelection.class;
    }

    @Override
    protected CuboidSelection createInterface(Plugin plugin) throws Exception {
        return new InstancesCuboidSelection(plugin);
    }

    private static class InstancesCuboidSelection implements CuboidSelection {

        private final Instances instances;

        public InstancesCuboidSelection(Plugin plugin) {
            this.instances = (Instances) plugin;
        }

        public Cuboid getCurrentSelection(Player player) throws InvocationException {
            Selection sel = instances.getSelection(player);
            if (sel.isValid()) {
                return sel.getCuboid();
            } else {
                throw new InvocationException("You do not currently have a valid selection.");
            }
        }

        public boolean isNative() {
            return true;
        }

        public Plugin getPlugin() {
            return instances;
        }
    }
} public class MultiInvWorldInheritanceFactory extends DependencyFactory<Instances, WorldInheritance> {
    public static final String PLUGIN_NAME = "MultiInv";

    public MultiInvWorldInheritanceFactory(Instances instances) {
        super(instances, PLUGIN_NAME);
    }

    @Override
    public Class<WorldInheritance> getInterfaceClass() {
        return WorldInheritance.class;
    }

    @Override
    protected WorldInheritance createInterface(Plugin plugin) throws Exception {
        return new MultiInvWorldInheritance(plugin);
    }

    private static class MultiInvWorldInheritance implements WorldInheritance {
        private final MultiInv plugin;

        public MultiInvWorldInheritance(Plugin plugin) {
            this.plugin = (MultiInv) plugin;
        }

        @Override
        public Plugin getPlugin() {
            return plugin;
        }

        @Override
        public void preAddInheritance(String parent, String child) {
            Map<String, String> groups = plugin.getAPI().getGroups();
            if (groups.containsKey(parent)) {
                groups.put(child, groups.get(parent));
            }
        }

        @Override
        public void postAddInheritance(String parent, String child) {
        }

        @Override
        public void preRemoveInheritance(String parent, String child) {
        }

        @Override
        public void postRemoveInheritance(String parent, String child) {
            Map<String,String> groups = plugin.getAPI().getGroups();
            groups.remove(child);
        }
    }
} public class MultiverseCoreWorldInheritanceFactory extends DependencyFactory<Instances, WorldInheritance> {
    public static final String PLUGIN_NAME = "Multiverse-Core";

    public MultiverseCoreWorldInheritanceFactory(Instances thisPlugin) {
        super(thisPlugin, PLUGIN_NAME);
    }

    @Override
    public Class<WorldInheritance> getInterfaceClass() {
        return WorldInheritance.class;
    }

    @Override
    protected WorldInheritance createInterface(Plugin plugin) throws Exception {
        return new MultiverseCoreWorldInheritance(plugin);
    }
    
    private class MultiverseCoreWorldInheritance implements WorldInheritance {
        private final MultiverseCore plugin;
        private final MVWorldManager worldManager;
        private final Field worlds;
        private final Field worldsFromTheConfig;

        public MultiverseCoreWorldInheritance(Plugin plugin) throws Exception {
            this.plugin = (MultiverseCore) plugin;
            this.worldManager = this.plugin.getMVWorldManager();
            worlds = WorldManager.class.getDeclaredField("worlds");
            worldsFromTheConfig = WorldManager.class.getDeclaredField("worldsFromTheConfig");
            worlds.setAccessible(true);
            worldsFromTheConfig.setAccessible(true);
        }

        public void preAddInheritance(String parent, String child) {
        }

        public void postAddInheritance(String parent, String child) {
            MultiverseWorld parentWorld = worldManager.getMVWorld(parent);
            if (parentWorld != null) {
                if (worldManager.addWorld(child, parentWorld.getEnvironment(), String.valueOf(parentWorld.getSeed()), parentWorld.getWorldType(), false, null, parentWorld.getAdjustSpawn())) {
                    MultiverseWorld childWorld = worldManager.getMVWorld(child);
                    // Do not set the alias or MV-Core has multiple worlds
                    // under the same alias.
                    // childWorld.setAlias(parentWorld.getAlias());
                    childWorld.setAllowAnimalSpawn(parentWorld.canAnimalsSpawn());
                    childWorld.setAllowFlight(parentWorld.getAllowFlight());
                    childWorld.setAllowMonsterSpawn(parentWorld.canMonstersSpawn());
                    childWorld.setAutoHeal(parentWorld.getAutoHeal());
                    // Never autoload instance worlds.
                    childWorld.setAutoLoad(false);
                    childWorld.setBedRespawn(parentWorld.getBedRespawn());
                    if (parentWorld.getColor() != null) {
                        childWorld.setColor(parentWorld.getColor().name());
                    }
                    childWorld.setCurrency(parentWorld.getCurrency());
                    // We have our own difficulty management.
                    // Do not inherit parent setting for this.
                    // childWorld.setDifficulty(parentWorld.getDifficulty());
                    childWorld.setEnableWeather(parentWorld.isWeatherEnabled());
                    childWorld.setGameMode(parentWorld.getGameMode());
                    childWorld.setHidden(parentWorld.isHidden());
                    childWorld.setHunger(parentWorld.getHunger());
                    // Do not keep spawn in memory for instanced worlds.
                    childWorld.setKeepSpawnInMemory(false);
                    childWorld.setPVPMode(parentWorld.isPVPEnabled());
                    childWorld.setPlayerLimit(parentWorld.getPlayerLimit());
                    childWorld.setPrice(parentWorld.getPrice());
                    if (parentWorld.getRespawnToWorld() != null) {
                        childWorld.setRespawnToWorld(parentWorld.getRespawnToWorld().getName());
                    }
                    childWorld.setScaling(parentWorld.getScaling());
                    childWorld.setSpawnLocation(parentWorld.getSpawnLocation());
                    if (parentWorld.getStyle() != null) {
                        childWorld.setStyle(parentWorld.getStyle().name());
                    }
                    childWorld.setTime(parentWorld.getTime());
                }
            }
        }

        public void preRemoveInheritance(String parent, String child) {
            try {
                Map worldsMap = (Map) worlds.get(plugin.getMVWorldManager());
                Map worldsFromTheConfigMap = (Map) worldsFromTheConfig.get(plugin.getMVWorldManager());
                worldsMap.remove(child);
                worldsFromTheConfigMap.remove(child);
            } catch (IllegalArgumentException ex) {
                getThisPlugin().getLogger().log(Level.WARNING, null, ex);
            } catch (IllegalAccessException ex) {
                getThisPlugin().getLogger().log(Level.WARNING, null, ex);
            }
        }

        public void postRemoveInheritance(String parent, String child) {
        }

        public Plugin getPlugin() {
            return plugin;
        }
    }
} public class MultiverseInventoriesWorldInheritanceFactory extends DependencyFactory<Instances, WorldInheritance> {
    public static final String PLUGIN_NAME = "Multiverse-Inventories";

    public MultiverseInventoriesWorldInheritanceFactory(Instances thisPlugin) {
        super(thisPlugin, PLUGIN_NAME);
    }

    @Override
    public Class<WorldInheritance> getInterfaceClass() {
        return WorldInheritance.class;
    }

    @Override
    protected WorldInheritance createInterface(Plugin plugin) throws Exception {
        return new MultiverseInventoriesWorldInheritance(plugin);
    }

    private static class MultiverseInventoriesWorldInheritance implements WorldInheritance {
        private MultiverseInventories plugin;

        public MultiverseInventoriesWorldInheritance(Plugin plugin) {
            this.plugin = (MultiverseInventories) plugin;
        }

        public void preAddInheritance(String parent, String child) {
        }

        public void postAddInheritance(String parent, String child) {
            for (WorldGroupProfile profile : plugin.getGroupManager().getGroupsForWorld(parent)) {
                profile.addWorld(child);
            }
        }

        public void preRemoveInheritance(String parent, String child) {
            for (WorldGroupProfile profile : plugin.getGroupManager().getGroupsForWorld(parent)) {
                profile.removeWorld(child);
            }
        }

        public void postRemoveInheritance(String parent, String child) {
        }

        public Plugin getPlugin() {
            return plugin;
        }
    }
} public class PEXWorldInheritanceFactory extends DependencyFactory<Instances, WorldInheritance> {
    public static final String PLUGIN_NAME = "PermissionsEx";

    public PEXWorldInheritanceFactory(Instances thisPlugin) {
        super(thisPlugin, PLUGIN_NAME);
    }

    @Override
    public Class<WorldInheritance> getInterfaceClass() {
        return WorldInheritance.class;
    }

    @Override
    protected WorldInheritance createInterface(Plugin plugin) throws Exception {
        return new PEXWorldInheritance(plugin);
    }

    public static class PEXWorldInheritance implements WorldInheritance {
        private final PermissionsEx plugin;
        private final PermissionManager permissionManager;

        public PEXWorldInheritance(Plugin plugin) {
            this.plugin = (PermissionsEx) plugin;
            permissionManager = PermissionsEx.getPermissionManager();
        }

        public void preAddInheritance(String parent, String child) {
            PermissionManager pm = PermissionsEx.getPermissionManager();
            pm.setWorldInheritance(child, Collections.singletonList(parent));
        }

        public void postAddInheritance(String parent, String child) {
        }

        public void preRemoveInheritance(String parent, String child) {
        }

        public void postRemoveInheritance(String parent, String child) {
            PermissionManager pm = PermissionsEx.getPermissionManager();
            pm.setWorldInheritance(child, Collections.emptyList());
        }

        public Plugin getPlugin() {
            return plugin;
        }
    }
} public class ProtocolLibPacketHooksFactory extends DependencyFactory<Instances, PacketHooks> {
    public static final String PLUGIN_NAME = "ProtocolLib";

    public ProtocolLibPacketHooksFactory(Instances thisPlugin) {
        super (thisPlugin, PLUGIN_NAME);
    }

    @Override
    public Class<PacketHooks> getInterfaceClass() {
        return PacketHooks.class;
    }

    @Override
    protected PacketHooks createInterface(Plugin plugin) throws Exception {
        return new ProtocolLibPacketHooks(plugin);
    }

    private final class ProtocolLibPacketHooks implements PacketHooks {
        private final ProtocolManager protocolManager;
        private final PacketAdapter packetAdapter;
        private final Plugin plugin;
        public ProtocolLibPacketHooks(Plugin plugin) {
            this.plugin = plugin;
            this.protocolManager = ProtocolLibrary.getProtocolManager();
            this.packetAdapter = new PacketAdapterImpl((Instances)getThisPlugin(), ConnectionSide.CLIENT_SIDE);
        }

        @Override
        public void install() {
            protocolManager.addPacketListener(packetAdapter);
        }

        @Override
        public void uninstall() {
            protocolManager.removePacketListener(packetAdapter);
        }

        @Override
        public Plugin getPlugin() {
            return plugin;
        }
    }
    

    private static final class PacketAdapterImpl extends PacketAdapter {
        private final Instances instances;
        private final boolean newProtocol;

        public PacketAdapterImpl(Instances plugin, ConnectionSide connectionSide) {
            super(plugin, PacketType.Play.Client.CUSTOM_PAYLOAD);
            this.instances = plugin;
            newProtocol = PacketType.Play.Client.CUSTOM_PAYLOAD.getCurrentVersion().compareTo(MinecraftVersion.WORLD_UPDATE) >= 0;
        }

        private String decodeCommand(ByteBuffer in) throws IOException {
            if (newProtocol) {
                int length = in.get() & 0xff;
                return new String(in.array(), in.arrayOffset() + in.position(), length, Charsets.UTF_8);
            } else {
                int sLength = in.getShort();
                if (sLength < 0 || sLength > 256) {
                    throw new IOException("Expected string length between 0 and 256 inclusive, got " + sLength);
                }
                StringBuilder commandString = new StringBuilder(sLength);
                for (int a = 0; a < sLength; a++) {
                    commandString.append(in.getChar());
                }
                return commandString.toString();
            }
        }

        @Override
        public void onPacketReceiving(PacketEvent event) {
            try {
                PacketContainer container = event.getPacket();
                StructureModifier<String> strings = container.getStrings();
                String tag = strings.readSafely(0);
                if ("MC|AdvCdm".equals(tag)) {
                    event.setCancelled(true);
                    final int length = container.getIntegers().readSafely(0);
                    final byte[] data = container.getByteArrays().readSafely(0);
                    ByteBuffer in = ByteBuffer.wrap(data, 0, length);
                    final byte b = newProtocol ? in.get() : 0;
                    if (b == 0) {
                        final int i = in.getInt();
                        final int j = in.getInt();
                        final int k = in.getInt();
                        final String command = decodeCommand(in);
                        final Player player = event.getPlayer();
                        getPlugin().getServer().getScheduler().runTask(getPlugin(), new UpdateCommandBlock(player, i, j, k, command));
                    } else if (b == 1) {
                        final int id = in.getInt();
                        final String command = decodeCommand(in);
                        final Player player = event.getPlayer();
                        getPlugin().getServer().getScheduler().runTask(getPlugin(), new UpdateCommandMinecart(player, id, command));
                    }
                }
            } catch (IOException ex) {
                getPlugin().getLogger().log(Level.WARNING, "Error decoding command block edit packet", ex);
            }
        }
        
        private abstract class AbstractUpdateCommand implements Runnable {
            protected final Player player;
            protected final String command;

            public AbstractUpdateCommand(Player player, String command) {
                this.player = player;
                this.command = command;
            }

            public boolean checkAccess() {
                if (instances.getEditCommandInCreative() && player.getGameMode() != GameMode.CREATIVE) {
                    player.sendMessage("You need to be in creative to edit command blocks.");
                    return false;
                }
                String[] parts = command.split(" ");
                if (parts.length > 0) {
                    if (!player.hasPermission("instances.general.cmd.set." + parts[0])) {
                        player.sendMessage("You do not have permission to use " + parts[0] + " with command blocks.");
                        return false;
                    }
                } else {
                    if (!player.hasPermission("instances.general.cmd.reset")) {
                        player.sendMessage("You do not have permission to reset command blocks.");
                        return false;
                    }
                }
                return true;
            }
        }

        private class UpdateCommandMinecart extends AbstractUpdateCommand {
            private final int entityId;

            public UpdateCommandMinecart(Player player, int entityId, String command) {
                super (player, command);
                this.entityId = entityId;
            }

            @Override
            public void run() {
                NBTTools nbtTools = instances.getNBTTools();
                Entity e = nbtTools.getEntityById(player.getWorld(), entityId);
                if (e instanceof CommandMinecart && checkAccess()) {
                    CompoundTag readEntity = nbtTools.readEntity(e);
                    readEntity.setString("Command", command);
                    nbtTools.updateEntity(e, readEntity);
                    player.sendMessage("Command set: " + command);
                }
            }
        }

        private class UpdateCommandBlock extends AbstractUpdateCommand {
            private final int x;
            private final int y;
            private final int z;
            
            public UpdateCommandBlock(Player player, int x, int y, int z, String command) {
                super(player, command);
                this.x = x;
                this.y = y;
                this.z = z;
            }
            
            @Override
            public void run() {
                Block b = player.getWorld().getBlockAt(x, y, z);
                if (b.getType() == Material.COMMAND && checkAccess()) {
                    // Set command.
                    NBTTools nbtTools = instances.getNBTTools();
                    CompoundTag e = nbtTools.readTileEntity(b);
                    e.setString("Command", command);
                    nbtTools.writeTileEntity(b, e);
                    player.sendMessage("Command set: " + command);
                }
            }
        }
    }

} public class VaultBankFactory extends DependencyFactory<Instances, Bank> {
    public static final String PLUGIN_NAME = "Vault";

    public VaultBankFactory(Instances thisPlugin) {
        super(thisPlugin, PLUGIN_NAME);
    }

    @Override
    public Class<Bank> getInterfaceClass() {
        return Bank.class;
    }

    @Override
    protected Bank createInterface(Plugin plugin) throws Exception {
        try {
            return new VaultBank(plugin);
        } catch (NullPointerException e) {
            // Currently no economies loaded
            return null;
        }
    }

    private static class VaultBank implements Bank {

        private final Vault plugin;
        private final RegisteredServiceProvider<Economy> provider;
        private final Economy economy;

        public VaultBank(Plugin plugin) {
            this.plugin = (Vault) plugin;
            this.provider = plugin.getServer().getServicesManager().getRegistration(Economy.class);
            this.economy = provider.getProvider();
            if (economy == null) {
                throw new NullPointerException();
            }
        }

        public boolean deduct(Player player, double amount) {
            EconomyResponse status = economy.bankWithdraw(player.getName(), amount);
            return status.transactionSuccess();
        }

        public Plugin getPlugin() {
            return plugin;
        }
    }
} public class WorldEditCuboidSelectionFactory extends DependencyFactory<Instances, CuboidSelection> {

    public static final String PLUGIN_NAME = "WorldEdit";

    public WorldEditCuboidSelectionFactory(Instances thisPlugin) {
        super(thisPlugin, PLUGIN_NAME);
    }

    @Override
    public Class<CuboidSelection> getInterfaceClass() {
        return CuboidSelection.class;
    }

    @Override
    protected CuboidSelection createInterface(Plugin plugin) throws Exception {
        return new WorldEditCuboidSelection(plugin);
    }

    public static class WorldEditCuboidSelection implements CuboidSelection {

        private final WorldEditPlugin plugin;
        private final WorldEditAPI api;

        public WorldEditCuboidSelection(Plugin plugin) {
            this.plugin = (WorldEditPlugin) plugin;
            this.api = new WorldEditAPI(this.plugin);
        }

        public Cuboid getCurrentSelection(Player player) throws InvocationException {
            LocalSession session = api.getSession(player);
            LocalWorld world = session.getSelectionWorld();
            try {
                Region selection = session.getSelection(world);
                if (selection instanceof CuboidRegion) {
                    CuboidRegion cubeSelection = (CuboidRegion) selection;
                    Vector pos1 = cubeSelection.getPos1();
                    Vector pos2 = cubeSelection.getPos2();
                    return new Cuboid(world.getName(), pos1.getBlockX(), pos2.getBlockX(), pos1.getBlockY(), pos2.getBlockY(), pos1.getBlockZ(), pos2.getBlockZ());
                } else {
                    throw new InvocationException("You can only create portals for cuboid regions.");
                }
            } catch (IncompleteRegionException ex) {
                if (ex.getLocalizedMessage() != null) 
                    throw new InvocationException(ex.getLocalizedMessage());
                if (ex.getMessage() != null)
                    throw new InvocationException(ex.getMessage());
                throw new InvocationException("Incomplete region");
            }
        }

        public boolean isNative() {
            return false;
        }

        public Plugin getPlugin() {
            return plugin;
        }
    }
} public class WorldGuardWorldInheritanceFactory extends DependencyFactory<Instances, WorldInheritance> {
    private static final String PLUGIN_NAME = "WorldGuard";
    
    public WorldGuardWorldInheritanceFactory(Instances thisPlugin) {
        super(thisPlugin, PLUGIN_NAME);
    }

    @Override
    public Class<WorldInheritance> getInterfaceClass() {
        return WorldInheritance.class;
    }

    @Override
    protected WorldInheritance createInterface(Plugin plugin) throws Exception {
        return new WorldGuardWorldInheritance(plugin);
    }

    private final class WorldGuardWorldInheritance implements WorldInheritance {
        private static final String EXCUSE = "Worldguard was not open to patches to make this possible without horrible hacks.";
        private final WorldGuardPlugin worldGuard;
        private final Field regionContainerContainer;
        private final Field regionContainerImplMapping;

        public WorldGuardWorldInheritance(Plugin plugin) throws NoSuchFieldException {
            this.worldGuard = (WorldGuardPlugin) plugin;
            this.regionContainerContainer = RegionContainer.class.getDeclaredField("container");
            this.regionContainerContainer.setAccessible(true);
            this.regionContainerImplMapping = RegionContainerImpl.class.getDeclaredField("mapping");
            this.regionContainerImplMapping.setAccessible(true);
        }

        public void preAddInheritance(String parent, String child) {
            try {
                RegionContainer regionContainer = worldGuard.getRegionContainer();
                RegionContainerImpl regionContainerImpl = (RegionContainerImpl) regionContainerContainer.get(regionContainer);
                RegionManager parentRegionManager = regionContainerImpl.get(parent);
                if (parentRegionManager != null) {
                    Normal childNormal = Normal.normal(child);
                    ConcurrentMap<Normal, RegionManager> mapping = (ConcurrentMap<Normal, RegionManager>) regionContainerImplMapping.get(regionContainerImpl);
                    mapping.putIfAbsent(childNormal, parentRegionManager);
                }
            } catch (IllegalArgumentException | IllegalAccessException ex) {
                getThisPlugin().getLogger().log(Level.WARNING, null, ex);
            }
        }

        public void postAddInheritance(String parent, String child) {
        }

        public void preRemoveInheritance(String parent, String child) {
            try {
                RegionContainer regionContainer = worldGuard.getRegionContainer();
                RegionContainerImpl regionContainerImpl = (RegionContainerImpl) regionContainerContainer.get(regionContainer);
                RegionManager parentRegionManager = regionContainerImpl.get(parent);
                Normal childNormal = Normal.normal(child);
                ConcurrentMap<Normal, RegionManager> mapping = (ConcurrentMap<Normal, RegionManager>) regionContainerImplMapping.get(regionContainerImpl);
                if (mapping.get(childNormal) == parentRegionManager) {
                    // Dont force save
                    mapping.remove(childNormal);
                }
            } catch (IllegalArgumentException | IllegalAccessException ex) {
                getThisPlugin().getLogger().log(Level.WARNING, null, ex);
            }
        }

        public void postRemoveInheritance(String parent, String child) {
        }

        public Plugin getPlugin() {
            return worldGuard;
        }
    }
} public abstract class DependencyFactory<P extends Plugin, T extends Dependency> {

    private final P thisPlugin;
    private final String plugin;
    private boolean cannotLoad = false;
    private T dependency;

    public DependencyFactory(P thisPlugin, String plugin) {
        this.thisPlugin = thisPlugin;
        this.plugin = plugin;
    }

    public final String getPlugin() {
        return plugin;
    }

    public final P getThisPlugin() {
        return thisPlugin;
    }

    public final T getDependecy() {
        Plugin depPlugin;
        if (plugin.equals(thisPlugin.getName()))  {
            depPlugin = thisPlugin;
            if (dependency != null) {
                return dependency;
            }
        } else {
            depPlugin = thisPlugin.getServer().getPluginManager().getPlugin(plugin);
            if (depPlugin == null || !depPlugin.isEnabled()) {
                dependency = null;
                return null;
            }
            if (dependency != null) {
                if (dependency.getPlugin() != depPlugin) {
                    dependency = null;
                } else {
                    return dependency;
                }
            }
            if (cannotLoad) {
                return dependency;
            }
        }
        try {
            dependency = createInterface(depPlugin);
        } catch (Throwable ex) {
            cannotLoad = true;
            thisPlugin.getLogger().log(Level.WARNING,
                    "Error loading dependecy interface " + getInterfaceClass().getName() + " for plugin " + plugin, ex);
        }
        return dependency;
    }

    public abstract Class<T> getInterfaceClass();

    protected abstract T createInterface(Plugin plugin) throws Exception;
} public class DependencyUtil {

    public static <T extends Dependency> T merge(final Logger log, final Class<T> type, final List<DependencyFactory<?,T>> factories, Map<String, Boolean> enabled) {
        Iterator<DependencyFactory<?,T>> i = factories.iterator();
        while (i.hasNext()) {
            DependencyFactory<?,T> factory = i.next();
            if (!enabled.get(factory.getPlugin())) {
                i.remove();
            }
        }
        return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[] { type }, new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // We only support merging for methods which return void.
                if (method.getReturnType() != Void.TYPE)  {
                    throw new UnsupportedOperationException();
                }
                Iterator<DependencyFactory<?, T>> i = factories.iterator();
                while(i.hasNext()) {
                    DependencyFactory<? extends Plugin, T> factory = i.next();
                    T instance = factory.getDependecy();
                    if (instance != null) {
                        try {
                            method.invoke(instance, args);
                        } catch (InvocationTargetException e) {
                            // Allow declared Exceptions and RuntimeExceptions to be
                            // passed up the stack.
                            for (Class<?> exceptionType : method.getExceptionTypes()) {
                                if (exceptionType.isInstance(e.getTargetException()))
                                    throw e.getTargetException();
                            }
                            if (e.getTargetException() instanceof RuntimeException) {
                                throw e.getTargetException();
                            }
                            // The exception is not declared or a runtime exception,
                            // blacklist the dependency.
                            log.log(Level.WARNING, "Blacklisting dependency on " + factory.getPlugin(), e.getTargetException());
                            i.remove();
                        } catch (Throwable t) {
                            log.log(Level.WARNING, "Blacklisting dependency on " + factory.getPlugin(), t);
                            i.remove();
                        }
                    }
                }
                return null;
            }
        });
    }

    public static <T extends Dependency> T first(final Logger log, final Class<T> type, final List<DependencyFactory<?, T>> factories, Map<String,Boolean> enabled) {
        Iterator<DependencyFactory<?,T>> i = factories.iterator();
        while (i.hasNext()) {
            DependencyFactory<?,T> factory = i.next();
            if (!enabled.get(factory.getPlugin())) {
                i.remove();
            }
        }
        
        return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[] { type }, new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Iterator<DependencyFactory<? extends Plugin, T>> i = factories.iterator();
                while(i.hasNext()) {
                    DependencyFactory<?, T> factory = i.next();
                    T instance = factory.getDependecy();
                    if (instance != null) {
                        try {
                            return method.invoke(instance, args);
                        } catch (InvocationTargetException e) {
                            // Allow declared Exceptions and RuntimeExceptions to be
                            // passed up the stack.
                            for (Class<?> exceptionType : method.getExceptionTypes()) {
                                if (exceptionType.isInstance(e.getTargetException()))
                                    throw e.getTargetException();
                            }
                            if (e.getTargetException() instanceof RuntimeException) {
                                throw e.getTargetException();
                            }
                            // The exception is not declared or a runtime exception,
                            // blacklist the dependency.
                            log.log(Level.WARNING, "Blacklisting dependency on " + factory.getPlugin(), e.getTargetException());
                            i.remove();
                        } catch (Throwable t) {
                            log.log(Level.WARNING, "Blacklisting dependency on " + factory.getPlugin(), t);
                            i.remove();
                        }
                    }
                }
                throw new UnsupportedOperationException();
            }
        });
    }
} public class StringUtil {
    private StringUtil() {}
    public static String error(String msg) {
        return "§4" + msg;
    }
    public static String success(String msg) {
        return "§2" + msg;
    }
}
import java.io.BufferedReader;
import java.util.logging.Level;
import org.bukkit.configuration.ConfigurationSection;
import org.cyberiantiger.minecraft.instances.command.SenderType;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.bukkit.Difficulty;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
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.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitTask;
import org.cyberiantiger.minecraft.Coord;
import org.cyberiantiger.minecraft.Cuboid;
import org.cyberiantiger.minecraft.Facing;
import org.cyberiantiger.minecraft.instances.command.Cmd;
import org.cyberiantiger.minecraft.instances.command.Command;
import org.cyberiantiger.minecraft.instances.command.CreatePortal;
import org.cyberiantiger.minecraft.instances.command.DeletePortal;
import org.cyberiantiger.minecraft.instances.command.Genocide;
import org.cyberiantiger.minecraft.instances.command.Home;
import org.cyberiantiger.minecraft.instances.command.InvocationException;
import org.cyberiantiger.minecraft.instances.command.Mob;
import org.cyberiantiger.minecraft.instances.command.ModifyPortal;
import org.cyberiantiger.minecraft.instances.command.Motd;
import org.cyberiantiger.minecraft.instances.command.NotAvailableException;
import org.cyberiantiger.minecraft.instances.command.PartyChat;
import org.cyberiantiger.minecraft.instances.command.PartyCreate;
import org.cyberiantiger.minecraft.instances.command.PartyDisband;
import org.cyberiantiger.minecraft.instances.command.PartyEmote;
import org.cyberiantiger.minecraft.instances.command.PartyInfo;
import org.cyberiantiger.minecraft.instances.command.PartyInvite;
import org.cyberiantiger.minecraft.instances.command.PartyJoin;
import org.cyberiantiger.minecraft.instances.command.PartyKick;
import org.cyberiantiger.minecraft.instances.command.PartyLeader;
import org.cyberiantiger.minecraft.instances.command.PartyLeave;
import org.cyberiantiger.minecraft.instances.command.PartyList;
import org.cyberiantiger.minecraft.instances.command.PartyUninvite;
import org.cyberiantiger.minecraft.instances.command.PortalList;
import org.cyberiantiger.minecraft.instances.command.Reload;
import org.cyberiantiger.minecraft.instances.command.Save;
import org.cyberiantiger.minecraft.instances.command.SelectionTool;
import org.cyberiantiger.minecraft.instances.command.SetDestination;
import org.cyberiantiger.minecraft.instances.command.SetEntrance;
import org.cyberiantiger.minecraft.instances.command.SetHome;
import org.cyberiantiger.minecraft.instances.command.SetSpawn;
import org.cyberiantiger.minecraft.instances.command.Spawn;
import org.cyberiantiger.minecraft.instances.command.Spawner;
import org.cyberiantiger.minecraft.unsafe.InstanceTools;
import org.cyberiantiger.minecraft.unsafe.CBShim;
import org.cyberiantiger.minecraft.instances.unsafe.depend.PacketHooks;
import org.cyberiantiger.minecraft.instances.unsafe.depend.Bank;
import org.cyberiantiger.minecraft.instances.unsafe.depend.WorldInheritance;
import org.cyberiantiger.minecraft.instances.unsafe.depend.CuboidSelection;
import org.cyberiantiger.minecraft.instances.unsafe.depend.InstancesCuboidSelectionFactory;
import org.cyberiantiger.minecraft.instances.unsafe.depend.MultiInvWorldInheritanceFactory;
import org.cyberiantiger.minecraft.instances.unsafe.depend.MultiverseCoreWorldInheritanceFactory;
import org.cyberiantiger.minecraft.instances.unsafe.depend.MultiverseInventoriesWorldInheritanceFactory;
import org.cyberiantiger.minecraft.instances.unsafe.depend.PEXWorldInheritanceFactory;
import org.cyberiantiger.minecraft.instances.unsafe.depend.ProtocolLibPacketHooksFactory;
import org.cyberiantiger.minecraft.instances.unsafe.depend.VaultBankFactory;
import org.cyberiantiger.minecraft.instances.unsafe.depend.WorldEditCuboidSelectionFactory;
import org.cyberiantiger.minecraft.instances.unsafe.depend.WorldGuardWorldInheritanceFactory;
import org.cyberiantiger.minecraft.instances.util.DependencyFactory;
import org.cyberiantiger.minecraft.instances.util.DependencyUtil;
import org.cyberiantiger.minecraft.instances.util.StringUtil;
import org.cyberiantiger.minecraft.unsafe.NBTTools;
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.cyberiantiger.minecraft.instances;


/**
 *
 * @author antony
 */
public class Instances extends JavaPlugin implements Listener {

    private static final String[] DEFAULT_HOOKS = new String[] {
        "Instances",
        "MultiInv",
        "Multiverse-Core",
        "Multiverse-Inventories",
        "PermissionsEx",
        "ProtocolLib",
        "Vault",
        "WorldEdit",
        "WorldGuard",
    };
    public static final Charset CHARSET = Charset.forName("UTF-8");
    private String[] motd;
    private String partyNamePrefix;
    private String partyNameSuffix;
    private boolean leaderInvite;
    private boolean leaderKick;
    private boolean leaderDisband;
    private boolean editCommandInCreative = true;
    private String spawnName;
    private Map<String, Boolean> hooks = new HashMap<String, Boolean>();
    private Map<String, Party> parties = new HashMap<String, Party>();
    private Map<Player, Party> partyMap = new HashMap<Player, Party>();
    private Map<String, PortalPair> portals = new HashMap<String, PortalPair>();
    private Map<String, List<Portal>> portalMap = new HashMap<String, List<Portal>>();
    private Map<Player, InstanceEntrancePortal> lastPortal = new HashMap<Player, InstanceEntrancePortal>();
    private Map<String, HomeLocation> homes = new HashMap<String, HomeLocation>();
    private Map<String, List<Instance>> instanceMap = new HashMap<String, List<Instance>>();
    private ItemStack selectionTool;
    private Map<Player, Session> sessions = new HashMap<Player, Session>();
    private final static Map<String, Command> commands = new HashMap<String, Command>();
    private Bank bank;
    private WorldInheritance worldInheritance;
    private CuboidSelection cuboidSelection;
    private PacketHooks packetHooks;
    private NBTTools nbtTools;
    private InstanceTools instanceTools;
    private FilePurgeTask filePurgeTask;

    /* pp */ final Object INSTANCE_LOCK = new Object();

    {
        commands.put("p", new PartyChat());
        commands.put("pme", new PartyEmote());
        commands.put("pcreate", new PartyCreate());
        commands.put("pdisband", new PartyDisband());
        commands.put("pinfo", new PartyInfo());
        commands.put("pinvite", new PartyInvite());
        commands.put("pjoin", new PartyJoin());
        commands.put("pkick", new PartyKick());
        commands.put("pleader", new PartyLeader());
        commands.put("pleave", new PartyLeave());
        commands.put("puninvite", new PartyUninvite());
        commands.put("plist", new PartyList());