package me.desht.scrollingmenusign.views;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import me.desht.dhutils.*;
import me.desht.scrollingmenusign.SMSException;
import me.desht.scrollingmenusign.SMSMenu;
import me.desht.scrollingmenusign.SMSMenuItem;
import me.desht.scrollingmenusign.ScrollingMenuSign;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.block.BlockRedstoneEvent;
/**
* A view that tracks the redstone powered state of one or more blocks in the world and executes commands in
* response to changes in the state.
*/
public class SMSRedstoneView extends SMSView {
// attributes
public static final String POWERTOGGLE = "powertoggle";
public static final String POWEROFF = "poweroff";
public static final String POWERON = "poweron";
public static final String PLAYERRADIUS = "playerradius";
public static final String AFFECTONLYNEAREST = "affectonlynearest";
public SMSRedstoneView(String name, SMSMenu menu) {
super(name, menu);
registerAttribute(POWERON, "", "Item to run when redstone power goes on");
registerAttribute(POWEROFF, "", "Item to run when redstone power goes off");
registerAttribute(POWERTOGGLE, "", "Item to run when redstone power changes");
registerAttribute(PLAYERRADIUS, 0.0, "Command will be run on players within this radius");
registerAttribute(AFFECTONLYNEAREST, true, "If true, command will only be run on nearest player");
}
public SMSRedstoneView(SMSMenu menu) {
this(null, menu);
}
public SMSRedstoneView(SMSMenu menu, Location loc) throws SMSException {
this(menu);
addLocation(loc);
}
@Override
public void update(Observable menu, Object arg1) {
// A redstone view doesn't have any visual appearance to redraw
}
@Override
public String getType() {
return "redstone";
}
@Override
public String toString() {
Location[] locs = getLocationsArray();
return "redstone @ " + (locs.length == 0 ? "NONE" : MiscUtil.formatLocation(locs[0]));
}
private void execute(Location loc, String attr) {
try {
String label = getAttributeAsString(attr);
if (label == null || label.isEmpty())
return;
SMSMenuItem item = getNativeMenu().getItem(label);
List<Player> players = getAffectedPlayers(loc);
if (item != null) {
if (players != null) {
// run the command for each affected player
for (Player p : players) {
if (PermissionUtils.isAllowedTo(p, "scrollingmenusign.use.redstone")) {
item.executeCommand(p, this);
item.feedbackMessage(p);
}
}
} else {
// no affected players - run this as a console command
item.executeCommand(Bukkit.getConsoleSender(), this);
}
} else {
LogUtils.warning("No such menu item '" + label + "' in menu " + getNativeMenu().getName());
}
} catch (SMSException e) {
LogUtils.warning(e.getMessage());
}
}
/**
* Get a list of the players affected by this view during an execution event. Returns null
* if this view doesn't affect players (PLAYERRADIUS <= 0), or a list of players (which may
* be empty) otherwise. If AFFECTONLYNEAREST is true, then the list will contain one element
* only - the closest player to the view.
*
* @param loc The view's location - where the event occurred
* @return A list of affected players
*/
private List<Player> getAffectedPlayers(Location loc) {
double radius = (Double) getAttribute(PLAYERRADIUS);
if (radius <= 0) {
return null;
}
double radius2 = radius * radius;
double minDist = Double.MAX_VALUE;
List<Player> res = new ArrayList<Player>();
if ((Boolean) getAttribute(AFFECTONLYNEAREST)) {
// get a list containing only the closest player (who must also be within PLAYERRADIUS)
Player closest = null;
for (Player p : loc.getWorld().getPlayers()) {
double dist = p.getLocation().distanceSquared(loc);
if (dist < radius2 && dist < minDist) {
closest = p;
minDist = dist;
}
}
if (closest != null) {
res.add(closest);
}
} else {
// get a list of all players within PLAYERRADIUS
for (Player p : loc.getWorld().getPlayers()) {
double dist = p.getLocation().distanceSquared(loc);
if (dist < radius2) {
res.add(p);
}
}
}
return res;
}
/**
* Check if the power level for the given location has changed
*
* @param loc The location to check
* @param newCurrent The new current at the given location
* @return true if the new current represents a power level different from the block's current powered status
*/
public boolean hasPowerChanged(Location loc, int newCurrent) {
boolean curPower = loc.getBlock().isBlockPowered() || loc.getBlock().isBlockIndirectlyPowered();
boolean newPower = newCurrent > 0;
return curPower != newPower;
}
@Override
public void processEvent(ScrollingMenuSign plugin, BlockRedstoneEvent event) {
Block b = event.getBlock();
Debugger.getInstance().debug("block redstone event @ " + b.getLocation() + ", view = "
+ getName() + ", menu = " + getNativeMenu().getName()
+ ", current = " + event.getOldCurrent() + "->" + event.getNewCurrent());
if (event.getNewCurrent() > event.getOldCurrent()) {
execute(b.getLocation(), POWERON);
} else if (event.getOldCurrent() > event.getNewCurrent()) {
execute(b.getLocation(), POWEROFF);
}
if (event.getOldCurrent() != event.getNewCurrent()) {
execute(b.getLocation(), POWERTOGGLE);
}
}
/* (non-Javadoc)
* @see me.desht.scrollingmenusign.views.SMSView#onConfigurationValidate(me.desht.dhutils.ConfigurationManager, java.lang.String, java.lang.String)
*/
@Override
public Object onConfigurationValidate(ConfigurationManager configurationManager, String attribute, Object oldVal, Object newVal) {
newVal = super.onConfigurationValidate(configurationManager, attribute, oldVal, newVal);
if (attribute.equals(POWERON) || attribute.equals(POWEROFF) || attribute.equals(POWERTOGGLE)) {
String label = newVal.toString();
if (!label.isEmpty()) {
if (getNativeMenu().indexOfItem(label) == -1) {
throw new SMSException("Menu " + getNativeMenu().getName() + " does not contain the item '" + newVal + "'");
}
}
}
return newVal;
}
}