package me.desht.scrollingmenusign.views; import com.google.common.base.Joiner; import me.desht.dhutils.Debugger; import me.desht.dhutils.ItemGlow; import me.desht.dhutils.LogUtils; import me.desht.dhutils.PermissionUtils; import me.desht.scrollingmenusign.*; import me.desht.scrollingmenusign.enums.SMSUserAction; import me.desht.scrollingmenusign.util.Substitutions; import org.apache.commons.lang.StringUtils; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Represents an item linked to a SMS menu. Not a traditional view, since all the * information needed is held in the item's metadata, not in a SMSView subclassed object. */ public class ActiveItem extends CommandTrigger { private static final String MENU_MARKER = ChatColor.BLACK + "\u2637"; private static final String SEPARATOR = " \u2237 " + ChatColor.RESET; private static final String SUBMENU_SEPARATOR = " \u25b6 "; private static final String NO_ITEMS = ChatColor.ITALIC + "\u223c no entries"; private final ItemStack stack; private final List<MenuPos> menus = new ArrayList<MenuPos>(); /** * Get the active item object from an item with existing active item metadata. * * @param stack the item to retrieve the active item for * @throws SMSException if the item's metadata does not point to an active item */ public ActiveItem(ItemStack stack) { SMSValidate.isTrue(stack.getType() != Material.AIR, "You can't create an active item from air!"); this.stack = stack; ItemMeta meta = stack.getItemMeta(); SMSValidate.notNull(meta, "There was a problem getting item metadata for your " + stack.getType()); List<String> lore = meta.getLore(); SMSValidate.isTrue(lore != null && !lore.isEmpty() && meta.getDisplayName() != null, "Item is not an SMS active item"); String last = lore.get(lore.size() - 1); SMSValidate.isTrue(last.startsWith(MENU_MARKER) && last.length() > MENU_MARKER.length(), "Item is not an SMS active item"); SMSValidate.isTrue(meta.getDisplayName().contains(SEPARATOR), "Item name is not correctly formed"); String[] menuPath = last.substring(MENU_MARKER.length()).split(SUBMENU_SEPARATOR); for (String menuName : menuPath) { String[] f = menuName.split(":"); SMSValidate.isTrue(f.length == 2 && StringUtils.isNumeric(f[1]), "Item lore is not correctly formed"); menus.add(new MenuPos(ScrollingMenuSign.getInstance().getMenuManager().getMenu(f[0]), Integer.parseInt(f[1]))); } } /** * Create a new active item object for the given itemstack and SMS menu. * * @param stack the item to turn into an active item * @param menu the menu to associate the item with */ public ActiveItem(ItemStack stack, SMSMenu menu) { SMSValidate.isTrue(stack.getType() != Material.AIR, "You can't create an active item from air!"); this.stack = stack; this.menus.add(new MenuPos(menu, 1)); buildItemStack(); } /** * Get the active item that the player is holding. * * @param player the player * @return the player's active item, or null if the player isn't holding an active item * @throws SMSException if the active item is somehow invalid */ public static ActiveItem get(Player player) { ItemStack item = player.getItemInHand(); if (ActiveItem.isActiveItem(item)) { return new ActiveItem(item); } else { return null; } } private void buildItemStack() { ItemMeta meta = stack.getItemMeta(); SMSValidate.notNull(meta, "There was a problem getting item metadata for your " + stack.getType()); SMSMenuItem menuItem = getActiveMenuItemAt(null, getSelectedItem()); List<String> lore = new ArrayList<String>(); if (menuItem != null) { meta.setDisplayName( Substitutions.viewVariableSubs(null, getActiveMenuTitle(null)) + SEPARATOR + Substitutions.viewVariableSubs(null, menuItem.getLabel()) ); Collections.addAll(lore, menuItem.getLore()); } else { meta.setDisplayName(getActiveMenuTitle(null) + SEPARATOR + NO_ITEMS); } List<String> names = new ArrayList<String>(menus.size()); for (MenuPos menu : menus) { names.add(menu.menu.getName() + ':' + menu.pos); } lore.add(MENU_MARKER + Joiner.on(SUBMENU_SEPARATOR).join(names)); meta.setLore(lore); stack.setItemMeta(meta); if (ScrollingMenuSign.getInstance().isProtocolLibEnabled()) { ItemGlow.setGlowing(stack, true); } } /* (non-Javadoc) * @see me.desht.scrollingmenusign.CommandTrigger#getActiveMenu(java.lang.String) */ @Override public SMSMenu getActiveMenu(Player player) { return menus.get(menus.size() - 1).menu; } /* (non-Javadoc) * @see me.desht.scrollingmenusign.views.CommandTrigger#getNativeMenu() */ @Override public SMSMenu getNativeMenu() { return menus.get(0).menu; } /** * Get the active menu for this ActiveItem object. * * @return the active menu */ public SMSMenu getActiveMenu() { return menus.get(menus.size() - 1).menu; } /** * Get the currently selected item index for the active menu. * * @return the item index */ public int getSelectedItem() { return menus.get(menus.size() - 1).pos; } /** * Change the currenty selected item index for the active menu. * * @param idx the new item index */ public void setSelectedItem(int idx) { menus.get(menus.size() - 1).pos = idx; } /** * Execute the currently selected menu item for this ActiveItem object. * * @param player the player who is executing the menu item */ public void execute(Player player) { if (getActiveMenuItemCount(null) == 0) { return; } PermissionUtils.requirePerms(player, "scrollingmenusign.use.item"); SMSValidate.isTrue(getActiveMenu().hasOwnerPermission(player), "This menu is owned by someone else"); SMSMenuItem item = getActiveMenuItemAt(null, getSelectedItem()); Debugger.getInstance().debug("ActiveItem: about to execute: " + item); if (item != null) { item.executeCommand(player, this, player.isSneaking()); } else { LogUtils.warning("index " + getSelectedItem() + " out of range for " + getActiveMenu().getName()); } } /** * Scroll the menu for this ActiveItem object. * * @param player the player who is scrolling the menu * @param delta the number of slots to scroll forward or backward by */ public void scroll(Player player, int delta) { PermissionUtils.requirePerms(player, "scrollingmenusign.use.item"); if (!getActiveMenu().hasOwnerPermission(player)) { throw new SMSException("This menu is owned by someone else"); } setSelectedItem(getSelectedItem() + delta); if (getSelectedItem() > getActiveMenuItemCount(player)) { setSelectedItem(1); } else if (getSelectedItem() < 1) { setSelectedItem(getActiveMenuItemCount(player)); } buildItemStack(); } /** * Retrieve the ItemStack for this ActiveItem * * @return the item stack */ public ItemStack toItemStack() { return stack; } /** * Deactivate this ActiveItem. Subequent calls to {@link #toItemStack()} will * return an ItemStack with no special meta data. */ public void deactivate() { ItemMeta meta = stack.getItemMeta(); meta.setDisplayName(null); meta.setLore(null); stack.setItemMeta(meta); if (ScrollingMenuSign.getInstance().isProtocolLibEnabled()) { ItemGlow.setGlowing(stack, false); } } /** * Process the given action on this ActiveItem for the given player. * * @param player the player who is carrying out the action * @param action the action to carry out */ public void processAction(Player player, SMSUserAction action) { Debugger.getInstance().debug("active item + " + this + ": " + action + " for " + player.getName()); switch (action) { case EXECUTE: execute(player); break; case SCROLLDOWN: scroll(player, 1); player.setItemInHand(toItemStack()); break; case SCROLLUP: scroll(player, -1); player.setItemInHand(toItemStack()); break; default: break; } } @Override public String toString() { return "[" + stack.getType() + ":" + getActiveMenu().getName() + "/" + getSelectedItem() + "]"; } /** * Check if the given item stack is an ActiveItem. * * @param stack the item to check * @return true if the item is an active item, false otherwise */ public static boolean isActiveItem(ItemStack stack) { return isActiveItem(stack.getItemMeta()); } public static boolean isActiveItem(ItemMeta meta) { if (meta == null || meta.getDisplayName() == null || !meta.getDisplayName().contains(SEPARATOR)) { return false; } List<String> lore = meta.getLore(); return !(lore == null || !lore.get(lore.size() - 1).startsWith(MENU_MARKER)); } /* (non-Javadoc) * @see me.desht.scrollingmenusign.views.CommandTrigger#pushMenu(java.lang.String, me.desht.scrollingmenusign.SMSMenu) */ @Override public void pushMenu(Player player, SMSMenu newActive) { menus.add(new MenuPos(newActive, 1)); buildItemStack(); } /* (non-Javadoc) * @see me.desht.scrollingmenusign.views.CommandTrigger#popMenu(java.lang.String) */ @Override public SMSMenu popMenu(Player player) { SMSMenu popped = getActiveMenu(); menus.remove(menus.size() - 1); buildItemStack(); return popped; } /* (non-Javadoc) * @see me.desht.scrollingmenusign.views.CommandTrigger#getName() */ @Override public String getName() { return "Active:" + stack.getType(); } private class MenuPos { private final SMSMenu menu; private int pos; private MenuPos(SMSMenu menu, int pos) { this.menu = menu; this.pos = pos; } } }