diff --git a/src/com/massivecraft/factions/engine/EngineExploit.java b/src/com/massivecraft/factions/engine/EngineExploit.java index b65b34d3..68fca26a 100644 --- a/src/com/massivecraft/factions/engine/EngineExploit.java +++ b/src/com/massivecraft/factions/engine/EngineExploit.java @@ -1,13 +1,18 @@ package com.massivecraft.factions.engine; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.UUID; import org.bukkit.block.Block; +import org.bukkit.entity.Player; import org.bukkit.entity.TNTPrimed; import org.bukkit.event.block.BlockFromToEvent; import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.plugin.Plugin; @@ -17,6 +22,8 @@ import org.bukkit.Material; import com.massivecraft.factions.Factions; import com.massivecraft.factions.entity.MConf; import com.massivecraft.massivecore.EngineAbstract; +import com.massivecraft.massivecore.ps.PS; +import com.massivecraft.massivecore.util.MUtil; public class EngineExploit extends EngineAbstract @@ -137,4 +144,121 @@ public class EngineExploit extends EngineAbstract } } + + // -------------------------------------------- // + // NETHER PORTAL TRAP + // -------------------------------------------- // + // A nether portal trap can be created by the destination portal being enclosed (trapped) - resulting in the player not being able to run commands. + // This fix removes the portal blocks (client side) from the destination until they are away from the portal. + + private int NETHER_TRAP_RADIUS_CHECK = 5; + private int NETHER_TRAP_RESET_RADIUS = 3; + + private HashMap> netherTrapBlockSet = new HashMap>(); + + // Detect teleport from a nether portal and remove animation if required + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void netherportalTrapRemoveAnimation(PlayerTeleportEvent event) + { + if ( ! MConf.get().handleNetherPortalTrap) return; + + if (event.getCause() != TeleportCause.NETHER_PORTAL) return; + + // If they can build at the target destination then we will not do any further checks + if (EngineMain.canPlayerBuildAt(event.getPlayer(), PS.valueOf(event.getTo()), false)) return; + + final Player player = event.getPlayer(); + final UUID uuid = player.getUniqueId(); + + Block from = event.getTo().getBlock(); + + // If a list exists, then we're dealing with a new portal - so revert the old portal blocks + this.netherportalReset(player); + + // Store some temporary data + this.netherTrapBlockSet.put(uuid, this.getPortalBlocks(from)); + + // Send the air update + this.netherportalSendAir(player); + + } + + // When they leave the portal we will update it, otherwise send the updates again + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void netherportalTrapUpdate(PlayerMoveEvent event) + { + if ( ! MConf.get().handleNetherPortalTrap) return; + + // Only check if we're changing blocks + if (MUtil.isSameBlock(event)) return; + + Player player = event.getPlayer(); + UUID uuid = player.getUniqueId(); + + // Check if we're sending updates to this player + if ( ! this.netherTrapBlockSet.containsKey(uuid)) return; + + // If they've changed worlds we don't bother checking this + if (event.getTo().getWorld() != this.netherTrapBlockSet.get(uuid).get(0).getWorld()) + { + this.netherportalReset(player); + return; + } + + // When the player moves away from the portal, we put it back to normal + if (event.getTo().distance(this.netherTrapBlockSet.get(uuid).get(0).getLocation()) > NETHER_TRAP_RESET_RADIUS) + { + this.netherportalReset(player); + } + else + { + // Send updates as air + this.netherportalSendAir(player); + } + } + + // Sends block update to player with original blocks (if required) + @SuppressWarnings("deprecation") + public void netherportalReset(Player player) + { + UUID uuid = player.getUniqueId(); + + if ( ! this.netherTrapBlockSet.containsKey(uuid)) return; + + // Only send updates if they're in the same world + if (this.netherTrapBlockSet.get(uuid).get(0).getWorld() == player.getWorld()) for (Block block : this.netherTrapBlockSet.get(uuid)) player.sendBlockChange(block.getLocation(), block.getType(), block.getData()); + + // Remove the block set + this.netherTrapBlockSet.remove(uuid); + } + + // Send air block updates to player + @SuppressWarnings("deprecation") + public void netherportalSendAir(Player player) + { + for (Block block : this.netherTrapBlockSet.get(player.getUniqueId())) player.sendBlockChange(block.getLocation(), Material.AIR, (byte) 0); + } + + // Get portal blocks near a block + public List getPortalBlocks(Block from) { + List blocks = new ArrayList(); + + // Check in a radius of the block to find the portal blocks + for (int x = -(NETHER_TRAP_RADIUS_CHECK); x <= NETHER_TRAP_RADIUS_CHECK; x ++) + { + for (int y = -(NETHER_TRAP_RADIUS_CHECK); y <= NETHER_TRAP_RADIUS_CHECK; y ++) + { + for (int z = -(NETHER_TRAP_RADIUS_CHECK); z <= NETHER_TRAP_RADIUS_CHECK; z ++) + { + if (from.getRelative(x, y, z).getType() == Material.PORTAL) + { + // Store block + blocks.add(from.getRelative(x, y, z)); + } + } + } + } + + return blocks; + } } diff --git a/src/com/massivecraft/factions/entity/MConf.java b/src/com/massivecraft/factions/entity/MConf.java index 97b45ed7..e9d0ebbc 100644 --- a/src/com/massivecraft/factions/entity/MConf.java +++ b/src/com/massivecraft/factions/entity/MConf.java @@ -441,6 +441,7 @@ public class MConf extends Entity public boolean handleExploitObsidianGenerators = true; public boolean handleExploitEnderPearlClipping = true; public boolean handleExploitTNTWaterlog = false; + public boolean handleNetherPortalTrap = true; // -------------------------------------------- // // SEE CHUNK