package tc.oc.pgm.tracker.trackers; import java.util.HashMap; import java.util.Map; import javax.inject.Inject; import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.event.player.PlayerOnGroundEvent; import tc.oc.pgm.events.ListenerScope; import tc.oc.pgm.match.Match; import tc.oc.pgm.match.MatchPlayer; import tc.oc.pgm.match.MatchScope; import tc.oc.pgm.events.ParticipantBlockTransformEvent; import tc.oc.pgm.tracker.EntityResolver; import tc.oc.pgm.tracker.damage.DamageInfo; import tc.oc.pgm.tracker.damage.ExplosionInfo; import tc.oc.pgm.tracker.damage.PhysicalInfo; import tc.oc.pgm.tracker.damage.PlayerInfo; import tc.oc.pgm.tracker.damage.SpleefInfo; import tc.oc.pgm.tracker.event.PlayerSpleefEvent; import tc.oc.commons.bukkit.util.Materials; /** * Tracks blocks broken by players and fires a {@link PlayerSpleefEvent} * when it appears to cause a player to leave the ground. */ @ListenerScope(MatchScope.RUNNING) public class SpleefTracker implements Listener { private static final float PLAYER_WIDTH = 0.6f; private static final float PLAYER_RADIUS = PLAYER_WIDTH / 2.0f; // A player must leave the ground within this many ticks of a block being broken // under them for the fall to be caused by a spleef from that block public static final long MAX_SPLEEF_TICKS = 20; private final EntityResolver entityResolver; private final Match match; private final Map<Block, SpleefInfo> brokenBlocks = new HashMap<>(); @Inject SpleefTracker(EntityResolver entityResolver, Match match) { this.entityResolver = entityResolver; this.match = match; } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onBlockBreak(final ParticipantBlockTransformEvent event) { if(!event.isBreak()) return; if(!Materials.isColliding(event.getOldState())) return; final Block block = event.getBlock(); DamageInfo breaker = null; if(event.getCause() instanceof EntityExplodeEvent) { PhysicalInfo explosive = entityResolver.resolveInfo(((EntityExplodeEvent) event.getCause()).getEntity(), PhysicalInfo.class); if(explosive != null) { breaker = new ExplosionInfo(explosive); } } if(breaker == null) { breaker = new PlayerInfo(event.getPlayerState()); } final SpleefInfo info = new SpleefInfo(breaker, match.getClock().now()); brokenBlocks.put(block, info); match.getScheduler(MatchScope.RUNNING).createDelayedTask(MAX_SPLEEF_TICKS + 1, () -> { // Only remove the BrokenBlock if it's the same one we added. It may have been replaced since then. if(info == brokenBlocks.get(block)) { brokenBlocks.remove(block); } }); } @EventHandler(priority = EventPriority.MONITOR) public void onPlayerOnGroundChanged(final PlayerOnGroundEvent event) { MatchPlayer player = match.getParticipant(event.getPlayer()); if(player == null) return; Block block = this.lastBlockBrokenUnderPlayer(player); if(block != null) { SpleefInfo info = brokenBlocks.get(block); if(match.getClock().now().tick - info.getTime().tick <= MAX_SPLEEF_TICKS) { match.callEvent(new PlayerSpleefEvent(player, block, info)); } } } public Block lastBlockBrokenUnderPlayer(MatchPlayer player) { Location playerLocation = player.getBukkit().getLocation(); int y = (int) Math.floor(playerLocation.getY() - 0.1); int x1 = (int) Math.floor(playerLocation.getX() - PLAYER_RADIUS); int z1 = (int) Math.floor(playerLocation.getZ() - PLAYER_RADIUS); int x2 = (int) Math.floor(playerLocation.getX() + PLAYER_RADIUS); int z2 = (int) Math.floor(playerLocation.getZ() + PLAYER_RADIUS); long latestTick = Long.MIN_VALUE; Block latestBlock = null; for(int x = x1; x <= x2; ++x) { for(int z = z1; z <= z2; ++z) { Block block = playerLocation.getBlock(); SpleefInfo info = this.brokenBlocks.get(block); if(info != null) { long tick = info.getTime().tick; if(tick > latestTick) { latestTick = tick; latestBlock = block; } } } } return latestBlock; } }