package org.mctourney.autoreferee.listeners;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.Action;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityDamageEvent.DamageCause;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.entity.PotionSplashEvent;
import org.bukkit.event.entity.ProjectileLaunchEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerPickupItemEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.inventory.DoubleChestInventory;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.material.PressurePlate;
import org.bukkit.plugin.Plugin;
import org.bukkit.event.Listener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerRegisterChannelEvent;
import org.bukkit.plugin.messaging.PluginMessageListener;
import org.bukkit.ChatColor;
import com.google.common.collect.Maps;
import org.mctourney.autoreferee.AutoRefMatch;
import org.mctourney.autoreferee.AutoRefPlayer;
import org.mctourney.autoreferee.AutoRefSpectator;
import org.mctourney.autoreferee.AutoRefTeam;
import org.mctourney.autoreferee.AutoReferee;
import org.mctourney.autoreferee.goals.BlockGoal;
import org.mctourney.autoreferee.regions.AutoRefRegion;
import org.mctourney.autoreferee.util.LocationUtil;
import org.mctourney.autoreferee.util.PlayerUtil;
import org.mctourney.autoreferee.util.TeleportationUtil;
public class SpectatorListener implements PluginMessageListener, Listener
{
public static final char DELIMITER = '|';
public static final double SPECTATOR_VISIBILITY_RADIUS = 8.0;
AutoReferee plugin = null;
// mapping spectators to the matches they died in
Map<String, AutoRefMatch> deadSpectators = Maps.newHashMap();
// convenience for changing defaults
public enum ToolAction
{
TOOL_WINCOND(Material.GOLD_SPADE),
TOOL_STARTMECH(Material.GOLD_AXE),
TOOL_PROTECT(Material.GOLD_SWORD),
SPECTATOR_CYCLE(Material.FEATHER),
SPECTATOR_TELEPORT(Material.COMPASS),
;
public final Material tooltype;
ToolAction(Material type)
{ this.tooltype = type; }
private static Map<Material, ToolAction> _map;
static
{
_map = Maps.newHashMap();
for (ToolAction tool : ToolAction.values())
_map.put(tool.tooltype, tool);
}
public static ToolAction fromMaterial(Material material)
{ return _map.get(material); }
}
public SpectatorListener(Plugin p)
{ plugin = (AutoReferee) p; }
public void onPluginMessageReceived(String channel, Player player, byte[] mbytes)
{
if (AutoReferee.REFEREE_PLUGIN_CHANNEL.equals(channel)) try
{
String message = new String(mbytes, AutoReferee.PLUGIN_CHANNEL_ENC);
AutoRefMatch match = plugin.getMatch(player.getWorld());
if (match == null || !match.isSpectator(player)) return;
String[] parts = message.trim().split("\\|");
if ("tp".equalsIgnoreCase(parts[0]))
{
Location loc = null;
if ("player".equalsIgnoreCase(parts[1]))
{
AutoRefPlayer apl = match.getPlayer(parts[2]);
if (apl != null)
{
if ("player".equalsIgnoreCase(parts[3])) loc = apl.getLocation();
else if ("death".equalsIgnoreCase(parts[3])) loc = apl.getLastDeathLocation();
else if ("spawn".equalsIgnoreCase(parts[3])) loc = apl.getBedLocation();
}
}
else if ("team".equalsIgnoreCase(parts[1]))
{
AutoRefTeam team = match.getTeam(parts[2]);
if (team != null)
{
if ("vm".equalsIgnoreCase(parts[3])) loc = team.getVictoryMonumentLocation();
else if ("spawn".equalsIgnoreCase(parts[3])) loc = team.getSpawnLocation();
}
}
// teleport to the location, if any
loc = TeleportationUtil.locationTeleport(loc);
if (loc == null) player.sendMessage(ChatColor.DARK_GRAY +
"You cannot teleport to this location: invalid or unsafe.");
else
{
match.getSpectator(player).setPrevLocation(player.getLocation());
player.teleport(loc); player.setFlying(true);
}
}
else if ("inventory".equalsIgnoreCase(parts[0]))
{
if ("player".equalsIgnoreCase(parts[1]))
{
AutoRefPlayer apl = match.getPlayer(parts[2]);
boolean old = parts.length > 3 && "prev".equalsIgnoreCase(parts[3]);
// if we are unable to show the inventory, tell the streamer that
if (apl == null || !apl.showInventory(player, old))
player.sendMessage(ChatColor.DARK_GRAY + "Cannot show inventory for " + parts[2]);
}
}
}
catch (UnsupportedEncodingException e)
{ AutoReferee.log("Unsupported encoding: " + AutoReferee.PLUGIN_CHANNEL_ENC); }
}
@EventHandler
public void channelRegistration(PlayerRegisterChannelEvent event)
{
Player pl = event.getPlayer();
AutoRefMatch match = plugin.getMatch(pl.getWorld());
if (AutoReferee.REFEREE_PLUGIN_CHANNEL.equals(event.getChannel()) && match != null)
{
// if this is a player, complain and force them to quit their team!
if (match.isPlayer(pl) && !pl.isOp())
{
AutoRefPlayer apl = match.getPlayer(pl);
for (Player ref : match.getReferees(true)) ref.sendMessage(apl.getDisplayName() +
ChatColor.DARK_GRAY + " attempted to log in with a modified client!");
match.leaveTeam(pl, true);
}
// update a referee with the latest information regarding the match
if (match.isReferee(pl)) match.updateReferee(pl);
}
}
@EventHandler(priority= EventPriority.MONITOR, ignoreCancelled=true)
public void entityInteract(PlayerInteractEntityEvent event)
{
Player pl = event.getPlayer();
Entity entity = event.getRightClicked();
AutoRefMatch match = plugin.getMatch(pl.getWorld());
if (match != null) match.checkWinConditions();
if (entity.getType() == EntityType.PLAYER && match != null
&& match.isSpectator(pl) && match.isPlayer((Player) entity))
{
AutoRefPlayer a = match.getPlayer((Player) entity);
a.showInventory(pl);
}
}
@EventHandler(priority=EventPriority.HIGHEST)
public void potionSplash(PotionSplashEvent event)
{
World world = event.getEntity().getWorld();
AutoRefMatch match = plugin.getMatch(world);
for (LivingEntity living : event.getAffectedEntities())
if (living.getType() == EntityType.PLAYER)
if (match != null && !match.isPlayer((Player) living))
event.setIntensity(living, 0.0);
}
@EventHandler
public void playerRespawn(PlayerRespawnEvent event)
{
String name = event.getPlayer().getName();
if (deadSpectators.containsKey(name))
{
AutoRefMatch match = deadSpectators.get(name);
if (match != null) event.setRespawnLocation(match.getWorldSpawn());
deadSpectators.remove(name); return;
}
}
@EventHandler(priority=EventPriority.MONITOR)
public void spectatorMove(PlayerMoveEvent event)
{
Player player = event.getPlayer();
World world = player.getWorld();
AutoRefMatch match = plugin.getMatch(world);
if (match == null || !match.isSpectator(player)) return;
AutoRefSpectator spec = match.getSpectator(player);
boolean pvis = spec.isInvisible();
double bdistance = Double.MAX_VALUE;
for (AutoRefPlayer apl : match.getPlayers()) if (apl.isOnline())
{
Location ploc = apl.getPlayer().getLocation();
double dist = ploc.distanceSquared(player.getLocation());
if (dist < bdistance) { bdistance = dist; }
}
// if the spectator should be invisible, change visibility and then redo visibility
boolean vis = bdistance <= SPECTATOR_VISIBILITY_RADIUS * SPECTATOR_VISIBILITY_RADIUS;
spec.setInvisible(vis);
// if the visibility status has changed, reconfigure
if (pvis != vis) match.setupVisibility(player);
}
@EventHandler(priority=EventPriority.MONITOR)
public void spectatorDeath(PlayerDeathEvent event)
{
World world = event.getEntity().getWorld();
AutoRefMatch match = plugin.getMatch(world);
if (match != null && !match.isPlayer(event.getEntity()))
{
deadSpectators.put(event.getEntity().getName(), match);
event.getDrops().clear();
}
}
@EventHandler(priority=EventPriority.MONITOR)
public void spectatorDeath(EntityDamageEvent event)
{
World world = event.getEntity().getWorld();
AutoRefMatch match = plugin.getMatch(world);
if (event.getEntityType() != EntityType.PLAYER) return;
Player player = (Player) event.getEntity();
if (match != null && match.isSpectator(player))
{
Location loc = player.getLocation();
event.setCancelled(true);
if (loc.getY() < -64 && event.getCause() == DamageCause.VOID)
player.teleport(match.getPlayerSpawn(player));
player.setFallDistance(0);
}
}
@EventHandler(priority=EventPriority.HIGHEST, ignoreCancelled=true)
public void foreignInventoryEvent(InventoryClickEvent event)
{
Player player = (Player) event.getWhoClicked();
AutoRefMatch match = plugin.getMatch(player.getWorld());
if (match != null && !match.isPlayer(player))
switch (event.getInventory().getType())
{
case PLAYER:
case CREATIVE:
break;
default: event.setCancelled(true);
}
}
@EventHandler(priority=EventPriority.HIGHEST, ignoreCancelled=true)
public void projectileLaunch(ProjectileLaunchEvent event)
{
LivingEntity entity = event.getEntity().getShooter();
AutoRefMatch match = plugin.getMatch(event.getEntity().getWorld());
if (!(entity instanceof Player) || match == null) return;
if (!match.isPlayer((Player) entity))
{ event.setCancelled(true); return; }
}
@EventHandler(priority=EventPriority.HIGHEST, ignoreCancelled=true)
public void projectileLaunch(PlayerInteractEvent event)
{
Player player = event.getPlayer();
ItemStack itemInHand = player.getItemInHand();
AutoRefMatch match = plugin.getMatch(player.getWorld());
if (match != null && itemInHand != null)
switch (itemInHand.getType())
{
case EYE_OF_ENDER:
event.setCancelled(true);
break;
default:
break;
}
}
// restrict item pickup by referees
@EventHandler(priority=EventPriority.HIGHEST)
public void refereePickup(PlayerPickupItemEvent event)
{
AutoRefMatch match = plugin.getMatch(event.getPlayer().getWorld());
if (match != null && match.getCurrentState().inProgress()
&& !match.isPlayer(event.getPlayer())) event.setCancelled(true);
}
// restrict item pickup by referees
@EventHandler(priority=EventPriority.HIGHEST)
public void refereeDrop(PlayerDropItemEvent event)
{
AutoRefMatch match = plugin.getMatch(event.getPlayer().getWorld());
if (match != null && match.getCurrentState().inProgress()
&& !match.isPlayer(event.getPlayer())) event.setCancelled(true);
if (PlayerUtil.hasClientMod(event.getPlayer())) event.setCancelled(true);
}
@EventHandler
public void toolUsage(PlayerInteractEvent event)
{
AutoRefMatch match = plugin.getMatch(event.getPlayer().getWorld());
if (match == null) return;
Block block;
// this event is not an "item" event
if (!event.hasItem()) return;
// get type id of the event and check if its one of our tools
ToolAction action = ToolAction.fromMaterial(event.getMaterial());
if (action == null) return;
// get which action to perform
switch (action)
{
// this is the tool built for setting win conditions
case TOOL_WINCOND:
if (match.getCurrentState().inProgress()) return;
// if there is no block involved in this event, nothing
if (!event.hasBlock()) return;
block = event.getClickedBlock();
// if the player doesn't have configure permissions, nothing
if (!event.getPlayer().hasPermission("autoreferee.configure")) return;
for (AutoRefTeam team : match.getTeams())
{
boolean canBuild = !team.hasFlag(block.getLocation(), AutoRefRegion.Flag.NO_BUILD);
boolean canEnter = !team.hasFlag(block.getLocation(), AutoRefRegion.Flag.NO_ENTRY);
if (canBuild && canEnter) team.addGoal(new BlockGoal(team, block));
}
break;
// this is the tool built for setting start mechanisms
case TOOL_STARTMECH:
if (match.getCurrentState().inProgress()) return;
// if there is no block involved in this event, nothing
if (!event.hasBlock()) return;
// if the player doesn't have configure permissions, nothing
if (!event.getPlayer().hasPermission("autoreferee.configure")) return;
// determine who owns the region that the clicked block is in
block = event.getClickedBlock();
// get the start mechanism
AutoRefMatch.StartMechanism sm = match.toggleStartMech(block);
if (sm != null) event.getPlayer().sendMessage(ChatColor.RED +
"" + sm + ChatColor.RESET + " is a start mechanism.");
else
{
String coords = LocationUtil.toBlockCoords(block.getLocation());
event.getPlayer().sendMessage(ChatColor.RED + "" +
coords + ChatColor.RESET + " is NOT a start mechanism.");
}
break;
case SPECTATOR_CYCLE:
// this tool only valid for spectators
if (!match.isSpectator(event.getPlayer())) return;
switch (event.getAction())
{
case LEFT_CLICK_AIR:
case LEFT_CLICK_BLOCK:
match.getSpectator(event.getPlayer()).cyclePrevPlayer();
break;
case RIGHT_CLICK_AIR:
case RIGHT_CLICK_BLOCK:
match.getSpectator(event.getPlayer()).cycleNextPlayer();
break;
default: break;
}
break;
// this isn't one of our tools...
default: return;
}
// cancel the event, since it was one of our tools being used properly
event.setCancelled(true);
}
@EventHandler
public void toolUsage(PlayerInteractEntityEvent event)
{
AutoRefMatch match = plugin.getMatch(event.getPlayer().getWorld());
if (match == null) return;
// this event is not an "item" event
ItemStack item = event.getPlayer().getItemInHand();
if (item == null) return;
// get type id of the event and check if its one of our tools
ToolAction action = ToolAction.fromMaterial(item.getType());
if (action == null) return;
// get which action to perform
switch (action)
{
// this is the tool built for protecting entities
case TOOL_PROTECT:
if (match.getCurrentState().inProgress()) return;
// if there is no entity involved in this event, nothing
if (event.getRightClicked() == null) return;
// if the player doesn't have configure permissions, nothing
if (!event.getPlayer().hasPermission("autoreferee.configure")) return;
// entity name
String ename = String.format("%s @ %s", event.getRightClicked().getType().getName(),
LocationUtil.toBlockCoords(event.getRightClicked().getLocation()));
// save the entity's unique id
UUID uid; match.toggleProtection(uid = event.getRightClicked().getUniqueId());
match.broadcast(ChatColor.RED + ename + ChatColor.RESET + " is " +
(match.isProtected(uid) ? "" : "not ") + "a protected entity");
break;
// this isn't one of our tools...
default: return;
}
// cancel the event, since it was one of our tools being used properly
event.setCancelled(true);
}
@EventHandler(priority=EventPriority.HIGHEST, ignoreCancelled=true)
public void blockInteract(PlayerInteractEvent event)
{
Player player = event.getPlayer();
Location loc = event.getClickedBlock().getLocation();
AutoRefMatch match = plugin.getMatch(loc.getWorld());
if (match == null) return;
if (match.isSpectator(player))
{
if (!match.isReferee(player) && match.getCurrentState().inProgress())
event.setCancelled(true);
Material type = event.getClickedBlock().getType();
if (PressurePlate.class.isAssignableFrom(type.getData()) && event.getAction().equals(Action.PHYSICAL)
&& match.getCurrentState().inProgress()) { event.setCancelled(true); return; }
if (event.getClickedBlock().getState() instanceof InventoryHolder
&& event.getAction() == Action.RIGHT_CLICK_BLOCK && match.getCurrentState().inProgress()
&& !event.getPlayer().isSneaking())
{
InventoryHolder invh = (InventoryHolder) event.getClickedBlock().getState();
Inventory inv = invh.getInventory(), newinv;
if (inv instanceof DoubleChestInventory)
newinv = Bukkit.getServer().createInventory(player, 54, "Large Chest");
else newinv = Bukkit.getServer().createInventory(player, inv.getType());
newinv.setContents(inv.getContents());
player.openInventory(newinv);
event.setCancelled(true); return;
}
}
}
}