package tc.oc.pgm.ghostsquadron; import static com.google.common.base.Preconditions.checkNotNull; import java.util.Date; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; import org.bukkit.*; import org.bukkit.block.BlockFace; import org.bukkit.entity.*; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.Action; import org.bukkit.event.entity.*; import org.bukkit.event.entity.EntityDamageEvent.DamageCause; import org.bukkit.event.player.*; import org.bukkit.inventory.ItemStack; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.bukkit.scheduler.BukkitTask; import tc.oc.api.docs.PlayerId; import tc.oc.api.docs.UserId; import tc.oc.pgm.events.ListenerScope; import tc.oc.pgm.match.Competitor; import tc.oc.pgm.match.Match; import tc.oc.pgm.match.MatchPlayer; import tc.oc.pgm.PGMTranslations; import tc.oc.pgm.classes.ClassMatchModule; import tc.oc.pgm.classes.PlayerClass; import tc.oc.pgm.ghostsquadron.RevealTask.RevealEntry; import tc.oc.pgm.match.MatchModule; import tc.oc.pgm.match.MatchScope; import tc.oc.pgm.utils.MatchPlayers; @ListenerScope(MatchScope.RUNNING) public class GhostSquadronMatchModule extends MatchModule implements Listener { BukkitTask mainTask; BukkitTask revealTask; final ClassMatchModule classMatchModule; public final Map<Location, PlayerId> landmines = Maps.newHashMap(); public final Map<Location, Competitor> landmineTeams = Maps.newHashMap(); public final Map<UserId, Date> spideySenses = Maps.newHashMap(); final Map<Player, Double> walkDistance = Maps.newHashMap(); public final Map<Player, RevealEntry> revealMap = Maps.newHashMap(); // classes final Optional<PlayerClass> trackerClass; final Optional<PlayerClass> spiderClass; final Optional<PlayerClass> leprechaunClass; final Optional<PlayerClass> demoClass; // final PlayerClass ninjaClass; public GhostSquadronMatchModule(Match match, ClassMatchModule classMatchModule) { super(match); this.classMatchModule = checkNotNull(classMatchModule, "class match module"); this.trackerClass = classMatchModule.findClass("Tracker"); this.spiderClass = classMatchModule.findClass("Spider"); this.leprechaunClass = classMatchModule.findClass("Leprechaun"); this.demoClass = classMatchModule.findClass("Demo"); // this.ninjaClass = checkNotNull(classMatchModule.getPlayerClass("Ninja"), "Ninja class not found"); } @Override public void enable() { GhostSquadronTask task = new GhostSquadronTask(this.match, this, this.classMatchModule); this.mainTask = Bukkit.getScheduler().runTaskTimer(this.match.getPlugin(), task, 0, 10); this.revealTask = Bukkit.getScheduler().runTaskTimer(this.match.getPlugin(), new RevealTask(this), 0, 1); } @Override public void disable() { this.mainTask.cancel(); this.revealTask.cancel(); } /* * GENERAL */ @EventHandler public void cancelDrop(final PlayerDropItemEvent event) { Material hand = event.getPlayer().getItemInHand().getType(); if(!GhostSquadron.ALLOWED_DROPS.contains(hand)) { MatchPlayer player = this.match.getPlayer(event.getPlayer()); if(MatchPlayers.canInteract(player)) { event.setCancelled(true); } } } @EventHandler public void cancelItemSpawn(final ItemSpawnEvent event) { Material hand = event.getEntity().getItemStack().getType(); if(!GhostSquadron.ALLOWED_DROPS.contains(hand)) { event.setCancelled(true); } } @EventHandler public void cancelPickup(final PlayerPickupItemEvent event) { event.setCancelled(true); } @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void noExplosionBlockDamage(EntityExplodeEvent event) { event.blockList().clear(); } @EventHandler(priority = EventPriority.MONITOR) public void enforceFireTickLimit(EntityDamageEvent event) { event.getEntity().setFireTicks(Math.min(event.getEntity().getFireTicks(), GhostSquadron.MAX_FIRE_TICKS)); } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void resetRevealTicks(PlayerDeathEvent event) { this.revealMap.remove(event.getEntity()); } private void reveal(Player player) { this.reveal(player, GhostSquadron.REVEAL_STANDARD_DURATION); } private void reveal(Player player, int ticks) { RevealEntry entry = this.revealMap.get(player); if(entry == null) entry = new RevealEntry(); entry.revealTicks = ticks; for(PotionEffect e : player.getActivePotionEffects()) { if(e.getType().equals(PotionEffectType.INVISIBILITY)) { entry.potionTicks = e.getDuration(); } } player.removePotionEffect(PotionEffectType.INVISIBILITY); this.revealMap.put(player, entry); } /* * ARCHER */ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void revealOnArrow(final EntityDamageByEntityEvent event) { if(event.getCause() == DamageCause.PROJECTILE && event.getDamager() instanceof Arrow && event.getEntity() instanceof Player) { this.reveal((Player) event.getEntity(), GhostSquadron.ARROW_REVEAL_DURATION); } } /* * TRACKER */ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void trackerMove(final PlayerMoveEvent event) { MatchPlayer enemy = this.getMatch().getPlayer(event.getPlayer()); if(!MatchPlayers.canInteract(enemy)) return; ImmutableList.builder(); double distance = event.getFrom().distance(event.getTo()); final Double walkedRaw = this.walkDistance.get(event.getPlayer()); final double walkedStart = walkedRaw != null ? walkedRaw.doubleValue() : 0; final int stepStart = (int) Math.floor(walkedStart / GhostSquadron.TRACKER_FOOTSTEP_SPACING); final double walkedEnd = walkedStart + distance; final int stepEnd = (int) Math.floor(walkedEnd / GhostSquadron.TRACKER_FOOTSTEP_SPACING); this.walkDistance.put(event.getPlayer(), walkedEnd); Location normal = event.getTo().clone().subtract(event.getFrom()); normal.multiply(1.0 / normal.length()); for(int step = stepStart; step < stepEnd; step++) { double distanceFromStart = (step + 1) * GhostSquadron.TRACKER_FOOTSTEP_SPACING - walkedStart; Location stepLoc = normal.clone().multiply(distanceFromStart).add(event.getFrom()).add(0, GhostSquadron.TRACKER_FOOTSTEP_DY, 0); for(UserId userId : this.classMatchModule.getClassMembers(this.trackerClass)) { MatchPlayer tracker = this.match.getPlayer(userId); if(MatchPlayers.canInteract(tracker) && tracker.getParty() != enemy.getParty()) { tracker.getBukkit().playEffect(stepLoc, Effect.FOOTSTEP, 0, 0, 0f, 0f, 0f, 0f, 1, 50); } } } } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void clearWalkDistanceOnDeath(PlayerDeathEvent event) { // players are killed when leaving team or quitting, so this covers every instance this.walkDistance.remove(event.getEntity()); } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void trackerMelee(final EntityDamageByEntityEvent event) { if(event.getDamager() instanceof Player && event.getEntity() instanceof Player) { MatchPlayer damager = this.getMatch().getPlayer((Player) event.getDamager()); MatchPlayer damaged = this.getMatch().getPlayer((Player) event.getEntity()); if(damager != null && damaged != null && this.isClass(damager, this.trackerClass) && damager.getParty() != damaged.getParty()) { ItemStack hand = damager.getBukkit().getItemInHand(); if(hand != null && hand.getType() == Material.COMPASS) { this.reveal(damaged.getBukkit(), GhostSquadron.TRACKER_REVEAL_DURATION); } } } } /* * LEPRECHAUN */ @EventHandler public void dontPickupExp(final PlayerPickupExperienceEvent event) { event.setCancelled(true); } @EventHandler public void fastLiquids(final PlayerMoveEvent event) { final MatchPlayer player = this.getMatch().getParticipant(event.getPlayer()); if(player != null && this.isClass(player, this.leprechaunClass)) { if(event.getTo().getBlock().isLiquid()) { event.getPlayer().setAllowFlight(true); event.getPlayer().setFlying(true); } else { event.getPlayer().setFlying(false); } } } /* * DEMO */ @EventHandler public void landminePlace(final PlayerInteractEvent event) { Player player = event.getPlayer(); MatchPlayer mPlayer = match.getPlayer(player); if(!MatchPlayers.canInteract(mPlayer) || !this.isClass(mPlayer, this.demoClass)) return; final ItemStack item = event.getPlayer().getItemInHand(); if(event.getAction() == Action.RIGHT_CLICK_BLOCK && item != null && item.getType() == Material.TNT) { if(event.getClickedBlock().getRelative(BlockFace.UP).getType() != Material.AIR) { mPlayer.sendWarning(ChatColor.RED + PGMTranslations.t("match.ghostSquadron.landmine.invalidLocation", mPlayer), true); return; } if(this.landmines.containsKey(event.getClickedBlock().getLocation())) { mPlayer.sendWarning(ChatColor.RED + PGMTranslations.t("match.ghostSquadron.landmine.alreadyExists", mPlayer), true); return; } Location place = event.getClickedBlock().getLocation().add(.5, 0, .5); for(Location loc : this.landmines.keySet()) { boolean xClose = Math.abs(place.getX() - loc.getX()) <= GhostSquadron.LANDMINE_SPACING; boolean zClose = Math.abs(place.getZ() - loc.getZ()) <= GhostSquadron.LANDMINE_SPACING; if(xClose && zClose) { mPlayer.sendWarning(ChatColor.RED + PGMTranslations.t("match.ghostSquadron.landmine.tooClose", mPlayer), true); return; } } event.setCancelled(true); this.landmines.put(place, mPlayer.getPlayerId()); this.landmineTeams.put(place, mPlayer.getCompetitor()); if(item.getAmount() > 1) { item.setAmount(item.getAmount() - 1); } else { event.getPlayer().setItemInHand(null); } player.sendMessage(ChatColor.GREEN + PGMTranslations.t("ghostSquadron.landminePlanted", mPlayer)); } } @EventHandler public void landmineExplode(final PlayerMoveEvent event) { MatchPlayer player = this.getMatch().getPlayer(event.getPlayer()); if(!MatchPlayers.canInteract(player)) return; Location to = event.getTo(); Iterator<Entry<Location, PlayerId>> iterator = this.landmines.entrySet().iterator(); while(iterator.hasNext()) { Map.Entry<Location, PlayerId> entry = iterator.next(); Location landmine = entry.getKey(); MatchPlayer placer = this.getMatch().getPlayer(entry.getValue()); if(placer == null || !placer.isParticipating()) { iterator.remove(); continue; } Competitor placerTeam = this.landmineTeams.get(landmine); if(placerTeam == player.getParty()) continue; if(to.distanceSquared(landmine) < GhostSquadron.LANDMINE_ACTIVATION_DISTANCE_SQ) { TNTPrimed tnt = (TNTPrimed) landmine.getWorld().spawnEntity(landmine.clone().add(0, 1, 0), EntityType.PRIMED_TNT); tnt.setFuseTicks(0); tnt.setYield(1); this.reveal(player.getBukkit()); getMatch().callEvent(new ExplosionPrimeByEntityEvent(tnt, placer.getBukkit())); iterator.remove(); this.landmineTeams.remove(landmine); } } } /* * SPIDER */ public void spideySense(final MatchPlayer player) { UserId userId = player.getPlayerId(); Date when = this.spideySenses.get(userId); Date now = new Date(); if(when == null || now.getTime() > when.getTime() + GhostSquadron.SPIDEY_SENSE_COOLDOWN) { this.spideySenses.put(userId, now); player.getBukkit().addPotionEffect(new PotionEffect(PotionEffectType.NIGHT_VISION, 4 * 20, 0), true); player.getBukkit().addPotionEffect(new PotionEffect(PotionEffectType.SPEED, 4 * 20, 1), true); player.getBukkit().playSound(player.getBukkit().getLocation(), Sound.ENTITY_SPIDER_AMBIENT, 5, 0); player.getBukkit().playSound(player.getBukkit().getLocation(), Sound.ENTITY_SPIDER_AMBIENT, 5, 0.25f); player.getBukkit().playSound(player.getBukkit().getLocation(), Sound.ENTITY_SPIDER_AMBIENT, 5, 0.5f); } } @EventHandler public void webBow(final EntityShootBowEvent event) { if(!(event.getEntity() instanceof Player)) return; Player player = (Player) event.getEntity(); if(!this.isClass(this.getMatch().getPlayer(player), spiderClass)) return; FallingBlock web = event.getEntity().getWorld().spawnFallingBlock(event.getProjectile().getLocation(), Material.WEB, (byte) 0); web.setDropItem(false); web.setVelocity(event.getProjectile().getVelocity()); event.setProjectile(web); } @EventHandler public void webLand(final EntityChangeBlockEvent event) { if(!(event.getEntity() instanceof FallingBlock)) return; FallingBlock block = (FallingBlock) event.getEntity(); if(block.getMaterial() != Material.WEB) return; event.getEntity().getLocation().getBlock().setType(Material.WEB); } /* * NINJA - Temporarily disabled */ /* @EventHandler public void hookPlayer(final PlayerFishEvent event) { if(event.getState() == PlayerFishEvent.State.FISHING || event.getState() == PlayerFishEvent.State.FAILED_ATTEMPT) return; MatchPlayer caster = this.match.getPlayer(event.getPlayer()); if (!this.isClass(caster, this.ninjaClass)) return; Location center = event.getHook().getLocation(); Vector pullTowards = event.getPlayer().getLocation().toVector(); for(Player player : event.getPlayer().getWorld().getPlayers()) { if(player == event.getPlayer()) continue; if(player.getLocation().distance(center) > 5) continue; MatchPlayer matchPlayer = this.match.getPlayer(player); if(!matchPlayer.canInteract() || matchPlayer.getTeam() == caster.getTeam()) continue; Vector velocity = pullTowards.subtract(player.getLocation().toVector()).divide(new Vector(3, 3, 3)); double MIN = -2; double MAX = 2; velocity.setX(clamp(MIN, MAX, velocity.getX())); velocity.setY(clamp(MIN, MAX, velocity.getY())); velocity.setZ(clamp(MIN, MAX, velocity.getZ())); player.setVelocity(velocity); this.reveal(player); } event.getHook().remove(); } */ private static double clamp(double min, double max, double def) { if(def < min) return min; if(def > max) return max; return def; } private boolean isClass(MatchPlayer player, Optional<PlayerClass> playerClass) { return playerClass.isPresent() && isClass(player, playerClass.get()); } private boolean isClass(MatchPlayer player, PlayerClass playerClass) { return classMatchModule.playingClass(player) .filter(playerClass::equals) .isPresent(); } }