package com.massivecraft.factions.engine;
import com.massivecraft.factions.Factions;
import com.massivecraft.factions.TerritoryAccess;
import com.massivecraft.factions.entity.Board;
import com.massivecraft.factions.entity.BoardColl;
import com.massivecraft.factions.entity.Faction;
import com.massivecraft.factions.entity.FactionColl;
import com.massivecraft.factions.entity.MConf;
import com.massivecraft.factions.entity.MPerm;
import com.massivecraft.factions.entity.MPlayer;
import com.massivecraft.factions.integration.spigot.IntegrationSpigot;
import com.massivecraft.factions.util.EnumerationUtil;
import com.massivecraft.massivecore.Engine;
import com.massivecraft.massivecore.ps.PS;
import com.massivecraft.massivecore.util.MUtil;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockDamageEvent;
import org.bukkit.event.block.BlockFromToEvent;
import org.bukkit.event.block.BlockPistonExtendEvent;
import org.bukkit.event.block.BlockPistonRetractEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.block.SignChangeEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.hanging.HangingBreakByEntityEvent;
import org.bukkit.event.hanging.HangingBreakEvent;
import org.bukkit.event.hanging.HangingPlaceEvent;
import org.bukkit.event.player.PlayerBucketEmptyEvent;
import org.bukkit.event.player.PlayerBucketFillEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import java.util.Map;
public class EnginePermBuild extends Engine
{
// -------------------------------------------- //
// INSTANCE & CONSTRUCT
// -------------------------------------------- //
private static EnginePermBuild i = new EnginePermBuild();
public static EnginePermBuild get() { return i; }
// -------------------------------------------- //
// PERM: BUILD
// -------------------------------------------- //
public static boolean canPlayerBuildAt(Object senderObject, PS ps, boolean verboose)
{
MPlayer mplayer = MPlayer.get(senderObject);
if (mplayer == null) return false;
String name = mplayer.getName();
if (MConf.get().playersWhoBypassAllProtection.contains(name)) return true;
if (mplayer.isOverriding()) return true;
if (!MPerm.getPermBuild().has(mplayer, ps, false) && MPerm.getPermPainbuild().has(mplayer, ps, false))
{
if (verboose)
{
Faction hostFaction = BoardColl.get().getFactionAt(ps);
mplayer.msg("<b>It is painful to build in the territory of %s<b>.", hostFaction.describeTo(mplayer));
Player player = mplayer.getPlayer();
if (player != null)
{
player.damage(MConf.get().actionDeniedPainAmount);
}
}
return true;
}
return MPerm.getPermBuild().has(mplayer, ps, verboose);
}
@EventHandler(priority = EventPriority.NORMAL)
public void blockBuild(BlockPlaceEvent event)
{
if (!event.canBuild()) return;
boolean verboose = ! isFake(event);
if (canPlayerBuildAt(event.getPlayer(), PS.valueOf(event.getBlock()), verboose)) return;
event.setBuild(false);
event.setCancelled(true);
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void blockBuild(BlockBreakEvent event)
{
boolean verboose = ! isFake(event);
if (canPlayerBuildAt(event.getPlayer(), PS.valueOf(event.getBlock()), verboose)) return;
event.setCancelled(true);
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void blockBuild(BlockDamageEvent event)
{
if ( ! event.getInstaBreak()) return;
boolean verboose = ! isFake(event);
if (canPlayerBuildAt(event.getPlayer(), PS.valueOf(event.getBlock()), verboose)) return;
event.setCancelled(true);
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void blockBuild(SignChangeEvent event)
{
boolean verboose = ! isFake(event);
if (canPlayerBuildAt(event.getPlayer(), PS.valueOf(event.getBlock()), verboose)) return;
event.setCancelled(true);
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void blockBuild(BlockPistonExtendEvent event)
{
// Is using Spigot or is checking deactivated by MConf?
if (IntegrationSpigot.get().isIntegrationActive() || ! MConf.get().handlePistonProtectionThroughDenyBuild) return;
Block block = event.getBlock();
// Targets end-of-the-line empty (air) block which is being pushed into, including if piston itself would extend into air
Block targetBlock = block.getRelative(event.getDirection(), event.getLength() + 1);
// Factions involved
Faction pistonFaction = BoardColl.get().getFactionAt(PS.valueOf(block));
Faction targetFaction = BoardColl.get().getFactionAt(PS.valueOf(targetBlock));
// Members of a faction might not have build rights in their own territory, but pistons should still work regardless
if (targetFaction == pistonFaction) return;
// if potentially pushing into air/water/lava in another territory, we need to check it out
if ((targetBlock.isEmpty() || targetBlock.isLiquid()) && ! MPerm.getPermBuild().has(pistonFaction, targetFaction))
{
event.setCancelled(true);
}
/*
* note that I originally was testing the territory of each affected block, but since I found that pistons can only push
* up to 12 blocks and the width of any territory is 16 blocks, it should be safe (and much more lightweight) to test
* only the final target block as done above
*/
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void blockBuild(BlockPistonRetractEvent event)
{
// Is using Spigot or is checking deactivated by MConf?
if (IntegrationSpigot.get().isIntegrationActive() || ! MConf.get().handlePistonProtectionThroughDenyBuild) return;
// If not a sticky piston, retraction should be fine
if ( ! event.isSticky()) return;
Block retractBlock = event.getRetractLocation().getBlock();
PS retractPs = PS.valueOf(retractBlock);
// if potentially retracted block is just air/water/lava, no worries
if (retractBlock.isEmpty() || retractBlock.isLiquid()) return;
// Factions involved
Faction pistonFaction = BoardColl.get().getFactionAt(PS.valueOf(event.getBlock()));
Faction targetFaction = BoardColl.get().getFactionAt(retractPs);
// Members of a faction might not have build rights in their own territory, but pistons should still work regardless
if (targetFaction == pistonFaction) return;
if (MPerm.getPermBuild().has(pistonFaction, targetFaction)) return;
event.setCancelled(true);
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void blockBuild(HangingPlaceEvent event)
{
boolean verboose = ! isFake(event);
if (canPlayerBuildAt(event.getPlayer(), PS.valueOf(event.getEntity().getLocation()), verboose)) return;
event.setCancelled(true);
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void blockBuild(HangingBreakEvent event)
{
if (! (event instanceof HangingBreakByEntityEvent)) return;
HangingBreakByEntityEvent entityEvent = (HangingBreakByEntityEvent)event;
Entity breaker = entityEvent.getRemover();
if (MUtil.isntPlayer(breaker)) return;
boolean verboose = ! isFake(event);
if ( ! canPlayerBuildAt(breaker, PS.valueOf(event.getEntity().getLocation()), verboose))
{
event.setCancelled(true);
}
}
// Check for punching out fires where players should not be able to
@SuppressWarnings("deprecation")
@EventHandler(priority = EventPriority.NORMAL)
public void blockBuild(PlayerInteractEvent event)
{
// ... if it is a left click on block ...
if (event.getAction() != Action.LEFT_CLICK_BLOCK) return;
// .. and the clicked block is not null ...
if (event.getClickedBlock() == null) return;
Block potentialBlock = event.getClickedBlock().getRelative(BlockFace.UP, 1);
// .. and the potential block is not null ...
if (potentialBlock == null) return;
// ... and we're only going to check for fire ... (checking everything else would be bad performance wise)
if (potentialBlock.getType() != Material.FIRE) return;
// ... check if they can build ...
if (canPlayerBuildAt(event.getPlayer(), PS.valueOf(potentialBlock), true)) return;
// ... nope, cancel it
event.setCancelled(true);
// .. and compensate for client side prediction
event.getPlayer().sendBlockChange(potentialBlock.getLocation(), potentialBlock.getType(), potentialBlock.getState().getRawData());
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void blockLiquidFlow(BlockFromToEvent event)
{
if ( ! MConf.get().protectionLiquidFlowEnabled) return;
// Prepare fields
Block fromBlock = event.getBlock();
int fromCX = fromBlock.getX() >> 4;
int fromCZ = fromBlock.getZ() >> 4;
BlockFace face = event.getFace();
int toCX = (fromBlock.getX() + face.getModX()) >> 4;
int toCZ = (fromBlock.getZ() + face.getModZ()) >> 4;
// If a liquid (or dragon egg) moves from one chunk to another ...
if (toCX == fromCX && toCZ == fromCZ) return;
Board board = BoardColl.get().getFixed(fromBlock.getWorld().getName().toLowerCase(), false);
if (board == null) return;
Map<PS, TerritoryAccess> map = board.getMapRaw();
if (map.isEmpty()) return;
PS fromPs = PS.valueOf(fromCX, fromCZ);
PS toPs = PS.valueOf(toCX, toCZ);
TerritoryAccess fromTa = map.get(fromPs);
TerritoryAccess toTa = map.get(toPs);
String fromId = fromTa != null ? fromTa.getHostFactionId() : Factions.ID_NONE;
String toId = toTa != null ? toTa.getHostFactionId() : Factions.ID_NONE;
// ... and the chunks belong to different factions ...
if (toId.equals(fromId)) return;
// ... and the faction "from" can not build at "to" ...
Faction fromFac = FactionColl.get().getFixed(fromId);
Faction toFac = FactionColl.get().getFixed(toId);
if (MPerm.getPermBuild().has(fromFac, toFac)) return;
// ... cancel!
event.setCancelled(true);
}
// -------------------------------------------- //
// ASSORTED BUILD AND INTERACT
// -------------------------------------------- //
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onPlayerDamageEntity(EntityDamageByEntityEvent event)
{
// If a player ...
Entity edamager = MUtil.getLiableDamager(event);
if (MUtil.isntPlayer(edamager)) return;
Player player = (Player)edamager;
// ... damages an entity which is edited on damage ...
Entity edamagee = event.getEntity();
if (edamagee == null) return;
if ( ! EnumerationUtil.isEntityTypeEditOnDamage(edamagee.getType())) return;
// ... and the player can't build there ...
if (canPlayerBuildAt(player, PS.valueOf(edamagee.getLocation()), true)) return;
// ... then cancel the event.
event.setCancelled(true);
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onPlayerInteract(PlayerInteractEvent event)
{
// only need to check right-clicks and physical as of MC 1.4+; good performance boost
if (event.getAction() != Action.RIGHT_CLICK_BLOCK && event.getAction() != Action.PHYSICAL) return;
Block block = event.getClickedBlock();
Player player = event.getPlayer();
if (block == null) return; // clicked in air, apparently
if ( ! canPlayerUseBlock(player, block, true))
{
event.setCancelled(true);
return;
}
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return; // only interested on right-clicks for below
if ( ! playerCanUseItemHere(player, PS.valueOf(block), event.getMaterial(), true))
{
event.setCancelled(true);
return;
}
}
public static boolean playerCanUseItemHere(Player player, PS ps, Material material, boolean verboose)
{
if (MUtil.isntPlayer(player)) return true;
if ( ! EnumerationUtil.isMaterialEditTool(material)) return true;
String name = player.getName();
if (MConf.get().playersWhoBypassAllProtection.contains(name)) return true;
MPlayer mplayer = MPlayer.get(player);
if (mplayer.isOverriding()) return true;
return MPerm.getPermBuild().has(mplayer, ps, verboose);
}
public static boolean canPlayerUseBlock(Player player, Block block, boolean verboose)
{
if (MUtil.isntPlayer(player)) return true;
String name = player.getName();
if (MConf.get().playersWhoBypassAllProtection.contains(name)) return true;
MPlayer me = MPlayer.get(player);
if (me.isOverriding()) return true;
PS ps = PS.valueOf(block);
Material material = block.getType();
if (EnumerationUtil.isMaterialEditOnInteract(material) && ! MPerm.getPermBuild().has(me, ps, verboose)) return false;
if (EnumerationUtil.isMaterialContainer(material) && ! MPerm.getPermContainer().has(me, ps, verboose)) return false;
if (EnumerationUtil.isMaterialDoor(material) && ! MPerm.getPermDoor().has(me, ps, verboose)) return false;
if (material == Material.STONE_BUTTON && ! MPerm.getPermButton().has(me, ps, verboose)) return false;
if (material == Material.LEVER && ! MPerm.getPermLever().has(me, ps, verboose)) return false;
return true;
}
// This event will not fire for Minecraft 1.8 armor stands.
// Armor stands are handled in EngineSpigot instead.
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onPlayerInteractEntity(PlayerInteractEntityEvent event)
{
// Ignore Off Hand
if (isOffHand(event)) return;
// Gather Info
final Player player = event.getPlayer();
final Entity entity = event.getRightClicked();
final boolean verboose = true;
// If we can't use ...
if (canPlayerUseEntity(player, entity, verboose)) return;
// ... block use.
event.setCancelled(true);
}
public static boolean canPlayerUseEntity(Player player, Entity entity, boolean verboose)
{
// If a player ...
if (MUtil.isntPlayer(player)) return true;
// ... interacts with an entity ...
if (entity == null) return true;
EntityType type = entity.getType();
PS ps = PS.valueOf(entity.getLocation());
// ... and the player does not bypass all protections ...
String name = player.getName();
if (MConf.get().playersWhoBypassAllProtection.contains(name)) return true;
// ... and the player is not using admin mode ...
MPlayer me = MPlayer.get(player);
if (me.isOverriding()) return true;
// ... check container entity rights ...
if (EnumerationUtil.isEntityTypeContainer(type) && ! MPerm.getPermContainer().has(me, ps, verboose)) return false;
// ... check build entity rights ...
if (EnumerationUtil.isEntityTypeEditOnInteract(type) && ! MPerm.getPermBuild().has(me, ps, verboose)) return false;
// ... otherwise we may use the entity.
return true;
}
// For some reason onPlayerInteract() sometimes misses bucket events depending on distance (something like 2-3 blocks away isn't detected),
// but these separate bucket events below always fire without fail
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onPlayerBucketEmpty(PlayerBucketEmptyEvent event)
{
Block block = event.getBlockClicked();
Player player = event.getPlayer();
if (playerCanUseItemHere(player, PS.valueOf(block), event.getBucket(), true)) return;
event.setCancelled(true);
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onPlayerBucketFill(PlayerBucketFillEvent event)
{
Block block = event.getBlockClicked();
Player player = event.getPlayer();
if (playerCanUseItemHere(player, PS.valueOf(block), event.getBucket(), true)) return;
event.setCancelled(true);
}
}