package me.desht.scrollingmenusign.views; import com.google.common.base.Joiner; import me.desht.dhutils.ConfigurationManager; import me.desht.dhutils.LogUtils; import me.desht.dhutils.MiscUtil; import me.desht.dhutils.PersistableLocation; import me.desht.scrollingmenusign.*; import me.desht.scrollingmenusign.enums.RedstoneOutputMode; import me.desht.scrollingmenusign.enums.SMSUserAction; import me.desht.scrollingmenusign.views.redout.Switch; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.Sign; import org.bukkit.configuration.Configuration; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitTask; import org.bukkit.util.Vector; import java.util.*; /** * This is just like a {@link SMSScrollableView} but maintains only a single player context * for scroll position tracking. Generally used as the base class for views implemented by * blocks in the world, such as sign views. * <p/> * It also maintains a set of output switches which are powered/unpowered depending on * the selected item in this view, and tracks the location of a possible tooltip sign. */ public abstract class SMSGlobalScrollableView extends SMSScrollableView { protected static final int SIGN_WIDTH = 15; protected static final int SIGN_LINES = 4; public static final String RS_OUTPUT_MODE = "rsoutputmode"; public static final String PULSE_TICKS = "pulseticks"; private final Set<Switch> switches = new HashSet<Switch>(); private final Set<RedstoneControlSign> controlSigns = new HashSet<RedstoneControlSign>(); private PersistableLocation tooltipSign; private BukkitTask pulseResetTask; public SMSGlobalScrollableView(SMSMenu menu) { this(null, menu); } public SMSGlobalScrollableView(String name, SMSMenu menu) { super(name, menu); Configuration config = ScrollingMenuSign.getInstance().getConfig(); registerAttribute(RS_OUTPUT_MODE, RedstoneOutputMode.SELECTED, "Redstone output mode when menu is scrolled/clicked"); registerAttribute(PULSE_TICKS, config.getLong("sms.redstoneoutput.pulseticks"), "Pulse duration for " + RS_OUTPUT_MODE + "=pulse"); pulseResetTask = null; tooltipSign = null; } @Override public void pushMenu(Player player, SMSMenu newActive) { super.pushMenu(player, newActive); updateTooltipSign(); } @Override public SMSMenu popMenu(Player player) { SMSMenu menu = super.popMenu(player); updateTooltipSign(); return menu; } @Override public void update(Observable menu, Object arg) { super.update(menu, arg); updateTooltipSign(); } public void addSwitch(Switch sw) { switches.add(sw); autosave(); } public void removeSwitch(Switch sw) { switches.remove(sw); autosave(); } public void addControlSign(RedstoneControlSign sign) { controlSigns.add(sign); autosave(); } public void removeControlSign(RedstoneControlSign sign) { controlSigns.remove(sign); autosave(); } @Override protected UUID getPlayerContext(Player player) { return GLOBAL_PLAYER_UUID; } /** * Get the last scroll position (currently-selected item) for this view. If the scroll position * is out of range (possibly because an item was deleted from the menu), it will be automatically * adjusted to be in range before being returned. * * @return The scroll position */ public int getScrollPos() { return super.getScrollPos(null); } public void updateSwitchPower() { SMSMenuItem item = getActiveMenuItemAt(null, getScrollPos()); if (item == null) { return; } for (Switch sw : switches) { sw.setPowered(sw.getTrigger().equals(item.getLabel())); } } /** * Toggle the switch status for the currently selected menu item */ public void toggleSwitchPower() { SMSMenuItem item = getActiveMenuItemAt(null, getScrollPos()); if (item == null) { return; } for (Switch sw : switches) { if (sw.getTrigger().equals(item.getLabel())) { sw.setPowered(!sw.getPowered()); } } } /** * Set the switch status for the currently selected menu item on, and all others off. */ public void radioSwitchPower() { SMSMenuItem item = getActiveMenuItemAt(null, getScrollPos()); if (item == null) { return; } for (Switch sw : switches) { sw.setPowered(sw.getTrigger().equals(item.getLabel())); } } /** * Set the switch status for the selected menu item on for a given time, then off again. * * @param pulseAll if true, pulse the switch status for <em>all</em> switches */ public void pulseSwitchPower(boolean pulseAll) { SMSMenuItem item = getActiveMenuItemAt(null, getScrollPos()); if (item == null) { return; } final List<Switch> affected = new ArrayList<Switch>(); for (Switch sw : switches) { if (pulseAll || sw.getTrigger().equals(item.getLabel())) { sw.setPowered(true); affected.add(sw); } } if (!affected.isEmpty()) { long delay = (Long) getAttribute(PULSE_TICKS); pulseResetTask = Bukkit.getScheduler().runTaskLater(ScrollingMenuSign.getInstance(), new Runnable() { @Override public void run() { for (Switch sw : affected) { sw.setPowered(false); } pulseResetTask = null; } }, delay); } } public Set<Switch> getSwitches() { return switches; } public Set<RedstoneControlSign> getControlSigns() { return controlSigns; } @Override public Map<String, Object> freeze() { Map<String, Object> map = super.freeze(); Map<String, Map<String, Object>> l = new HashMap<String, Map<String, Object>>(); for (Switch sw : switches) { l.put(sw.getName(), sw.freeze()); } map.put("switches", l); List<PersistableLocation> locs = new ArrayList<PersistableLocation>(); for (RedstoneControlSign s : controlSigns) { PersistableLocation pl = new PersistableLocation(s.getlocation()); pl.setSavePitchAndYaw(false); locs.add(pl); } map.put("controlSigns", locs); if (tooltipSign != null) { map.put("tooltip", tooltipSign); } return map; } @SuppressWarnings("unchecked") @Override protected void thaw(ConfigurationSection node) throws SMSException { super.thaw(node); ConfigurationSection sw = node.getConfigurationSection("switches"); if (sw != null) { for (String k : sw.getKeys(false)) { ConfigurationSection conf = node.getConfigurationSection("switches." + k); try { addSwitch(new Switch(this, conf)); } catch (SMSException e) { // world not loaded Switch.deferLoading(this, conf); } } updateSwitchPower(); } List<PersistableLocation> rcSignLocs = (List<PersistableLocation>) node.getList("controlSigns"); if (rcSignLocs != null) { for (PersistableLocation pl : rcSignLocs) { try { RedstoneControlSign.getControlSign(pl.getLocation(), this); } catch (IllegalStateException e) { // world not loaded RedstoneControlSign.deferLoading(pl.getWorldName(), new Vector(pl.getX(), pl.getY(), pl.getZ())); } catch (SMSException e) { LogUtils.warning("can't load redstone control sign at " + MiscUtil.formatLocation(pl.getLocation()) + ": " + e.getMessage()); } } } tooltipSign = (PersistableLocation) node.get("tooltip"); if (tooltipSign != null) { Location loc = tooltipSign.getLocation(); ScrollingMenuSign.getInstance().getLocationManager().registerLocation(loc, new TooltipSign(this)); } } @Override public void onScrolled(Player player, SMSUserAction action) { super.onScrolled(player, action); RedstoneOutputMode mode = (RedstoneOutputMode) getAttribute(RS_OUTPUT_MODE); if (mode == RedstoneOutputMode.SELECTED) { updateSwitchPower(); } updateTooltipSign(); } @Override public void onExecuted(Player player) { super.onExecuted(player); RedstoneOutputMode mode = (RedstoneOutputMode) getAttribute(RS_OUTPUT_MODE); switch (mode) { case TOGGLE: toggleSwitchPower(); break; case PULSE: pulseSwitchPower(false); break; case PULSEANY: pulseSwitchPower(true); break; case RADIO: radioSwitchPower(); default: break; } } @Override public void onDeleted(boolean permanent) { super.onDeleted(permanent); if (permanent) { if (tooltipSign != null) { Block b = tooltipSign.getBlock(); if (b.getType() == Material.SIGN_POST || b.getType() == Material.WALL_SIGN) { Sign sign = (Sign) b.getState(); for (int i = 0; i < SIGN_LINES; i++) { sign.setLine(i, ""); } sign.update(); } removeTooltipSign(); } } } @Override public void onConfigurationChanged(ConfigurationManager configurationManager, String key, Object oldVal, Object newVal) { super.onConfigurationChanged(configurationManager, key, oldVal, newVal); if (key.equals(RS_OUTPUT_MODE)) { switch ((RedstoneOutputMode) newVal) { case SELECTED: updateSwitchPower(); break; default: for (Switch sw : getSwitches()) { sw.setPowered(false); } } if (pulseResetTask != null) { pulseResetTask.cancel(); pulseResetTask = null; } } } public void addTooltipSign(Location loc) { tooltipSign = new PersistableLocation(loc); tooltipSign.setSavePitchAndYaw(false); ScrollingMenuSign.getInstance().getLocationManager().registerLocation(loc, new TooltipSign(this)); autosave(); } public Location getTooltipSign() { return tooltipSign == null ? null : tooltipSign.getLocation(); } public void removeTooltipSign() { ScrollingMenuSign.getInstance().getLocationManager().unregisterLocation(tooltipSign.getLocation()); tooltipSign = null; autosave(); } public void updateTooltipSign() { if (tooltipSign == null) { return; } Block b = tooltipSign.getBlock(); if (b.getType() != Material.WALL_SIGN && b.getType() != Material.SIGN_POST) { LogUtils.warning("Block " + b + " is not a sign. Removing tooltip from view " + getName()); removeTooltipSign(); } else { Sign sign = (Sign) b.getState(); String[] text = getTooltipText(); for (int i = 0; i < SIGN_LINES; i++) { sign.setLine(i, text[i]); } sign.update(); } } public String[] getTooltipText() { List<String> lore = doVariableSubstitutions(null, getActiveMenuItemAt(null, getScrollPos()).getLoreAsList()); Scanner scanner = new Scanner(Joiner.on(" ").join(lore)); StringBuilder sb = new StringBuilder(); String[] text = new String[SIGN_LINES]; int i = 0; while (scanner.hasNext() && i < text.length) { String word = scanner.next(); if (sb.length() + word.length() >= SIGN_WIDTH) { text[i++] = sb.toString(); sb.setLength(0); } if (sb.length() > 0) sb.append(" "); sb.append(word); } scanner.close(); if (i < text.length) text[i] = sb.toString(); return text; } }