package org.mctourney.autoreferee.listeners; import java.lang.management.ManagementFactory; import java.util.Map; import java.util.WeakHashMap; import org.bukkit.block.Block; import org.bukkit.entity.Arrow; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.entity.Projectile; import org.bukkit.entity.TNTPrimed; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityDamageEvent.DamageCause; import org.bukkit.event.vehicle.VehicleDamageEvent; import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.entity.EntityShootBowEvent; import org.bukkit.event.entity.ExplosionPrimeEvent; import org.bukkit.event.entity.FoodLevelChangeEvent; import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.plugin.Plugin; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.Material; import org.mctourney.autoreferee.AutoRefMatch; import org.mctourney.autoreferee.AutoRefPlayer; import org.mctourney.autoreferee.AutoReferee; import org.mctourney.autoreferee.regions.AutoRefRegion.Flag; import org.mctourney.autoreferee.util.AchievementPoints; //import org.mctourney.autoreferee.util.SportBukkitUtil; import com.google.common.collect.Maps; public class CombatListener implements Listener { private static final int TNT_PRIME_RANGE = 10; private static final long PIGMEN_COOLDOWN_MS = 300000; AutoReferee plugin = null; private Map<Location, AutoRefPlayer> tntPropagation = Maps.newHashMap(); private Map<Projectile, Location> shotArrows = new WeakHashMap<Projectile, Location>(); private Map<AutoRefPlayer, Long> lastPigmenAggro = Maps.newHashMap(); public CombatListener(Plugin p) { plugin = (AutoReferee) p; } @EventHandler(priority=EventPriority.HIGHEST) public void playerDeath(PlayerDeathEvent event) { AutoRefMatch match = plugin.getMatch(event.getEntity().getWorld()); if (match != null) { // get victim, and killer (maybe null) of this player Player victim = event.getEntity(); AutoRefPlayer vapl = match.getPlayer(victim); Player killer = victim.getKiller(); AutoRefPlayer kapl = match.getPlayer(killer); String dmsg = event.getDeathMessage(); EntityDamageEvent lastDmg = victim.getLastDamageCause(); // if the death was due to intervention by the plugin // let's change the death message to reflect this fact if (lastDmg == AutoRefPlayer.VOID_DEATH) { dmsg = victim.getName() + " entered the void lane"; event.getDrops().clear(); } Location locKiller = null; if (lastDmg instanceof EntityDamageByEntityEvent) { EntityDamageByEntityEvent ed = (EntityDamageByEntityEvent) lastDmg; switch (ed.getDamager().getType()) { case CREEPER: dmsg = victim.getName() + " was blown up by Creeper"; break; case PRIMED_TNT: dmsg = victim.getName() + " was blown up by TNT"; if (plugin.getTNTOwner(ed.getDamager()) != vapl) { kapl = plugin.getTNTOwner(ed.getDamager()); if (kapl != null) dmsg = victim.getName() + " was blown up by " + kapl.getName(); } break; default: // noop break; } if (ed.getDamager() instanceof Projectile) locKiller = shotArrows.get(ed.getDamager()); } // update the death message with the changes event.setDeathMessage(dmsg); if (match.getCurrentState().inProgress()) { // register the death and kill if (vapl != null) vapl.registerDeath(event, locKiller); if (kapl != null && kapl != vapl) kapl.registerKill(event); } // handle respawn modes if (vapl != null && match.getCurrentState().inProgress()) { respawn: switch (match.getRespawnMode()) { case BEDS_ONLY: // INTENTIONAL FALL-THROUGH HERE! if (vapl.getTeam() != null) for (AutoRefPlayer mate : vapl.getTeam().getPlayers()) { if (mate == vapl) continue; boolean couldRespawn = mate.isOnline() && mate.getPlayer().getBedSpawnLocation() != null; if (!mate.isDead() || couldRespawn) break respawn; } if (victim.getBedSpawnLocation() != null) break respawn; case DISALLOW: case ALLOW: if (match.getCurrentState().inProgress() && !vapl.hasLives()) match.eliminatePlayer(event.getEntity()); break; // typically, no action should be taken default: break; } } } else for (Player pl : event.getEntity().getWorld().getPlayers()) pl.sendMessage(event.getDeathMessage()); // remove the death message (so we can control who receives it) event.setDeathMessage(null); } public static Player entityToPlayer(Entity e) { // damaging entity is an actual player, easy! if ((e instanceof Player)) return (Player) e; // damaging entity is an arrow, then who was bow? if ((e instanceof Projectile)) { LivingEntity shooter = (LivingEntity) ((Projectile) e).getShooter(); if ((shooter instanceof Player)) return (Player) shooter; } return null; } @EventHandler(priority=EventPriority.HIGHEST) public void damageDealt(EntityDamageEvent event) { AutoRefMatch match = plugin.getMatch(event.getEntity().getWorld()); if (match == null || !match.getCurrentState().inProgress()) return; if (event.getEntityType() == EntityType.PLAYER) { AutoRefPlayer apl = match.getPlayer((Player) event.getEntity()); if (apl != null && apl.isGodMode()) { event.setDamage(0); return; } } if (match.getCurrentState().inProgress() && event instanceof EntityDamageByEntityEvent) { EntityDamageByEntityEvent ed = (EntityDamageByEntityEvent) event; Player damaged = entityToPlayer(ed.getEntity()); // enderpearls are a special case! if (ed.getDamager().getType() == EntityType.ENDER_PEARL) return; Player damager = entityToPlayer(ed.getDamager()); if (null != damager && ed.getDamager() instanceof Arrow) { AutoRefPlayer apl = match.getPlayer(damager); if (apl != null) apl.incrementShotsHit(); Arrow arrow = (Arrow) ed.getDamager(); if (((LivingEntity)arrow.getShooter()).getType() == EntityType.PLAYER) { AutoRefPlayer shooter = match.getPlayer((Player) arrow.getShooter()); Location shotFrom = shotArrows.get(arrow); if (shooter != null && shotFrom != null) shooter.setFurthestShot(arrow.getLocation().distance(shotFrom)); } } // spectators cannot cause damage to any entity if (match.getCurrentState().inProgress() && null != damager && match.isSpectator(damager)) { event.setCancelled(true); return; } if (null != damager && ed.getEntityType() == EntityType.PIG_ZOMBIE) { AutoRefPlayer apl = match.getPlayer(damager); Long lastAggro = lastPigmenAggro.get(apl); long currentTime = ManagementFactory.getRuntimeMXBean().getUptime(); if (lastAggro == null || currentTime > PIGMEN_COOLDOWN_MS + lastAggro) { for (Player ref : match.getReferees(false)) ref.sendMessage(apl.getDisplayName() + ChatColor.GRAY + " has angered the Zombie Pigmen"); lastPigmenAggro.put(apl, currentTime); } } // if either of these aren't players, nothing to do here if (null == damager || null == damaged) return; AutoRefPlayer aapl = match.getPlayer(damager); AutoRefPlayer vapl = match.getPlayer(damaged); // FIXME - define behavior for when attacker or victim is not actually in the match // (and therefore aapl/vapl is null) if (!aapl.isInsideLane()) { event.setCancelled(true); return; } // if the match is in progress and player is in start region // cancel any damage dealt to the player if (match.getCurrentState().inProgress() && vapl != null && !vapl.isActive()) { event.setCancelled(true); return; } // if both players are not on a team, quit if (aapl.getTeam() == null && vapl.getTeam() == null) return; // if the attacked isn't on a team, or same team (w/ no FF), cancel if (vapl.getTeam() == null || (aapl.getTeam() == vapl.getTeam() && !match.allowFriendlyFire())) { event.setCancelled(true); return; } } // only allow damage before a match if it is a direct attack if (match.getCurrentState().isBeforeMatch() && event.getCause() != DamageCause.ENTITY_ATTACK) { event.setCancelled(true); return; } } @EventHandler(priority=EventPriority.HIGHEST) public void vehicleDamageDealt(VehicleDamageEvent event) { AutoRefMatch match = plugin.getMatch(event.getVehicle().getWorld()); if (match == null || !match.getCurrentState().inProgress()) return; if (match.getCurrentState().inProgress() && event.getAttacker() != null) { Player damager = entityToPlayer(event.getAttacker()); if (!match.getPlayer(damager).isInsideLane()) { event.setCancelled(true); return; } // spectators cannot cause damage to any vehicle if (match.getCurrentState().inProgress() && null != damager && match.isSpectator(damager)) { event.setCancelled(true); return; } AutoRefPlayer apl = match.getPlayer(damager); if (apl != null) { Location loc = event.getVehicle().getLocation(); if (!apl.isInsideLane() || apl.getTeam().hasFlag(loc, Flag.NO_ACCESS)) { event.setCancelled(true); return; } } } } @EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true) public void damageDealtMonitor(EntityDamageEvent event) { AutoRefMatch match = plugin.getMatch(event.getEntity().getWorld()); if (match == null || !match.getCurrentState().inProgress()) return; // save player data if the damaged entity was a player if (event.getEntityType() == EntityType.PLAYER) { AutoRefPlayer pdata = match.getPlayer((Player) event.getEntity()); if (pdata != null) { Player damager = (event instanceof EntityDamageByEntityEvent) ? entityToPlayer(((EntityDamageByEntityEvent) event).getDamager()) : null; pdata.registerDamage(event, damager); } } } @EventHandler(priority=EventPriority.MONITOR) public void entityDeath(EntityDeathEvent event) { AutoRefMatch match = plugin.getMatch(event.getEntity().getWorld()); if (match == null || !match.getCurrentState().inProgress()) return; EntityDamageEvent cause = event.getEntity().getLastDamageCause(); if (cause instanceof EntityDamageByEntityEvent) { Player dmgr = entityToPlayer(((EntityDamageByEntityEvent) cause).getDamager()); AchievementPoints ach = AchievementPoints.getMonsterKill(event.getEntityType()); if (match.isPlayer(dmgr)) match.getPlayer(dmgr).addPoints(ach); } } @EventHandler public void playerBowFire(EntityShootBowEvent event) { // if the entity is not a player, we don't care if (event.getEntityType() != EntityType.PLAYER) return; Player player = (Player) event.getEntity(); AutoRefMatch match = plugin.getMatch(player.getWorld()); if (match == null || !match.getCurrentState().inProgress()) return; AutoRefPlayer apl = match.getPlayer(player); if (apl != null) apl.incrementShotsFired(); shotArrows.put((Projectile) event.getProjectile(), event.getEntity().getLocation().clone()); } @EventHandler(priority=EventPriority.HIGHEST) public void hungerChange(FoodLevelChangeEvent event) { AutoRefMatch match = plugin.getMatch(event.getEntity().getWorld()); if (match != null && !match.getCurrentState().inProgress()) event.setFoodLevel(20); } @EventHandler(priority=EventPriority.MONITOR) public void explosionPrime(ExplosionPrimeEvent event) { AutoRefMatch match = plugin.getMatch(event.getEntity().getWorld()); // if (!SportBukkitUtil.hasSportBukkitApi() || match == null) return; if (event.getEntityType() == EntityType.PRIMED_TNT) { TNTPrimed entity = (TNTPrimed) event.getEntity(); AutoRefPlayer apl; Location tntLocation = entity.getLocation().getBlock().getLocation(); apl = tntPropagation.remove(tntLocation); // if there was no propagation chain if (apl == null) { // Try the new API try { Entity tntSource = entity.getSource(); if (tntSource instanceof Player) apl = match.getPlayer((Player) tntSource); } catch (Throwable ignored) {} } // Try position-based inference if (apl == null) { // try to determine if this was the first tnt in a chain if ((apl = match.getNearestPlayer(tntLocation)) == null) return; Location plLocation = apl.getLocation(); if (plLocation.distanceSquared(tntLocation) > TNT_PRIME_RANGE * TNT_PRIME_RANGE) return; } // add an owner for this tnt object if (apl != null) plugin.setTNTOwner(entity, apl); } } @EventHandler(priority=EventPriority.MONITOR) public void entityExplode(EntityExplodeEvent event) { // remove this entity from the table if present AutoRefPlayer apl = plugin.clearTNTOwner(event.getEntity()); if (apl != null) for (Block b : event.blockList()) if (b.getType() == Material.TNT) tntPropagation.put(b.getLocation(), apl); } }