package org.mctourney.autoreferee.regions;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Constructor;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerMoveEvent;
import org.jdom2.Element;
import org.mctourney.autoreferee.AutoRefMatch;
import org.mctourney.autoreferee.AutoRefPlayer;
import org.mctourney.autoreferee.AutoRefTeam;
import org.mctourney.autoreferee.AutoReferee;
import org.mctourney.autoreferee.util.TeleportationUtil;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
public abstract class AutoRefRegion
{
private static final long NOTIFICATION_THRESHOLD_MS = 5 * 60 * 1000L;
private static final Random random = new Random();
public static enum Flag
{
NO_BUILD (1 << 0, true, 'b', "nobuild"),
NO_ENTRY (1 << 1, true, 'n', "noentry"),
SAFE (1 << 2, false, 's', "safe"),
NO_EXPLOSIONS (1 << 3, false, 'e', "noexplosion"),
NO_ACCESS (1 << 4, false, 'a', "noaccess"),
NO_TELEPORT (1 << 5, false, 't', "noteleport"),
SPAWNERS_ONLY (1 << 6, false, 'w', "spawnersonly"),
NO_FLOW (1 << 7, true, 'f', "noflow");
// generated from above values
public static final String OPTIONS = "abenstwf";
private int value;
private String name;
private char mark;
public boolean defaultValue;
public int getValue() { return value; }
public String getName() { return name; }
public char getMark() { return mark; }
public static Flag fromName(String name)
{
for (Flag f : values())
if (f.name.equals(name)) return f;
return null;
}
Flag(int val, boolean def, char c, String name)
{ this.value = val; this.name = name; this.mark = c; this.defaultValue = def; }
}
private int flags;
protected Integer yaw = null; // yaw = f * 90, SOUTH = 0
protected Integer pitch = null; // -90 = up, 0 = level, 90 = down
private Set<AutoRefTeam> owners = Sets.newHashSet();
private String regionName = null;
private Map<AutoRefTeam, Long> lastNotification = Maps.newHashMap();
public AutoRefRegion()
{ flags = 0; }
public String getName()
{ return this.regionName; }
public void setName(String name)
{ this.regionName = name; }
public void announceRegion(AutoRefPlayer apl)
{
AutoRefTeam team = apl.getTeam();
if (this.regionName == null || !owners.contains(team)) return;
Long last = lastNotification.get(team);
long ctime = ManagementFactory.getRuntimeMXBean().getUptime();
if (last == null || ctime - last > NOTIFICATION_THRESHOLD_MS)
{
String msg = apl.getDisplayName() + " has entered " +
team.getColor() + ChatColor.BOLD + this.getName();
AutoReferee.log(msg);
for (Player ref : apl.getMatch().getReferees(false)) ref.sendMessage(msg);
lastNotification.put(team, ctime);
}
}
// these methods need to be implemented
public abstract double distanceToRegion(Location loc);
public abstract Location getRandomLocation(Random r);
public abstract CuboidRegion getBoundingCuboid();
public abstract Element toElement();
private int ANGLE_RND = 15;
public Integer getFixedYaw()
{
if (yaw == null) return null;
return Math.round(((float)yaw/ANGLE_RND)*ANGLE_RND);
}
public Integer getFixedPitch()
{
if (pitch == null) return null;
return Math.round(((float)pitch/ANGLE_RND)*ANGLE_RND);
}
public Location getLocation()
{
Location loc = getRandomLocation(random);
if (pitch != null) loc.setPitch(getFixedPitch());
if (yaw != null) loc.setYaw(getFixedYaw());
return loc.add(0.0, 0.5, 0.0);
}
public Location getCenter()
{
CuboidRegion cuboid = this.getBoundingCuboid();
// Get points of the region
Location min = cuboid.getMinimumPoint();
Location max = cuboid.getMaximumPoint();
World world = cuboid.world;
double pointX = (min.getBlockX() + max.getBlockX()) / 2.0;
double pointY = (min.getBlockY() + max.getBlockY()) / 2.0;
double pointZ = (min.getBlockZ() + max.getBlockZ()) / 2.0;
Location loc = new Location(world, pointX, pointY, pointZ);
if (pitch != null) loc.setPitch(getFixedPitch());
if (yaw != null) loc.setYaw(getFixedYaw());
return loc;
}
public Location getGroundedCenter()
{
// keep searching down until we hit ground
Location loc = getCenter();
while (loc.getBlockY() > 0)
{
Location next = loc.clone().add(0,-1,0);
if (!TeleportationUtil.isBlockPassable(next.getBlock())) break;
loc = next;
}
return loc;
}
public boolean contains(Location loc)
{ return distanceToRegion(loc) <= 0.0; }
public boolean containsBlock(Block block)
{ return this.contains(block.getLocation().clone().add(0.5, 0.5, 0.5)); }
public boolean is(Flag flag)
{ return 0 != (flag.getValue() & this.flags); }
public Set<Flag> getFlags()
{
Set<Flag> fset = Sets.newHashSet();
for (Flag f : Flag.values())
if ((f.getValue() & this.flags) != 0) fset.add(f);
return fset;
}
public AutoRefRegion toggle(Flag flag)
{ if (flag != null) flags ^= flag.getValue(); return this; }
public AutoRefRegion addFlags(Element elt)
{
if (elt != null) for (Element c : elt.getChildren())
try { flags |= Flag.fromName(c.getName()).getValue(); }
catch (Exception e) { AutoReferee.log("Unrecognized flag: " + c.getName()); }
return this;
}
public boolean isEnterEvent(PlayerMoveEvent event)
{ return !contains(event.getFrom()) && contains(event.getTo()); }
protected AutoRefRegion getRegionSettings(AutoRefMatch match, Element e)
{
for (Element owner : e.getChildren("owner"))
this.addOwners(match.getTeam(owner.getTextTrim()));
return this.getRegionSettings(match.getWorld(), e);
}
protected AutoRefRegion getRegionSettings(World world, Element e)
{
if (e.getAttribute("yaw") != null)
yaw = Integer.parseInt(e.getAttributeValue("yaw"));
if (e.getAttribute("pitch") != null)
pitch = Integer.parseInt(e.getAttributeValue("pitch"));
if (e.getAttribute("name") != null)
this.setName(e.getAttributeValue("name"));
this.addFlags(e.getChild("flags"));
return this;
}
protected Element setRegionSettings(Element e)
{
Set<Flag> fset = getFlags();
if (!fset.isEmpty())
{
Element eFlags; e.addContent(eFlags = new Element("flags"));
for (Flag f : fset) eFlags.addContent(new Element(f.name.toLowerCase()));
}
if (getOwners() != null) for (AutoRefTeam team : getOwners())
e.addContent(new Element("owner").setText(team.getDefaultName()));
if (yaw != null) e.setAttribute("yaw", Integer.toString(getFixedYaw()));
if (pitch != null) e.setAttribute("pitch", Integer.toString(getFixedPitch()));
// set the custom region name if one has been specified
if (regionName != null) e.setAttribute("name", this.getName());
return e;
}
public Set<AutoRefTeam> getOwners()
{ return owners; }
public void addOwners(AutoRefTeam ...teams)
{ for (AutoRefTeam team : teams) if (team != null) owners.add(team); }
public boolean isOwner(AutoRefTeam team)
{ return owners.contains(team); }
public static CuboidRegion combine(AutoRefRegion reg1, AutoRefRegion reg2)
{
// handle nulls gracefully
if (reg1 == null && reg2 == null) return null;
if (reg1 == null) return reg2.getBoundingCuboid();
if (reg2 == null) return reg1.getBoundingCuboid();
return CuboidRegion.combine(reg1.getBoundingCuboid(), reg2.getBoundingCuboid());
}
// wrote this dumb helper function because `distanceToRegion` was looking ugly...
protected static double multimax(double base, double ... more )
{ for ( double x : more ) base = Math.max(base, x); return base; }
public static Map<String, Class<? extends AutoRefRegion>> elementNames = Maps.newHashMap();
static
{
elementNames.put("location", PointRegion.class);
elementNames.put("cuboid", CuboidRegion.class);
elementNames.put("cylinder", CylinderRegion.class);
}
public static void addRegionType(String tag, Class<? extends AutoRefRegion> cls)
{ elementNames.put(tag, cls); }
public static AutoRefRegion fromElement(AutoRefMatch match, Element elt)
{
Class<? extends AutoRefRegion> cls = elementNames.get(elt.getName());
if (cls == null) return null;
try
{
Constructor<? extends AutoRefRegion> cons = cls.getConstructor(AutoRefMatch.class, Element.class);
return cons.newInstance(match, elt).getRegionSettings(match, elt);
}
catch (Exception e) { e.printStackTrace(); return null; }
}
public static AutoRefRegion fromElement(World world, Element elt)
{
Class<? extends AutoRefRegion> cls = elementNames.get(elt.getName());
if (cls == null) return null;
try
{
Constructor<? extends AutoRefRegion> cons = cls.getConstructor(World.class, Element.class);
return cons.newInstance(world, elt).getRegionSettings(world, elt);
}
catch (Exception e) { e.printStackTrace(); return null; }
}
}